Browse Source

Use !XamlIlPopulateOverride to populate class with precompiled xaml at runtime

pull/2322/head
Nikita Tsukanov 7 years ago
parent
commit
5a47b295e3
  1. 10
      packages/Avalonia/AvaloniaBuildTasks.targets
  2. 48
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  3. 34
      src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs
  4. 33
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  5. 2
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs
  6. 2
      src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
  7. 7
      tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
  8. 5
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/Style1.xaml
  9. 5
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/Style2.xaml
  10. 6
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlClassWithPrecompiledXaml.xaml
  11. 25
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs

10
packages/Avalonia/AvaloniaBuildTasks.targets

@ -24,11 +24,15 @@
</ItemGroup>
</Target>
<PropertyGroup>
<BuildAvaloniaResourcesDependsOn>$(BuildAvaloniaResourcesDependsOn);AddAvaloniaResources;ResolveReferences</BuildAvaloniaResourcesDependsOn>
</PropertyGroup>
<Target Name="GenerateAvaloniaResources"
BeforeTargets="CoreCompile;CoreResGen"
Inputs="@(AvaloniaResource);$(MSBuildAllProjects)"
Outputs="$(AvaloniaResourcesTemporaryFilePath)"
DependsOnTargets="$(BuildAvaloniaResourcesDependsOn);AddAvaloniaResources;ResolveReferences">
DependsOnTargets="$(BuildAvaloniaResourcesDependsOn)">
<GenerateAvaloniaResourcesTask
Condition="'$(_AvaloniaUseExternalMSBuild)' != 'true'"
Output="$(AvaloniaResourcesTemporaryFilePath)"
@ -37,7 +41,7 @@
EmbeddedResources="@(EmbeddedResources)"/>
<Exec
Condition="'$(_AvaloniaUseExternalMSBuild)' == 'true'"
Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:GenerateAvaloniaResources /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:BuildProjectReferences=false"/>
Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:GenerateAvaloniaResources /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:TargetFramework=$(TargetFramework) /p:BuildProjectReferences=false"/>
</Target>
@ -64,7 +68,7 @@
/>
<Exec
Condition="'$(_AvaloniaUseExternalMSBuild)' == 'true'"
Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:CompileAvaloniaXaml /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:BuildProjectReferences=false"/>
Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:CompileAvaloniaXaml /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:TargetFramework=$(TargetFramework) /p:BuildProjectReferences=false"/>
</Target>

48
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<XamlIlAstXmlDirective>()
.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<XamlIlAstXmlDirective>()
.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));

34
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<Action<IServiceProvider, object>>(
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<object>(
target => { populateCb(XamlIlRuntimeHelpers.RootServiceProviderV1, target); }));
try
{
return Activator.CreateInstance(targetType);
}
finally
{
overrideField.SetValue(null, null);
}
}
var createCb = Expression.Lambda<Func<IServiceProvider, object>>(
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<Action<IServiceProvider, object>>(
Expression.Call(populate, isp, Expression.Convert(epar, populate.GetParameters()[1].ParameterType)),
isp, epar).Compile();
populateCb(XamlIlRuntimeHelpers.RootServiceProviderV1, rootInstance);
return rootInstance;
}

33
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<T>(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<XamlIlAstXmlDirective>().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);

2
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);
}
}

2
src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github

@ -1 +1 @@
Subproject commit 66537c5772941c4216878799aa63aa1742d3f1c7
Subproject commit c0da3c49810316a96b47a00a83ae79d4f28406a1

7
tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj

@ -29,13 +29,10 @@
<EmbeddedResource Include="Xaml\Style1.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Xaml\Style2.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<AvaloniaResource Include="Xaml\XamlIlClassWithPrecompiledXaml.xaml"/>
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="..\..\build\BuildTargets.targets" />
</Project>

5
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/Style1.xaml

@ -1,8 +1,9 @@
<Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Precompile="False">
<Style.Resources>
<Color x:Key="Red">Red</Color>
<Color x:Key="Green">Green</Color>
<Color x:Key="Blue">Blue</Color>
</Style.Resources>
</Style>
</Style>

5
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/Style2.xaml

@ -1,8 +1,9 @@
<Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Precompile="False">
<Style.Resources>
<SolidColorBrush x:Key="RedBrush" Color="{DynamicResource Red}"/>
<SolidColorBrush x:Key="GreenBrush" Color="{DynamicResource Green}"/>
<SolidColorBrush x:Key="BlueBrush" Color="{DynamicResource Blue}"/>
</Style.Resources>
</Style>
</Style>

6
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlClassWithPrecompiledXaml.xaml

@ -0,0 +1,6 @@
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
x:Class='Avalonia.Markup.Xaml.UnitTests.XamlIlClassWithPrecompiledXaml'
Background="Red">
</UserControl>

25
tests/Avalonia.Markup.Xaml.UnitTests/XamlIlBugTests.cs → 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(@"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
x:Class='Avalonia.Markup.Xaml.UnitTests.XamlIlClassWithPrecompiledXaml'
Opacity='0'>
</UserControl>");
Assert.Equal(loaded.Opacity, 0);
Assert.Null(loaded.Background);
}
}
@ -50,4 +67,8 @@ namespace Avalonia.Markup.Xaml.UnitTests
}
}
public class XamlIlClassWithPrecompiledXaml : UserControl
{
}
}
Loading…
Cancel
Save