ILRepack/Steps/ResourcesRepackStep.cs (239 lines of code) (raw):

// // Copyright (c) 2011 Francois Valdy // Copyright (c) 2015 Timotei Dolean // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // using ILRepacking.Steps.ResourceProcessing; using Mono.Cecil; using System.Collections.Generic; using System.IO; using System.Linq; using System.Resources; using System.Runtime.Serialization.Formatters.Binary; namespace ILRepacking.Steps { internal class ResourcesRepackStep : IRepackStep { internal const string ILRepackListResourceName = "ILRepack.List"; private readonly ILogger _logger; private readonly IRepackContext _repackContext; private readonly RepackOptions _options; private readonly ModuleDefinition _targetAssemblyMainModule; public ResourcesRepackStep( ILogger logger, IRepackContext repackContext, RepackOptions options) { _logger = logger; _options = options; _repackContext = repackContext; _targetAssemblyMainModule = _repackContext.TargetAssemblyMainModule; } public void Perform() { _logger.Info("Processing resources"); // merge resources IEnumerable<string> repackList = new List<string>(); Dictionary<string, List<int>> ikvmExportsLists = new Dictionary<string, List<int>>(); if (!_options.NoRepackRes) { repackList = _repackContext.MergedAssemblies.Select(a => a.FullName).ToList(); } bool areCollectedStreamsWritten = false; var bamlStreamCollector = new BamlStreamCollector(_logger, _repackContext); var bamlResourcePatcher = new BamlResourcePatcher(_repackContext); var commonProcessors = new List<IResProcessor> { new StringResourceProcessor(_repackContext), new GenericResourceProcessor(_repackContext) }; var primaryAssemblyProcessors = new[] { bamlResourcePatcher }.Union(commonProcessors).ToList(); var otherAssemblyProcessors = new List<IResProcessor> { bamlResourcePatcher, bamlStreamCollector }.Union(commonProcessors).ToList(); // Primary Assembly *must* be the last one in order to properly gather the resources // from dependencies var assembliesList = _repackContext.OtherAssemblies.Concat(new[] { _repackContext.PrimaryAssemblyDefinition }); foreach (var assembly in assembliesList) { bool isPrimaryAssembly = assembly == _repackContext.PrimaryAssemblyDefinition; var assemblyProcessors = isPrimaryAssembly ? primaryAssemblyProcessors : otherAssemblyProcessors; foreach (var resource in assembly.Modules.SelectMany(x => x.Resources)) { if (resource.Name == ILRepackListResourceName) { if (!_options.NoRepackRes && resource is EmbeddedResource) { repackList = repackList.Union(GetRepackListFromResource((EmbeddedResource)resource)); } } else if (resource.Name == "ikvm.exports") { if (resource is EmbeddedResource) { ikvmExportsLists = MergeIkvmExports( ikvmExportsLists, GetIkvmExportsListsFromResource((EmbeddedResource)resource)); } } else { if (!_options.AllowDuplicateResources && _targetAssemblyMainModule.Resources.Any(x => x.Name == resource.Name)) { // Not much we can do about 'ikvm__META-INF!MANIFEST.MF' _logger.Warn("Ignoring duplicate resource " + resource.Name); } else { _logger.Verbose("- Importing " + resource.Name); var newResource = resource; switch (resource.ResourceType) { case ResourceType.AssemblyLinked: // TODO _logger.Warn("AssemblyLinkedResource reference may need to be fixed (to link to newly created assembly)" + resource.Name); break; case ResourceType.Linked: // TODO ? (or not) break; case ResourceType.Embedded: var er = (EmbeddedResource)resource; if (er.Name.EndsWith(".resources")) { // we don't want to write the bamls to other embedded resource files bool shouldWriteCollectedBamlStreams = isPrimaryAssembly && $"{assembly.Name.Name}.g.resources".Equals(er.Name); if (shouldWriteCollectedBamlStreams) areCollectedStreamsWritten = true; newResource = FixResxResource(assembly, er, assemblyProcessors, shouldWriteCollectedBamlStreams ? bamlStreamCollector : null); } break; } _targetAssemblyMainModule.Resources.Add(newResource); } } } } if (ikvmExportsLists.Count > 0) _targetAssemblyMainModule.Resources.Add( GenerateIkvmExports(ikvmExportsLists)); if (!_options.NoRepackRes) _targetAssemblyMainModule.Resources.Add( GenerateRepackListResource(repackList.ToList())); CreateNewBamlResourceIfNeeded(areCollectedStreamsWritten, bamlStreamCollector); } private void CreateNewBamlResourceIfNeeded(bool areCollectedStreamsWritten, BamlStreamCollector bamlStreamCollector) { // if there weren't any (BAML) resources in the original assembly, then we need to create a new resource if (areCollectedStreamsWritten || !bamlStreamCollector.HasBamlStreams) return; string resourceName = _repackContext.PrimaryAssemblyDefinition.Name.Name + ".g.resources"; EmbeddedResource resource = new EmbeddedResource(resourceName, ManifestResourceAttributes.Public, new byte[0]); var output = new MemoryStream(); var rw = new ResourceWriter(output); // do a final processing, if any, on the embeddedResource itself bamlStreamCollector.Process(resource, rw); rw.Generate(); output.Position = 0; _targetAssemblyMainModule.Resources.Add( new EmbeddedResource(resource.Name, resource.Attributes, output)); } private static Dictionary<string, List<int>> MergeIkvmExports( Dictionary<string, List<int>> currentExports, Dictionary<string, List<int>> extraExports) { Dictionary<string, List<int>> result = new Dictionary<string, List<int>>(currentExports); foreach (var pair in extraExports) { List<int> values; if (!currentExports.TryGetValue(pair.Key, out values)) { currentExports.Add(pair.Key, pair.Value); } else if (values != null) { if (pair.Value == null) // wildcard export currentExports[pair.Key] = null; else currentExports[pair.Key] = values.Union(pair.Value).ToList(); } } return result; } private static Dictionary<string, List<int>> GetIkvmExportsListsFromResource(EmbeddedResource extra) { Dictionary<string, List<int>> ikvmExportsLists = new Dictionary<string, List<int>>(); BinaryReader rdr = new BinaryReader(extra.GetResourceStream()); int assemblyCount = rdr.ReadInt32(); for (int i = 0; i < assemblyCount; i++) { var str = rdr.ReadString(); int typeCount = rdr.ReadInt32(); if (typeCount == 0) { ikvmExportsLists.Add(str, null); } else { var types = new List<int>(); ikvmExportsLists.Add(str, types); for (int j = 0; j < typeCount; j++) types.Add(rdr.ReadInt32()); } } return ikvmExportsLists; } private static EmbeddedResource GenerateIkvmExports(Dictionary<string, List<int>> lists) { using (var stream = new MemoryStream()) { var bw = new BinaryWriter(stream); bw.Write(lists.Count); foreach (KeyValuePair<string, List<int>> kv in lists) { bw.Write(kv.Key); if (kv.Value == null) { // wildcard export bw.Write(0); } else { bw.Write(kv.Value.Count); foreach (int hash in kv.Value) { bw.Write(hash); } } } return new EmbeddedResource("ikvm.exports", ManifestResourceAttributes.Public, stream.ToArray()); } } private Resource FixResxResource( AssemblyDefinition containingAssembly, EmbeddedResource er, List<IResProcessor> resourcePrcessors, IEmbeddedResourceProcessor embeddedResourceProcessor) { MemoryStream stream = (MemoryStream)er.GetResourceStream(); var output = new MemoryStream((int)stream.Length); var rw = new ResourceWriter(output); using (var rr = new ResReader(stream)) { foreach (var res in rr) { foreach (var processor in resourcePrcessors) { if (processor.Process(res, containingAssembly, er, rr, rw)) break; } } } // do a final processing, if any, on the embeddedResource itself embeddedResourceProcessor?.Process(er, rw); rw.Generate(); output.Position = 0; return new EmbeddedResource(er.Name, er.Attributes, output); } private static string[] GetRepackListFromResource(EmbeddedResource resource) { return (string[])new BinaryFormatter().Deserialize(resource.GetResourceStream()); } private static EmbeddedResource GenerateRepackListResource(List<string> repackList) { repackList.Sort(); using (var stream = new MemoryStream()) { new BinaryFormatter().Serialize(stream, repackList.ToArray()); return new EmbeddedResource(ILRepackListResourceName, ManifestResourceAttributes.Public, stream.ToArray()); } } } }