|
|
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|
|
using System.IO;
|
|
|
using System.Linq;
|
|
|
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
|
|
|
+using Avalonia.Platform;
|
|
|
using Microsoft.Build.Framework;
|
|
|
using Mono.Cecil;
|
|
|
using Mono.Cecil.Cil;
|
|
|
@@ -94,7 +95,7 @@ namespace Avalonia.Build.Tasks
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
- engine.LogError(BuildEngineErrorCode.Unknown, "", ex.Message);
|
|
|
+ engine.LogError(BuildEngineErrorCode.Unknown, "", ex);
|
|
|
return new CompileResult(false);
|
|
|
}
|
|
|
}
|
|
|
@@ -134,6 +135,10 @@ namespace Avalonia.Build.Tasks
|
|
|
engine.LogMessage("Debugging cancelled.", MessageImportance.Normal);
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ // Some transformers might need to parse "avares://" Uri.
|
|
|
+ AssetLoader.RegisterResUriParsers();
|
|
|
+
|
|
|
var asm = typeSystem.TargetAssemblyDefinition;
|
|
|
var avares = new AvaloniaResources(asm, projectDirectory);
|
|
|
if (avares.Resources.Count(CheckXamlName) == 0)
|
|
|
@@ -231,15 +236,14 @@ namespace Avalonia.Build.Tasks
|
|
|
});
|
|
|
asm.MainModule.Types.Add(typeDef);
|
|
|
var builder = typeSystem.CreateTypeBuilder(typeDef);
|
|
|
-
|
|
|
- var populateMethodsToTransform = new List<(MethodDefinition populateMethod, string resourceFilePath)>();
|
|
|
-
|
|
|
- foreach (var res in group.Resources.Where(CheckXamlName).OrderBy(x=>x.FilePath.ToLowerInvariant()))
|
|
|
+
|
|
|
+ IReadOnlyCollection<XamlDocumentResource> parsedXamlDocuments = new List<XamlDocumentResource>();
|
|
|
+ foreach (var res in group.Resources.Where(CheckXamlName).OrderBy(x => x.FilePath.ToLowerInvariant()))
|
|
|
{
|
|
|
+ engine.LogMessage($"XAMLIL: {res.Name} -> {res.Uri}", logImportance);
|
|
|
+
|
|
|
try
|
|
|
{
|
|
|
- engine.LogMessage($"XAMLIL: {res.Name} -> {res.Uri}", logImportance);
|
|
|
-
|
|
|
// StreamReader is needed here to handle BOM
|
|
|
var xaml = new StreamReader(new MemoryStream(res.FileContents)).ReadToEnd();
|
|
|
var parsed = XDocumentXamlParser.Parse(xaml);
|
|
|
@@ -276,9 +280,9 @@ namespace Avalonia.Build.Tasks
|
|
|
|
|
|
|
|
|
compiler.Transform(parsed);
|
|
|
+
|
|
|
var populateName = classType == null ? "Populate:" + res.Name : "!XamlIlPopulate";
|
|
|
- var buildName = classType == null ? "Build:" + res.Name : null;
|
|
|
-
|
|
|
+ var buildName = classType == null ? "Build:" + res.Name : null;
|
|
|
var classTypeDefinition =
|
|
|
classType == null ? null : typeSystem.GetTypeReference(classType).Resolve();
|
|
|
|
|
|
@@ -286,11 +290,57 @@ namespace Avalonia.Build.Tasks
|
|
|
var populateBuilder = classTypeDefinition == null ?
|
|
|
builder :
|
|
|
typeSystem.CreateTypeBuilder(classTypeDefinition);
|
|
|
- compiler.Compile(parsed,
|
|
|
- contextClass,
|
|
|
+
|
|
|
+ ((List<XamlDocumentResource>)parsedXamlDocuments).Add(new XamlDocumentResource(
|
|
|
+ parsed, res.Uri, res, classType,
|
|
|
+ populateBuilder,
|
|
|
compiler.DefinePopulateMethod(populateBuilder, parsed, populateName,
|
|
|
classTypeDefinition == null),
|
|
|
- buildName == null ? null : compiler.DefineBuildMethod(builder, parsed, buildName, true),
|
|
|
+ buildName == null ? null : compiler.DefineBuildMethod(builder, parsed, buildName, true)));
|
|
|
+ }
|
|
|
+ catch (Exception e)
|
|
|
+ {
|
|
|
+ int lineNumber = 0, linePosition = 0;
|
|
|
+ if (e is XamlParseException xe)
|
|
|
+ {
|
|
|
+ lineNumber = xe.LineNumber;
|
|
|
+ linePosition = xe.LinePosition;
|
|
|
+ }
|
|
|
+
|
|
|
+ engine.LogError(BuildEngineErrorCode.TransformError, res.FilePath, e, lineNumber, linePosition);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ try
|
|
|
+ {
|
|
|
+ compiler.TransformGroup(parsedXamlDocuments);
|
|
|
+ }
|
|
|
+ catch (XamlDocumentParseException e)
|
|
|
+ {
|
|
|
+ engine.LogError(BuildEngineErrorCode.TransformError, e.FilePath, e, e.LineNumber, e.LinePosition);
|
|
|
+ }
|
|
|
+ catch (XamlParseException e)
|
|
|
+ {
|
|
|
+ engine.LogError(BuildEngineErrorCode.TransformError, "", e, e.LineNumber, e.LinePosition);
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach (var document in parsedXamlDocuments)
|
|
|
+ {
|
|
|
+ var parsed = document.XamlDocument;
|
|
|
+ var res = (IResource)document.FileSource;
|
|
|
+ var classType = document.ClassType;
|
|
|
+ var populateBuilder = document.TypeBuilder;
|
|
|
+
|
|
|
+ try
|
|
|
+ {
|
|
|
+ var classTypeDefinition =
|
|
|
+ classType == null ? null : typeSystem.GetTypeReference(classType).Resolve();
|
|
|
+
|
|
|
+ compiler.Compile(parsed,
|
|
|
+ contextClass,
|
|
|
+ document.PopulateMethod,
|
|
|
+ document.BuildMethod,
|
|
|
builder.DefineSubType(compilerConfig.WellKnownTypes.Object, "NamespaceInfo:" + res.Name,
|
|
|
true),
|
|
|
(closureName, closureBaseType) =>
|
|
|
@@ -300,23 +350,11 @@ namespace Avalonia.Build.Tasks
|
|
|
res.Uri, res
|
|
|
);
|
|
|
|
|
|
- var compiledPopulateMethod = typeSystem.GetTypeReference(populateBuilder).Resolve()
|
|
|
- .Methods.First(m => m.Name == populateName);
|
|
|
-
|
|
|
- // Include populate method and all nested methods/closures used in the populate method,
|
|
|
- // So we can replace style/resource includes in all of them.
|
|
|
- populateMethodsToTransform.Add((compiledPopulateMethod, res.FilePath));
|
|
|
- populateMethodsToTransform.AddRange(compiledPopulateMethod.Body.Instructions
|
|
|
- .Where(b => b.OpCode == OpCodes.Call || b.OpCode == OpCodes.Callvirt || b.OpCode == OpCodes.Ldftn)
|
|
|
- .Select(b => b.Operand)
|
|
|
- .OfType<MethodReference>()
|
|
|
- .Where(m => compiledPopulateMethod.DeclaringType.NestedTypes.Contains(m.DeclaringType))
|
|
|
- .Select(m => m.Resolve())
|
|
|
- .Where(m => m.HasBody)
|
|
|
- .Select(m => (m, res.FilePath)));
|
|
|
-
|
|
|
if (classTypeDefinition != null)
|
|
|
{
|
|
|
+ var compiledPopulateMethod = typeSystem.GetTypeReference(populateBuilder).Resolve()
|
|
|
+ .Methods.First(m => m.Name == document.PopulateMethod.Name);
|
|
|
+
|
|
|
var designLoaderFieldType = typeSystem
|
|
|
.GetType("System.Action`1")
|
|
|
.MakeGenericType(typeSystem.GetType("System.Object"));
|
|
|
@@ -370,15 +408,11 @@ namespace Avalonia.Build.Tasks
|
|
|
if (i[c].OpCode == OpCodes.Call)
|
|
|
{
|
|
|
var op = i[c].Operand as MethodReference;
|
|
|
-
|
|
|
- // TODO: Throw an error
|
|
|
- // This usually happens when the same XAML resource was added twice for some weird reason
|
|
|
- // We currently support it for dual-named default theme resources
|
|
|
if (op != null
|
|
|
&& op.Name == TrampolineName)
|
|
|
{
|
|
|
- foundXamlLoader = true;
|
|
|
- break;
|
|
|
+ throw new InvalidProgramException("Same XAML file was loaded twice." +
|
|
|
+ "Make sure there is no x:Class duplicates no files were added to the AvaloniaResource msbuild items group twice.");
|
|
|
}
|
|
|
if (op != null
|
|
|
&& op.Name == "Load"
|
|
|
@@ -417,12 +451,12 @@ namespace Avalonia.Build.Tasks
|
|
|
|
|
|
}
|
|
|
|
|
|
- if (buildName != null || classTypeDefinition != null)
|
|
|
+ if (document.BuildMethod != null || classTypeDefinition != null)
|
|
|
{
|
|
|
- var compiledBuildMethod = buildName == null ?
|
|
|
+ var compiledBuildMethod = document.BuildMethod == null ?
|
|
|
null :
|
|
|
typeSystem.GetTypeReference(builder).Resolve()
|
|
|
- .Methods.First(m => m.Name == buildName);
|
|
|
+ .Methods.First(m => m.Name == document.BuildMethod?.Name);
|
|
|
var parameterlessConstructor = compiledBuildMethod != null ?
|
|
|
null :
|
|
|
classTypeDefinition.GetConstructors().FirstOrDefault(c =>
|
|
|
@@ -459,22 +493,12 @@ namespace Avalonia.Build.Tasks
|
|
|
lineNumber = xe.LineNumber;
|
|
|
linePosition = xe.LinePosition;
|
|
|
}
|
|
|
- engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", res.FilePath,
|
|
|
- lineNumber, linePosition, lineNumber, linePosition,
|
|
|
- e.Message, "", "Avalonia"));
|
|
|
+ engine.LogError(BuildEngineErrorCode.EmitError, res.FilePath, e, lineNumber, linePosition);
|
|
|
return false;
|
|
|
}
|
|
|
res.Remove();
|
|
|
}
|
|
|
|
|
|
- foreach (var (populateMethod, resourceFilePath) in populateMethodsToTransform)
|
|
|
- {
|
|
|
- if (!TransformXamlIncludes(engine, typeSystem, populateMethod, resourceFilePath, createRootServiceProviderMethod))
|
|
|
- {
|
|
|
- return false;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
// Technically that's a hack, but it fixes corert incompatibility caused by deterministic builds
|
|
|
int dupeCounter = 1;
|
|
|
foreach (var grp in typeDef.NestedTypes.GroupBy(x => x.Name))
|
|
|
@@ -551,116 +575,5 @@ namespace Avalonia.Build.Tasks
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
-
|
|
|
- private static bool TransformXamlIncludes(
|
|
|
- IBuildEngine engine, CecilTypeSystem typeSystem,
|
|
|
- MethodDefinition populateMethod, string resourceFilePath,
|
|
|
- MethodReference createRootServiceProviderMethod)
|
|
|
- {
|
|
|
- var asm = typeSystem.TargetAssemblyDefinition;
|
|
|
- foreach (var instruction in populateMethod.Body.Instructions.ToArray())
|
|
|
- {
|
|
|
- const string resolveStyleIncludeName = "ResolveStyleInclude";
|
|
|
- const string resolveResourceInclude = "ResolveResourceInclude";
|
|
|
- if (instruction.OpCode == OpCodes.Call
|
|
|
- && instruction.Operand is MethodReference
|
|
|
- {
|
|
|
- Name: resolveStyleIncludeName or resolveResourceInclude,
|
|
|
- DeclaringType: { Name: "XamlIlRuntimeHelpers" }
|
|
|
- })
|
|
|
- {
|
|
|
- int lineNumber = 0, linePosition = 0;
|
|
|
- bool instructionsModified = false;
|
|
|
- try
|
|
|
- {
|
|
|
- var assetSource = (string)instruction.Previous.Previous.Previous.Operand;
|
|
|
- lineNumber = GetConstValue(instruction.Previous.Previous);
|
|
|
- linePosition = GetConstValue(instruction.Previous);
|
|
|
-
|
|
|
- var index = populateMethod.Body.Instructions.IndexOf(instruction);
|
|
|
-
|
|
|
- assetSource = assetSource.Replace("avares://", "");
|
|
|
-
|
|
|
- var assemblyNameSeparator = assetSource.IndexOf('/');
|
|
|
- var fileNameSeparator = assetSource.LastIndexOf('/');
|
|
|
- if (assemblyNameSeparator < 0 || fileNameSeparator < 0)
|
|
|
- {
|
|
|
- throw new InvalidProgramException(
|
|
|
- $"Invalid asset source \"{assetSource}\". It must contain assembly name and relative path.");
|
|
|
- }
|
|
|
-
|
|
|
- var assetAssemblyName = assetSource.Substring(0, assemblyNameSeparator);
|
|
|
- var assetAssembly = typeSystem.FindAssembly(assetAssemblyName)
|
|
|
- ?? throw new InvalidProgramException(
|
|
|
- $"Unable to resolve assembly \"{assetAssemblyName}\"");
|
|
|
-
|
|
|
- var fileName = Path.GetFileNameWithoutExtension(assetSource.Replace('/', '.'));
|
|
|
- if (assetAssembly.FindType(fileName) is { } type
|
|
|
- && type.FindConstructor() is { } ctor)
|
|
|
- {
|
|
|
- var ctorMethod =
|
|
|
- asm.MainModule.ImportReference(typeSystem.GetMethodReference(ctor));
|
|
|
- instructionsModified = true;
|
|
|
- populateMethod.Body.Instructions[index - 3] = Instruction.Create(OpCodes.Nop);
|
|
|
- populateMethod.Body.Instructions[index - 2] = Instruction.Create(OpCodes.Nop);
|
|
|
- populateMethod.Body.Instructions[index - 1] = Instruction.Create(OpCodes.Nop);
|
|
|
- populateMethod.Body.Instructions[index] = Instruction.Create(OpCodes.Newobj, ctorMethod);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- var resources = assetAssembly.FindType("CompiledAvaloniaXaml.!AvaloniaResources")
|
|
|
- ?? throw new InvalidOperationException(
|
|
|
- $"Unable to resolve \"!AvaloniaResources\" type on \"{assetAssemblyName}\" assembly");
|
|
|
-
|
|
|
- var relativeName = "Build:" + assetSource.Substring(assemblyNameSeparator);
|
|
|
- var buildMethod = resources.FindMethod(m => m.Name == relativeName)
|
|
|
- ?? throw new InvalidOperationException(
|
|
|
- $"Unable to resolve build method \"{relativeName}\" resource on the \"{assetAssemblyName}\" assembly");
|
|
|
-
|
|
|
- var methodReference = asm.MainModule.ImportReference(typeSystem.GetMethodReference(buildMethod));
|
|
|
- instructionsModified = true;
|
|
|
- populateMethod.Body.Instructions[index - 3] = Instruction.Create(OpCodes.Nop);
|
|
|
- populateMethod.Body.Instructions[index - 2] = Instruction.Create(OpCodes.Nop);
|
|
|
- populateMethod.Body.Instructions[index - 1] =
|
|
|
- Instruction.Create(OpCodes.Call, createRootServiceProviderMethod);
|
|
|
- populateMethod.Body.Instructions[index] = Instruction.Create(OpCodes.Call, methodReference);
|
|
|
- }
|
|
|
- }
|
|
|
- catch (Exception e)
|
|
|
- {
|
|
|
- if (instructionsModified)
|
|
|
- {
|
|
|
- engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", resourceFilePath,
|
|
|
- lineNumber, linePosition, lineNumber, linePosition,
|
|
|
- e.Message, "", "Avalonia"));
|
|
|
- return false;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", "XAMLIL",
|
|
|
- resourceFilePath,
|
|
|
- lineNumber, linePosition, lineNumber, linePosition,
|
|
|
- e.Message, "", "Avalonia"));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- static int GetConstValue(Instruction instruction)
|
|
|
- {
|
|
|
- if (instruction.OpCode is { Code : >= Code.Ldc_I4_0 and <= Code.Ldc_I4_8 })
|
|
|
- {
|
|
|
- return instruction.OpCode.Code - Code.Ldc_I4_0;
|
|
|
- }
|
|
|
- if (instruction.Operand is not null)
|
|
|
- {
|
|
|
- return Convert.ToInt32(instruction.Operand);
|
|
|
- }
|
|
|
-
|
|
|
- return 0;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return true;
|
|
|
- }
|
|
|
}
|
|
|
}
|