RefAssemblyGenerator.cs 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. using System.Collections.Generic;
  2. using System.IO;
  3. using System.IO.Compression;
  4. using System.Linq;
  5. using ILRepacking;
  6. using Mono.Cecil;
  7. using Mono.Cecil.Cil;
  8. public class RefAssemblyGenerator
  9. {
  10. class Resolver : DefaultAssemblyResolver, IAssemblyResolver
  11. {
  12. private readonly string _dir;
  13. Dictionary<string, AssemblyDefinition> _cache = new();
  14. public Resolver(string dir)
  15. {
  16. _dir = dir;
  17. }
  18. public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters)
  19. {
  20. if (_cache.TryGetValue(name.Name, out var asm))
  21. return asm;
  22. var path = Path.Combine(_dir, name.Name + ".dll");
  23. if (File.Exists(path))
  24. return _cache[name.Name] = AssemblyDefinition.ReadAssembly(path, parameters);
  25. return base.Resolve(name, parameters);
  26. }
  27. }
  28. public static void PatchRefAssembly(string file)
  29. {
  30. var reader = typeof(RefAssemblyGenerator).Assembly.GetManifestResourceStream("avalonia.snk");
  31. var snk = new byte[reader.Length];
  32. reader.Read(snk, 0, snk.Length);
  33. var def = AssemblyDefinition.ReadAssembly(file, new ReaderParameters
  34. {
  35. ReadWrite = true,
  36. InMemory = true,
  37. ReadSymbols = true,
  38. SymbolReaderProvider = new DefaultSymbolReaderProvider(false),
  39. AssemblyResolver = new Resolver(Path.GetDirectoryName(file))
  40. });
  41. var obsoleteAttribute = def.MainModule.ImportReference(new TypeReference("System", "ObsoleteAttribute", def.MainModule,
  42. def.MainModule.TypeSystem.CoreLibrary));
  43. var obsoleteCtor = def.MainModule.ImportReference(new MethodReference(".ctor",
  44. def.MainModule.TypeSystem.Void, obsoleteAttribute)
  45. {
  46. Parameters = { new ParameterDefinition(def.MainModule.TypeSystem.String) }
  47. });
  48. foreach(var t in def.MainModule.Types)
  49. ProcessType(t, obsoleteCtor);
  50. def.Write(file, new WriterParameters()
  51. {
  52. StrongNameKeyBlob = snk,
  53. WriteSymbols = def.MainModule.HasSymbols,
  54. SymbolWriterProvider = new EmbeddedPortablePdbWriterProvider(),
  55. DeterministicMvid = def.MainModule.HasSymbols
  56. });
  57. }
  58. static bool HasPrivateApi(IEnumerable<CustomAttribute> attrs) => attrs.Any(a =>
  59. a.AttributeType.FullName == "Avalonia.Metadata.PrivateApiAttribute");
  60. static void ProcessType(TypeDefinition type, MethodReference obsoleteCtor)
  61. {
  62. foreach (var nested in type.NestedTypes)
  63. ProcessType(nested, obsoleteCtor);
  64. var hideMembers = (type.IsInterface && type.Name.EndsWith("Impl"))
  65. || HasPrivateApi(type.CustomAttributes);
  66. var injectMethod = hideMembers
  67. || type.CustomAttributes.Any(a =>
  68. a.AttributeType.FullName == "Avalonia.Metadata.NotClientImplementableAttribute");
  69. if (injectMethod)
  70. {
  71. type.Methods.Add(new MethodDefinition(
  72. "(This interface or abstract class is -not- implementable by user code !)",
  73. MethodAttributes.Assembly
  74. | MethodAttributes.Abstract
  75. | MethodAttributes.NewSlot
  76. | MethodAttributes.HideBySig, type.Module.TypeSystem.Void));
  77. }
  78. var forceUnstable = type.CustomAttributes.FirstOrDefault(a =>
  79. a.AttributeType.FullName == "Avalonia.Metadata.UnstableAttribute");
  80. foreach (var m in type.Methods)
  81. {
  82. if (hideMembers || HasPrivateApi(m.CustomAttributes))
  83. {
  84. HideMethod(m);
  85. }
  86. MarkAsUnstable(m, obsoleteCtor, forceUnstable);
  87. }
  88. foreach (var p in type.Properties)
  89. {
  90. if (HasPrivateApi(p.CustomAttributes))
  91. {
  92. if (p.SetMethod != null)
  93. HideMethod(p.SetMethod);
  94. if (p.GetMethod != null)
  95. HideMethod(p.GetMethod);
  96. }
  97. }
  98. foreach (var f in type.Fields)
  99. {
  100. if (hideMembers || HasPrivateApi(f.CustomAttributes))
  101. {
  102. var dflags = FieldAttributes.Public | FieldAttributes.Family | FieldAttributes.FamORAssem |
  103. FieldAttributes.FamANDAssem | FieldAttributes.Assembly;
  104. f.Attributes = ((f.Attributes | dflags) ^ dflags) | FieldAttributes.Assembly;
  105. }
  106. }
  107. foreach (var cl in type.NestedTypes)
  108. {
  109. ProcessType(cl, obsoleteCtor);
  110. if (hideMembers)
  111. {
  112. var dflags = TypeAttributes.Public;
  113. cl.Attributes = ((cl.Attributes | dflags) ^ dflags) | TypeAttributes.NotPublic;
  114. }
  115. }
  116. foreach (var m in type.Properties)
  117. MarkAsUnstable(m, obsoleteCtor, forceUnstable);
  118. foreach (var m in type.Events)
  119. MarkAsUnstable(m, obsoleteCtor, forceUnstable);
  120. }
  121. static void HideMethod(MethodDefinition m)
  122. {
  123. var dflags = MethodAttributes.Public | MethodAttributes.Family | MethodAttributes.FamORAssem |
  124. MethodAttributes.FamANDAssem | MethodAttributes.Assembly;
  125. m.Attributes = ((m.Attributes | dflags) ^ dflags) | MethodAttributes.Assembly;
  126. }
  127. static void MarkAsUnstable(IMemberDefinition def, MethodReference obsoleteCtor, ICustomAttribute unstableAttribute)
  128. {
  129. if (def.CustomAttributes.Any(a => a.AttributeType.FullName == "System.ObsoleteAttribute"))
  130. return;
  131. unstableAttribute = def.CustomAttributes.FirstOrDefault(a =>
  132. a.AttributeType.FullName == "Avalonia.Metadata.UnstableAttribute") ?? unstableAttribute;
  133. if (unstableAttribute is null)
  134. return;
  135. var message = unstableAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString();
  136. if (string.IsNullOrEmpty(message))
  137. {
  138. message = "This is a part of unstable API and can be changed in minor releases. Consider replacing it with alternatives or reach out developers on GitHub.";
  139. }
  140. def.CustomAttributes.Add(new CustomAttribute(obsoleteCtor)
  141. {
  142. ConstructorArguments =
  143. {
  144. new CustomAttributeArgument(obsoleteCtor.Module.TypeSystem.String, message)
  145. }
  146. });
  147. }
  148. public static void GenerateRefAsmsInPackage(string packagePath)
  149. {
  150. using (var archive = new ZipArchive(File.Open(packagePath, FileMode.Open, FileAccess.ReadWrite),
  151. ZipArchiveMode.Update))
  152. {
  153. foreach (var entry in archive.Entries.ToList())
  154. {
  155. if (entry.FullName.StartsWith("ref/"))
  156. entry.Delete();
  157. }
  158. foreach (var entry in archive.Entries.ToList())
  159. {
  160. if (entry.FullName.StartsWith("lib/") && entry.Name.EndsWith(".xml"))
  161. {
  162. var newEntry = archive.CreateEntry("ref/" + entry.FullName.Substring(4),
  163. CompressionLevel.Optimal);
  164. using (var src = entry.Open())
  165. using (var dst = newEntry.Open())
  166. src.CopyTo(dst);
  167. }
  168. }
  169. var libs = archive.Entries.Where(e => e.FullName.StartsWith("lib/") && e.FullName.EndsWith(".dll"))
  170. .Select((e => new { s = e.FullName.Split('/'), e = e }))
  171. .Select(e => new { Tfm = e.s[1], Name = e.s[2], Entry = e.e })
  172. .GroupBy(x => x.Tfm);
  173. foreach(var tfm in libs)
  174. using (Helpers.UseTempDir(out var temp))
  175. {
  176. foreach (var l in tfm)
  177. l.Entry.ExtractToFile(Path.Combine(temp, l.Name));
  178. foreach (var l in tfm)
  179. PatchRefAssembly(Path.Combine(temp, l.Name));
  180. foreach (var l in tfm)
  181. archive.CreateEntryFromFile(Path.Combine(temp, l.Name), $"ref/{l.Tfm}/{l.Name}");
  182. }
  183. }
  184. }
  185. }