Browse Source

Inject generated types into Ref assembly as well

pull/9413/head
Max Katz 3 years ago
parent
commit
fcf26fe4f2
  1. 1
      packages/Avalonia/AvaloniaBuildTasks.targets
  2. 23
      src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
  3. 8
      src/Avalonia.Build.Tasks/Program.cs
  4. 264
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  5. 3
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  6. 94
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs
  7. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  8. 13
      src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
  9. 8
      tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
  10. 6
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
  11. 32
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs

1
packages/Avalonia/AvaloniaBuildTasks.targets

@ -99,6 +99,7 @@
AssemblyFile="@(IntermediateAssembly)" AssemblyFile="@(IntermediateAssembly)"
ReferencesFilePath="$(AvaloniaXamlReferencesTemporaryFilePath)" ReferencesFilePath="$(AvaloniaXamlReferencesTemporaryFilePath)"
OriginalCopyPath="$(AvaloniaXamlOriginalCopyFilePath)" OriginalCopyPath="$(AvaloniaXamlOriginalCopyFilePath)"
RefAssemblyFile="@(IntermediateRefAssembly)"
ProjectDirectory="$(MSBuildProjectDirectory)" ProjectDirectory="$(MSBuildProjectDirectory)"
VerifyIl="$(AvaloniaXamlIlVerifyIl)" VerifyIl="$(AvaloniaXamlIlVerifyIl)"
ReportImportance="$(AvaloniaXamlReportImportance)" ReportImportance="$(AvaloniaXamlReportImportance)"

23
src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs

@ -12,12 +12,16 @@ namespace Avalonia.Build.Tasks
Enum.TryParse(ReportImportance, true, out MessageImportance outputImportance); Enum.TryParse(ReportImportance, true, out MessageImportance outputImportance);
OutputPath = OutputPath ?? AssemblyFile; OutputPath = OutputPath ?? AssemblyFile;
RefOutputPath = RefOutputPath ?? RefAssemblyFile;
var outputPdb = GetPdbPath(OutputPath); var outputPdb = GetPdbPath(OutputPath);
var input = AssemblyFile; var input = AssemblyFile;
var refInput = RefOutputPath;
var inputPdb = GetPdbPath(input); var inputPdb = GetPdbPath(input);
// Make a copy and delete the original file to prevent MSBuild from thinking that everything is OK // Make a copy and delete the original file to prevent MSBuild from thinking that everything is OK
if (OriginalCopyPath != null) if (OriginalCopyPath != null)
{ {
var originalCopyPathRef = Path.ChangeExtension(OriginalCopyPath, ".ref.dll");
File.Copy(AssemblyFile, OriginalCopyPath, true); File.Copy(AssemblyFile, OriginalCopyPath, true);
input = OriginalCopyPath; input = OriginalCopyPath;
File.Delete(AssemblyFile); File.Delete(AssemblyFile);
@ -29,14 +33,24 @@ namespace Avalonia.Build.Tasks
File.Delete(inputPdb); File.Delete(inputPdb);
inputPdb = copyPdb; inputPdb = copyPdb;
} }
if (!string.IsNullOrWhiteSpace(RefAssemblyFile) && File.Exists(RefAssemblyFile))
{
// We also copy ref assembly just for case if needed later for testing.
// But do not remove the original one, as MSBuild actually complains about it with multi-thread compiling.
File.Copy(RefAssemblyFile, originalCopyPathRef, true);
refInput = originalCopyPathRef;
}
} }
var msg = $"CompileAvaloniaXamlTask -> AssemblyFile:{AssemblyFile}, ProjectDirectory:{ProjectDirectory}, OutputPath:{OutputPath}"; var msg = $"CompileAvaloniaXamlTask -> AssemblyFile:{AssemblyFile}, ProjectDirectory:{ProjectDirectory}, OutputPath:{OutputPath}";
BuildEngine.LogMessage(msg, outputImportance < MessageImportance.Low ? MessageImportance.High : outputImportance); BuildEngine.LogMessage(msg, outputImportance < MessageImportance.Low ? MessageImportance.High : outputImportance);
var res = XamlCompilerTaskExecutor.Compile(BuildEngine, input, var res = XamlCompilerTaskExecutor.Compile(BuildEngine,
input, OutputPath,
refInput, RefOutputPath,
File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(), File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(),
ProjectDirectory, OutputPath, VerifyIl, DefaultCompileBindings, outputImportance, ProjectDirectory, VerifyIl, DefaultCompileBindings, outputImportance,
(SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null, SkipXamlCompilation, DebuggerLaunch); (SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null, SkipXamlCompilation, DebuggerLaunch);
if (!res.Success) if (!res.Success)
return false; return false;
@ -68,6 +82,9 @@ namespace Avalonia.Build.Tasks
[Required] [Required]
public string ProjectDirectory { get; set; } public string ProjectDirectory { get; set; }
public string RefAssemblyFile { get; set; }
public string RefOutputPath { get; set; }
public string OutputPath { get; set; } public string OutputPath { get; set; }
public bool VerifyIl { get; set; } public bool VerifyIl { get; set; }

8
src/Avalonia.Build.Tasks/Program.cs

@ -14,7 +14,7 @@ namespace Avalonia.Build.Tasks
static int Main(string[] args) static int Main(string[] args)
{ {
if (args.Length != 3) if (args.Length < 3)
{ {
if (args.Length == 1) if (args.Length == 1)
{ {
@ -27,11 +27,12 @@ namespace Avalonia.Build.Tasks
Console.WriteLine(@$"Usage: Console.WriteLine(@$"Usage:
1) dotnet ./Avalonia.Build.Tasks.dll <ReferencesOutputPath> 1) dotnet ./Avalonia.Build.Tasks.dll <ReferencesOutputPath>
, where <ReferencesOutputPath> likes {referencesOutputPath} , where <ReferencesOutputPath> likes {referencesOutputPath}
2) dotnet ./Avalonia.Build.Tasks.dll <AssemblyFilePath> <ReferencesFilePath> <OutputPath> 2) dotnet ./Avalonia.Build.Tasks.dll <AssemblyFilePath> <ReferencesFilePath> <OutputPath> <RefAssemblyFile>
, where: , where:
- <AssemblyFilePath> likes {referencesOutputPath}/{OriginalDll} - <AssemblyFilePath> likes {referencesOutputPath}/{OriginalDll}
- <ReferencesFilePath> likes {referencesOutputPath}/{References} - <ReferencesFilePath> likes {referencesOutputPath}/{References}
- <OutputPath> likes {referencesOutputPath}/{OutDll}"); - <OutputPath> likes {referencesOutputPath}/{OutDll}
- <RefAssemblyFile> Likes {referencesOutputPath}/original.ref.dll");
return 1; return 1;
} }
@ -42,6 +43,7 @@ namespace Avalonia.Build.Tasks
AssemblyFile = args[0], AssemblyFile = args[0],
ReferencesFilePath = args[1], ReferencesFilePath = args[1],
OutputPath = args[2], OutputPath = args[2],
RefAssemblyFile = args.Length > 3 ? args[3] : null,
BuildEngine = new ConsoleBuildEngine(), BuildEngine = new ConsoleBuildEngine(),
ProjectDirectory = Directory.GetCurrentDirectory(), ProjectDirectory = Directory.GetCurrentDirectory(),
VerifyIl = true VerifyIl = true

264
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions; using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
using Microsoft.Build.Framework; using Microsoft.Build.Framework;
using Mono.Cecil; using Mono.Cecil;
@ -21,6 +21,8 @@ namespace Avalonia.Build.Tasks
{ {
public static partial class XamlCompilerTaskExecutor public static partial class XamlCompilerTaskExecutor
{ {
private const string CompiledAvaloniaXamlNamespace = "CompiledAvaloniaXaml";
static bool CheckXamlName(IResource r) => r.Name.ToLowerInvariant().EndsWith(".xaml") static bool CheckXamlName(IResource r) => r.Name.ToLowerInvariant().EndsWith(".xaml")
|| r.Name.ToLowerInvariant().EndsWith(".paml") || r.Name.ToLowerInvariant().EndsWith(".paml")
|| r.Name.ToLowerInvariant().EndsWith(".axaml"); || r.Name.ToLowerInvariant().EndsWith(".axaml");
@ -37,43 +39,58 @@ namespace Avalonia.Build.Tasks
} }
} }
public static CompileResult Compile(IBuildEngine engine, string input, string[] references, public static CompileResult Compile(IBuildEngine engine,
string projectDirectory, string input, string output,
string output, bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance, string strongNameKey, string refInput, string refOutput,
string[] references, string projectDirectory,
bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance, string strongNameKey,
bool skipXamlCompilation) bool skipXamlCompilation)
{ {
return Compile(engine, input, references, projectDirectory, output, verifyIl, defaultCompileBindings, logImportance, strongNameKey, skipXamlCompilation, debuggerLaunch:false); return Compile(engine, input, output, refInput, refOutput, references, projectDirectory, verifyIl, defaultCompileBindings, logImportance, strongNameKey, skipXamlCompilation, debuggerLaunch:false);
} }
internal static CompileResult Compile(IBuildEngine engine, string input, string[] references, internal static CompileResult Compile(IBuildEngine engine,
string projectDirectory, string input, string output,
string output, bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance, string strongNameKey, bool skipXamlCompilation, bool debuggerLaunch) string refInput, string refOutput,
string[] references, string projectDirectory,
bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance, string strongNameKey, bool skipXamlCompilation, bool debuggerLaunch)
{ {
try try
{ {
var typeSystem = new CecilTypeSystem( references = references.Where(r => !r.ToLowerInvariant().EndsWith("avalonia.build.tasks.dll")).ToArray();
references.Where(r => !r.ToLowerInvariant().EndsWith("avalonia.build.tasks.dll")), var typeSystem = new CecilTypeSystem(references, input);
input); var refTypeSystem = !string.IsNullOrWhiteSpace(refInput) && File.Exists(refInput) ? new CecilTypeSystem(references, refInput) : null;
var asm = typeSystem.TargetAssemblyDefinition; var asm = typeSystem.TargetAssemblyDefinition;
var refAsm = refTypeSystem?.TargetAssemblyDefinition;
if (!skipXamlCompilation) if (!skipXamlCompilation)
{ {
var compileRes = CompileCore(engine, typeSystem, projectDirectory, verifyIl, defaultCompileBindings, var compileRes = CompileCore(engine, typeSystem, projectDirectory, verifyIl, defaultCompileBindings, logImportance, debuggerLaunch);
logImportance, debuggerLaunch); if (compileRes == null)
if (compileRes == null) return new CompileResult(true);
return new CompileResult(true); if (compileRes == false)
if (compileRes == false) return new CompileResult(false);
return new CompileResult(false);
} if (refTypeSystem is not null)
{
var writerParameters = new WriterParameters { WriteSymbols = asm.MainModule.HasSymbols }; var refCompileRes = CompileCoreForRefAssembly(engine, typeSystem, refTypeSystem);
if (!string.IsNullOrWhiteSpace(strongNameKey)) if (refCompileRes == false)
writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey); return new CompileResult(false);
}
asm.Write(output, writerParameters); }
return new CompileResult(true, true); var writerParameters = new WriterParameters { WriteSymbols = asm.MainModule.HasSymbols };
if (!string.IsNullOrWhiteSpace(strongNameKey))
writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey);
asm.Write(output, writerParameters);
var refWriterParameters = new WriterParameters { WriteSymbols = false };
if (!string.IsNullOrWhiteSpace(strongNameKey))
writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey);
refAsm?.Write(refOutput, refWriterParameters);
return new CompileResult(true, true);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -122,6 +139,7 @@ namespace Avalonia.Build.Tasks
if (avares.Resources.Count(CheckXamlName) == 0) if (avares.Resources.Count(CheckXamlName) == 0)
// Nothing to do // Nothing to do
return null; return null;
if (typeSystem.FindType("System.Reflection.AssemblyMetadataAttribute") is {} asmMetadata) if (typeSystem.FindType("System.Reflection.AssemblyMetadataAttribute") is {} asmMetadata)
{ {
var ctor = asm.MainModule.ImportReference(typeSystem.GetTypeReference(asmMetadata).Resolve() var ctor = asm.MainModule.ImportReference(typeSystem.GetTypeReference(asmMetadata).Resolve()
@ -131,14 +149,14 @@ namespace Avalonia.Build.Tasks
var arg2 = new CustomAttributeArgument(strType, defaultCompileBindings.ToString()); var arg2 = new CustomAttributeArgument(strType, defaultCompileBindings.ToString());
asm.CustomAttributes.Add(new CustomAttribute(ctor) { ConstructorArguments = { arg1, arg2 } }); asm.CustomAttributes.Add(new CustomAttribute(ctor) { ConstructorArguments = { arg1, arg2 } });
} }
var clrPropertiesDef = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlHelpers", var clrPropertiesDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "XamlIlHelpers",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object); TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
asm.MainModule.Types.Add(clrPropertiesDef); asm.MainModule.Types.Add(clrPropertiesDef);
var indexerAccessorClosure = new TypeDefinition("CompiledAvaloniaXaml", "!IndexerAccessorFactoryClosure", var indexerAccessorClosure = new TypeDefinition(CompiledAvaloniaXamlNamespace, "!IndexerAccessorFactoryClosure",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object); TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
asm.MainModule.Types.Add(indexerAccessorClosure); asm.MainModule.Types.Add(indexerAccessorClosure);
var trampolineBuilder = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlTrampolines", var trampolineBuilder = new TypeDefinition(CompiledAvaloniaXamlNamespace, "XamlIlTrampolines",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object); TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
asm.MainModule.Types.Add(trampolineBuilder); asm.MainModule.Types.Add(trampolineBuilder);
@ -154,7 +172,7 @@ namespace Avalonia.Build.Tasks
new DeterministicIdGenerator()); new DeterministicIdGenerator());
var contextDef = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlContext", var contextDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "XamlIlContext",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object); TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
asm.MainModule.Types.Add(contextDef); asm.MainModule.Types.Add(contextDef);
@ -175,8 +193,8 @@ namespace Avalonia.Build.Tasks
typeSystem.GetTypeReference(runtimeHelpers).Resolve().Methods typeSystem.GetTypeReference(runtimeHelpers).Resolve().Methods
.First(x => x.Name == "CreateRootServiceProviderV2")); .First(x => x.Name == "CreateRootServiceProviderV2"));
var loaderDispatcherDef = new TypeDefinition("CompiledAvaloniaXaml", "!XamlLoader", var loaderDispatcherDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "!XamlLoader",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object); TypeAttributes.Class | TypeAttributes.Public, asm.MainModule.TypeSystem.Object);
loaderDispatcherDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor) loaderDispatcherDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor)
@ -204,8 +222,8 @@ namespace Avalonia.Build.Tasks
bool CompileGroup(IResourceGroup group) bool CompileGroup(IResourceGroup group)
{ {
var typeDef = new TypeDefinition("CompiledAvaloniaXaml", "!"+ group.Name, var typeDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "!"+ group.Name,
TypeAttributes.Class, asm.MainModule.TypeSystem.Object); TypeAttributes.Class | TypeAttributes.Public, asm.MainModule.TypeSystem.Object);
typeDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor) typeDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor)
{ {
@ -213,7 +231,9 @@ namespace Avalonia.Build.Tasks
}); });
asm.MainModule.Types.Add(typeDef); asm.MainModule.Types.Add(typeDef);
var builder = typeSystem.CreateTypeBuilder(typeDef); 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())) foreach (var res in group.Resources.Where(CheckXamlName).OrderBy(x=>x.FilePath.ToLowerInvariant()))
{ {
try try
@ -279,13 +299,24 @@ namespace Avalonia.Build.Tasks
populateBuilder.DefineDelegateSubType(closureName, false, returnType, parameterTypes), populateBuilder.DefineDelegateSubType(closureName, false, returnType, parameterTypes),
res.Uri, res 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) if (classTypeDefinition != null)
{ {
var compiledPopulateMethod = typeSystem.GetTypeReference(populateBuilder).Resolve()
.Methods.First(m => m.Name == populateName);
var designLoaderFieldType = typeSystem var designLoaderFieldType = typeSystem
.GetType("System.Action`1") .GetType("System.Action`1")
.MakeGenericType(typeSystem.GetType("System.Object")); .MakeGenericType(typeSystem.GetType("System.Object"));
@ -435,8 +466,94 @@ namespace Avalonia.Build.Tasks
} }
res.Remove(); res.Remove();
} }
foreach (var (populateMethod, resourceFilePath) in populateMethodsToTransform)
{
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 = 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"));
}
}
}
}
}
// Technically that's a hack, but it fixes corert incompatibility caused by deterministic builds // Technically that's a hack, but it fixes corert incompatibility caused by deterministic builds
int dupeCounter = 1; int dupeCounter = 1;
foreach (var grp in typeDef.NestedTypes.GroupBy(x => x.Name)) foreach (var grp in typeDef.NestedTypes.GroupBy(x => x.Name))
@ -463,6 +580,55 @@ namespace Avalonia.Build.Tasks
loaderDispatcherMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); loaderDispatcherMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
return true; return true;
} }
static bool? CompileCoreForRefAssembly(
IBuildEngine engine, CecilTypeSystem sourceTypeSystem, CecilTypeSystem refTypeSystem)
{
var asm = refTypeSystem.TargetAssemblyDefinition;
var compiledTypes = sourceTypeSystem.TargetAssemblyDefinition.MainModule.Types
.Where(t => t.Namespace.StartsWith(CompiledAvaloniaXamlNamespace) && t.IsPublic).ToArray();
if (compiledTypes.Length == 0)
{
return null;
}
try
{
foreach (var ogType in compiledTypes)
{
var wrappedOgType = sourceTypeSystem.TargetAssembly.FindType(ogType.FullName);
var clrPropertiesDef = new TypeDefinition(ogType.Namespace, ogType.Name,
TypeAttributes.Class | TypeAttributes.Public, asm.MainModule.TypeSystem.Object);
asm.MainModule.Types.Add(clrPropertiesDef);
foreach (var attribute in ogType.CustomAttributes)
{
var method = asm.MainModule.ImportReference(attribute.Constructor);
clrPropertiesDef.CustomAttributes.Add(new CustomAttribute(method, attribute.GetBlob()));
}
var typeBuilder = refTypeSystem.CreateTypeBuilder(clrPropertiesDef);
foreach (var ogMethod in wrappedOgType.Methods.Where(m => m.IsPublic && m.IsStatic))
{
var method = typeBuilder.DefineMethod(ogMethod.ReturnType, ogMethod.Parameters, ogMethod.Name,
ogMethod.IsPublic, ogMethod.IsStatic, false);
method.Generator.Ldnull();
method.Generator.Throw();
}
typeBuilder.CreateType();
}
}
catch (Exception e)
{
engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", "",
0, 0, 0, 0,
e.Message, "", "Avalonia"));
return false;
}
return true;
}
} }
} }

3
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs

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

94
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs

@ -0,0 +1,94 @@
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
{
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)
{
return node;
}
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))
{
// Don't transform node with any other property, as we don't know how to transform them.
return node;
}
if (sourceProperty.Values.OfType<XamlAstTextNode>().FirstOrDefault() is not { } sourceTextNode)
{
// 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("/")))
{
// Only "avares" protocol supported or relative paths.
return node;
}
var runtimeHelpers = context.Configuration.TypeSystem.FindType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers");
var markerMethodName = "Resolve" + objectNodeType.Name;
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);
}
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);
}
}
}

2
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -102,6 +102,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType TextDecorations { get; } public IXamlType TextDecorations { get; }
public IXamlType TextTrimming { get; } public IXamlType TextTrimming { get; }
public IXamlType ISetter { get; } public IXamlType ISetter { get; }
public IXamlType IStyle { get; }
public IXamlType IResourceDictionary { get; } public IXamlType IResourceDictionary { get; }
public IXamlType ResourceDictionary { get; } public IXamlType ResourceDictionary { get; }
public IXamlMethod ResourceDictionaryDeferredAdd { get; } public IXamlMethod ResourceDictionaryDeferredAdd { get; }
@ -232,6 +233,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
TextDecorations = cfg.TypeSystem.GetType("Avalonia.Media.TextDecorations"); TextDecorations = cfg.TypeSystem.GetType("Avalonia.Media.TextDecorations");
TextTrimming = cfg.TypeSystem.GetType("Avalonia.Media.TextTrimming"); TextTrimming = cfg.TypeSystem.GetType("Avalonia.Media.TextTrimming");
ISetter = cfg.TypeSystem.GetType("Avalonia.Styling.ISetter"); ISetter = cfg.TypeSystem.GetType("Avalonia.Styling.ISetter");
IStyle = cfg.TypeSystem.GetType("Avalonia.Styling.IStyle");
IResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.IResourceDictionary"); IResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.IResourceDictionary");
ResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.ResourceDictionary"); ResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.ResourceDictionary");
ResourceDictionaryDeferredAdd = ResourceDictionary.FindMethod("AddDeferred", XamlIlTypes.Void, true, XamlIlTypes.Object, ResourceDictionaryDeferredAdd = ResourceDictionary.FindMethod("AddDeferred", XamlIlTypes.Void, true, XamlIlTypes.Object,

13
src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs

@ -5,7 +5,10 @@ using System.Reflection;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Styling;
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
// ReSharper disable UnusedParameter.Global // ReSharper disable UnusedParameter.Global
@ -14,6 +17,16 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
{ {
public static class XamlIlRuntimeHelpers 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, public static Func<IServiceProvider, object> DeferredTransformationFactoryV1(Func<IServiceProvider, object> builder,
IServiceProvider provider) IServiceProvider provider)
{ {

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

@ -22,12 +22,12 @@
<ProjectReference Include="..\Avalonia.UnitTests\Avalonia.UnitTests.csproj" /> <ProjectReference Include="..\Avalonia.UnitTests\Avalonia.UnitTests.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Xaml\Style1.xaml"> <AvaloniaResource Include="Xaml\Style1.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
</EmbeddedResource> </AvaloniaResource>
<EmbeddedResource Include="Xaml\Style2.xaml"> <AvaloniaResource Include="Xaml\Style2.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
</EmbeddedResource> </AvaloniaResource>
<AvaloniaResource Include="Xaml\XamlIlClassWithPrecompiledXaml.xaml" /> <AvaloniaResource Include="Xaml\XamlIlClassWithPrecompiledXaml.xaml" />
<AvaloniaResource Include="Xaml\XamlIlClassWithCustomProperty.xaml" /> <AvaloniaResource Include="Xaml\XamlIlClassWithCustomProperty.xaml" />
</ItemGroup> </ItemGroup>

6
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs

@ -473,13 +473,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.True(styles.Count == 1); Assert.True(styles.Count == 1);
var styleInclude = styles.First() as StyleInclude; var styleInclude = styles.First() as IStyle;
Assert.NotNull(styleInclude); Assert.NotNull(styleInclude);
var style = styleInclude.Loaded;
Assert.NotNull(style);
} }
} }

32
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs

@ -113,27 +113,41 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
public void StyleInclude_Is_Built() public void StyleInclude_Is_Built()
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow using (UnitTestApplication.Start(TestServices.StyledWindow
.With(theme: () => new Styles()))) .With(theme: () => new Styles())))
{ {
var xaml = @" var xaml = @"
<ContentControl xmlns='https://github.com/avaloniaui'> <ContentControl xmlns='https://github.com/avaloniaui'>
<ContentControl.Styles> <ContentControl.Styles>
<StyleInclude Source='resm:Avalonia.Markup.Xaml.UnitTests.Xaml.Style1.xaml?assembly=Avalonia.Markup.Xaml.UnitTests'/> <StyleInclude Source='avares://Avalonia.Markup.Xaml.UnitTests/Xaml/Style1.xaml'/>
</ContentControl.Styles> </ContentControl.Styles>
</ContentControl>"; </ContentControl>";
var window = AvaloniaRuntimeXamlLoader.Parse<ContentControl>(xaml); var window = AvaloniaRuntimeXamlLoader.Parse<ContentControl>(xaml);
Assert.IsType<Style>(window.Styles[0]);
}
}
[Fact]
public void StyleInclude_Is_Built_Resources()
{
using (UnitTestApplication.Start(TestServices.StyledWindow
.With(theme: () => new Styles())))
{
var xaml = @"
<ContentControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<ContentControl.Resources>
<StyleInclude x:Key='Include' Source='avares://Avalonia.Markup.Xaml.UnitTests/Xaml/Style1.xaml'/>
</ContentControl.Resources>
</ContentControl>";
Assert.Single(window.Styles); var window = AvaloniaRuntimeXamlLoader.Parse<ContentControl>(xaml);
var styleInclude = window.Styles[0] as StyleInclude;
Assert.NotNull(styleInclude); Assert.IsType<Style>(window.Resources["Include"]);
Assert.NotNull(styleInclude.Source);
Assert.NotNull(styleInclude.Loaded);
} }
} }
[Fact] [Fact]
public void Setter_Can_Contain_Template() public void Setter_Can_Contain_Template()
{ {

Loading…
Cancel
Save