Răsfoiți Sursa

Support group transformations in the XAML Build Task

Max Katz 2 ani în urmă
părinte
comite
a9e904b0d2

+ 1 - 0
src/Avalonia.Base/Utilities/StringBuilderCache.cs

@@ -9,6 +9,7 @@ using System;
 using System.Text;
 
 namespace Avalonia.Utilities;
+#nullable enable
 
 // <summary>Provide a cached reusable instance of stringbuilder per thread.</summary>
 internal static class StringBuilderCache

+ 3 - 0
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@@ -55,6 +55,9 @@
       <Compile Include="../Avalonia.Base/Utilities/MathUtilities.cs">
         <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
       </Compile>
+      <Compile Include="..\Avalonia.Base\Platform\AssetLoader.cs">
+        <Link>Markup\AssetLoader.cs</Link>
+      </Compile>
       <Compile Include="..\Avalonia.Base\Utilities\StringBuilderCache.cs" Link="Utilities\StringBuilderCache.cs" />
       <Compile Include="..\Markup\Avalonia.Markup\Markup\Parsers\ArgumentListParser.cs">
         <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>

+ 2 - 0
src/Avalonia.Build.Tasks/BuildEngineErrorCode.cs

@@ -5,6 +5,8 @@ namespace Avalonia.Build.Tasks
         InvalidXAML = 1,
         DuplicateXClass = 2,
         LegacyResmScheme = 3,
+        TransformError = 4,
+        EmitError = 4,
 
         Unknown = 9999
     }

+ 8 - 4
src/Avalonia.Build.Tasks/Extensions.cs

@@ -7,15 +7,19 @@ namespace Avalonia.Build.Tasks
     {
         static string FormatErrorCode(BuildEngineErrorCode code) => $"AVLN:{(int)code:0000}";
 
-        public static void LogError(this IBuildEngine engine, BuildEngineErrorCode code, string file, string message)
+        public static void LogError(this IBuildEngine engine, BuildEngineErrorCode code, string file, string message,
+            int lineNumber = 0, int linePosition = 0)
         {
-            engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", FormatErrorCode(code), file ?? "", 0, 0, 0, 0, message,
+            engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", FormatErrorCode(code), file ?? "",
+                lineNumber, linePosition, lineNumber, linePosition, message,
                 "", "Avalonia"));
         }
 
-        public static void LogWarning(this IBuildEngine engine, BuildEngineErrorCode code, string file, string message)
+        public static void LogWarning(this IBuildEngine engine, BuildEngineErrorCode code, string file, string message,
+            int lineNumber = 0, int linePosition = 0)
         {
-            engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", FormatErrorCode(code), file ?? "", 0, 0, 0, 0, message,
+            engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", FormatErrorCode(code), file ?? "",
+                lineNumber, linePosition, lineNumber, linePosition, message,
                 "", "Avalonia"));
         }
 

+ 60 - 15
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@@ -233,13 +233,14 @@ namespace Avalonia.Build.Tasks
                 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 +277,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 +287,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.Message, lineNumber, linePosition);
+                        return false;
+                    }
+                }
+
+                try
+                {
+                    compiler.TransformGroup(parsedXamlDocuments);
+                }
+                catch (XamlDocumentParseException e)
+                {
+                    engine.LogError(BuildEngineErrorCode.TransformError, e.FilePath, e.Message, e.LineNumber, e.LinePosition);
+                }
+                catch (XamlParseException e)
+                {
+                    engine.LogError(BuildEngineErrorCode.TransformError, "", e.Message, 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) =>
@@ -417,12 +464,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,9 +506,7 @@ 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.Message, lineNumber, linePosition);
                         return false;
                     }
                     res.Remove();