Browse Source

Inject the call to compiled xaml to x:Class-type

xamlil-debug-info
Nikita Tsukanov 7 years ago
parent
commit
6ab1fbd432
  1. 9
      samples/ControlCatalog/Pages/ButtonPage.xaml.cs
  2. 30
      src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs
  3. 15
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  4. 129
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs
  5. 143
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  6. 2
      src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github

9
samples/ControlCatalog/Pages/ButtonPage.xaml.cs

@ -5,14 +5,5 @@ namespace ControlCatalog.Pages
{ {
public class ButtonPage : UserControl public class ButtonPage : UserControl
{ {
public ButtonPage()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
} }
} }

30
src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs

@ -46,6 +46,36 @@ namespace Avalonia.Utilities
Entries = entries Entries = entries
}); });
} }
public static byte[] Create(Dictionary<string, byte[]> data)
{
var sources = data.ToList();
var offsets = new Dictionary<string, int>();
var coffset = 0;
foreach (var s in sources)
{
offsets[s.Key] = coffset;
coffset += s.Value.Length;
}
var index = sources.Select(s => new AvaloniaResourcesIndexEntry
{
Path = s.Key,
Size = s.Value.Length,
Offset = offsets[s.Key]
}).ToList();
var output = new MemoryStream();
var ms = new MemoryStream();
AvaloniaResourcesIndexReaderWriter.Write(ms, index);
new BinaryWriter(output).Write((int)ms.Length);
ms.Position = 0;
ms.CopyTo(output);
foreach (var s in sources)
{
output.Write(s.Value,0,s.Value.Length);
}
return output.ToArray();
}
} }
[DataContract] [DataContract]

15
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@ -43,7 +43,7 @@
<Compile Remove="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\**\obj\**\*.cs" /> <Compile Remove="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\**\obj\**\*.cs" />
<Compile Remove="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\src\XamlIl\TypeSystem\SreTypeSystem.cs" /> <Compile Remove="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\src\XamlIl\TypeSystem\SreTypeSystem.cs" />
<PackageReference Include="Avalonia.Unofficial.Cecil" Version="20190417.2.0" PrivateAssets="All"/> <PackageReference Include="Avalonia.Unofficial.Cecil" Version="20190417.2.0" PrivateAssets="All" />
<PackageReference Condition="$(TargetFramework) == 'netstandard2.0'" Include="ILRepack.MSBuild.Task" Version="2.0.3" PrivateAssets="All" /> <PackageReference Condition="$(TargetFramework) == 'netstandard2.0'" Include="ILRepack.MSBuild.Task" Version="2.0.3" PrivateAssets="All" />
<PackageReference Include="Microsoft.Build.Framework" Version="15.1.548" PrivateAssets="All" /> <PackageReference Include="Microsoft.Build.Framework" Version="15.1.548" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
@ -57,17 +57,12 @@
<ItemGroup> <ItemGroup>
<InputAssemblies Include="Mono.Cecil.dll" /> <InputAssemblies Include="Mono.Cecil.dll" />
</ItemGroup> </ItemGroup>
<ILRepack <ILRepack OutputType="$(OutputType)" MainAssembly="$(AssemblyName).dll" OutputAssembly="$(AssemblyName).dll" InputAssemblies="@(InputAssemblies)" WorkingDirectory="$(WorkingDirectory)" />
OutputType="$(OutputType)"
MainAssembly="$(AssemblyName).dll"
OutputAssembly="$(AssemblyName).dll"
InputAssemblies="@(InputAssemblies)"
WorkingDirectory="$(WorkingDirectory)" />
<ItemGroup> <ItemGroup>
<DeleteNonNeededResults Include="$(WorkingDirectory)/*.dll"/> <DeleteNonNeededResults Include="$(WorkingDirectory)/*.dll" />
<DeleteNonNeededResults Remove="$(WorkingDirectory)/Avalonia.Build.Tasks.dll"/> <DeleteNonNeededResults Remove="$(WorkingDirectory)/Avalonia.Build.Tasks.dll" />
</ItemGroup> </ItemGroup>
<Delete Files="@(DeleteNonNeededResults)"/> <Delete Files="@(DeleteNonNeededResults)" />
</Target> </Target>
</Project> </Project>

129
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs

@ -0,0 +1,129 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Avalonia.Utilities;
using Mono.Cecil;
namespace Avalonia.Build.Tasks
{
public static partial class XamlCompilerTaskExecutor
{
interface IResource
{
string Uri { get; }
string Name { get; }
byte[] GetData();
string FilePath { get; }
void Remove();
}
interface IResourceGroup
{
string Name { get; }
IEnumerable<IResource> Resources { get; }
}
class EmbeddedResources : IResourceGroup
{
private readonly AssemblyDefinition _asm;
public string Name => "EmbeddedResource";
public IEnumerable<IResource> Resources => _asm.MainModule.Resources.OfType<EmbeddedResource>()
.Select(r => new WrappedResource(_asm, r)).ToList();
public EmbeddedResources(AssemblyDefinition asm)
{
_asm = asm;
}
class WrappedResource : IResource
{
private readonly AssemblyDefinition _asm;
private readonly EmbeddedResource _res;
public WrappedResource(AssemblyDefinition asm, EmbeddedResource res)
{
_asm = asm;
_res = res;
}
public string Uri => $"resm:{Name}?assembly={_asm.Name}";
public string Name => _res.Name;
public byte[] GetData() => _res.GetResourceData();
public string FilePath => Name;
public void Remove() => _asm.MainModule.Resources.Remove(_res);
}
}
class AvaloniaResources : IResourceGroup
{
private readonly AssemblyDefinition _asm;
Dictionary<string, AvaloniaResource> _resources = new Dictionary<string, AvaloniaResource>();
private EmbeddedResource _embedded;
public AvaloniaResources(AssemblyDefinition asm, string projectDir)
{
_asm = asm;
_embedded = ((EmbeddedResource)asm.MainModule.Resources.FirstOrDefault(r =>
r.ResourceType == ResourceType.Embedded && r.Name == "!AvaloniaResources"));
if (_embedded == null)
return;
using (var stream = _embedded.GetResourceStream())
{
var br = new BinaryReader(stream);
var index = AvaloniaResourcesIndexReaderWriter.Read(new MemoryStream(br.ReadBytes(br.ReadInt32())));
var baseOffset = stream.Position;
foreach (var e in index)
{
stream.Position = e.Offset + baseOffset;
_resources[e.Path] = new AvaloniaResource(this, projectDir, e.Path, br.ReadBytes(e.Size));
}
}
}
public void Save()
{
if (_embedded != null)
{
_asm.MainModule.Resources.Remove(_embedded);
_embedded = null;
}
if (_resources.Count == 0)
return;
_embedded = new EmbeddedResource("!AvaloniaResources", ManifestResourceAttributes.Public,
AvaloniaResourcesIndexReaderWriter.Create(_resources.ToDictionary(x => x.Key,
x => x.Value.GetData())));
_asm.MainModule.Resources.Add(_embedded);
}
public string Name => "AvaloniaResources";
public IEnumerable<IResource> Resources => _resources.Values.ToList();
class AvaloniaResource : IResource
{
private readonly AvaloniaResources _grp;
private readonly byte[] _data;
public AvaloniaResource(AvaloniaResources grp,
string projectDir,
string name, byte[] data)
{
_grp = grp;
_data = data;
Name = name;
FilePath = Path.Combine(projectDir, name.TrimStart('/'));
Uri = $"avares://{grp._asm.Name}/{name.TrimStart('/')}";
}
public string Uri { get; }
public string Name { get; }
public byte[] GetData() => _data;
public string FilePath { get; }
public void Remove() => _grp._resources.Remove(Name);
}
}
}
}

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

@ -9,45 +9,22 @@ using Microsoft.Build.Framework;
using Mono.Cecil; using Mono.Cecil;
using XamlIl.TypeSystem; using XamlIl.TypeSystem;
using Avalonia.Utilities; using Avalonia.Utilities;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks; using Mono.Cecil.Rocks;
using XamlIl; using XamlIl;
using XamlIl.Ast; using XamlIl.Ast;
using XamlIl.Parsers; using XamlIl.Parsers;
using XamlIl.Transform; using XamlIl.Transform;
using MethodAttributes = Mono.Cecil.MethodAttributes;
using TypeAttributes = Mono.Cecil.TypeAttributes; using TypeAttributes = Mono.Cecil.TypeAttributes;
namespace Avalonia.Build.Tasks namespace Avalonia.Build.Tasks
{ {
public static class XamlCompilerTaskExecutor public static partial class XamlCompilerTaskExecutor
{ {
static bool CheckXamlName(string n) => n.ToLowerInvariant().EndsWith(".xaml") static bool CheckXamlName(IResource r) => r.Name.ToLowerInvariant().EndsWith(".xaml")
|| n.ToLowerInvariant().EndsWith(".paml"); || r.Name.ToLowerInvariant().EndsWith(".paml");
static Dictionary<string, byte[]> ReadAvaloniaXamlResources(AssemblyDefinition asm)
{
var rv = new Dictionary<string, byte[]>();
var stream = ((EmbeddedResource)asm.MainModule.Resources.FirstOrDefault(r =>
r.ResourceType == ResourceType.Embedded && r.Name == "!AvaloniaResources"))?.GetResourceStream();
if (stream == null)
return rv;
var br = new BinaryReader(stream);
var index = AvaloniaResourcesIndexReaderWriter.Read(new MemoryStream(br.ReadBytes(br.ReadInt32())));
var baseOffset = stream.Position;
foreach (var e in index.Where(e => CheckXamlName(e.Path)))
{
stream.Position = e.Offset + baseOffset;
rv[e.Path] = br.ReadBytes(e.Size);
}
return rv;
}
static Dictionary<string, byte[]> ReadEmbeddedXamlResources(AssemblyDefinition asm)
{
var rv = new Dictionary<string, byte[]>();
foreach (var r in asm.MainModule.Resources.OfType<EmbeddedResource>().Where(r => CheckXamlName(r.Name)))
rv[r.Name] = r.GetResourceData();
return rv;
}
public class CompileResult public class CompileResult
{ {
@ -65,11 +42,12 @@ namespace Avalonia.Build.Tasks
{ {
var typeSystem = new CecilTypeSystem(references.Concat(new[] {input}), input); var typeSystem = new CecilTypeSystem(references.Concat(new[] {input}), input);
var asm = typeSystem.TargetAssemblyDefinition; var asm = typeSystem.TargetAssemblyDefinition;
var emres = ReadEmbeddedXamlResources(asm); var emres = new EmbeddedResources(asm);
var avares = ReadAvaloniaXamlResources(asm); var avares = new AvaloniaResources(asm, projectDirectory);
if (avares.Count == 0 && emres.Count == 0) if (avares.Resources.Count(CheckXamlName) == 0 && emres.Resources.Count(CheckXamlName) == 0)
// Nothing to do // Nothing to do
return new CompileResult(true); return new CompileResult(true);
var xamlLanguage = AvaloniaXamlIlLanguage.Configure(typeSystem); var xamlLanguage = AvaloniaXamlIlLanguage.Configure(typeSystem);
var compilerConfig = new XamlIlTransformerConfiguration(typeSystem, var compilerConfig = new XamlIlTransformerConfiguration(typeSystem,
typeSystem.TargetAssembly, typeSystem.TargetAssembly,
@ -94,10 +72,9 @@ namespace Avalonia.Build.Tasks
asm.MainModule.ImportReference(editorBrowsableAttribute.GetConstructors() asm.MainModule.ImportReference(editorBrowsableAttribute.GetConstructors()
.First(c => c.Parameters.Count == 1)); .First(c => c.Parameters.Count == 1));
bool CompileGroup(Dictionary<string, byte[]> resources, string name, Func<string, string> uriTransform, bool CompileGroup(IResourceGroup group)
Func<string, string> pathTransform)
{ {
var typeDef = new TypeDefinition("CompiledAvaloniaXaml", name, var typeDef = new TypeDefinition("CompiledAvaloniaXaml", group.Name,
TypeAttributes.Class, asm.MainModule.TypeSystem.Object); TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
@ -107,23 +84,24 @@ namespace Avalonia.Build.Tasks
}); });
asm.MainModule.Types.Add(typeDef); asm.MainModule.Types.Add(typeDef);
var builder = typeSystem.CreateTypeBuilder(typeDef); var builder = typeSystem.CreateTypeBuilder(typeDef);
foreach (var res in resources)
foreach (var res in group.Resources.Where(CheckXamlName))
{ {
try try
{ {
// StreamReader is needed here to handle BOM // StreamReader is needed here to handle BOM
var xaml = new StreamReader(new MemoryStream(res.Value)).ReadToEnd(); var xaml = new StreamReader(new MemoryStream(res.GetData())).ReadToEnd();
var parsed = XDocumentXamlIlParser.Parse(xaml); var parsed = XDocumentXamlIlParser.Parse(xaml);
var initialRoot = (XamlIlAstObjectNode)parsed.Root; var initialRoot = (XamlIlAstObjectNode)parsed.Root;
var classDirective = initialRoot.Children.OfType<XamlIlAstXmlDirective>() var classDirective = initialRoot.Children.OfType<XamlIlAstXmlDirective>()
.FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Class"); .FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Class");
IXamlIlType classType = null;
if (classDirective != null) if (classDirective != null)
{ {
if (classDirective.Values.Count != 1 || !(classDirective.Values[0] is XamlIlAstTextNode tn)) if (classDirective.Values.Count != 1 || !(classDirective.Values[0] is XamlIlAstTextNode tn))
throw new XamlIlParseException("x:Class should have a string value", classDirective); throw new XamlIlParseException("x:Class should have a string value", classDirective);
var classType = typeSystem.TargetAssembly.FindType(tn.Text); classType = typeSystem.TargetAssembly.FindType(tn.Text);
if (classType == null) if (classType == null)
throw new XamlIlParseException($"Unable to find type `{tn.Text}`", classDirective); throw new XamlIlParseException($"Unable to find type `{tn.Text}`", classDirective);
initialRoot.Type = new XamlIlAstClrTypeReference(classDirective, classType); initialRoot.Type = new XamlIlAstClrTypeReference(classDirective, classType);
@ -131,9 +109,72 @@ namespace Avalonia.Build.Tasks
compiler.Transform(parsed); compiler.Transform(parsed);
var populateName = "Populate:" + res.Name;
var buildName = classType != null ? "Build:" + res.Name : null;
compiler.Compile(parsed, builder, contextClass, compiler.Compile(parsed, builder, contextClass,
"Populate:" + res.Key, "Build:" + res.Key, populateName, buildName,
"NamespaceInfo:" + res.Key, uriTransform(res.Key)); "NamespaceInfo:" + res.Name, res.Uri);
if (classType != null)
{
var compiledPopulateMethod = typeSystem.GetTypeReference(builder).Resolve()
.Methods.First(m => m.Name == populateName);
var classTypeDefinition = typeSystem.GetTypeReference(classType).Resolve();
var trampoline = new MethodDefinition("!XamlIlPopulateTrampoline",
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.Ldnull));
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));
var foundXamlLoader = false;
// Find AvaloniaXamlLoader.Load(this) and replace it with !XamlIlPopulateTrampoline(this)
foreach (var method in classTypeDefinition.Methods
.Where(m => !m.Attributes.HasFlag(MethodAttributes.Static)))
{
var i = method.Body.Instructions;
for (var c = 1; c < i.Count; c++)
{
if (i[c - 1].OpCode == OpCodes.Ldarg_0
&& i[c].OpCode == OpCodes.Call)
{
var op = i[c].Operand as MethodReference;
if (op != null
&& op.Name == "Load"
&& op.Parameters.Count == 1
&& op.Parameters[0].ParameterType.FullName == "System.Object"
&& op.DeclaringType.FullName == "Avalonia.Markup.Xaml.AvaloniaXamlLoader")
{
i[c].Operand = trampoline;
foundXamlLoader = true;
}
}
}
}
if (!foundXamlLoader)
{
var ctors = classTypeDefinition.GetConstructors().ToList();
// We can inject xaml loader into default constructor
if (ctors.Count == 1 && ctors[0].Body.Instructions.Count(o=>o.OpCode!=OpCodes.Nop) == 3)
{
var i = ctors[0].Body.Instructions;
var retIdx = i.IndexOf(i.Last(x => x.OpCode == OpCodes.Ret));
i.Insert(retIdx, Instruction.Create(OpCodes.Call, trampoline));
i.Insert(retIdx, Instruction.Create(OpCodes.Ldarg_0));
}
else
{
throw new InvalidProgramException(
$"No call to AvaloniaXamlLoader.Load(this) call found anywhere in the type {classType.FullName} and type seems to have custom constructors.");
}
}
}
} }
catch (Exception e) catch (Exception e)
{ {
@ -143,26 +184,28 @@ namespace Avalonia.Build.Tasks
lineNumber = xe.Line; lineNumber = xe.Line;
linePosition = xe.Position; linePosition = xe.Position;
} }
engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", pathTransform(res.Key), engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", res.FilePath,
lineNumber, linePosition, lineNumber, linePosition, lineNumber, linePosition, lineNumber, linePosition,
e.Message, "", "Avalonia")); e.Message, "", "Avalonia"));
return false; return false;
} }
res.Remove();
} }
return true; return true;
} }
if (emres.Count != 0)
if (!CompileGroup(emres, "EmbeddedResource", if (emres.Resources.Count(CheckXamlName) != 0)
name => $"resm:{name}?assembly={asm.Name}", name => name)) if (!CompileGroup(emres))
return new CompileResult(false); return new CompileResult(false);
if (avares.Count != 0) if (avares.Resources.Count(CheckXamlName) != 0)
if (!CompileGroup(avares, "AvaloniaResource", {
name => $"avares://{asm.Name}/{name}", if (!CompileGroup(avares))
name => Path.Combine(projectDirectory, name.TrimStart('/'))))
return new CompileResult(false); return new CompileResult(false);
avares.Save();
}
var ms = new MemoryStream(); var ms = new MemoryStream();
asm.Write(ms); asm.Write(ms);
return new CompileResult(true, ms.ToArray()); return new CompileResult(true, ms.ToArray());

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

@ -1 +1 @@
Subproject commit 2c9c9cb392d36af4624578254aa376ef0bb5a964 Subproject commit c28bd89bf4c315c188394139f500f7f4efed4b94
Loading…
Cancel
Save