Browse Source

Make AvaloniaXamlIncludeTransformer a group transformer and remove old code with manual IL emit

Max Katz 2 years ago
parent
commit
47b9112138

+ 9 - 2
src/Avalonia.Base/Platform/AssetLoader.cs

@@ -3,16 +3,22 @@ using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Reflection;
+#if !BUILDTASK
 using Avalonia.Platform.Internal;
 using Avalonia.Utilities;
+#endif
 
 namespace Avalonia.Platform
 {
     /// <summary>
     /// Loads assets compiled into the application binary.
     /// </summary>
-    public class AssetLoader : IAssetLoader
+    public class AssetLoader
+#if !BUILDTASK
+        : IAssetLoader
+#endif
     {
+#if !BUILDTASK
         private static IAssemblyDescriptorResolver s_assemblyDescriptorResolver = new AssemblyDescriptorResolver();
 
         private AssemblyDescriptor? _defaultResmAssembly;
@@ -206,7 +212,8 @@ namespace Avalonia.Platform
 
             return null;
         }
-
+#endif
+        
         public static void RegisterResUriParsers()
         {
             if (!UriParser.IsKnownScheme("avares"))

+ 3 - 136
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@@ -231,8 +231,6 @@ namespace Avalonia.Build.Tasks
                 });
                 asm.MainModule.Types.Add(typeDef);
                 var builder = typeSystem.CreateTypeBuilder(typeDef);
-
-                var populateMethodsToTransform = new List<(MethodDefinition populateMethod, string resourceFilePath)>();
                 
                 IReadOnlyCollection<XamlDocumentResource> parsedXamlDocuments = new List<XamlDocumentResource>();
                 foreach (var res in group.Resources.Where(CheckXamlName).OrderBy(x => x.FilePath.ToLowerInvariant()))
@@ -347,23 +345,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"));
@@ -512,14 +498,6 @@ namespace Avalonia.Build.Tasks
                     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))
@@ -596,116 +574,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;
-        }
     }
 }

+ 1 - 2
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@@ -56,8 +56,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 new AvaloniaXamlIlSetterTransformer(),
                 new AvaloniaXamlIlConstructorServiceProviderTransformer(),
                 new AvaloniaXamlIlTransitionsTypeMetadataTransformer(),
-                new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer(),
-                new AvaloniaXamlIlAssetIncludeTransformer()
+                new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer()
             );
             InsertBefore<ConvertPropertyValuesToAssignmentsTransformer>(
                 new AvaloniaXamlIlOptionMarkupExtensionTransformer());

+ 133 - 0
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs

@@ -0,0 +1,133 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
+using Avalonia.Platform;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Emit;
+using XamlX.IL;
+using XamlX.Transform;
+using XamlX.Transform.Transformers;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers;
+
+internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
+{
+    public IXamlAstNode Transform(AstGroupTransformationContext context, IXamlAstNode node)
+    {
+        if (node is not XamlValueWithManipulationNode valueNode
+            || valueNode.Value is not XamlAstNewClrObjectNode objectNode
+            || (objectNode.Type.GetClrType() != context.GetAvaloniaTypes().StyleInclude
+                && objectNode.Type.GetClrType() != context.GetAvaloniaTypes().ResourceInclude))
+        {
+            return node;
+        }
+
+        var nodeTypeName = objectNode.Type.GetClrType().Name;
+        if (valueNode.Manipulation is not XamlObjectInitializationNode
+            {
+                Manipulation: XamlPropertyAssignmentNode { Property: { Name: "Source" } } sourceProperty
+            })
+        {
+            return context.ParseError($"Source property must be set on the \"{nodeTypeName}\" node.", node);
+        }
+
+        if (sourceProperty.Values.OfType<XamlAstNewClrObjectNode>().FirstOrDefault() is not { } sourceUriNode
+            || sourceUriNode.Type.GetClrType() != context.GetAvaloniaTypes().Uri
+            || sourceUriNode.Arguments.FirstOrDefault() is not XamlConstantNode { Constant: string originalAssetPath })
+        {
+            // TODO: make it a compiler warning
+            // Source value can be set with markup extension instead of the Uri object node, we don't support it here yet.
+            return node;
+        }
+
+        if (originalAssetPath.StartsWith("avares://"))
+        {
+        }
+        else if (originalAssetPath.StartsWith("/"))
+        {
+            var baseUrl = context.CurrentDocument.Uri ?? throw new InvalidOperationException("CurrentDocument URI is null.");
+            originalAssetPath = baseUrl.Substring(0, baseUrl.LastIndexOf('/')) + originalAssetPath;
+        }
+        else
+        {
+            return context.ParseError(
+                $"Avalonia supports only \"avares://\" sources or relative sources starting with \"/\" on the \"{nodeTypeName}\" node.",
+                node);
+        }
+        
+        AssetLoader.RegisterResUriParsers();
+
+        originalAssetPath = Uri.UnescapeDataString(new Uri(originalAssetPath).AbsoluteUri);
+        var assetPath = originalAssetPath.Replace("avares://", "");
+        var assemblyNameSeparator = assetPath.IndexOf('/');
+        var assembly = assetPath.Substring(0, assemblyNameSeparator);
+        var fullTypeName = Path.GetFileNameWithoutExtension(assetPath.Replace('/', '.'));
+
+        if (context.Documents.FirstOrDefault(d => string.Equals(d.Uri, originalAssetPath, StringComparison.InvariantCultureIgnoreCase)) is {} targetDocument)
+        {
+            if (targetDocument.ClassType is not null)
+            {
+                return FromType(context, targetDocument.ClassType, node);
+            }
+
+            if (targetDocument.BuildMethod is null)
+            {
+                return context.ParseError($"\"{originalAssetPath}\" cannot be instantiated.", node);
+            }
+
+            return FromMethod(context, targetDocument.BuildMethod, node);
+        }
+
+
+        if (context.Configuration.TypeSystem.FindAssembly(assembly) is not { } assetAssembly)
+        {
+            return context.ParseError($"Assembly \"{assembly}\" was not found from the \"{originalAssetPath}\" source.", node);
+        }
+
+        if (assetAssembly.FindType(fullTypeName) is { } type
+            && type.FindMethod(m => m.Name == "!XamlIlPopulate") is not null)
+        {
+            return FromType(context, type, node);
+        }
+        else
+        {
+            var avaResType = assetAssembly.FindType("CompiledAvaloniaXaml.!AvaloniaResources");
+            if (avaResType is null)
+            {
+                return context.ParseError(
+                    $"Unable to resolve \"!AvaloniaResources\" type on \"{assembly}\" assembly.", node);
+            }
+
+            var relativeName = "Build:" + assetPath.Substring(assemblyNameSeparator);
+            var buildMethod = avaResType.FindMethod(m => m.Name == relativeName);
+            if (buildMethod is null)
+            {
+                return context.ParseError(
+                    $"Unable to resolve build method \"{relativeName}\" resource on the \"{assembly}\" assembly.",
+                    node);
+            }
+
+            return FromMethod(context, buildMethod, node);
+        }
+    }
+
+    private static IXamlAstNode FromType(AstTransformationContext context, IXamlType type, IXamlLineInfo li)
+    {
+        IXamlAstNode newObjNode = new XamlAstObjectNode(li, new XamlAstClrTypeReference(li, type, false));
+        newObjNode = new AvaloniaXamlIlConstructorServiceProviderTransformer().Transform(context, newObjNode);
+        newObjNode = new ConstructableObjectTransformer().Transform(context, newObjNode);
+        return new NewObjectTransformer().Transform(context, newObjNode);
+    }
+
+    private static IXamlAstNode FromMethod(AstTransformationContext context, IXamlMethod method, IXamlLineInfo li)
+    {
+        var sp = context.Configuration.TypeMappings.ServiceProvider;
+        return new XamlStaticOrTargetedReturnMethodCallNode(li, method,
+            new[] { new AvaloniaXamlIlConstructorServiceProviderTransformer.InjectServiceProviderNode(sp, li, false) });
+    }
+}

+ 0 - 93
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs

@@ -1,93 +0,0 @@
-using System.Linq;
-using XamlX;
-using XamlX.Ast;
-using XamlX.Emit;
-using XamlX.IL;
-using XamlX.Transform;
-using XamlX.TypeSystem;
-
-namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
-
-internal class AvaloniaXamlIlAssetIncludeTransformer : IXamlAstTransformer
-{
-    public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
-    {
-        if (node is not XamlAstObjectNode objectNode
-            || (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<XamlAstXamlPropertyValueNode>().FirstOrDefault(n => n.Property.GetClrProperty().Name == "Source");
-        var directives = objectNode.Children.OfType<XamlAstXmlDirective>().ToList();
-        if (sourceProperty is null
-            || objectNode.Children.Count != (directives.Count + 1))
-        {
-            throw new XamlParseException($"Unexpected property on the {nodeTypeName} node", node);
-        }
-
-        if (sourceProperty.Values.OfType<XamlAstTextNode>().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;
-        }
-
-        var originalAssetPath = sourceTextNode.Text;
-        if (!(originalAssetPath.StartsWith("avares://") || originalAssetPath.StartsWith("/")))
-        {
-            return node;
-        }
-
-        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 \"{nodeTypeName}\" node", node);
-        }
-        
-        return new XamlValueWithManipulationNode(
-            node,
-            new AssetIncludeMethodNode(node, markerMethod, originalAssetPath),
-            new XamlManipulationGroupNode(node, directives));
-    }
-
-    private class AssetIncludeMethodNode : XamlAstNode, IXamlAstValueNode, IXamlAstILEmitableNode
-    {
-        private readonly IXamlMethod _method;
-        private readonly string _originalAssetPath;
-
-        public AssetIncludeMethodNode(
-            IXamlAstNode original, IXamlMethod method, string originalAssetPath)
-            : base(original)
-        {
-            _method = method;
-            _originalAssetPath = originalAssetPath;
-        }
-
-        public IXamlAstTypeReference Type => new XamlAstClrTypeReference(this, _method.ReturnType, false);
-
-        public XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
-        {
-            var absoluteSource = _originalAssetPath;
-            if (absoluteSource.StartsWith("/"))
-            {
-                // Avoid Uri class here to avoid potential problems with escaping.
-                // Keeping string as close to the original as possible.
-                var absoluteBaseUrl =  context.RuntimeContext.BaseUrl;
-                absoluteSource = absoluteBaseUrl.Substring(0, absoluteBaseUrl.LastIndexOf('/')) + absoluteSource;
-            }
-
-            codeGen.Ldstr(absoluteSource);
-            codeGen.Ldc_I4(Line);
-            codeGen.Ldc_I4(Position);
-            codeGen.EmitCall(_method);
-
-            return XamlILNodeEmitResult.Type(0, _method.ReturnType);
-        }
-    }
-}

+ 19 - 5
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs

@@ -21,7 +21,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                         c.IsPublic && !c.IsStatic && c.Parameters.Count == 1 && c.Parameters[0]
                             .Equals(sp)))
                     {
-                        on.Arguments.Add(new InjectServiceProviderNode(sp, on));
+                        on.Arguments.Add(new InjectServiceProviderNode(sp, on, true));
                     }
                 }
             }
@@ -29,19 +29,33 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
             return node;
         }
 
-        class InjectServiceProviderNode : XamlAstNode, IXamlAstValueNode,IXamlAstNodeNeedsParentStack,
+        internal class InjectServiceProviderNode : XamlAstNode, IXamlAstValueNode,IXamlAstNodeNeedsParentStack,
             IXamlAstEmitableNode<IXamlILEmitter, XamlILNodeEmitResult>
         {
-            public InjectServiceProviderNode(IXamlType type, IXamlLineInfo lineInfo) : base(lineInfo)
+            private readonly bool _inheritContext;
+
+            public InjectServiceProviderNode(IXamlType type, IXamlLineInfo lineInfo, bool inheritContext) : base(lineInfo)
             {
+                _inheritContext = inheritContext;
                 Type = new XamlAstClrTypeReference(lineInfo, type, false);
             }
 
             public IXamlAstTypeReference Type { get; }
-            public bool NeedsParentStack => true;
+            public bool NeedsParentStack => _inheritContext;
             public XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
             {
-                codeGen.Ldloc(context.ContextLocal);
+                if (_inheritContext)
+                {
+                    codeGen.Ldloc(context.ContextLocal);
+                }
+                else
+                {
+                    var method = context.GetAvaloniaTypes().RuntimeHelpers
+                        .FindMethod(m => m.Name == "CreateRootServiceProviderV2");
+                    codeGen.EmitCall(method);
+                    context.RuntimeContext.Factory(codeGen);
+                }
+
                 return XamlILNodeEmitResult.Type(0, Type.GetClrType());
             }
         }

+ 0 - 10
src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs

@@ -17,16 +17,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
 {
     public static class XamlIlRuntimeHelpers
     {
-        public static IStyle ResolveStyleInclude(string absoluteSource, int line, int position)
-        {
-            return new StyleInclude((Uri)null) { Source = new Uri(absoluteSource) }.Loaded;
-        }
-
-        public static IResourceDictionary ResolveResourceInclude(string absoluteSource, int line, int position)
-        {
-            return new ResourceInclude((Uri)null) { Source = new Uri(absoluteSource) }.Loaded;
-        }
-
         public static Func<IServiceProvider, object> DeferredTransformationFactoryV1(Func<IServiceProvider, object> builder,
             IServiceProvider provider)
         {