XamlCompilerTaskExecutor.cs 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
  6. using Avalonia.Platform;
  7. using Microsoft.Build.Framework;
  8. using Mono.Cecil;
  9. using Mono.Cecil.Cil;
  10. using Mono.Cecil.Rocks;
  11. using XamlX;
  12. using XamlX.Ast;
  13. using XamlX.Parsers;
  14. using XamlX.Transform;
  15. using XamlX.TypeSystem;
  16. using FieldAttributes = Mono.Cecil.FieldAttributes;
  17. using MethodAttributes = Mono.Cecil.MethodAttributes;
  18. using TypeAttributes = Mono.Cecil.TypeAttributes;
  19. using XamlX.IL;
  20. namespace Avalonia.Build.Tasks
  21. {
  22. public static partial class XamlCompilerTaskExecutor
  23. {
  24. private const string CompiledAvaloniaXamlNamespace = "CompiledAvaloniaXaml";
  25. static bool CheckXamlName(IResource r) => r.Name.ToLowerInvariant().EndsWith(".xaml")
  26. || r.Name.ToLowerInvariant().EndsWith(".paml")
  27. || r.Name.ToLowerInvariant().EndsWith(".axaml");
  28. public class CompileResult
  29. {
  30. public bool Success { get; set; }
  31. public bool WrittenFile { get; }
  32. public CompileResult(bool success, bool writtenFile = false)
  33. {
  34. Success = success;
  35. WrittenFile = writtenFile;
  36. }
  37. }
  38. public static CompileResult Compile(IBuildEngine engine,
  39. string input, string output,
  40. string refInput, string refOutput,
  41. string[] references, string projectDirectory,
  42. bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance, string strongNameKey,
  43. bool skipXamlCompilation)
  44. {
  45. return Compile(engine, input, output, refInput, refOutput, references, projectDirectory, verifyIl, defaultCompileBindings, logImportance, strongNameKey, skipXamlCompilation, debuggerLaunch:false);
  46. }
  47. internal static CompileResult Compile(IBuildEngine engine,
  48. string input, string output,
  49. string refInput, string refOutput,
  50. string[] references, string projectDirectory,
  51. bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance, string strongNameKey, bool skipXamlCompilation, bool debuggerLaunch)
  52. {
  53. try
  54. {
  55. references = references.Where(r => !r.ToLowerInvariant().EndsWith("avalonia.build.tasks.dll")).ToArray();
  56. var typeSystem = new CecilTypeSystem(references, input);
  57. var refTypeSystem = !string.IsNullOrWhiteSpace(refInput) && File.Exists(refInput) ? new CecilTypeSystem(references, refInput) : null;
  58. var asm = typeSystem.TargetAssemblyDefinition;
  59. var refAsm = refTypeSystem?.TargetAssemblyDefinition;
  60. if (!skipXamlCompilation)
  61. {
  62. var compileRes = CompileCore(engine, typeSystem, projectDirectory, verifyIl, defaultCompileBindings, logImportance, debuggerLaunch);
  63. if (compileRes == null)
  64. return new CompileResult(true);
  65. if (compileRes == false)
  66. return new CompileResult(false);
  67. if (refTypeSystem is not null)
  68. {
  69. var refCompileRes = CompileCoreForRefAssembly(engine, typeSystem, refTypeSystem);
  70. if (refCompileRes == false)
  71. return new CompileResult(false);
  72. }
  73. }
  74. var writerParameters = new WriterParameters { WriteSymbols = asm.MainModule.HasSymbols };
  75. if (!string.IsNullOrWhiteSpace(strongNameKey))
  76. writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey);
  77. asm.Write(output, writerParameters);
  78. var refWriterParameters = new WriterParameters { WriteSymbols = false };
  79. if (!string.IsNullOrWhiteSpace(strongNameKey))
  80. writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey);
  81. refAsm?.Write(refOutput, refWriterParameters);
  82. return new CompileResult(true, true);
  83. }
  84. catch (Exception ex)
  85. {
  86. engine.LogError(BuildEngineErrorCode.Unknown, "", ex);
  87. return new CompileResult(false);
  88. }
  89. }
  90. static bool? CompileCore(IBuildEngine engine, CecilTypeSystem typeSystem,
  91. string projectDirectory, bool verifyIl,
  92. bool defaultCompileBindings,
  93. MessageImportance logImportance
  94. , bool debuggerLaunch = false)
  95. {
  96. if (debuggerLaunch)
  97. {
  98. // According this https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.debugger.launch?view=net-6.0#remarks
  99. // documentation, on not windows platform Debugger.Launch() always return true without running a debugger.
  100. if (System.Diagnostics.Debugger.Launch())
  101. {
  102. // Set timeout at 1 minut.
  103. var time = new System.Diagnostics.Stopwatch();
  104. var timeout = TimeSpan.FromMinutes(1);
  105. time.Start();
  106. // wait for the debugger to be attacked or timeout.
  107. while (!System.Diagnostics.Debugger.IsAttached && time.Elapsed < timeout)
  108. {
  109. engine.LogMessage($"[PID:{System.Diagnostics.Process.GetCurrentProcess().Id}] Wating attach debugger. Elapsed {time.Elapsed}...", MessageImportance.High);
  110. System.Threading.Thread.Sleep(100);
  111. }
  112. time.Stop();
  113. if (time.Elapsed >= timeout)
  114. {
  115. engine.LogMessage("Wating attach debugger timeout.", MessageImportance.Normal);
  116. }
  117. }
  118. else
  119. {
  120. engine.LogMessage("Debugging cancelled.", MessageImportance.Normal);
  121. }
  122. }
  123. // Some transformers might need to parse "avares://" Uri.
  124. AssetLoader.RegisterResUriParsers();
  125. var asm = typeSystem.TargetAssemblyDefinition;
  126. var avares = new AvaloniaResources(asm, projectDirectory);
  127. if (avares.Resources.Count(CheckXamlName) == 0)
  128. // Nothing to do
  129. return null;
  130. if (typeSystem.FindType("System.Reflection.AssemblyMetadataAttribute") is {} asmMetadata)
  131. {
  132. var ctor = asm.MainModule.ImportReference(typeSystem.GetTypeReference(asmMetadata).Resolve()
  133. .GetConstructors().First(c => c.Parameters.Count == 2).Resolve());
  134. var strType = asm.MainModule.TypeSystem.String;
  135. var arg1 = new CustomAttributeArgument(strType, "AvaloniaUseCompiledBindingsByDefault");
  136. var arg2 = new CustomAttributeArgument(strType, defaultCompileBindings.ToString());
  137. asm.CustomAttributes.Add(new CustomAttribute(ctor) { ConstructorArguments = { arg1, arg2 } });
  138. }
  139. var clrPropertiesDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "XamlIlHelpers",
  140. TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
  141. asm.MainModule.Types.Add(clrPropertiesDef);
  142. var indexerAccessorClosure = new TypeDefinition(CompiledAvaloniaXamlNamespace, "!IndexerAccessorFactoryClosure",
  143. TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
  144. asm.MainModule.Types.Add(indexerAccessorClosure);
  145. var trampolineBuilder = new TypeDefinition(CompiledAvaloniaXamlNamespace, "XamlIlTrampolines",
  146. TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
  147. asm.MainModule.Types.Add(trampolineBuilder);
  148. var (xamlLanguage , emitConfig) = AvaloniaXamlIlLanguage.Configure(typeSystem);
  149. var compilerConfig = new AvaloniaXamlIlCompilerConfiguration(typeSystem,
  150. typeSystem.TargetAssembly,
  151. xamlLanguage,
  152. XamlXmlnsMappings.Resolve(typeSystem, xamlLanguage),
  153. AvaloniaXamlIlLanguage.CustomValueConverter,
  154. new XamlIlClrPropertyInfoEmitter(typeSystem.CreateTypeBuilder(clrPropertiesDef)),
  155. new XamlIlPropertyInfoAccessorFactoryEmitter(typeSystem.CreateTypeBuilder(indexerAccessorClosure)),
  156. new XamlIlTrampolineBuilder(typeSystem.CreateTypeBuilder(trampolineBuilder)),
  157. new DeterministicIdGenerator());
  158. var contextDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "XamlIlContext",
  159. TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
  160. asm.MainModule.Types.Add(contextDef);
  161. var contextClass = XamlILContextDefinition.GenerateContextClass(typeSystem.CreateTypeBuilder(contextDef), typeSystem,
  162. xamlLanguage, emitConfig);
  163. var compiler = new AvaloniaXamlIlCompiler(compilerConfig, emitConfig, contextClass) { EnableIlVerification = verifyIl, DefaultCompileBindings = defaultCompileBindings };
  164. var editorBrowsableAttribute = typeSystem
  165. .GetTypeReference(typeSystem.FindType("System.ComponentModel.EditorBrowsableAttribute"))
  166. .Resolve();
  167. var editorBrowsableCtor =
  168. asm.MainModule.ImportReference(editorBrowsableAttribute.GetConstructors()
  169. .First(c => c.Parameters.Count == 1));
  170. var runtimeHelpers = typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers");
  171. var createRootServiceProviderMethod = asm.MainModule.ImportReference(
  172. typeSystem.GetTypeReference(runtimeHelpers).Resolve().Methods
  173. .First(x => x.Name == "CreateRootServiceProviderV3"));
  174. var serviceProviderType = createRootServiceProviderMethod.ReturnType;
  175. var loaderDispatcherDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "!XamlLoader",
  176. TypeAttributes.Class | TypeAttributes.Public, asm.MainModule.TypeSystem.Object);
  177. loaderDispatcherDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor)
  178. {
  179. ConstructorArguments = {new CustomAttributeArgument(editorBrowsableCtor.Parameters[0].ParameterType, 1)}
  180. });
  181. var loaderDispatcherMethod = new MethodDefinition("TryLoad",
  182. MethodAttributes.Static | MethodAttributes.Public,
  183. asm.MainModule.TypeSystem.Object)
  184. {
  185. Parameters =
  186. {
  187. new ParameterDefinition(serviceProviderType),
  188. new ParameterDefinition(asm.MainModule.TypeSystem.String)
  189. },
  190. };
  191. var loaderDispatcherMethodOld = new MethodDefinition("TryLoad",
  192. MethodAttributes.Static | MethodAttributes.Public,
  193. asm.MainModule.TypeSystem.Object)
  194. {
  195. Parameters =
  196. {
  197. new ParameterDefinition(asm.MainModule.TypeSystem.String)
  198. },
  199. Body =
  200. {
  201. Instructions =
  202. {
  203. Instruction.Create(OpCodes.Ldnull),
  204. Instruction.Create(OpCodes.Ldarg_0),
  205. Instruction.Create(OpCodes.Call, loaderDispatcherMethod),
  206. Instruction.Create(OpCodes.Ret)
  207. }
  208. }
  209. };
  210. loaderDispatcherDef.Methods.Add(loaderDispatcherMethod);
  211. loaderDispatcherDef.Methods.Add(loaderDispatcherMethodOld);
  212. asm.MainModule.Types.Add(loaderDispatcherDef);
  213. var stringEquals = asm.MainModule.ImportReference(asm.MainModule.TypeSystem.String.Resolve().Methods.First(
  214. m =>
  215. m.IsStatic && m.Name == "Equals" && m.Parameters.Count == 3 &&
  216. m.ReturnType.FullName == "System.Boolean"
  217. && m.Parameters[0].ParameterType.FullName == "System.String"
  218. && m.Parameters[1].ParameterType.FullName == "System.String"
  219. && m.Parameters[2].ParameterType.FullName == "System.StringComparison"));
  220. bool CompileGroup(IResourceGroup group)
  221. {
  222. var typeDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "!"+ group.Name,
  223. TypeAttributes.Class | TypeAttributes.Public, asm.MainModule.TypeSystem.Object);
  224. typeDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor)
  225. {
  226. ConstructorArguments = {new CustomAttributeArgument(editorBrowsableCtor.Parameters[0].ParameterType, 1)}
  227. });
  228. asm.MainModule.Types.Add(typeDef);
  229. var builder = typeSystem.CreateTypeBuilder(typeDef);
  230. IReadOnlyCollection<XamlDocumentResource> parsedXamlDocuments = new List<XamlDocumentResource>();
  231. foreach (var res in group.Resources.Where(CheckXamlName).OrderBy(x => x.FilePath.ToLowerInvariant()))
  232. {
  233. engine.LogMessage($"XAMLIL: {res.Name} -> {res.Uri}", logImportance);
  234. try
  235. {
  236. // StreamReader is needed here to handle BOM
  237. var xaml = new StreamReader(new MemoryStream(res.FileContents)).ReadToEnd();
  238. var parsed = XDocumentXamlParser.Parse(xaml);
  239. var initialRoot = (XamlAstObjectNode)parsed.Root;
  240. var precompileDirective = initialRoot.Children.OfType<XamlAstXmlDirective>()
  241. .FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Precompile");
  242. if (precompileDirective != null)
  243. {
  244. var precompileText = (precompileDirective.Values[0] as XamlAstTextNode)?.Text.Trim()
  245. .ToLowerInvariant();
  246. if (precompileText == "false")
  247. continue;
  248. if (precompileText != "true")
  249. throw new XamlParseException("Invalid value for x:Precompile", precompileDirective);
  250. }
  251. var classModifierDirective = initialRoot.Children.OfType<XamlAstXmlDirective>()
  252. .FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "ClassModifier");
  253. bool? classModifierPublic = null;
  254. if (classModifierDirective != null)
  255. {
  256. var classModifierText = (classModifierDirective.Values[0] as XamlAstTextNode)?.Text.Trim()
  257. .ToLowerInvariant();
  258. if ("Public".Equals(classModifierText, StringComparison.OrdinalIgnoreCase))
  259. classModifierPublic = true;
  260. // XAML spec uses "Public" and "NotPublic" values,
  261. // When WPF documentation uses "public" and "internal".
  262. else if ("NotPublic".Equals(classModifierText, StringComparison.OrdinalIgnoreCase)
  263. || "Internal".Equals(classModifierText, StringComparison.OrdinalIgnoreCase))
  264. classModifierPublic = false;
  265. else
  266. throw new XamlParseException("Invalid value for x:ClassModifier. Expected value are: Public, NotPublic (internal).", classModifierDirective);
  267. }
  268. var classDirective = initialRoot.Children.OfType<XamlAstXmlDirective>()
  269. .FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Class");
  270. IXamlType classType = null;
  271. if (classDirective != null)
  272. {
  273. if (classDirective.Values.Count != 1 || !(classDirective.Values[0] is XamlAstTextNode tn))
  274. throw new XamlParseException("x:Class should have a string value", classDirective);
  275. classType = typeSystem.TargetAssembly.FindType(tn.Text);
  276. if (classType == null)
  277. throw new XamlParseException($"Unable to find type `{tn.Text}`", classDirective);
  278. var isClassPublic = typeSystem.GetTypeReference(classType).Resolve().IsPublic;
  279. classModifierPublic ??= isClassPublic;
  280. // We do not really need x:ClassModifier support for x:Class, but we can at least use it for validation here.
  281. if (classModifierPublic != isClassPublic)
  282. {
  283. throw new XamlParseException(
  284. "XAML file x:ClassModifier doesn't match the x:Class type modifiers.",
  285. precompileDirective);
  286. }
  287. compiler.OverrideRootType(parsed,
  288. new XamlAstClrTypeReference(classDirective, classType, false));
  289. initialRoot.Children.Remove(classDirective);
  290. }
  291. compiler.Transform(parsed);
  292. var populateName = classType == null ? "Populate:" + res.Name : "!XamlIlPopulate";
  293. var buildName = classType == null ? "Build:" + res.Name : null;
  294. var classTypeDefinition =
  295. classType == null ? null : typeSystem.GetTypeReference(classType).Resolve();
  296. // All XAML files are public by default.
  297. classModifierPublic ??= true;
  298. var populateBuilder = classTypeDefinition == null ?
  299. builder :
  300. typeSystem.CreateTypeBuilder(classTypeDefinition);
  301. ((List<XamlDocumentResource>)parsedXamlDocuments).Add(new XamlDocumentResource(
  302. parsed, res.Uri, res, classType,
  303. classModifierPublic.Value,
  304. () => new XamlDocumentTypeBuilderProvider(
  305. populateBuilder,
  306. compiler.DefinePopulateMethod(
  307. populateBuilder,
  308. parsed,
  309. populateName,
  310. classTypeDefinition == null && classModifierPublic.Value),
  311. buildName == null ?
  312. null :
  313. compiler.DefineBuildMethod(builder, parsed, buildName, classModifierPublic.Value))));
  314. }
  315. catch (Exception e)
  316. {
  317. int lineNumber = 0, linePosition = 0;
  318. if (e is XamlParseException xe)
  319. {
  320. lineNumber = xe.LineNumber;
  321. linePosition = xe.LinePosition;
  322. }
  323. engine.LogError(BuildEngineErrorCode.TransformError, res.FilePath, e, lineNumber, linePosition);
  324. return false;
  325. }
  326. }
  327. try
  328. {
  329. compiler.TransformGroup(parsedXamlDocuments);
  330. }
  331. catch (XamlDocumentParseException e)
  332. {
  333. engine.LogError(BuildEngineErrorCode.TransformError, e.FilePath, e, e.LineNumber, e.LinePosition);
  334. }
  335. catch (XamlParseException e)
  336. {
  337. engine.LogError(BuildEngineErrorCode.TransformError, "", e, e.LineNumber, e.LinePosition);
  338. }
  339. foreach (var document in parsedXamlDocuments)
  340. {
  341. var res = (IResource)document.FileSource!;
  342. if (document.Usage == XamlDocumentUsage.Merged && !document.IsPublic)
  343. {
  344. res.Remove();
  345. continue;
  346. }
  347. var parsed = document.XamlDocument;
  348. var classType = document.ClassType;
  349. var populateBuilder = document.TypeBuilderProvider.TypeBuilder;
  350. try
  351. {
  352. var classTypeDefinition =
  353. classType == null ? null : typeSystem.GetTypeReference(classType).Resolve();
  354. compiler.Compile(parsed,
  355. contextClass,
  356. document.TypeBuilderProvider.PopulateMethod,
  357. document.TypeBuilderProvider.BuildMethod,
  358. builder.DefineSubType(compilerConfig.WellKnownTypes.Object, "NamespaceInfo:" + res.Name, true),
  359. (closureName, closureBaseType) =>
  360. populateBuilder.DefineSubType(closureBaseType, closureName, false),
  361. (closureName, returnType, parameterTypes) =>
  362. populateBuilder.DefineDelegateSubType(closureName, false, returnType, parameterTypes),
  363. res.Uri, res
  364. );
  365. if (classTypeDefinition != null)
  366. {
  367. var compiledPopulateMethod = typeSystem.GetTypeReference(populateBuilder).Resolve()
  368. .Methods.First(m => m.Name == document.TypeBuilderProvider.PopulateMethod.Name);
  369. var designLoaderFieldType = typeSystem
  370. .GetType("System.Action`1")
  371. .MakeGenericType(typeSystem.GetType("System.Object"));
  372. var designLoaderFieldTypeReference = (GenericInstanceType)typeSystem.GetTypeReference(designLoaderFieldType);
  373. designLoaderFieldTypeReference.GenericArguments[0] =
  374. asm.MainModule.ImportReference(designLoaderFieldTypeReference.GenericArguments[0]);
  375. designLoaderFieldTypeReference = (GenericInstanceType)
  376. asm.MainModule.ImportReference(designLoaderFieldTypeReference);
  377. var designLoaderLoad =
  378. typeSystem.GetMethodReference(
  379. designLoaderFieldType.Methods.First(m => m.Name == "Invoke"));
  380. designLoaderLoad =
  381. asm.MainModule.ImportReference(designLoaderLoad);
  382. designLoaderLoad.DeclaringType = designLoaderFieldTypeReference;
  383. var designLoaderField = new FieldDefinition("!XamlIlPopulateOverride",
  384. FieldAttributes.Static | FieldAttributes.Private, designLoaderFieldTypeReference);
  385. classTypeDefinition.Fields.Add(designLoaderField);
  386. const string TrampolineName = "!XamlIlPopulateTrampoline";
  387. var trampolineMethodWithoutSP = new Lazy<MethodDefinition>(() => CreateTrampolineMethod(false));
  388. var trampolineMethodWithSP = new Lazy<MethodDefinition>(() => CreateTrampolineMethod(true));
  389. MethodDefinition CreateTrampolineMethod(bool hasSystemProviderArg)
  390. {
  391. var trampoline = new MethodDefinition(TrampolineName,
  392. MethodAttributes.Static | MethodAttributes.Private, asm.MainModule.TypeSystem.Void);
  393. if (hasSystemProviderArg)
  394. {
  395. trampoline.Parameters.Add(new ParameterDefinition(serviceProviderType));
  396. }
  397. trampoline.Parameters.Add(new ParameterDefinition(classTypeDefinition));
  398. classTypeDefinition.Methods.Add(trampoline);
  399. var regularStart = Instruction.Create(OpCodes.Nop);
  400. trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldsfld, designLoaderField));
  401. trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Brfalse, regularStart));
  402. trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldsfld, designLoaderField));
  403. trampoline.Body.Instructions.Add(Instruction.Create(hasSystemProviderArg ? OpCodes.Ldarg_1 : OpCodes.Ldarg_0));
  404. trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, designLoaderLoad));
  405. trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
  406. trampoline.Body.Instructions.Add(regularStart);
  407. trampoline.Body.Instructions.Add(Instruction.Create(hasSystemProviderArg ? OpCodes.Ldarg_0 : OpCodes.Ldnull));
  408. trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, createRootServiceProviderMethod));
  409. trampoline.Body.Instructions.Add(Instruction.Create(hasSystemProviderArg ? OpCodes.Ldarg_1 : OpCodes.Ldarg_0));
  410. trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, compiledPopulateMethod));
  411. trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
  412. CopyDebugDocument(trampoline, compiledPopulateMethod);
  413. return trampoline;
  414. }
  415. var foundXamlLoader = false;
  416. // Find AvaloniaXamlLoader.Load(this) or AvaloniaXamlLoader.Load(sp, this) and replace it with !XamlIlPopulateTrampoline(this)
  417. foreach (var method in classTypeDefinition.Methods.Where(m => m.Body is not null).ToArray())
  418. {
  419. var i = method.Body.Instructions;
  420. for (var c = 1; c < i.Count; c++)
  421. {
  422. if (i[c].OpCode == OpCodes.Call)
  423. {
  424. var op = i[c].Operand as MethodReference;
  425. if (op != null
  426. && op.Name == TrampolineName)
  427. {
  428. throw new InvalidProgramException("Same XAML file was loaded twice." +
  429. "Make sure there is no x:Class duplicates no files were added to the AvaloniaResource msbuild items group twice.");
  430. }
  431. if (op != null
  432. && op.Name == "Load"
  433. && op.Parameters.Count == 1
  434. && op.Parameters[0].ParameterType.FullName == "System.Object"
  435. && op.DeclaringType.FullName == "Avalonia.Markup.Xaml.AvaloniaXamlLoader")
  436. {
  437. if (MatchThisCall(i, c - 1))
  438. {
  439. i[c].Operand = trampolineMethodWithoutSP.Value;
  440. foundXamlLoader = true;
  441. }
  442. }
  443. if (op != null
  444. && op.Name == "Load"
  445. && op.Parameters.Count == 2
  446. && op.Parameters[0].ParameterType.FullName == "System.IServiceProvider"
  447. && op.Parameters[1].ParameterType.FullName == "System.Object"
  448. && op.DeclaringType.FullName == "Avalonia.Markup.Xaml.AvaloniaXamlLoader")
  449. {
  450. if (MatchThisCall(i, c - 1))
  451. {
  452. i[c].Operand = trampolineMethodWithSP.Value;
  453. foundXamlLoader = true;
  454. }
  455. }
  456. }
  457. }
  458. }
  459. if (!foundXamlLoader)
  460. {
  461. var ctors = classTypeDefinition.GetConstructors()
  462. .Where(c => !c.IsStatic).ToList();
  463. // We can inject xaml loader into default constructor
  464. if (ctors.Count == 1 && ctors[0].Body.Instructions.Count(o=>o.OpCode != OpCodes.Nop) == 3)
  465. {
  466. var i = ctors[0].Body.Instructions;
  467. var retIdx = i.IndexOf(i.Last(x => x.OpCode == OpCodes.Ret));
  468. i.Insert(retIdx, Instruction.Create(OpCodes.Call, trampolineMethodWithoutSP.Value));
  469. i.Insert(retIdx, Instruction.Create(OpCodes.Ldarg_0));
  470. }
  471. else
  472. {
  473. throw new InvalidProgramException(
  474. $"No call to AvaloniaXamlLoader.Load(this) call found anywhere in the type {classType.FullName} and type seems to have custom constructors.");
  475. }
  476. }
  477. }
  478. if (document.IsPublic
  479. && (document.TypeBuilderProvider.BuildMethod != null || classTypeDefinition != null))
  480. {
  481. var compiledBuildMethod = document.TypeBuilderProvider.BuildMethod is not { } buildMethod ?
  482. null :
  483. typeSystem.GetTypeReference(builder).Resolve()
  484. .Methods.First(m => m.Name == buildMethod.Name);
  485. var parameterlessConstructor = compiledBuildMethod != null ?
  486. null :
  487. classTypeDefinition.GetConstructors().FirstOrDefault(c =>
  488. c.IsPublic && !c.IsStatic && !c.HasParameters);
  489. var constructorWithSp = compiledBuildMethod != null ?
  490. null :
  491. classTypeDefinition.GetConstructors().FirstOrDefault(c =>
  492. c.IsPublic && !c.IsStatic && c.Parameters.Count == 1 && c.Parameters[0].ParameterType.FullName == serviceProviderType.FullName);
  493. if (compiledBuildMethod != null || parameterlessConstructor != null || constructorWithSp != null)
  494. {
  495. var i = loaderDispatcherMethod.Body.Instructions;
  496. var nop = Instruction.Create(OpCodes.Nop);
  497. i.Add(Instruction.Create(OpCodes.Ldarg_1));
  498. i.Add(Instruction.Create(OpCodes.Ldstr, res.Uri));
  499. i.Add(Instruction.Create(OpCodes.Ldc_I4, (int)StringComparison.OrdinalIgnoreCase));
  500. i.Add(Instruction.Create(OpCodes.Call, stringEquals));
  501. i.Add(Instruction.Create(OpCodes.Brfalse, nop));
  502. if (parameterlessConstructor != null)
  503. {
  504. i.Add(Instruction.Create(OpCodes.Newobj, parameterlessConstructor));
  505. }
  506. else if (constructorWithSp != null)
  507. {
  508. i.Add(Instruction.Create(OpCodes.Ldarg_0));
  509. i.Add(Instruction.Create(OpCodes.Call, createRootServiceProviderMethod));
  510. i.Add(Instruction.Create(OpCodes.Newobj, constructorWithSp));
  511. }
  512. else
  513. {
  514. i.Add(Instruction.Create(OpCodes.Ldarg_0));
  515. i.Add(Instruction.Create(OpCodes.Call, createRootServiceProviderMethod));
  516. i.Add(Instruction.Create(OpCodes.Call, compiledBuildMethod));
  517. }
  518. i.Add(Instruction.Create(OpCodes.Ret));
  519. i.Add(nop);
  520. }
  521. else
  522. {
  523. engine.LogWarning(BuildEngineErrorCode.Loader, "",
  524. $"XAML resource \"{res.Uri}\" won't be reachable via runtime loader, as no public constructor was found");
  525. }
  526. }
  527. }
  528. catch (Exception e)
  529. {
  530. int lineNumber = 0, linePosition = 0;
  531. if (e is XamlParseException xe)
  532. {
  533. lineNumber = xe.LineNumber;
  534. linePosition = xe.LinePosition;
  535. }
  536. engine.LogError(BuildEngineErrorCode.EmitError, res.FilePath, e, lineNumber, linePosition);
  537. return false;
  538. }
  539. res.Remove();
  540. }
  541. // Technically that's a hack, but it fixes corert incompatibility caused by deterministic builds
  542. int dupeCounter = 1;
  543. foreach (var grp in typeDef.NestedTypes.GroupBy(x => x.Name))
  544. {
  545. if (grp.Count() > 1)
  546. {
  547. foreach (var dupe in grp)
  548. dupe.Name += "_dup" + dupeCounter++;
  549. }
  550. }
  551. return true;
  552. }
  553. if (avares.Resources.Count(CheckXamlName) != 0)
  554. {
  555. if (!CompileGroup(avares))
  556. return false;
  557. avares.Save();
  558. }
  559. loaderDispatcherMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ldnull));
  560. loaderDispatcherMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
  561. return true;
  562. }
  563. static bool? CompileCoreForRefAssembly(
  564. IBuildEngine engine, CecilTypeSystem sourceTypeSystem, CecilTypeSystem refTypeSystem)
  565. {
  566. var asm = refTypeSystem.TargetAssemblyDefinition;
  567. var compiledTypes = sourceTypeSystem.TargetAssemblyDefinition.MainModule.Types
  568. .Where(t => t.Namespace.StartsWith(CompiledAvaloniaXamlNamespace) && t.IsPublic).ToArray();
  569. if (compiledTypes.Length == 0)
  570. {
  571. return null;
  572. }
  573. try
  574. {
  575. foreach (var ogType in compiledTypes)
  576. {
  577. var wrappedOgType = sourceTypeSystem.TargetAssembly.FindType(ogType.FullName);
  578. var clrPropertiesDef = new TypeDefinition(ogType.Namespace, ogType.Name,
  579. TypeAttributes.Class | TypeAttributes.Public, asm.MainModule.TypeSystem.Object);
  580. asm.MainModule.Types.Add(clrPropertiesDef);
  581. foreach (var attribute in ogType.CustomAttributes)
  582. {
  583. var method = asm.MainModule.ImportReference(attribute.Constructor);
  584. clrPropertiesDef.CustomAttributes.Add(new CustomAttribute(method, attribute.GetBlob()));
  585. }
  586. var typeBuilder = refTypeSystem.CreateTypeBuilder(clrPropertiesDef);
  587. foreach (var ogMethod in wrappedOgType.Methods.Where(m => m.IsPublic && m.IsStatic))
  588. {
  589. var method = typeBuilder.DefineMethod(ogMethod.ReturnType, ogMethod.Parameters, ogMethod.Name,
  590. ogMethod.IsPublic, ogMethod.IsStatic, false);
  591. method.Generator.Ldnull();
  592. method.Generator.Throw();
  593. }
  594. typeBuilder.CreateType();
  595. }
  596. }
  597. catch (Exception e)
  598. {
  599. engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", "",
  600. 0, 0, 0, 0,
  601. e.Message, "", "Avalonia"));
  602. return false;
  603. }
  604. return true;
  605. }
  606. }
  607. }