A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

181 lines
6.9 KiB

using System.Collections.Generic;
using System.IO;
using System.Linq;
using Avalonia.Utilities;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Collections.Generic;
using XamlX.TypeSystem;
namespace Avalonia.Build.Tasks
{
public static partial class XamlCompilerTaskExecutor
{
interface IResource : IFileSource
{
string Uri { get; }
string Name { 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.Name}";
public string Name => _res.Name;
public string FilePath => Name;
public byte[] FileContents => _res.GetResourceData();
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.FileContents)));
_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}/{name.TrimStart('/')}";
}
public string Uri { get; }
public string Name { get; }
public string FilePath { get; }
public byte[] FileContents => _data;
public void Remove() => _grp._resources.Remove(Name);
}
}
static void CopyDebugDocument(MethodDefinition method, MethodDefinition copyFrom)
{
if (!copyFrom.DebugInformation.HasSequencePoints)
return;
var dbg = method.DebugInformation;
dbg.Scope = new ScopeDebugInformation(method.Body.Instructions.First(), method.Body.Instructions.First())
{
End = new InstructionOffset(),
Import = new ImportDebugInformation()
};
dbg.SequencePoints.Add(new SequencePoint(method.Body.Instructions.First(),
copyFrom.DebugInformation.SequencePoints.First().Document)
{
StartLine = 0xfeefee,
EndLine = 0xfeefee
});
}
private static bool MatchThisCall(Collection<Instruction> instructions, int idx)
{
var i = instructions[idx];
// A "normal" way of passing `this` to a static method:
// ldarg.0
// call void [Avalonia.Markup.Xaml]Avalonia.Markup.Xaml.AvaloniaXamlLoader::Load(object)
if (i.OpCode == OpCodes.Ldarg_0 || (i.OpCode == OpCodes.Ldarg && i.Operand?.Equals(0) == true))
return true;
/* F# way of using `this` in constructor emits a monstrosity like this:
IL_01c7: ldarg.0
IL_01c8: ldfld class [FSharp.Core]Microsoft.FSharp.Core.FSharpRef`1<class FVim.Cursor> FVim.Cursor::this
IL_01cd: call instance !0 class [FSharp.Core]Microsoft.FSharp.Core.FSharpRef`1<class FVim.Cursor>::get_contents()
IL_01d2: call !!0 [FSharp.Core]Microsoft.FSharp.Core.LanguagePrimitives/IntrinsicFunctions::CheckThis<class FVim.Cursor>(!!0)
IL_01d7: call void [Avalonia.Markup.Xaml]Avalonia.Markup.Xaml.AvaloniaXamlLoader::Load(object)
We check for the previous call to be Microsoft.FSharp.Core.LanguagePrimitives/IntrinsicFunctions::CheckThis
since it actually returns `this`
*/
if (i.OpCode == OpCodes.Call
&& i.Operand is GenericInstanceMethod gim
&& gim.Name == "CheckThis"
&& gim.DeclaringType.FullName == "Microsoft.FSharp.Core.LanguagePrimitives/IntrinsicFunctions")
return true;
return false;
}
}
}