XamlCompilerTaskExecutor.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Reflection;
  6. using System.Text;
  7. using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
  8. using Microsoft.Build.Framework;
  9. using Mono.Cecil;
  10. using Avalonia.Utilities;
  11. using Mono.Cecil.Cil;
  12. using Mono.Cecil.Rocks;
  13. using XamlX;
  14. using XamlX.Ast;
  15. using XamlX.Parsers;
  16. using XamlX.Transform;
  17. using XamlX.TypeSystem;
  18. using FieldAttributes = Mono.Cecil.FieldAttributes;
  19. using MethodAttributes = Mono.Cecil.MethodAttributes;
  20. using TypeAttributes = Mono.Cecil.TypeAttributes;
  21. using XamlX.IL;
  22. namespace Avalonia.Build.Tasks
  23. {
  24. public static partial class XamlCompilerTaskExecutor
  25. {
  26. static bool CheckXamlName(IResource r) => r.Name.ToLowerInvariant().EndsWith(".xaml")
  27. || r.Name.ToLowerInvariant().EndsWith(".paml")
  28. || r.Name.ToLowerInvariant().EndsWith(".axaml");
  29. public class CompileResult
  30. {
  31. public bool Success { get; set; }
  32. public bool WrittenFile { get; }
  33. public CompileResult(bool success, bool writtenFile = false)
  34. {
  35. Success = success;
  36. WrittenFile = writtenFile;
  37. }
  38. }
  39. public static CompileResult Compile(IBuildEngine engine, string input, string[] references, string projectDirectory,
  40. string output, bool verifyIl, MessageImportance logImportance, string strongNameKey)
  41. {
  42. var typeSystem = new CecilTypeSystem(references.Concat(new[] {input}), input);
  43. var asm = typeSystem.TargetAssemblyDefinition;
  44. var emres = new EmbeddedResources(asm);
  45. var avares = new AvaloniaResources(asm, projectDirectory);
  46. if (avares.Resources.Count(CheckXamlName) == 0 && emres.Resources.Count(CheckXamlName) == 0)
  47. // Nothing to do
  48. return new CompileResult(true);
  49. var clrPropertiesDef = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlHelpers",
  50. TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
  51. asm.MainModule.Types.Add(clrPropertiesDef);
  52. var indexerAccessorClosure = new TypeDefinition("CompiledAvaloniaXaml", "!IndexerAccessorFactoryClosure",
  53. TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
  54. asm.MainModule.Types.Add(indexerAccessorClosure);
  55. var (xamlLanguage , emitConfig) = AvaloniaXamlIlLanguage.Configure(typeSystem);
  56. var compilerConfig = new AvaloniaXamlIlCompilerConfiguration(typeSystem,
  57. typeSystem.TargetAssembly,
  58. xamlLanguage,
  59. XamlXmlnsMappings.Resolve(typeSystem, xamlLanguage),
  60. AvaloniaXamlIlLanguage.CustomValueConverter,
  61. new XamlIlClrPropertyInfoEmitter(typeSystem.CreateTypeBuilder(clrPropertiesDef)),
  62. new XamlIlPropertyInfoAccessorFactoryEmitter(typeSystem.CreateTypeBuilder(indexerAccessorClosure)));
  63. var contextDef = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlContext",
  64. TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
  65. asm.MainModule.Types.Add(contextDef);
  66. var contextClass = XamlILContextDefinition.GenerateContextClass(typeSystem.CreateTypeBuilder(contextDef), typeSystem,
  67. xamlLanguage, emitConfig);
  68. var compiler = new AvaloniaXamlIlCompiler(compilerConfig, emitConfig, contextClass) { EnableIlVerification = verifyIl };
  69. var editorBrowsableAttribute = typeSystem
  70. .GetTypeReference(typeSystem.FindType("System.ComponentModel.EditorBrowsableAttribute"))
  71. .Resolve();
  72. var editorBrowsableCtor =
  73. asm.MainModule.ImportReference(editorBrowsableAttribute.GetConstructors()
  74. .First(c => c.Parameters.Count == 1));
  75. var runtimeHelpers = typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers");
  76. var createRootServiceProviderMethod = asm.MainModule.ImportReference(
  77. typeSystem.GetTypeReference(runtimeHelpers).Resolve().Methods
  78. .First(x => x.Name == "CreateRootServiceProviderV2"));
  79. var loaderDispatcherDef = new TypeDefinition("CompiledAvaloniaXaml", "!XamlLoader",
  80. TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
  81. loaderDispatcherDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor)
  82. {
  83. ConstructorArguments = {new CustomAttributeArgument(editorBrowsableCtor.Parameters[0].ParameterType, 1)}
  84. });
  85. var loaderDispatcherMethod = new MethodDefinition("TryLoad",
  86. MethodAttributes.Static | MethodAttributes.Public,
  87. asm.MainModule.TypeSystem.Object)
  88. {
  89. Parameters = {new ParameterDefinition(asm.MainModule.TypeSystem.String)}
  90. };
  91. loaderDispatcherDef.Methods.Add(loaderDispatcherMethod);
  92. asm.MainModule.Types.Add(loaderDispatcherDef);
  93. var stringEquals = asm.MainModule.ImportReference(asm.MainModule.TypeSystem.String.Resolve().Methods.First(
  94. m =>
  95. m.IsStatic && m.Name == "Equals" && m.Parameters.Count == 2 &&
  96. m.ReturnType.FullName == "System.Boolean"
  97. && m.Parameters[0].ParameterType.FullName == "System.String"
  98. && m.Parameters[1].ParameterType.FullName == "System.String"));
  99. bool CompileGroup(IResourceGroup group)
  100. {
  101. var typeDef = new TypeDefinition("CompiledAvaloniaXaml", "!"+ group.Name,
  102. TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
  103. typeDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor)
  104. {
  105. ConstructorArguments = {new CustomAttributeArgument(editorBrowsableCtor.Parameters[0].ParameterType, 1)}
  106. });
  107. asm.MainModule.Types.Add(typeDef);
  108. var builder = typeSystem.CreateTypeBuilder(typeDef);
  109. foreach (var res in group.Resources.Where(CheckXamlName).OrderBy(x=>x.FilePath.ToLowerInvariant()))
  110. {
  111. try
  112. {
  113. engine.LogMessage($"XAMLIL: {res.Name} -> {res.Uri}", logImportance);
  114. // StreamReader is needed here to handle BOM
  115. var xaml = new StreamReader(new MemoryStream(res.FileContents)).ReadToEnd();
  116. var parsed = XDocumentXamlParser.Parse(xaml);
  117. var initialRoot = (XamlAstObjectNode)parsed.Root;
  118. var precompileDirective = initialRoot.Children.OfType<XamlAstXmlDirective>()
  119. .FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Precompile");
  120. if (precompileDirective != null)
  121. {
  122. var precompileText = (precompileDirective.Values[0] as XamlAstTextNode)?.Text.Trim()
  123. .ToLowerInvariant();
  124. if (precompileText == "false")
  125. continue;
  126. if (precompileText != "true")
  127. throw new XamlParseException("Invalid value for x:Precompile", precompileDirective);
  128. }
  129. var classDirective = initialRoot.Children.OfType<XamlAstXmlDirective>()
  130. .FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Class");
  131. IXamlType classType = null;
  132. if (classDirective != null)
  133. {
  134. if (classDirective.Values.Count != 1 || !(classDirective.Values[0] is XamlAstTextNode tn))
  135. throw new XamlParseException("x:Class should have a string value", classDirective);
  136. classType = typeSystem.TargetAssembly.FindType(tn.Text);
  137. if (classType == null)
  138. throw new XamlParseException($"Unable to find type `{tn.Text}`", classDirective);
  139. compiler.OverrideRootType(parsed,
  140. new XamlAstClrTypeReference(classDirective, classType, false));
  141. initialRoot.Children.Remove(classDirective);
  142. }
  143. compiler.Transform(parsed);
  144. var populateName = classType == null ? "Populate:" + res.Name : "!XamlIlPopulate";
  145. var buildName = classType == null ? "Build:" + res.Name : null;
  146. var classTypeDefinition =
  147. classType == null ? null : typeSystem.GetTypeReference(classType).Resolve();
  148. var populateBuilder = classTypeDefinition == null ?
  149. builder :
  150. typeSystem.CreateTypeBuilder(classTypeDefinition);
  151. compiler.Compile(parsed, contextClass,
  152. compiler.DefinePopulateMethod(populateBuilder, parsed, populateName,
  153. classTypeDefinition == null),
  154. buildName == null ? null : compiler.DefineBuildMethod(builder, parsed, buildName, true),
  155. builder.DefineSubType(compilerConfig.WellKnownTypes.Object, "NamespaceInfo:" + res.Name,
  156. true),
  157. (closureName, closureBaseType) =>
  158. populateBuilder.DefineSubType(closureBaseType, closureName, false),
  159. res.Uri, res
  160. );
  161. if (classTypeDefinition != null)
  162. {
  163. var compiledPopulateMethod = typeSystem.GetTypeReference(populateBuilder).Resolve()
  164. .Methods.First(m => m.Name == populateName);
  165. var designLoaderFieldType = typeSystem
  166. .GetType("System.Action`1")
  167. .MakeGenericType(typeSystem.GetType("System.Object"));
  168. var designLoaderFieldTypeReference = (GenericInstanceType)typeSystem.GetTypeReference(designLoaderFieldType);
  169. designLoaderFieldTypeReference.GenericArguments[0] =
  170. asm.MainModule.ImportReference(designLoaderFieldTypeReference.GenericArguments[0]);
  171. designLoaderFieldTypeReference = (GenericInstanceType)
  172. asm.MainModule.ImportReference(designLoaderFieldTypeReference);
  173. var designLoaderLoad =
  174. typeSystem.GetMethodReference(
  175. designLoaderFieldType.Methods.First(m => m.Name == "Invoke"));
  176. designLoaderLoad =
  177. asm.MainModule.ImportReference(designLoaderLoad);
  178. designLoaderLoad.DeclaringType = designLoaderFieldTypeReference;
  179. var designLoaderField = new FieldDefinition("!XamlIlPopulateOverride",
  180. FieldAttributes.Static | FieldAttributes.Private, designLoaderFieldTypeReference);
  181. classTypeDefinition.Fields.Add(designLoaderField);
  182. const string TrampolineName = "!XamlIlPopulateTrampoline";
  183. var trampoline = new MethodDefinition(TrampolineName,
  184. MethodAttributes.Static | MethodAttributes.Private, asm.MainModule.TypeSystem.Void);
  185. trampoline.Parameters.Add(new ParameterDefinition(classTypeDefinition));
  186. classTypeDefinition.Methods.Add(trampoline);
  187. var regularStart = Instruction.Create(OpCodes.Call, createRootServiceProviderMethod);
  188. trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldsfld, designLoaderField));
  189. trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Brfalse, regularStart));
  190. trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldsfld, designLoaderField));
  191. trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0));
  192. trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, designLoaderLoad));
  193. trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
  194. trampoline.Body.Instructions.Add(regularStart);
  195. trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0));
  196. trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, compiledPopulateMethod));
  197. trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
  198. CopyDebugDocument(trampoline, compiledPopulateMethod);
  199. var foundXamlLoader = false;
  200. // Find AvaloniaXamlLoader.Load(this) and replace it with !XamlIlPopulateTrampoline(this)
  201. foreach (var method in classTypeDefinition.Methods
  202. .Where(m => !m.Attributes.HasFlag(MethodAttributes.Static)))
  203. {
  204. var i = method.Body.Instructions;
  205. for (var c = 1; c < i.Count; c++)
  206. {
  207. if (i[c].OpCode == OpCodes.Call)
  208. {
  209. var op = i[c].Operand as MethodReference;
  210. // TODO: Throw an error
  211. // This usually happens when same XAML resource was added twice for some weird reason
  212. // We currently support it for dual-named default theme resource
  213. if (op != null
  214. && op.Name == TrampolineName)
  215. {
  216. foundXamlLoader = true;
  217. break;
  218. }
  219. if (op != null
  220. && op.Name == "Load"
  221. && op.Parameters.Count == 1
  222. && op.Parameters[0].ParameterType.FullName == "System.Object"
  223. && op.DeclaringType.FullName == "Avalonia.Markup.Xaml.AvaloniaXamlLoader")
  224. {
  225. if (MatchThisCall(i, c - 1))
  226. {
  227. i[c].Operand = trampoline;
  228. foundXamlLoader = true;
  229. }
  230. }
  231. }
  232. }
  233. }
  234. if (!foundXamlLoader)
  235. {
  236. var ctors = classTypeDefinition.GetConstructors()
  237. .Where(c => !c.IsStatic).ToList();
  238. // We can inject xaml loader into default constructor
  239. if (ctors.Count == 1 && ctors[0].Body.Instructions.Count(o=>o.OpCode != OpCodes.Nop) == 3)
  240. {
  241. var i = ctors[0].Body.Instructions;
  242. var retIdx = i.IndexOf(i.Last(x => x.OpCode == OpCodes.Ret));
  243. i.Insert(retIdx, Instruction.Create(OpCodes.Call, trampoline));
  244. i.Insert(retIdx, Instruction.Create(OpCodes.Ldarg_0));
  245. }
  246. else
  247. {
  248. throw new InvalidProgramException(
  249. $"No call to AvaloniaXamlLoader.Load(this) call found anywhere in the type {classType.FullName} and type seems to have custom constructors.");
  250. }
  251. }
  252. }
  253. if (buildName != null || classTypeDefinition != null)
  254. {
  255. var compiledBuildMethod = buildName == null ?
  256. null :
  257. typeSystem.GetTypeReference(builder).Resolve()
  258. .Methods.First(m => m.Name == buildName);
  259. var parameterlessConstructor = compiledBuildMethod != null ?
  260. null :
  261. classTypeDefinition.GetConstructors().FirstOrDefault(c =>
  262. c.IsPublic && !c.IsStatic && !c.HasParameters);
  263. if (compiledBuildMethod != null || parameterlessConstructor != null)
  264. {
  265. var i = loaderDispatcherMethod.Body.Instructions;
  266. var nop = Instruction.Create(OpCodes.Nop);
  267. i.Add(Instruction.Create(OpCodes.Ldarg_0));
  268. i.Add(Instruction.Create(OpCodes.Ldstr, res.Uri));
  269. i.Add(Instruction.Create(OpCodes.Call, stringEquals));
  270. i.Add(Instruction.Create(OpCodes.Brfalse, nop));
  271. if (parameterlessConstructor != null)
  272. i.Add(Instruction.Create(OpCodes.Newobj, parameterlessConstructor));
  273. else
  274. {
  275. i.Add(Instruction.Create(OpCodes.Call, createRootServiceProviderMethod));
  276. i.Add(Instruction.Create(OpCodes.Call, compiledBuildMethod));
  277. }
  278. i.Add(Instruction.Create(OpCodes.Ret));
  279. i.Add(nop);
  280. }
  281. }
  282. }
  283. catch (Exception e)
  284. {
  285. int lineNumber = 0, linePosition = 0;
  286. if (e is XamlParseException xe)
  287. {
  288. lineNumber = xe.LineNumber;
  289. linePosition = xe.LinePosition;
  290. }
  291. engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", res.FilePath,
  292. lineNumber, linePosition, lineNumber, linePosition,
  293. e.Message, "", "Avalonia"));
  294. return false;
  295. }
  296. res.Remove();
  297. }
  298. return true;
  299. }
  300. if (emres.Resources.Count(CheckXamlName) != 0)
  301. if (!CompileGroup(emres))
  302. return new CompileResult(false);
  303. if (avares.Resources.Count(CheckXamlName) != 0)
  304. {
  305. if (!CompileGroup(avares))
  306. return new CompileResult(false);
  307. avares.Save();
  308. }
  309. loaderDispatcherMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ldnull));
  310. loaderDispatcherMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
  311. var writerParameters = new WriterParameters { WriteSymbols = asm.MainModule.HasSymbols };
  312. if (!string.IsNullOrWhiteSpace(strongNameKey))
  313. writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey);
  314. asm.Write(output, writerParameters);
  315. return new CompileResult(true, true);
  316. }
  317. }
  318. }