using System; using System.IO; using System.IO.Compression; using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; using Nuke.Common.Tooling; public class BuildTasksPatcher { /// /// This helper class, avoid argument null exception /// when cecil write AssemblyNameDefinition on MemoryStream. /// private class Wrapper : ISymbolWriterProvider { readonly ISymbolWriterProvider _provider; readonly string _filename; public Wrapper(ISymbolWriterProvider provider, string filename) { _provider = provider; _filename = filename; } public ISymbolWriter GetSymbolWriter(ModuleDefinition module, string fileName) => _provider.GetSymbolWriter(module, string.IsNullOrWhiteSpace(fileName) ? _filename : fileName); public ISymbolWriter GetSymbolWriter(ModuleDefinition module, Stream symbolStream) => _provider.GetSymbolWriter(module, symbolStream); } private static string GetSourceLinkInfo(string path) { try { using (var asm = AssemblyDefinition.ReadAssembly(path, new ReaderParameters { ReadWrite = true, InMemory = true, ReadSymbols = true, SymbolReaderProvider = new DefaultSymbolReaderProvider(false), })) { if (asm.MainModule.CustomDebugInformations?.OfType()?.FirstOrDefault() is { } sli) { return sli.Content; } } } catch { } return null; } public static void PatchBuildTasksInPackage(string packagePath, Tool ilRepackTool) { using (var archive = new ZipArchive(File.Open(packagePath, FileMode.Open, FileAccess.ReadWrite), ZipArchiveMode.Update)) { foreach (var entry in archive.Entries.ToList()) { if (entry.Name == "Avalonia.Build.Tasks.dll") { var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(tempDir); var temp = Path.Combine(tempDir, entry.Name); var output = temp + ".output"; File.Copy(GetAssemblyPath(typeof(Microsoft.Build.Framework.ITask)), Path.Combine(tempDir, "Microsoft.Build.Framework.dll")); var patched = new MemoryStream(); try { entry.ExtractToFile(temp, true); // Get Original SourceLinkInfo Content var sourceLinkInfoContent = GetSourceLinkInfo(temp); var cecilAsm = GetAssemblyPath(typeof(Mono.Cecil.AssemblyDefinition)); var cecilRocksAsm = GetAssemblyPath(typeof(Mono.Cecil.Rocks.MethodBodyRocks)); var cecilPdbAsm = GetAssemblyPath(typeof(Mono.Cecil.Pdb.PdbReaderProvider)); var cecilMdbAsm = GetAssemblyPath(typeof(Mono.Cecil.Mdb.MdbReaderProvider)); ilRepackTool.Invoke( $"/internalize /out:\"{output:nq}\" \"{temp:nq}\" \"{cecilAsm:nq}\" \"{cecilRocksAsm:nq}\" \"{cecilPdbAsm:nq}\" \"{cecilMdbAsm:nq}\"", tempDir); // 'hurr-durr assembly with the same name is already loaded' prevention using (var asm = AssemblyDefinition.ReadAssembly(output, new ReaderParameters { ReadWrite = true, InMemory = true, ReadSymbols = true, SymbolReaderProvider = new DefaultSymbolReaderProvider(false), })) { asm.Name = new AssemblyNameDefinition( "Avalonia.Build.Tasks." + Guid.NewGuid().ToString().Replace("-", ""), new Version(0, 0, 0)); var mainModule = asm.MainModule; // If we have SourceLink info copy to patched assembly. if (!string.IsNullOrEmpty(sourceLinkInfoContent)) { mainModule.CustomDebugInformations.Add(new SourceLinkDebugInformation(sourceLinkInfoContent)); } // Try to get SymbolWriter if it has it var reader = mainModule.SymbolReader; var hasDebugInfo = reader is not null; var proivder = reader?.GetWriterProvider() is ISymbolWriterProvider p ? new Wrapper(p, "Avalonia.Build.Tasks.dll") : default(ISymbolWriterProvider); var parameters = new WriterParameters { #if ISNETFULLFRAMEWORK StrongNameKeyPair = signingStep.KeyPair, #endif WriteSymbols = hasDebugInfo, SymbolWriterProvider = proivder, DeterministicMvid = hasDebugInfo, }; asm.Write(patched, parameters); patched.Position = 0; } } finally { try { if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); } catch { //ignore } } var fn = entry.FullName; entry.Delete(); var newEntry = archive.CreateEntry(fn, CompressionLevel.Optimal); using (var s = newEntry.Open()) patched.CopyTo(s); } } } } private static string GetAssemblyPath(Type typeInAssembly) => typeInAssembly.Assembly.GetModules()[0].FullyQualifiedName; }