committed by
GitHub
25 changed files with 509 additions and 125 deletions
@ -1,5 +1,5 @@ |
|||
<Project> |
|||
<PropertyGroup Condition="$(NETCoreSdkVersion.StartsWith('7.0'))"> |
|||
<PropertyGroup Condition="$([MSBuild]::VersionGreaterThanOrEquals($(NETCoreSdkVersion), '7.0'))"> |
|||
<DefineConstants>$(DefineConstants);NET7SDK</DefineConstants> |
|||
</PropertyGroup> |
|||
</Project> |
|||
|
|||
@ -0,0 +1,24 @@ |
|||
using System; |
|||
using System.IO; |
|||
using Nuke.Common.Utilities; |
|||
|
|||
class Helpers |
|||
{ |
|||
public static IDisposable UseTempDir(out string dir) |
|||
{ |
|||
var path = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); |
|||
Directory.CreateDirectory(path); |
|||
dir = path; |
|||
return DelegateDisposable.CreateBracket(null, () => |
|||
{ |
|||
try |
|||
{ |
|||
Directory.Delete(path, true); |
|||
} |
|||
catch |
|||
{ |
|||
// ignore
|
|||
} |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,171 @@ |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.IO.Compression; |
|||
using System.Linq; |
|||
using ILRepacking; |
|||
using Mono.Cecil; |
|||
using Mono.Cecil.Cil; |
|||
|
|||
public class RefAssemblyGenerator |
|||
{ |
|||
class Resolver : DefaultAssemblyResolver, IAssemblyResolver |
|||
{ |
|||
private readonly string _dir; |
|||
Dictionary<string, AssemblyDefinition> _cache = new(); |
|||
|
|||
public Resolver(string dir) |
|||
{ |
|||
_dir = dir; |
|||
} |
|||
|
|||
public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) |
|||
{ |
|||
if (_cache.TryGetValue(name.Name, out var asm)) |
|||
return asm; |
|||
var path = Path.Combine(_dir, name.Name + ".dll"); |
|||
if (File.Exists(path)) |
|||
return _cache[name.Name] = AssemblyDefinition.ReadAssembly(path, parameters); |
|||
return base.Resolve(name, parameters); |
|||
} |
|||
} |
|||
|
|||
public static void PatchRefAssembly(string file) |
|||
{ |
|||
var reader = typeof(RefAssemblyGenerator).Assembly.GetManifestResourceStream("avalonia.snk"); |
|||
var snk = new byte[reader.Length]; |
|||
reader.Read(snk, 0, snk.Length); |
|||
|
|||
var def = AssemblyDefinition.ReadAssembly(file, new ReaderParameters |
|||
{ |
|||
ReadWrite = true, |
|||
InMemory = true, |
|||
ReadSymbols = true, |
|||
SymbolReaderProvider = new DefaultSymbolReaderProvider(false), |
|||
AssemblyResolver = new Resolver(Path.GetDirectoryName(file)) |
|||
}); |
|||
|
|||
var obsoleteAttribute = def.MainModule.ImportReference(new TypeReference("System", "ObsoleteAttribute", def.MainModule, |
|||
def.MainModule.TypeSystem.CoreLibrary)); |
|||
var obsoleteCtor = def.MainModule.ImportReference(new MethodReference(".ctor", |
|||
def.MainModule.TypeSystem.Void, obsoleteAttribute) |
|||
{ |
|||
Parameters = { new ParameterDefinition(def.MainModule.TypeSystem.String) } |
|||
}); |
|||
|
|||
foreach(var t in def.MainModule.Types) |
|||
ProcessType(t, obsoleteCtor); |
|||
def.Write(file, new WriterParameters() |
|||
{ |
|||
StrongNameKeyBlob = snk, |
|||
WriteSymbols = def.MainModule.HasSymbols, |
|||
SymbolWriterProvider = new EmbeddedPortablePdbWriterProvider(), |
|||
DeterministicMvid = def.MainModule.HasSymbols |
|||
}); |
|||
} |
|||
|
|||
static void ProcessType(TypeDefinition type, MethodReference obsoleteCtor) |
|||
{ |
|||
foreach (var nested in type.NestedTypes) |
|||
ProcessType(nested, obsoleteCtor); |
|||
if (type.IsInterface) |
|||
{ |
|||
var hideMethods = type.Name.EndsWith("Impl") |
|||
|| (type.HasCustomAttributes && type.CustomAttributes.Any(a => |
|||
a.AttributeType.FullName == "Avalonia.Metadata.PrivateApiAttribute")); |
|||
|
|||
var injectMethod = hideMethods |
|||
|| type.CustomAttributes.Any(a => |
|||
a.AttributeType.FullName == "Avalonia.Metadata.NotClientImplementableAttribute"); |
|||
|
|||
if (hideMethods) |
|||
{ |
|||
foreach (var m in type.Methods) |
|||
{ |
|||
var dflags = MethodAttributes.Public | MethodAttributes.Family | MethodAttributes.FamORAssem | |
|||
MethodAttributes.FamANDAssem | MethodAttributes.Assembly; |
|||
m.Attributes = ((m.Attributes | dflags) ^ dflags) | MethodAttributes.Assembly; |
|||
} |
|||
} |
|||
|
|||
if(injectMethod) |
|||
{ |
|||
type.Methods.Add(new MethodDefinition("NotClientImplementable", |
|||
MethodAttributes.Assembly |
|||
| MethodAttributes.Abstract |
|||
| MethodAttributes.NewSlot |
|||
| MethodAttributes.HideBySig, type.Module.TypeSystem.Void)); |
|||
} |
|||
|
|||
var forceUnstable = type.CustomAttributes.Any(a => |
|||
a.AttributeType.FullName == "Avalonia.Metadata.UnstableAttribute"); |
|||
|
|||
foreach (var m in type.Methods) |
|||
MarkAsUnstable(m, obsoleteCtor, forceUnstable); |
|||
foreach (var m in type.Properties) |
|||
MarkAsUnstable(m, obsoleteCtor, forceUnstable); |
|||
foreach (var m in type.Events) |
|||
MarkAsUnstable(m, obsoleteCtor, forceUnstable); |
|||
|
|||
} |
|||
} |
|||
|
|||
static void MarkAsUnstable(IMemberDefinition def, MethodReference obsoleteCtor, bool force) |
|||
{ |
|||
if (!force && ( |
|||
def.HasCustomAttributes == false |
|||
|| def.CustomAttributes.All(a => a.AttributeType.FullName != "Avalonia.Metadata.UnstableAttribute"))) |
|||
return; |
|||
|
|||
if (def.CustomAttributes.Any(a => a.AttributeType.FullName == "System.ObsoleteAttribute")) |
|||
return; |
|||
|
|||
def.CustomAttributes.Add(new CustomAttribute(obsoleteCtor) |
|||
{ |
|||
ConstructorArguments = |
|||
{ |
|||
new CustomAttributeArgument(obsoleteCtor.Module.TypeSystem.String, |
|||
"This is a part of unstable API and can be changed in minor releases. You have been warned") |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public static void GenerateRefAsmsInPackage(string packagePath) |
|||
{ |
|||
using (var archive = new ZipArchive(File.Open(packagePath, FileMode.Open, FileAccess.ReadWrite), |
|||
ZipArchiveMode.Update)) |
|||
{ |
|||
foreach (var entry in archive.Entries.ToList()) |
|||
{ |
|||
if (entry.FullName.StartsWith("ref/")) |
|||
entry.Delete(); |
|||
} |
|||
|
|||
foreach (var entry in archive.Entries.ToList()) |
|||
{ |
|||
if (entry.FullName.StartsWith("lib/") && entry.Name.EndsWith(".xml")) |
|||
{ |
|||
var newEntry = archive.CreateEntry("ref/" + entry.FullName.Substring(4), |
|||
CompressionLevel.Optimal); |
|||
using (var src = entry.Open()) |
|||
using (var dst = newEntry.Open()) |
|||
src.CopyTo(dst); |
|||
} |
|||
} |
|||
|
|||
var libs = archive.Entries.Where(e => e.FullName.StartsWith("lib/") && e.FullName.EndsWith(".dll")) |
|||
.Select((e => new { s = e.FullName.Split('/'), e = e })) |
|||
.Select(e => new { Tfm = e.s[1], Name = e.s[2], Entry = e.e }) |
|||
.GroupBy(x => x.Tfm); |
|||
foreach(var tfm in libs) |
|||
using (Helpers.UseTempDir(out var temp)) |
|||
{ |
|||
foreach (var l in tfm) |
|||
l.Entry.ExtractToFile(Path.Combine(temp, l.Name)); |
|||
foreach (var l in tfm) |
|||
PatchRefAssembly(Path.Combine(temp, l.Name)); |
|||
foreach (var l in tfm) |
|||
archive.CreateEntryFromFile(Path.Combine(temp, l.Name), $"ref/{l.Tfm}/{l.Name}"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Metadata; |
|||
|
|||
[AttributeUsage(AttributeTargets.Interface)] |
|||
public sealed class PrivateApiAttribute : Attribute |
|||
{ |
|||
|
|||
} |
|||
@ -1,6 +1,18 @@ |
|||
using System.Collections.Generic; |
|||
using Avalonia.Collections; |
|||
|
|||
namespace Avalonia |
|||
{ |
|||
public sealed class Points : AvaloniaList<Point> { } |
|||
public sealed class Points : AvaloniaList<Point> |
|||
{ |
|||
public Points() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public Points(IEnumerable<Point> points) : base(points) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,71 @@ |
|||
using System.Collections.Generic; |
|||
using System.Reflection.Emit; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; |
|||
using XamlX; |
|||
using XamlX.Ast; |
|||
using XamlX.Emit; |
|||
using XamlX.IL; |
|||
using XamlX.Transform; |
|||
using XamlX.TypeSystem; |
|||
|
|||
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.AstNodes |
|||
{ |
|||
class AvaloniaXamlIlArrayConstantAstNode : XamlAstNode, IXamlAstValueNode, IXamlAstILEmitableNode |
|||
{ |
|||
private readonly IXamlType _elementType; |
|||
private readonly IReadOnlyList<IXamlAstValueNode> _values; |
|||
|
|||
public AvaloniaXamlIlArrayConstantAstNode(IXamlLineInfo lineInfo, IXamlType arrayType, IXamlType elementType, IReadOnlyList<IXamlAstValueNode> values) : base(lineInfo) |
|||
{ |
|||
_elementType = elementType; |
|||
_values = values; |
|||
|
|||
Type = new XamlAstClrTypeReference(lineInfo, arrayType, false); |
|||
|
|||
foreach (var element in values) |
|||
{ |
|||
if (!elementType.IsAssignableFrom(element.Type.GetClrType())) |
|||
{ |
|||
throw new XamlParseException("x:Array element is not assignable to the array element type!", lineInfo); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public IXamlAstTypeReference Type { get; } |
|||
|
|||
public XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen) |
|||
{ |
|||
codeGen.Ldc_I4(_values.Count) |
|||
.Newarr(_elementType); |
|||
|
|||
for (var index = 0; index < _values.Count; index++) |
|||
{ |
|||
var value = _values[index]; |
|||
|
|||
codeGen |
|||
.Dup() |
|||
.Ldc_I4(index); |
|||
|
|||
context.Emit(value, codeGen, _elementType); |
|||
|
|||
if (value.Type.GetClrType() is { IsValueType: true } valTypeInObjArr) |
|||
{ |
|||
if (!_elementType.IsValueType) |
|||
{ |
|||
codeGen.Box(valTypeInObjArr); |
|||
} |
|||
// It seems like ASM codegen for "stelem valuetype" and "stelem.i4" is identical,
|
|||
// so we don't need to try to optimize it here.
|
|||
codeGen.Emit(OpCodes.Stelem, valTypeInObjArr); |
|||
} |
|||
else |
|||
{ |
|||
codeGen.Stelem_ref(); |
|||
} |
|||
} |
|||
|
|||
return XamlILNodeEmitResult.Type(0, Type.GetClrType()); |
|||
} |
|||
} |
|||
} |
|||
@ -1 +1 @@ |
|||
Subproject commit 5dd0b042e144e677638224c49fec16dab66143e8 |
|||
Subproject commit 5d1025f30d0ed6d8f419d82959c148276301f393 |
|||
Loading…
Reference in new issue