From b8a4de969dd6024f75d5d1a09fa51e20a02faaff Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Tue, 10 Jan 2023 13:55:34 +0100 Subject: [PATCH] Made resources index binary Effectively removes System.Xml.Linq dependency --- .../Platform/Internal/AssemblyDescriptor.cs | 2 +- .../Utilities/AvaloniaResourcesIndex.cs | 149 ++++++++++-------- .../Avalonia.Build.Tasks.csproj | 3 + .../GenerateAvaloniaResourcesTask.cs | 26 +-- .../XamlCompilerTaskExecutor.Helpers.cs | 21 ++- 5 files changed, 103 insertions(+), 98 deletions(-) diff --git a/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs b/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs index df2a26ddd3..467cd530fc 100644 --- a/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs +++ b/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)); diff --git a/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs b/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs index 5235e23e08..3c7e82f080 100644 --- a/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs +++ b/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 Read(Stream stream) + private const int XmlLegacyVersion = 1; + private const int BinaryCurrentVersion = 2; + + public static List 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 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 entries) + private static List ReadXmlIndex() + => throw new NotSupportedException("Found legacy resources index format: please recompile your XAML files"); + + private static List 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(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 data) + public static void WriteIndex(Stream output, List entries) { - var sources = data.ToList(); - var offsets = new Dictionary(); - 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 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 Open)> resources) + { + var entries = new List(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 Entries { get; set; } = new List(); + 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; } } } diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj index 35b22f4f23..e44b7290af 100644 --- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj +++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj @@ -18,6 +18,9 @@ Shared/AvaloniaResourcesIndex.cs + + Shared/Constants.cs + Shared/AvaloniaResourceXamlInfo.cs diff --git a/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs b/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs index 65681a2d28..264a86b014 100644 --- a/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs +++ b/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs @@ -77,29 +77,9 @@ namespace Avalonia.Build.Tasks private void Pack(Stream output, List sources) { - var offsets = new Dictionary(); - 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) source.Open)).ToList()); } private bool PreProcessXamlFiles(List sources) diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs index f83e07bd74..079ea91db1 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs +++ b/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) (() => new MemoryStream(x.Value.FileContents)) + )).ToList()); + + output.Position = 0L; + _embedded = new EmbeddedResource(Constants.AvaloniaResourceName, ManifestResourceAttributes.Public, output); _asm.MainModule.Resources.Add(_embedded); }