Browse Source
Generate fake ref assemblies with patched *Impl and [NotClientImplementable] interfacespull/11072/head
committed by
GitHub
10 changed files with 219 additions and 18 deletions
@ -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 |
||||
|
{ |
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue