diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index 75abfe352f..974f8485c0 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs @@ -469,88 +469,9 @@ namespace Avalonia.Build.Tasks foreach (var (populateMethod, resourceFilePath) in populateMethodsToTransform) { - foreach (var instruction in populateMethod.Body.Instructions.ToArray()) + if (!TransformXamlIncludes(engine, typeSystem, populateMethod, resourceFilePath, createRootServiceProviderMethod)) { - 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 = Convert.ToInt32(instruction.Previous.Previous.Operand); - linePosition = Convert.ToInt32(instruction.Previous.Operand); - - 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")); - } - } - } + return false; } } @@ -630,5 +551,116 @@ 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; + } } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs index fa878ce72c..1572a4f140 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs @@ -10,28 +10,28 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; internal class AvaloniaXamlIlAssetIncludeTransformer : IXamlAstTransformer { - private const string StyleIncludeName = "StyleInclude"; - private const string ResourceIncludeName = "ResourceInclude"; - public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) { if (node is not XamlAstObjectNode objectNode - || objectNode.Type.GetClrType() is not {Name: StyleIncludeName or ResourceIncludeName} objectNodeType) + || (objectNode.Type.GetClrType() != context.GetAvaloniaTypes().StyleInclude + && objectNode.Type.GetClrType() != context.GetAvaloniaTypes().ResourceInclude)) { return node; } + var nodeTypeName = objectNode.Type.GetClrType().Name; + var sourceProperty = objectNode.Children.OfType().FirstOrDefault(n => n.Property.GetClrProperty().Name == "Source"); var directives = objectNode.Children.OfType().ToList(); if (sourceProperty is null || objectNode.Children.Count != (directives.Count + 1)) { - // Don't transform node with any other property, as we don't know how to transform them. - return node; + throw new XamlParseException($"Unexpected property on the {nodeTypeName} node", node); } if (sourceProperty.Values.OfType().FirstOrDefault() is not { } sourceTextNode) { + // TODO: make it a compiler warning // Source value can be set with markup extension instead of a text node, we don't support it here yet. return node; } @@ -39,18 +39,19 @@ internal class AvaloniaXamlIlAssetIncludeTransformer : IXamlAstTransformer var originalAssetPath = sourceTextNode.Text; if (!(originalAssetPath.StartsWith("avares://") || originalAssetPath.StartsWith("/"))) { - // Only "avares" protocol supported or relative paths. - return node; + throw new XamlParseException( + $"{nodeTypeName}.Source supports only \"avares://\" absolute paths or relative paths starting with \"/\"", + sourceTextNode); } - var runtimeHelpers = context.Configuration.TypeSystem.FindType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers"); - var markerMethodName = "Resolve" + objectNodeType.Name; + var runtimeHelpers = context.GetAvaloniaTypes().RuntimeHelpers; + var markerMethodName = "Resolve" + nodeTypeName; var markerMethod = runtimeHelpers.FindMethod(m => m.Name == markerMethodName && m.Parameters.Count == 3); if (markerMethod is null) { - throw new XamlParseException($"Marker method \"{markerMethodName}\" was not found for the \"{objectNodeType.Name}\" node", node); + throw new XamlParseException($"Marker method \"{markerMethodName}\" was not found for the \"{nodeTypeName}\" node", node); } - + return new XamlValueWithManipulationNode( node, new AssetIncludeMethodNode(node, markerMethod, originalAssetPath), diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index b5a9326a7d..dd37ae6c93 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -103,6 +103,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType TextTrimming { get; } public IXamlType ISetter { get; } public IXamlType IStyle { get; } + public IXamlType StyleInclude { get; } + public IXamlType ResourceInclude { get; } public IXamlType IResourceDictionary { get; } public IXamlType ResourceDictionary { get; } public IXamlMethod ResourceDictionaryDeferredAdd { get; } @@ -234,6 +236,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers TextTrimming = cfg.TypeSystem.GetType("Avalonia.Media.TextTrimming"); ISetter = cfg.TypeSystem.GetType("Avalonia.Styling.ISetter"); IStyle = cfg.TypeSystem.GetType("Avalonia.Styling.IStyle"); + StyleInclude = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Styling.StyleInclude"); + ResourceInclude = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Styling.ResourceInclude"); IResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.IResourceDictionary"); ResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.ResourceDictionary"); ResourceDictionaryDeferredAdd = ResourceDictionary.FindMethod("AddDeferred", XamlIlTypes.Void, true, XamlIlTypes.Object,