ILRepack/ILRepack.cs (427 lines of code) (raw):

// // Copyright (c) 2011 Francois Valdy // Copyright (c) 2018 Alexander Vostres // // 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 System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using ILRepacking.Steps; using Mono.Cecil; using ILRepacking.Mixins; using System.Runtime.Serialization.Formatters.Binary; using System.Diagnostics; using ILRepacking.Steps.SourceServerData; using ILRepacking.Steps.Win32Resources; using Mono.Cecil.Cil; namespace ILRepacking { public class ILRepack : IRepackContext { internal RepackOptions Options; internal ILogger Logger; internal IList<string> MergedAssemblyFiles { get; set; } internal string PrimaryAssemblyFile { get; set; } // contains all 'other' assemblies, but not the primary assembly public IList<AssemblyDefinition> OtherAssemblies { get; private set; } // contains all assemblies, primary (first one) and 'other' public IList<AssemblyDefinition> MergedAssemblies { get; private set; } public AssemblyDefinition TargetAssemblyDefinition { get; private set; } public AssemblyDefinition PrimaryAssemblyDefinition { get; private set; } public RepackAssemblyResolver GlobalAssemblyResolver { get; } = new RepackAssemblyResolver(); public ModuleDefinition TargetAssemblyMainModule => TargetAssemblyDefinition.MainModule; public ModuleDefinition PrimaryAssemblyMainModule => PrimaryAssemblyDefinition.MainModule; private IKVMLineIndexer _lineIndexer; private ReflectionHelper _reflectionHelper; private PlatformFixer _platformFixer; private MappingHandler _mappingHandler; private static readonly Regex TypeRegex = new Regex("^(.*?), ([^>,]+), .*$"); IKVMLineIndexer IRepackContext.LineIndexer => _lineIndexer; ReflectionHelper IRepackContext.ReflectionHelper => _reflectionHelper; PlatformFixer IRepackContext.PlatformFixer => _platformFixer; MappingHandler IRepackContext.MappingHandler => _mappingHandler; private readonly Dictionary<AssemblyDefinition, int> _aspOffsets = new Dictionary<AssemblyDefinition, int>(); private readonly RepackImporter _repackImporter; public ILRepack(RepackOptions options) : this(options, new RepackLogger()) { } public ILRepack(RepackOptions options, ILogger logger) { Options = options; Logger = logger; logger.ShouldLogVerbose = options.LogVerbose; _repackImporter = new RepackImporter(Logger, Options, this, _aspOffsets); } private void ReadInputAssemblies() { MergedAssemblyFiles = Options.ResolveFiles(); OtherAssemblies = new List<AssemblyDefinition>(); // TODO: this could be parallelized to gain speed var primary = MergedAssemblyFiles.FirstOrDefault(); var debugSymbolsRead = false; foreach (string assembly in MergedAssemblyFiles) { var result = ReadInputAssembly(assembly, primary == assembly); if (result.IsPrimary) { PrimaryAssemblyDefinition = result.Definition; PrimaryAssemblyFile = result.Assembly; } else OtherAssemblies.Add(result.Definition); debugSymbolsRead |= result.SymbolsRead; } // prevent writing PDB if we haven't read any Options.DebugInfo = debugSymbolsRead; MergedAssemblies = new List<AssemblyDefinition>(OtherAssemblies); MergedAssemblies.Insert(0, PrimaryAssemblyDefinition); } private AssemblyDefinitionContainer ReadInputAssembly(string assembly, bool isPrimary) { Logger.Info("Adding assembly for merge: " + assembly); try { ReaderParameters rp = new ReaderParameters(ReadingMode.Immediate) { AssemblyResolver = GlobalAssemblyResolver }; if (Options.DebugInfo) { rp.ReadSymbols = true; rp.SymbolReaderProvider = new DefaultSymbolReaderProvider(false); } AssemblyDefinition mergeAsm; try { mergeAsm = AssemblyDefinition.ReadAssembly(assembly, rp); } catch (BadImageFormatException e) when (!rp.ReadSymbols) { throw new InvalidOperationException( "ILRepack does not support merging non-.NET libraries (e.g.: native libraries)", e); } // cope with invalid symbol file catch (Exception) when (rp.ReadSymbols) { rp.ReadSymbols = false; try { mergeAsm = AssemblyDefinition.ReadAssembly(assembly, rp); } catch (BadImageFormatException e) { throw new InvalidOperationException( "ILRepack does not support merging non-.NET libraries (e.g.: native libraries)", e); } Logger.Info("Failed to load debug information for " + assembly); } if (!Options.AllowZeroPeKind && (mergeAsm.MainModule.Attributes & ModuleAttributes.ILOnly) == 0) throw new ArgumentException("Failed to load assembly with Zero PeKind: " + assembly); GlobalAssemblyResolver.RegisterAssembly(mergeAsm); return new AssemblyDefinitionContainer { Assembly = assembly, Definition = mergeAsm, IsPrimary = isPrimary, SymbolsRead = rp.ReadSymbols }; } catch { Logger.Error("Failed to load assembly " + assembly); throw; } } IMetadataScope IRepackContext.MergeScope(IMetadataScope scope) { if (scope is AssemblyNameReference) return TargetAssemblyMainModule.AssemblyReferences.AddUniquely((Mono.Cecil.AssemblyNameReference)scope); Logger.Warn("Merging a module scope, probably not supported"); return scope; } internal class AssemblyDefinitionContainer { public bool SymbolsRead { get; set; } public AssemblyDefinition Definition { get; set; } public string Assembly { get; set; } public bool IsPrimary { get; set; } } public enum Kind { Dll, Exe, WinExe, SameAsPrimaryAssembly } private TargetRuntime ParseTargetPlatform() { TargetRuntime runtime = PrimaryAssemblyMainModule.Runtime; if (Options.TargetPlatformVersion != null) { switch (Options.TargetPlatformVersion) { case "v2": runtime = TargetRuntime.Net_2_0; break; case "v4": runtime = TargetRuntime.Net_4_0; break; default: throw new ArgumentException($"Invalid TargetPlatformVersion: '{Options.TargetPlatformVersion}'"); } _platformFixer.ParseTargetPlatformDirectory(runtime, Options.TargetPlatformDirectory); } return runtime; } private string ResolveTargetPlatformDirectory(string version) { if (version == null) return null; var platformBasePath = Path.GetDirectoryName(Path.GetDirectoryName(typeof(string).Assembly.Location)); List<string> platformDirectories = new List<string>(Directory.GetDirectories(platformBasePath)); var platformDir = version.Substring(1); if (platformDir.Length == 1) platformDir = platformDir + ".0"; // mono platform dir is '2.0' while windows is 'v2.0.50727' var targetPlatformDirectory = platformDirectories .FirstOrDefault(x => Path.GetFileName(x).StartsWith(platformDir) || Path.GetFileName(x).StartsWith($"v{platformDir}")); if (targetPlatformDirectory == null) throw new ArgumentException($"Failed to find target platform '{Options.TargetPlatformVersion}' in '{platformBasePath}'"); Logger.Info($"Target platform directory resolved to {targetPlatformDirectory}"); return targetPlatformDirectory; } public static IEnumerable<AssemblyName> GetRepackAssemblyNames(Type typeInRepackedAssembly) { try { using (Stream stream = typeInRepackedAssembly.Assembly.GetManifestResourceStream(ResourcesRepackStep.ILRepackListResourceName)) if (stream != null) { string[] list = (string[])new BinaryFormatter().Deserialize(stream); return list.Select(x => new AssemblyName(x)); } } catch (Exception) { } return Enumerable.Empty<AssemblyName>(); } public static AssemblyName GetRepackAssemblyName(IEnumerable<AssemblyName> repackAssemblyNames, string repackedAssemblyName, Type fallbackType) { return repackAssemblyNames?.FirstOrDefault(name => name.Name == repackedAssemblyName) ?? fallbackType.Assembly.GetName(); } void PrintRepackHeader() { var assemblies = GetRepackAssemblyNames(typeof(ILRepack)); var ilRepack = GetRepackAssemblyName(assemblies, "ILRepack", typeof(ILRepack)); Logger.Info($"IL Repack - Version {ilRepack.Version.ToString(3)}"); Logger.Verbose($"Runtime: {typeof(ILRepack).Assembly.FullName}"); Logger.Info(Options.ToCommandLine()); } /// <summary> /// The actual repacking process, called by main after parsing arguments. /// When referencing this assembly, call this after setting the merge properties. /// </summary> public void Repack() { var timer = new Stopwatch(); timer.Start(); Options.Validate(); PrintRepackHeader(); var actualOutFile = Options.OutputFile; Options.OutputFile = GetTempFile(Options.OutputFile); _reflectionHelper = new ReflectionHelper(this); ResolveSearchDirectories(); // Read input assemblies only after all properties are set. ReadInputAssemblies(); _platformFixer = new PlatformFixer(this, PrimaryAssemblyMainModule.Runtime); _mappingHandler = new MappingHandler(); bool hadStrongName = PrimaryAssemblyDefinition.Name.HasPublicKey; ModuleKind kind = PrimaryAssemblyMainModule.Kind; if (Options.TargetKind.HasValue) { switch (Options.TargetKind.Value) { case Kind.Dll: kind = ModuleKind.Dll; break; case Kind.Exe: kind = ModuleKind.Console; break; case Kind.WinExe: kind = ModuleKind.Windows; break; } } TargetRuntime runtime = ParseTargetPlatform(); // change assembly's name to correspond to the file we create string mainModuleName = Path.GetFileNameWithoutExtension(Options.OutputFile); if (TargetAssemblyDefinition == null) { AssemblyNameDefinition asmName = Clone(PrimaryAssemblyDefinition.Name); asmName.Name = mainModuleName; TargetAssemblyDefinition = AssemblyDefinition.CreateAssembly(asmName, mainModuleName, new ModuleParameters() { Kind = kind, Architecture = PrimaryAssemblyMainModule.Architecture, AssemblyResolver = GlobalAssemblyResolver, Runtime = runtime }); } else { // TODO: does this work or is there more to do? TargetAssemblyMainModule.Kind = kind; TargetAssemblyMainModule.Runtime = runtime; TargetAssemblyDefinition.Name.Name = mainModuleName; TargetAssemblyMainModule.Name = mainModuleName; } // set the main module attributes TargetAssemblyMainModule.Attributes = PrimaryAssemblyMainModule.Attributes; var win32ResourceStep = new Win32ResourceStep(Logger, this, _aspOffsets); if (Options.Version != null) TargetAssemblyDefinition.Name.Version = Options.Version; _lineIndexer = new IKVMLineIndexer(this, Options.LineIndexation); var signingStep = new SigningStep(this, Options); var isUnixEnvironment = Environment.OSVersion.Platform == PlatformID.MacOSX || Environment.OSVersion.Platform == PlatformID.Unix; using (var sourceServerDataStep = GetSourceServerDataStep(isUnixEnvironment)) { List<IRepackStep> repackSteps = new List<IRepackStep> { win32ResourceStep, signingStep, new ReferencesRepackStep(Logger, this), new TypesRepackStep(Logger, this, _repackImporter, Options), new ResourcesRepackStep(Logger, this, Options), new AttributesRepackStep(Logger, this, _repackImporter, Options), new ReferencesFixStep(Logger, this, _repackImporter, Options), new XamlResourcePathPatcherStep(Logger, this), sourceServerDataStep }; foreach (var step in repackSteps) { step.Perform(); } var parameters = new WriterParameters { StrongNameKeyBlob = signingStep.KeyBlob, WriteSymbols = Options.DebugInfo && PrimaryAssemblyMainModule.SymbolReader != null, SymbolWriterProvider = PrimaryAssemblyMainModule.SymbolReader?.GetWriterProvider(), }; // create output directory if it does not exist var outputDir = Path.GetDirectoryName(Options.OutputFile); if (!string.IsNullOrEmpty(outputDir) && !Directory.Exists(outputDir)) { Logger.Info("Output directory does not exist. Creating output directory: " + outputDir); Directory.CreateDirectory(outputDir); } Logger.Info("Writing output assembly to disk"); TargetAssemblyDefinition.Write(Options.OutputFile, parameters); sourceServerDataStep.Write(); foreach (var assembly in MergedAssemblies) { assembly.Dispose(); } TargetAssemblyDefinition.Dispose(); GlobalAssemblyResolver.Dispose(); win32ResourceStep.Patch(Options.OutputFile); // resign output assembly after patching win32 resources if (signingStep.KeyBlob != null) { var rp = new ReaderParameters(ReadingMode.Immediate) { AssemblyResolver = GlobalAssemblyResolver }; using var ad = AssemblyDefinition.ReadAssembly(Options.OutputFile, rp); Options.OutputFile = GetTempFile(Options.OutputFile); ad.Write(Options.OutputFile, parameters); } MoveTempFile(Options.OutputFile, actualOutFile); Options.OutputFile = actualOutFile; // If this is an executable and we are on linux/osx we should copy file permissions from // the primary assembly if (isUnixEnvironment) { Logger.Info("Copying permissions from " + PrimaryAssemblyFile); LibC.Stat(PrimaryAssemblyFile, out var stat); LibC.ChMod(Options.OutputFile, stat.st_mode); } if (hadStrongName && !TargetAssemblyDefinition.Name.HasPublicKey) Options.StrongNameLost = true; // nice to have, merge .config (assembly configuration file) & .xml (assembly documentation) ConfigMerger.Process(this); if (Options.XmlDocumentation) DocumentationMerger.Process(this); } Logger.Info($"Finished in {timer.Elapsed}"); } private void MoveTempFile(string tempFile, string outFile) { var srcDir = Path.GetDirectoryName(tempFile); var tgtDir = Path.GetDirectoryName(outFile); if (!string.IsNullOrEmpty(tgtDir)) { Directory.CreateDirectory(tgtDir); } foreach (var srcFileName in Directory.EnumerateFiles(srcDir)) { var fileName = Path.GetFileName(srcFileName); var tgtFileName = Path.Combine(tgtDir, fileName); if (File.Exists(tgtFileName)) { File.Delete(tgtFileName); } File.Move(srcFileName, tgtFileName); } Directory.Delete(srcDir, false); } private string GetTempFile(string outputFileName) { var fileName = Path.GetFileName(outputFileName); var dirName = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); Directory.CreateDirectory(dirName); return Path.Combine(dirName, fileName); } private ISourceServerDataRepackStep GetSourceServerDataStep(bool isUnixEnvironment) { if (isUnixEnvironment) { return new NullSourceServerStep(Logger); } else { return new SourceServerDataRepackStep(Options.OutputFile, MergedAssemblyFiles); } } private void ResolveSearchDirectories() { foreach (var dir in Options.SearchDirectories) GlobalAssemblyResolver.AddSearchDirectory(dir); var targetPlatformDirectory = Options.TargetPlatformDirectory ?? ResolveTargetPlatformDirectory(Options.TargetPlatformVersion); if (targetPlatformDirectory != null) { GlobalAssemblyResolver.AddSearchDirectory(targetPlatformDirectory); var facadesDirectory = Path.Combine(targetPlatformDirectory, "Facades"); if (Directory.Exists(facadesDirectory)) GlobalAssemblyResolver.AddSearchDirectory(facadesDirectory); } } string IRepackContext.FixStr(string content) { return FixStr(content, false); } // string IRepackContext.FixReferenceInIkvmAttribute(string content) // { // return FixStr(content, true); // } private string FixStr(string content, bool javaAttribute) { if (String.IsNullOrEmpty(content) || content.Length > 512 || content.IndexOf(", ") == -1 || content.StartsWith("System.")) return content; // TODO fix "TYPE, ASSEMBLYNAME, CULTURE" pattern // TODO fix "TYPE, ASSEMBLYNAME, VERSION, CULTURE, TOKEN" pattern var match = TypeRegex.Match(content); if (match.Success) { string type = match.Groups[1].Value; string targetAssemblyName = TargetAssemblyDefinition.FullName; if (javaAttribute) targetAssemblyName = targetAssemblyName.Replace('.', '/') + ";"; if (MergedAssemblies.Any(x => x.Name.Name == match.Groups[2].Value)) { return type + ", " + targetAssemblyName; } } return content; } string IRepackContext.FixTypeName(string assemblyName, string typeName) { // TODO handle renames return typeName; } string IRepackContext.FixAssemblyName(string assemblyName) { if (MergedAssemblies.Any(x => x.FullName == assemblyName)) { // TODO no public key token ! return TargetAssemblyDefinition.FullName; } return assemblyName; } private AssemblyNameDefinition Clone(AssemblyNameDefinition assemblyName) { AssemblyNameDefinition asmName = new AssemblyNameDefinition(assemblyName.Name, assemblyName.Version); asmName.Attributes = assemblyName.Attributes; asmName.Culture = assemblyName.Culture; asmName.Hash = assemblyName.Hash; asmName.HashAlgorithm = assemblyName.HashAlgorithm; asmName.PublicKey = assemblyName.PublicKey; asmName.PublicKeyToken = assemblyName.PublicKeyToken; return asmName; } TypeDefinition IRepackContext.GetMergedTypeFromTypeRef(TypeReference reference) { return _mappingHandler.GetRemappedType(reference); } TypeReference IRepackContext.GetExportedTypeFromTypeRef(TypeReference type) { return _mappingHandler.GetExportedRemappedType(type) ?? type; } } }