diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets index 35feeaaa25..a455e2b3fc 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.targets +++ b/packages/Avalonia/AvaloniaBuildTasks.targets @@ -24,11 +24,15 @@ + + $(BuildAvaloniaResourcesDependsOn);AddAvaloniaResources;ResolveReferences + + + DependsOnTargets="$(BuildAvaloniaResourcesDependsOn)"> + Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:GenerateAvaloniaResources /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:TargetFramework=$(TargetFramework) /p:BuildProjectReferences=false"/> @@ -64,7 +68,7 @@ /> + Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:CompileAvaloniaXaml /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:TargetFramework=$(TargetFramework) /p:BuildProjectReferences=false"/> diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index b364a06688..d1bfb25220 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs @@ -15,6 +15,7 @@ using XamlIl; using XamlIl.Ast; using XamlIl.Parsers; using XamlIl.Transform; +using FieldAttributes = Mono.Cecil.FieldAttributes; using MethodAttributes = Mono.Cecil.MethodAttributes; using TypeAttributes = Mono.Cecil.TypeAttributes; @@ -125,6 +126,20 @@ namespace Avalonia.Build.Tasks var parsed = XDocumentXamlIlParser.Parse(xaml); var initialRoot = (XamlIlAstObjectNode)parsed.Root; + + + var precompileDirective = initialRoot.Children.OfType() + .FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Precompile"); + if (precompileDirective != null) + { + var precompileText = (precompileDirective.Values[0] as XamlIlAstTextNode)?.Text.Trim() + .ToLowerInvariant(); + if (precompileText == "false") + continue; + if (precompileText != "true") + throw new XamlIlParseException("Invalid value for x:Precompile", precompileDirective); + } + var classDirective = initialRoot.Children.OfType() .FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Class"); IXamlIlType classType = null; @@ -136,6 +151,7 @@ namespace Avalonia.Build.Tasks if (classType == null) throw new XamlIlParseException($"Unable to find type `{tn.Text}`", classDirective); initialRoot.Type = new XamlIlAstClrTypeReference(classDirective, classType, false); + initialRoot.Children.Remove(classDirective); } @@ -154,13 +170,43 @@ namespace Avalonia.Build.Tasks var compiledPopulateMethod = typeSystem.GetTypeReference(builder).Resolve() .Methods.First(m => m.Name == populateName); + var designLoaderFieldType = typeSystem + .GetType("System.Action`1") + .MakeGenericType(typeSystem.GetType("System.Object")); + + var designLoaderFieldTypeReference = (GenericInstanceType)typeSystem.GetTypeReference(designLoaderFieldType); + designLoaderFieldTypeReference.GenericArguments[0] = + asm.MainModule.ImportReference(designLoaderFieldTypeReference.GenericArguments[0]); + designLoaderFieldTypeReference = (GenericInstanceType) + asm.MainModule.ImportReference(designLoaderFieldTypeReference); + + var designLoaderLoad = + typeSystem.GetMethodReference( + designLoaderFieldType.Methods.First(m => m.Name == "Invoke")); + designLoaderLoad = + asm.MainModule.ImportReference(designLoaderLoad); + designLoaderLoad.DeclaringType = designLoaderFieldTypeReference; + + var designLoaderField = new FieldDefinition("!XamlIlPopulateOverride", + FieldAttributes.Static | FieldAttributes.Private, designLoaderFieldTypeReference); + classTypeDefinition.Fields.Add(designLoaderField); const string TrampolineName = "!XamlIlPopulateTrampoline"; var trampoline = new MethodDefinition(TrampolineName, MethodAttributes.Static | MethodAttributes.Private, asm.MainModule.TypeSystem.Void); trampoline.Parameters.Add(new ParameterDefinition(classTypeDefinition)); classTypeDefinition.Methods.Add(trampoline); - trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldsfld, rootServiceProviderField)); + + var regularStart = Instruction.Create(OpCodes.Ldsfld, rootServiceProviderField); + + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldsfld, designLoaderField)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Brfalse, regularStart)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldsfld, designLoaderField)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, designLoaderLoad)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); + + trampoline.Body.Instructions.Add(regularStart); trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, compiledPopulateMethod)); trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs index deea89695d..167b75603f 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs @@ -137,8 +137,36 @@ namespace Avalonia.Markup.Xaml.XamlIl static object LoadOrPopulate(Type created, object rootInstance) { var isp = Expression.Parameter(typeof(IServiceProvider)); + + + var epar = Expression.Parameter(typeof(object)); + var populate = created.GetMethod(AvaloniaXamlIlCompiler.PopulateName); + isp = Expression.Parameter(typeof(IServiceProvider)); + var populateCb = Expression.Lambda>( + Expression.Call(populate, isp, Expression.Convert(epar, populate.GetParameters()[1].ParameterType)), + isp, epar).Compile(); + if (rootInstance == null) { + var targetType = populate.GetParameters()[1].ParameterType; + var overrideField = targetType.GetField("!XamlIlPopulateOverride", + BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); + + if (overrideField != null) + { + overrideField.SetValue(null, + new Action( + target => { populateCb(XamlIlRuntimeHelpers.RootServiceProviderV1, target); })); + try + { + return Activator.CreateInstance(targetType); + } + finally + { + overrideField.SetValue(null, null); + } + } + var createCb = Expression.Lambda>( Expression.Convert(Expression.Call( created.GetMethod(AvaloniaXamlIlCompiler.BuildName), isp), typeof(object)), isp).Compile(); @@ -146,12 +174,6 @@ namespace Avalonia.Markup.Xaml.XamlIl } else { - var epar = Expression.Parameter(typeof(object)); - var populate = created.GetMethod(AvaloniaXamlIlCompiler.PopulateName); - isp = Expression.Parameter(typeof(IServiceProvider)); - var populateCb = Expression.Lambda>( - Expression.Call(populate, isp, Expression.Convert(epar, populate.GetParameters()[1].ParameterType)), - isp, epar).Compile(); populateCb(XamlIlRuntimeHelpers.RootServiceProviderV1, rootInstance); return rootInstance; } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs index ce47f4b3c6..1d7d93596e 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; using XamlIl; using XamlIl.Ast; @@ -11,11 +12,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions { class AvaloniaXamlIlCompiler : XamlIlCompiler { + private readonly XamlIlTransformerConfiguration _configuration; private readonly IXamlIlType _contextType; private readonly AvaloniaXamlIlDesignPropertiesTransformer _designTransformer; private AvaloniaXamlIlCompiler(XamlIlTransformerConfiguration configuration) : base(configuration, true) { + _configuration = configuration; + void InsertAfter(params IXamlIlAstTransformer[] t) => Transformers.InsertRange(Transformers.FindIndex(x => x is T) + 1, t); @@ -82,19 +86,34 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions {XamlNamespaces.Blend2008, XamlNamespaces.Blend2008} }); + var rootObject = (XamlIlAstObjectNode)parsed.Root; + + var classDirective = rootObject.Children + .OfType().FirstOrDefault(x => + x.Namespace == XamlNamespaces.Xaml2006 + && x.Name == "Class"); + + var rootType = + classDirective != null ? + new XamlIlAstClrTypeReference(classDirective, + _configuration.TypeSystem.GetType(((XamlIlAstTextNode)classDirective.Values[0]).Text), + false) : + XamlIlTypeReferenceResolver.ResolveType(CreateTransformationContext(parsed, true), + (XamlIlAstXmlTypeReference)rootObject.Type, true); + + if (overrideRootType != null) { - var rootObject = (XamlIlAstObjectNode)parsed.Root; + - var originalType = XamlIlTypeReferenceResolver.ResolveType(CreateTransformationContext(parsed, true), - (XamlIlAstXmlTypeReference)rootObject.Type, true); - - if (!originalType.Type.IsAssignableFrom(overrideRootType)) + if (!rootType.Type.IsAssignableFrom(overrideRootType)) throw new XamlIlLoadException( - $"Unable to substitute {originalType.Type.GetFqn()} with {overrideRootType.GetFqn()}", rootObject); - rootObject.Type = new XamlIlAstClrTypeReference(rootObject, overrideRootType, false); + $"Unable to substitute {rootType.Type.GetFqn()} with {overrideRootType.GetFqn()}", rootObject); + rootType = new XamlIlAstClrTypeReference(rootObject, overrideRootType, false); } + rootObject.Type = rootType; + Transform(parsed); Compile(parsed, tb, _contextType, PopulateName, BuildName, "__AvaloniaXamlIlNsInfo", baseUri, fileSource); diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs index 4ceba565e4..a79a4977ef 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs @@ -15,7 +15,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers { if (d.Namespace == XamlNamespaces.Xaml2006) { - if (d.Name == "Class") + if (d.Name == "Precompile" || d.Name == "Class") no.Children.Remove(d); } } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github index 66537c5772..c0da3c4981 160000 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github @@ -1 +1 @@ -Subproject commit 66537c5772941c4216878799aa63aa1742d3f1c7 +Subproject commit c0da3c49810316a96b47a00a83ae79d4f28406a1 diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj index 8b47bcec30..c9eb72757f 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj @@ -29,13 +29,10 @@ Designer - - Designer + - - - + diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/Style1.xaml b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/Style1.xaml index fc8e8bb2c8..1f195c4605 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/Style1.xaml +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/Style1.xaml @@ -1,8 +1,9 @@ \ No newline at end of file + diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/Style2.xaml b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/Style2.xaml index fd93c4a468..04db8fd2dd 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/Style2.xaml +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/Style2.xaml @@ -1,8 +1,9 @@ \ No newline at end of file + diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlClassWithPrecompiledXaml.xaml b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlClassWithPrecompiledXaml.xaml new file mode 100644 index 0000000000..ac2f75b893 --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlClassWithPrecompiledXaml.xaml @@ -0,0 +1,6 @@ + + + diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/XamlIlBugTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs similarity index 66% rename from tests/Avalonia.Markup.Xaml.UnitTests/XamlIlBugTests.cs rename to tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs index 839cd07755..f33054e5ce 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/XamlIlBugTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs @@ -7,7 +7,7 @@ using Xunit; namespace Avalonia.Markup.Xaml.UnitTests { - public class XamlIlBugTests + public class XamlIlTests { [Fact] public void Binding_Button_IsPressed_ShouldWork() @@ -34,7 +34,24 @@ namespace Avalonia.Markup.Xaml.UnitTests Assert.Equal(1, parsed.Transitions.Count); Assert.Equal(Visual.OpacityProperty, parsed.Transitions[0].Property); } - + + [Fact] + public void Parser_Should_Override_Precompiled_Xaml() + { + var precompiled = new XamlIlClassWithPrecompiledXaml(); + Assert.Equal(Brushes.Red, precompiled.Background); + Assert.Equal(1, precompiled.Opacity); + var loaded = (XamlIlClassWithPrecompiledXaml)AvaloniaXamlLoader.Parse(@" + + +"); + Assert.Equal(loaded.Opacity, 0); + Assert.Null(loaded.Background); + + } } @@ -50,4 +67,8 @@ namespace Avalonia.Markup.Xaml.UnitTests } } + public class XamlIlClassWithPrecompiledXaml : UserControl + { + } + }