Browse Source

Merge pull request #9949 from MrJul/binary-resources-index

Made resources index binary
pull/9968/head
Max Katz 4 years ago
committed by GitHub
parent
commit
11c4fe7f8b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  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); Resources.Remove(Constants.AvaloniaResourceName);
var indexLength = new BinaryReader(resources).ReadInt32(); 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; var baseOffset = indexLength + 4;
AvaloniaResources = index.ToDictionary(r => GetPathRooted(r), r => (IAssetDescriptor) AvaloniaResources = index.ToDictionary(r => GetPathRooted(r), r => (IAssetDescriptor)
new AvaloniaResourceDescriptor(assembly, baseOffset + r.Offset, r.Size)); new AvaloniaResourceDescriptor(assembly, baseOffset + r.Offset, r.Size));

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

@ -1,105 +1,116 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Runtime.Serialization; using System.Text;
using System.Xml.Linq;
using System.Linq;
using System.Diagnostics.CodeAnalysis;
namespace Avalonia.Utilities namespace Avalonia.Utilities
{ {
#if !BUILDTASK #if !BUILDTASK
public public
#endif #endif
static class AvaloniaResourcesIndexReaderWriter static class AvaloniaResourcesIndexReaderWriter
{ {
private const int LastKnownVersion = 1; private const int XmlLegacyVersion = 1;
public static List<AvaloniaResourcesIndexEntry> Read(Stream stream) private const int BinaryCurrentVersion = 2;
public static List<AvaloniaResourcesIndexEntry> ReadIndex(Stream stream)
{ {
var ver = new BinaryReader(stream).ReadInt32(); using var reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true);
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();
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")] private static List<AvaloniaResourcesIndexEntry> ReadXmlIndex()
public static void Write(Stream stream, List<AvaloniaResourcesIndexEntry> entries) => 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); var entryCount = reader.ReadInt32();
new DataContractSerializer(typeof(AvaloniaResourcesIndex)).WriteObject(stream, var entries = new List<AvaloniaResourcesIndexEntry>(entryCount);
new AvaloniaResourcesIndex()
{ for (var i = 0; i < entryCount; ++i)
Entries = entries {
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 void WriteIndex(Stream output, List<AvaloniaResourcesIndexEntry> entries)
public static byte[] Create(Dictionary<string, byte[]> data)
{ {
var sources = data.ToList(); using var writer = new BinaryWriter(output, Encoding.UTF8, leaveOpen: true);
var offsets = new Dictionary<string, int>();
var coffset = 0; WriteIndex(writer, entries);
foreach (var s in sources) }
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; writer.Write(entry.Path ?? string.Empty);
coffset += s.Value.Length; writer.Write(entry.Offset);
writer.Write(entry.Size);
} }
var index = sources.Select(s => new AvaloniaResourcesIndexEntry }
{
Path = s.Key, public static void WriteResources(Stream output, List<(string Path, int Size, Func<Stream> Open)> resources)
Size = s.Value.Length, {
Offset = offsets[s.Key] var entries = new List<AvaloniaResourcesIndexEntry>(resources.Count);
}).ToList(); var offset = 0;
var output = new MemoryStream();
var ms = new MemoryStream(); foreach (var resource in resources)
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); 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] var posBeforeEntries = output.Position;
#if !BUILDTASK WriteIndex(writer, entries);
public
#endif var posAfterEntries = output.Position;
class AvaloniaResourcesIndex var indexSize = (int) (posAfterEntries - posBeforeEntries);
{ output.Position = 0L;
[DataMember] writer.Write(indexSize);
public List<AvaloniaResourcesIndexEntry> Entries { get; set; } = new List<AvaloniaResourcesIndexEntry>(); output.Position = posAfterEntries;
foreach (var resource in resources)
{
using var resourceStream = resource.Open();
resourceStream.CopyTo(output);
}
}
} }
[DataContract]
#if !BUILDTASK #if !BUILDTASK
public public
#endif #endif
class AvaloniaResourcesIndexEntry class AvaloniaResourcesIndexEntry
{ {
[DataMember]
public string? Path { get; set; } public string? Path { get; set; }
[DataMember]
public int Offset { get; set; } public int Offset { get; set; }
[DataMember]
public int Size { get; set; } 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"> <Compile Include="../Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs">
<Link>Shared/AvaloniaResourcesIndex.cs</Link> <Link>Shared/AvaloniaResourcesIndex.cs</Link>
</Compile> </Compile>
<Compile Include="../Avalonia.Base/Platform/Internal/Constants.cs">
<Link>Shared/Constants.cs</Link>
</Compile>
<Compile Include="../Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaResourceXamlInfo.cs"> <Compile Include="../Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaResourceXamlInfo.cs">
<Link>Shared/AvaloniaResourceXamlInfo.cs</Link> <Link>Shared/AvaloniaResourceXamlInfo.cs</Link>
</Compile> </Compile>

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

@ -77,29 +77,9 @@ namespace Avalonia.Build.Tasks
private void Pack(Stream output, List<Source> sources) private void Pack(Stream output, List<Source> sources)
{ {
var offsets = new Dictionary<Source, int>(); AvaloniaResourcesIndexReaderWriter.WriteResources(
var coffset = 0; output,
foreach (var s in sources) sources.Select(source => (source.Path, source.Size, (Func<Stream>) source.Open)).ToList());
{
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);
}
} }
private bool PreProcessXamlFiles(List<Source> sources) 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.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Avalonia.Platform.Internal;
using Avalonia.Utilities; using Avalonia.Utilities;
using Mono.Cecil; using Mono.Cecil;
using Mono.Cecil.Cil; using Mono.Cecil.Cil;
@ -34,13 +36,13 @@ namespace Avalonia.Build.Tasks
{ {
_asm = asm; _asm = asm;
_embedded = ((EmbeddedResource)asm.MainModule.Resources.FirstOrDefault(r => _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) if (_embedded == null)
return; return;
using (var stream = _embedded.GetResourceStream()) using (var stream = _embedded.GetResourceStream())
{ {
var br = new BinaryReader(stream); 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; var baseOffset = stream.Position;
foreach (var e in index) foreach (var e in index)
{ {
@ -61,9 +63,18 @@ namespace Avalonia.Build.Tasks
if (_resources.Count == 0) if (_resources.Count == 0)
return; return;
_embedded = new EmbeddedResource("!AvaloniaResources", ManifestResourceAttributes.Public, var output = new MemoryStream();
AvaloniaResourcesIndexReaderWriter.Create(_resources.ToDictionary(x => x.Key,
x => x.Value.FileContents))); 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); _asm.MainModule.Resources.Add(_embedded);
} }

Loading…
Cancel
Save