Browse Source

Made resources index binary

Effectively removes System.Xml.Linq dependency
pull/9949/head
Julien Lebosquain 3 years ago
parent
commit
b8a4de969d
  1. 2
      src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs
  2. 149
      src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs
  3. 3
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  4. 26
      src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs
  5. 21
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs

2
src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs

@ -32,7 +32,7 @@ internal class AssemblyDescriptor : IAssemblyDescriptor
Resources.Remove(Constants.AvaloniaResourceName);
var indexLength = new BinaryReader(resources).ReadInt32();
var index = AvaloniaResourcesIndexReaderWriter.Read(new SlicedStream(resources, 4, indexLength));
var index = AvaloniaResourcesIndexReaderWriter.ReadIndex(new SlicedStream(resources, 4, indexLength));
var baseOffset = indexLength + 4;
AvaloniaResources = index.ToDictionary(r => GetPathRooted(r), r => (IAssetDescriptor)
new AvaloniaResourceDescriptor(assembly, baseOffset + r.Offset, r.Size));

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

@ -1,105 +1,116 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Xml.Linq;
using System.Linq;
using System.Diagnostics.CodeAnalysis;
using System.Text;
namespace Avalonia.Utilities
{
#if !BUILDTASK
#if !BUILDTASK
public
#endif
#endif
static class AvaloniaResourcesIndexReaderWriter
{
private const int LastKnownVersion = 1;
public static List<AvaloniaResourcesIndexEntry> Read(Stream stream)
private const int XmlLegacyVersion = 1;
private const int BinaryCurrentVersion = 2;
public static List<AvaloniaResourcesIndexEntry> ReadIndex(Stream stream)
{
var ver = new BinaryReader(stream).ReadInt32();
if (ver > LastKnownVersion)
throw new Exception("Resources index format version is not known");
var assetDoc = XDocument.Load(stream);
XNamespace assetNs = assetDoc.Root!.Attribute("xmlns")!.Value;
List<AvaloniaResourcesIndexEntry> entries =
(from entry in assetDoc.Root.Element(assetNs + "Entries")!.Elements(assetNs + "AvaloniaResourcesIndexEntry")
select new AvaloniaResourcesIndexEntry
{
Path = entry.Element(assetNs + "Path")!.Value,
Offset = int.Parse(entry.Element(assetNs + "Offset")!.Value),
Size = int.Parse(entry.Element(assetNs + "Size")!.Value)
}).ToList();
using var reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true);
return entries;
var version = reader.ReadInt32();
return version switch
{
XmlLegacyVersion => ReadXmlIndex(),
BinaryCurrentVersion => ReadBinaryIndex(reader),
_ => throw new Exception($"Unknown resources index format version {version}")
};
}
[RequiresUnreferencedCode("AvaloniaResources uses Data Contract Serialization, which might require unreferenced code")]
public static void Write(Stream stream, List<AvaloniaResourcesIndexEntry> entries)
private static List<AvaloniaResourcesIndexEntry> ReadXmlIndex()
=> throw new NotSupportedException("Found legacy resources index format: please recompile your XAML files");
private static List<AvaloniaResourcesIndexEntry> ReadBinaryIndex(BinaryReader reader)
{
new BinaryWriter(stream).Write(LastKnownVersion);
new DataContractSerializer(typeof(AvaloniaResourcesIndex)).WriteObject(stream,
new AvaloniaResourcesIndex()
{
Entries = entries
var entryCount = reader.ReadInt32();
var entries = new List<AvaloniaResourcesIndexEntry>(entryCount);
for (var i = 0; i < entryCount; ++i)
{
entries.Add(new AvaloniaResourcesIndexEntry {
Path = reader.ReadString(),
Offset = reader.ReadInt32(),
Size = reader.ReadInt32()
});
}
return entries;
}
[RequiresUnreferencedCode("AvaloniaResources uses Data Contract Serialization, which might require unreferenced code")]
public static byte[] Create(Dictionary<string, byte[]> data)
public static void WriteIndex(Stream output, List<AvaloniaResourcesIndexEntry> entries)
{
var sources = data.ToList();
var offsets = new Dictionary<string, int>();
var coffset = 0;
foreach (var s in sources)
using var writer = new BinaryWriter(output, Encoding.UTF8, leaveOpen: true);
WriteIndex(writer, entries);
}
private static void WriteIndex(BinaryWriter writer, List<AvaloniaResourcesIndexEntry> entries)
{
writer.Write(BinaryCurrentVersion);
writer.Write(entries.Count);
foreach (var entry in entries)
{
offsets[s.Key] = coffset;
coffset += s.Value.Length;
writer.Write(entry.Path ?? string.Empty);
writer.Write(entry.Offset);
writer.Write(entry.Size);
}
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)
}
public static void WriteResources(Stream output, List<(string Path, int Size, Func<Stream> Open)> resources)
{
var entries = new List<AvaloniaResourcesIndexEntry>(resources.Count);
var offset = 0;
foreach (var resource in resources)
{
output.Write(s.Value,0,s.Value.Length);
entries.Add(new AvaloniaResourcesIndexEntry
{
Path = resource.Path,
Offset = offset,
Size = resource.Size
});
offset += resource.Size;
}
return output.ToArray();
}
}
using var writer = new BinaryWriter(output, Encoding.UTF8, leaveOpen: true);
writer.Write(0); // index size placeholder, overwritten below
[DataContract]
#if !BUILDTASK
public
#endif
class AvaloniaResourcesIndex
{
[DataMember]
public List<AvaloniaResourcesIndexEntry> Entries { get; set; } = new List<AvaloniaResourcesIndexEntry>();
var posBeforeEntries = output.Position;
WriteIndex(writer, entries);
var posAfterEntries = output.Position;
var indexSize = (int) (posAfterEntries - posBeforeEntries);
output.Position = 0L;
writer.Write(indexSize);
output.Position = posAfterEntries;
foreach (var resource in resources)
{
using var resourceStream = resource.Open();
resourceStream.CopyTo(output);
}
}
}
[DataContract]
#if !BUILDTASK
public
#endif
class AvaloniaResourcesIndexEntry
{
[DataMember]
public string? Path { get; set; }
[DataMember]
public int Offset { get; set; }
[DataMember]
public int Size { get; set; }
}
}

3
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@ -18,6 +18,9 @@
<Compile Include="../Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs">
<Link>Shared/AvaloniaResourcesIndex.cs</Link>
</Compile>
<Compile Include="../Avalonia.Base/Platform/Internal/Constants.cs">
<Link>Shared/Constants.cs</Link>
</Compile>
<Compile Include="../Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaResourceXamlInfo.cs">
<Link>Shared/AvaloniaResourceXamlInfo.cs</Link>
</Compile>

26
src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs

@ -77,29 +77,9 @@ namespace Avalonia.Build.Tasks
private void Pack(Stream output, List<Source> sources)
{
var offsets = new Dictionary<Source, int>();
var coffset = 0;
foreach (var s in sources)
{
offsets[s] = coffset;
coffset += s.Size;
}
var index = sources.Select(s => new AvaloniaResourcesIndexEntry
{
Path = s.Path,
Size = s.Size,
Offset = offsets[s]
}).ToList();
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)
{
using (var input = s.Open())
input.CopyTo(output);
}
AvaloniaResourcesIndexReaderWriter.WriteResources(
output,
sources.Select(source => (source.Path, source.Size, (Func<Stream>) source.Open)).ToList());
}
private bool PreProcessXamlFiles(List<Source> sources)

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

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Avalonia.Platform.Internal;
using Avalonia.Utilities;
using Mono.Cecil;
using Mono.Cecil.Cil;
@ -34,13 +36,13 @@ namespace Avalonia.Build.Tasks
{
_asm = asm;
_embedded = ((EmbeddedResource)asm.MainModule.Resources.FirstOrDefault(r =>
r.ResourceType == ResourceType.Embedded && r.Name == "!AvaloniaResources"));
r.ResourceType == ResourceType.Embedded && r.Name == Constants.AvaloniaResourceName));
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 index = AvaloniaResourcesIndexReaderWriter.ReadIndex(new MemoryStream(br.ReadBytes(br.ReadInt32())));
var baseOffset = stream.Position;
foreach (var e in index)
{
@ -61,9 +63,18 @@ namespace Avalonia.Build.Tasks
if (_resources.Count == 0)
return;
_embedded = new EmbeddedResource("!AvaloniaResources", ManifestResourceAttributes.Public,
AvaloniaResourcesIndexReaderWriter.Create(_resources.ToDictionary(x => x.Key,
x => x.Value.FileContents)));
var output = new MemoryStream();
AvaloniaResourcesIndexReaderWriter.WriteResources(
output,
_resources.Select(x => (
Path: x.Key,
Size: x.Value.FileContents.Length,
Open: (Func<Stream>) (() => new MemoryStream(x.Value.FileContents))
)).ToList());
output.Position = 0L;
_embedded = new EmbeddedResource(Constants.AvaloniaResourceName, ManifestResourceAttributes.Public, output);
_asm.MainModule.Resources.Add(_embedded);
}

Loading…
Cancel
Save