diff --git a/ILMerge.Tests/BaselineTests.cs b/ILMerge.Tests/BaselineTests.cs new file mode 100644 index 0000000..156c3b0 --- /dev/null +++ b/ILMerge.Tests/BaselineTests.cs @@ -0,0 +1,17 @@ +using System.Reflection; +using NUnit.Framework; + +namespace ILMerging.Tests +{ + [TestFixture] + public sealed class BaselineTests + { + [Test] + public void Single_input() + { + var ilMerge = new ILMerge(); + ilMerge.SetUpInputAssemblyForTest(Assembly.GetExecutingAssembly()); + ilMerge.MergeToTempFileForTest(".dll"); + } + } +} diff --git a/ILMerge.Tests/Extensions.cs b/ILMerge.Tests/Extensions.cs new file mode 100644 index 0000000..7e2cf8b --- /dev/null +++ b/ILMerge.Tests/Extensions.cs @@ -0,0 +1,24 @@ +using System.Linq; +using System.Reflection; +using ILMerging.Tests.Helpers; + +namespace ILMerging.Tests +{ + internal static class Extensions + { + public static void SetUpInputAssemblyForTest(this ILMerge ilMerge, Assembly inputAssembly) + { + ilMerge.SetSearchDirectories(ShadowCopyUtils.GetTransitiveClosureDirectories(inputAssembly).ToArray()); + ilMerge.SetInputAssemblies(new[] { inputAssembly.Location }); + } + + public static void MergeToTempFileForTest(this ILMerge ilMerge, string outputExtension) + { + using (var outputFile = TempFile.WithExtension(outputExtension)) + { + ilMerge.OutputFile = outputFile; + ilMerge.Merge(); + } + } + } +} diff --git a/ILMerge.Tests/Helpers/ProcessUtils.cs b/ILMerge.Tests/Helpers/ProcessUtils.cs new file mode 100644 index 0000000..7718c5c --- /dev/null +++ b/ILMerge.Tests/Helpers/ProcessUtils.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace ILMerging.Tests.Helpers +{ + public static class ProcessUtils + { + public static ProcessResult Run(ProcessStartInfo startInfo) + { + startInfo.UseShellExecute = false; + startInfo.RedirectStandardOutput = true; + startInfo.RedirectStandardError = true; + startInfo.CreateNoWindow = true; + + var process = new Process { StartInfo = startInfo }; + + var standardStreamData = new List(); + var currentData = new StringBuilder(); + var currentDataIsError = false; + + process.OutputDataReceived += (sender, e) => + { + if (e.Data == null) return; + if (currentDataIsError) + { + if (currentData.Length != 0) + standardStreamData.Add(new StandardStreamData(currentDataIsError, currentData.ToString())); + currentData.Clear(); + currentDataIsError = false; + } + currentData.AppendLine(e.Data); + }; + process.ErrorDataReceived += (sender, e) => + { + if (e.Data == null) return; + if (!currentDataIsError) + { + if (currentData.Length != 0) + standardStreamData.Add(new StandardStreamData(currentDataIsError, currentData.ToString())); + currentData.Clear(); + currentDataIsError = true; + } + currentData.AppendLine(e.Data); + }; + + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + process.WaitForExit(); + + if (currentData.Length != 0) + standardStreamData.Add(new StandardStreamData(currentDataIsError, currentData.ToString())); + + return new ProcessResult(process.ExitCode, standardStreamData.ToArray()); + } + + [DebuggerDisplay("{ToString(),nq}")] + public struct ProcessResult + { + public ProcessResult(int exitCode, StandardStreamData[] standardStreamData) + { + ExitCode = exitCode; + StandardStreamData = standardStreamData; + } + + public int ExitCode { get; } + public StandardStreamData[] StandardStreamData { get; } + + public override string ToString() => ToString(true); + + /// If true, appends "[stdout] " or "[stderr] " to the beginning of each line. + public string ToString(bool showStreamSource) + { + var r = new StringBuilder("Exit code ").Append(ExitCode); + + if (StandardStreamData.Length != 0) r.AppendLine(); + + foreach (var data in StandardStreamData) + { + if (showStreamSource) + { + var lines = data.Data.Split(new[] { Environment.NewLine }, StringSplitOptions.None); + + // StandardStreamData.Data always ends with a blank line, so skip that + for (var i = 0; i < lines.Length - 1; i++) + r.Append(data.IsError ? "[stderr] " : "[stdout] ").AppendLine(lines[i]); + } + else + { + r.Append(data.Data); + } + } + + return r.ToString(); + } + } + + [DebuggerDisplay("{ToString(),nq}")] + public struct StandardStreamData + { + public StandardStreamData(bool isError, string data) + { + IsError = isError; + Data = data; + } + + public bool IsError { get; } + public string Data { get; } + + public override string ToString() => (IsError ? "[stderr] " : "[stdout] ") + Data; + } + } +} diff --git a/ILMerge.Tests/Helpers/ShadowCopyUtils.cs b/ILMerge.Tests/Helpers/ShadowCopyUtils.cs new file mode 100644 index 0000000..18769ce --- /dev/null +++ b/ILMerge.Tests/Helpers/ShadowCopyUtils.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; + +namespace ILMerging.Tests.Helpers +{ + public static class ShadowCopyUtils + { + public static IEnumerable GetTransitiveClosure(params Assembly[] assemblies) + { + var finishedAssemblies = new HashSet(); + + using (var en = StackEnumerator.Create(assemblies)) + foreach (var assembly in en) + { + if (!finishedAssemblies.Add(assembly)) continue; + yield return assembly; + en.Recurse(assembly.GetReferencedAssemblies().Select(Assembly.Load)); + } + } + + /// + /// Necessary because of test runners like ReSharper and NCrunch which shadow copy each assembly to an isolated directory + /// + public static IEnumerable GetTransitiveClosureDirectories(params Assembly[] assemblies) + { + return GetTransitiveClosure(assemblies) + .Where(_ => !_.GlobalAssemblyCache) + .Select(_ => Path.GetDirectoryName(_.Location)) + .Distinct(StringComparer.OrdinalIgnoreCase); + } + + /// + /// Necessary because of test runners like ReSharper and NCrunch which shadow copy each assembly to an isolated directory + /// + public static string GenerateILMergeLibCliSwitches(params Assembly[] assemblies) + { + return string.Join(" ", GetTransitiveClosureDirectories(Assembly.GetExecutingAssembly()).Select(_ => $"/lib:\"{_}\"")); + } + } +} diff --git a/ILMerge.Tests/Helpers/StackEnumerator.cs b/ILMerge.Tests/Helpers/StackEnumerator.cs new file mode 100644 index 0000000..460822d --- /dev/null +++ b/ILMerge.Tests/Helpers/StackEnumerator.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; + +namespace ILMerging.Tests.Helpers +{ + public static class StackEnumerator + { + public static StackEnumerator Create(params T[] initial) => new StackEnumerator(initial); + public static StackEnumerator Create(IEnumerable initial) => new StackEnumerator(initial); + public static StackEnumerator Create(IEnumerator initial) => new StackEnumerator(initial); + public static StackEnumerator Create(TContext initialContext, params T[] initial) => new StackEnumerator(initialContext, initial); + public static StackEnumerator Create(TContext initialContext, IEnumerable initial) => new StackEnumerator(initialContext, initial); + public static StackEnumerator Create(TContext initialContext, IEnumerator initial) => new StackEnumerator(initialContext, initial); + } + + public sealed class StackEnumerator : IDisposable + { + private readonly Stack> stack = new Stack>(); + private IEnumerator current; + + public bool MoveNext() + { + while (!current.MoveNext()) + { + current.Dispose(); + if (stack.Count == 0) return false; + current = stack.Pop(); + } + + return true; + } + + public T Current => current.Current; + + public void Recurse(IEnumerator newCurrent) + { + if (newCurrent == null) return; + stack.Push(current); + current = newCurrent; + } + public void Recurse(IEnumerable newCurrent) + { + if (newCurrent == null) return; + Recurse(newCurrent.GetEnumerator()); + } + public void Recurse(params T[] newCurrent) + { + Recurse((IEnumerable)newCurrent); + } + + public StackEnumerator(IEnumerator initial) + { + current = initial ?? System.Linq.Enumerable.Empty().GetEnumerator(); + } + public StackEnumerator(IEnumerable initial) : this(initial?.GetEnumerator()) + { + } + public StackEnumerator(params T[] initial) : this((IEnumerable)initial) + { + } + + // Foreach support + [EditorBrowsable(EditorBrowsableState.Never)] + public StackEnumerator GetEnumerator() + { + return this; + } + + public void Dispose() + { + current.Dispose(); + foreach (var item in stack) + item.Dispose(); + stack.Clear(); + } + } + + public sealed class StackEnumerator : IDisposable + { + public struct ContextCurrent + { + public TContext Context { get; } + + public T Current { get; } + + public ContextCurrent(TContext context, T current) + { + Context = context; + Current = current; + } + } + + private readonly Stack>> stack = new Stack>>(); + private Tuple> current; + + public bool MoveNext() + { + while (!current.Item2.MoveNext()) + { + current.Item2.Dispose(); + if (stack.Count == 0) return false; + current = stack.Pop(); + } + + return true; + } + + public ContextCurrent Current => new ContextCurrent(current.Item1, current.Item2.Current); + + public void Recurse(TContext newContext, IEnumerator newCurrent) + { + if (newCurrent == null) return; + stack.Push(current); + current = Tuple.Create(newContext, newCurrent); + } + public void Recurse(TContext newContext, IEnumerable newCurrent) + { + if (newCurrent == null) return; + Recurse(newContext, newCurrent.GetEnumerator()); + } + public void Recurse(TContext newContext, params T[] newCurrent) + { + Recurse(newContext, (IEnumerable)newCurrent); + } + + public StackEnumerator(TContext initialContext, IEnumerator initial) + { + current = Tuple.Create(initialContext, initial ?? System.Linq.Enumerable.Empty().GetEnumerator()); + } + public StackEnumerator(TContext initialContext, IEnumerable initial) : this(initialContext, initial?.GetEnumerator()) + { + } + public StackEnumerator(TContext initialContext, params T[] initial) : this(initialContext, (IEnumerable)initial) + { + } + + // Foreach support + [EditorBrowsable(EditorBrowsableState.Never)] + public StackEnumerator GetEnumerator() + { + return this; + } + + public void Dispose() + { + current.Item2.Dispose(); + foreach (var item in stack) + item.Item2.Dispose(); + stack.Clear(); + } + } +} diff --git a/ILMerge.Tests/Helpers/TempFile.cs b/ILMerge.Tests/Helpers/TempFile.cs new file mode 100644 index 0000000..2df1d48 --- /dev/null +++ b/ILMerge.Tests/Helpers/TempFile.cs @@ -0,0 +1,41 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; + +namespace ILMerging.Tests.Helpers +{ + [DebuggerDisplay("{ToString(),nq}")] + public sealed class TempFile : IDisposable + { + public TempFile() : this(System.IO.Path.GetTempFileName()) + { + } + + public TempFile(string path) + { + this.path = path; + } + + public static TempFile WithExtension(string extension) + { + return new TempFile( + System.IO.Path.Combine( + System.IO.Path.GetTempPath(), + System.IO.Path.ChangeExtension(System.IO.Path.GetRandomFileName(), extension))); + } + + private string path; + public string Path => path; + + public static implicit operator string(TempFile tempFile) => tempFile.path; + + public override string ToString() => path; + + public void Dispose() + { + var path = Interlocked.Exchange(ref this.path, null); + if (path != null) File.Delete(path); + } + } +} diff --git a/ILMerge.Tests/ILMerge.Tests.csproj b/ILMerge.Tests/ILMerge.Tests.csproj new file mode 100644 index 0000000..b463462 --- /dev/null +++ b/ILMerge.Tests/ILMerge.Tests.csproj @@ -0,0 +1,109 @@ + + + + Debug + AnyCPU + {734805E8-B2FB-41E3-A669-80429AA50C98} + Library + Properties + ILMerging.Tests + ILMerge.Tests + v4.0 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + 3.5 + + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + + {84b41834-efde-4e52-a8ce-b51dfcb1d1b2} + ILMerge + + + {D7E16B38-3893-4EEF-847F-A3BE807E9546} + System.Compiler + + + + + + + False + + + False + + + False + + + False + + + + + + + + \ No newline at end of file diff --git a/ILMerge.Tests/Integration/ConsoleTests.cs b/ILMerge.Tests/Integration/ConsoleTests.cs new file mode 100644 index 0000000..d453774 --- /dev/null +++ b/ILMerge.Tests/Integration/ConsoleTests.cs @@ -0,0 +1,54 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using ILMerging.Tests.Helpers; +using NUnit.Framework; + +namespace ILMerging.Tests.Integration +{ + [TestFixture, Category("Integration")] + public sealed class ConsoleTests + { + [TestCase(true, TestName = "{m}(with mscorsn in path)")] + [TestCase(false, TestName = "{m}(without mscorsn in path)")] + public void No_DLL_load_crashes_when_given_PFX(bool withMscorsnInPath) + { + var ilMergeExePath = typeof(ILMerge).Assembly.Location; + var inputAssembly = Assembly.GetExecutingAssembly(); + + using (var outputFile = TempFile.WithExtension(".dll")) + { + var startInfo = new ProcessStartInfo( + ilMergeExePath, + $"{ShadowCopyUtils.GenerateILMergeLibCliSwitches(inputAssembly)} /keyfile:\"{TestFiles.TestPfx}\" /out:\"{outputFile}\" \"{inputAssembly.Location}\"") + { + WorkingDirectory = Path.GetDirectoryName(inputAssembly.Location) + }; + + if (withMscorsnInPath) + startInfo.EnvironmentVariables["PATH"] = $"{Environment.GetEnvironmentVariable("PATH")};{RuntimeEnvironment.GetRuntimeDirectory()}"; + + // The system runs .NET executables as 64-bit no matter what the architecture of the calling process is. + var result = ProcessUtils.Run(startInfo); + + Assert.That(result.ToString(), Does.Not.Contain("Unable to load DLL 'mscorsn.dll'")); + Assert.That(result.ToString(), Does.Not.Contain("An attempt was made to load a program with an incorrect format.")); + + + // Test failures: + + if (withMscorsnInPath && !Environment.Is64BitOperatingSystem) Assert.Inconclusive("This test can only be run on a 64-bit OS."); + + Assert.That( + result.ToString(), + Does.Not.Contain("Unhandled Exception: System.IO.FileNotFoundException"), + "The test is not being run properly. If you are using ReSharper, disable shadow copy. " + + "If you are using NCrunch, go to NCrunch's configuration for the ILMerge project and " + + "make sure \"Copy referenced assemblies to workspace\" is set to True. " + + "(Both ReSharper and NCrunch settings are saved in the repo, so this should not happen.)"); + } + } + } +} diff --git a/ILMerge.Tests/KeyTests.cs b/ILMerge.Tests/KeyTests.cs new file mode 100644 index 0000000..6207b29 --- /dev/null +++ b/ILMerge.Tests/KeyTests.cs @@ -0,0 +1,49 @@ +using System.IO; +using System.Reflection; +using ILMerging.Tests.Helpers; +using NUnit.Framework; + +namespace ILMerging.Tests +{ + [TestFixture] + public sealed class KeyTests + { + [Test] + public void Can_sign_using_keyfile() + { + using (var outputFile = TempFile.WithExtension(".dll")) + { + var ilMerge = new ILMerge { KeyFile = TestFiles.TestSnk, OutputFile = outputFile }; + ilMerge.SetUpInputAssemblyForTest(Assembly.GetExecutingAssembly()); + ilMerge.Merge(); + + Assert.That( + AssemblyName.GetAssemblyName(outputFile).GetPublicKey(), + Is.EqualTo(new StrongNameKeyPair(File.ReadAllBytes(TestFiles.TestSnk)).PublicKey)); + } + } + + [Test] + public void Bad_keyfile_gives_diagnostic_warning() + { + using (var logFile = new TempFile()) + using (var outputFile = TempFile.WithExtension(".dll")) + { + var ilMerge = new ILMerge + { + KeyFile = TestFiles.TestPfx, + OutputFile = outputFile, + LogFile = logFile + }; + ilMerge.SetUpInputAssemblyForTest(Assembly.GetExecutingAssembly()); + + ilMerge.Merge(); + + var logText = File.ReadAllText(logFile); + Assert.That(logText, Contains.Substring("Unable to obtain public key for StrongNameKeyPair.")); + Assert.That(logText, Contains.Substring("PFX")); + Assert.That(logText, Contains.Substring("key container")); + } + } + } +} diff --git a/ILMerge.Tests/Properties/AssemblyInfo.cs b/ILMerge.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..6b3c68a --- /dev/null +++ b/ILMerge.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ILMerge.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ILMerge.Tests")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("734805e8-b2fb-41e3-a669-80429aa50c98")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ILMerge.Tests/TestFiles.cs b/ILMerge.Tests/TestFiles.cs new file mode 100644 index 0000000..a06e964 --- /dev/null +++ b/ILMerge.Tests/TestFiles.cs @@ -0,0 +1,8 @@ +namespace ILMerging.Tests +{ + internal static class TestFiles + { + public const string TestSnk = "test.snk"; + public const string TestPfx = "test.pfx"; + } +} diff --git a/ILMerge.Tests/project.json b/ILMerge.Tests/project.json new file mode 100644 index 0000000..9d63c0d --- /dev/null +++ b/ILMerge.Tests/project.json @@ -0,0 +1,11 @@ +{ + "dependencies": { + "NUnit": "3.6.0" + }, + "frameworks": { + "net40": {} + }, + "runtimes": { + "win": {} + } +} \ No newline at end of file diff --git a/ILMerge.Tests/test.pfx b/ILMerge.Tests/test.pfx new file mode 100644 index 0000000..99aa269 Binary files /dev/null and b/ILMerge.Tests/test.pfx differ diff --git a/ILMerge.Tests/test.snk b/ILMerge.Tests/test.snk new file mode 100644 index 0000000..482d44e Binary files /dev/null and b/ILMerge.Tests/test.snk differ diff --git a/ILMerge.sln b/ILMerge.sln index 249699b..3423505 100644 --- a/ILMerge.sln +++ b/ILMerge.sln @@ -6,15 +6,26 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ILMerge", "ILMerge\ILMerge. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Compiler", "System.Compiler\System.Compiler.csproj", "{D7E16B38-3893-4EEF-847F-A3BE807E9546}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ILMerge.Tests", "ILMerge.Tests\ILMerge.Tests.csproj", "{734805E8-B2FB-41E3-A669-80429AA50C98}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {84B41834-EFDE-4E52-A8CE-B51DFCB1D1B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {84B41834-EFDE-4E52-A8CE-B51DFCB1D1B2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {84B41834-EFDE-4E52-A8CE-B51DFCB1D1B2}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {84B41834-EFDE-4E52-A8CE-B51DFCB1D1B2}.Release|Any CPU.Build.0 = Debug|Any CPU {D7E16B38-3893-4EEF-847F-A3BE807E9546}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D7E16B38-3893-4EEF-847F-A3BE807E9546}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7E16B38-3893-4EEF-847F-A3BE807E9546}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {D7E16B38-3893-4EEF-847F-A3BE807E9546}.Release|Any CPU.Build.0 = Debug|Any CPU + {734805E8-B2FB-41E3-A669-80429AA50C98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {734805E8-B2FB-41E3-A669-80429AA50C98}.Debug|Any CPU.Build.0 = Debug|Any CPU + {734805E8-B2FB-41E3-A669-80429AA50C98}.Release|Any CPU.ActiveCfg = Release|Any CPU + {734805E8-B2FB-41E3-A669-80429AA50C98}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ILMerge.sln.DotSettings b/ILMerge.sln.DotSettings new file mode 100644 index 0000000..340b3e4 --- /dev/null +++ b/ILMerge.sln.DotSettings @@ -0,0 +1,2 @@ + + False \ No newline at end of file diff --git a/ILMerge.v3.ncrunchsolution b/ILMerge.v3.ncrunchsolution new file mode 100644 index 0000000..10420ac --- /dev/null +++ b/ILMerge.v3.ncrunchsolution @@ -0,0 +1,6 @@ + + + True + True + + \ No newline at end of file diff --git a/ILMerge/ILMerge.cs b/ILMerge/ILMerge.cs index b86d774..b9274b7 100644 --- a/ILMerge/ILMerge.cs +++ b/ILMerge/ILMerge.cs @@ -2316,8 +2316,11 @@ public void Merge() { WriteToLog("ILMerge: Signed assembly '{0}' with a strong name.", outputFileName); } } - catch (AssemblyCouldNotBeSignedException) { - WriteToLog("ILMerge error: The target assembly was not able to be strongly named (did you forget to use the /delaysign option?)."); + catch (AssemblyCouldNotBeSignedException ex) { + if (ex.Message == AssemblyCouldNotBeSignedException.DefaultMessage) + WriteToLog("ILMerge error: The target assembly was not able to be strongly named (did you forget to use the /delaysign option?)."); + else + WriteToLog("ILMerge error: The target assembly was not able to be strongly named. " + ex.Message); } } #endregion diff --git a/ILMerge/ILMerge.v3.ncrunchproject b/ILMerge/ILMerge.v3.ncrunchproject new file mode 100644 index 0000000..7b5b213 --- /dev/null +++ b/ILMerge/ILMerge.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/System.Compiler/ClrStrongName.cs b/System.Compiler/ClrStrongName.cs new file mode 100644 index 0000000..4eefaa2 --- /dev/null +++ b/System.Compiler/ClrStrongName.cs @@ -0,0 +1,33 @@ +using System.Runtime.InteropServices; + +namespace System.Compiler +{ + public static class ClrStrongName + { + private static IClrStrongName clrStrongName; + private static IClrStrongName GetClrStrongName() + { + return clrStrongName ?? (clrStrongName = + (IClrStrongName)RuntimeEnvironment.GetRuntimeInterfaceAsObject( + new Guid("B79B0ACD-F5CD-409b-B5A5-A16244610B92"), + typeof(IClrStrongName).GUID)); + } + + public static void SignatureGeneration(string filePath, string keyContainer, byte[] keyBlob) + { + GetClrStrongName().StrongNameSignatureGeneration(filePath, keyContainer, keyBlob, keyBlob.Length, IntPtr.Zero, IntPtr.Zero); + } + + [ComImport, ComConversionLoss, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("9FD93CCF-3280-4391-B3A9-96E1CDE77C8D")] + private interface IClrStrongName + { + void StrongNameSignatureGeneration( + [MarshalAs(UnmanagedType.LPWStr)] string pwzFilePath, + [MarshalAs(UnmanagedType.LPWStr)] string pwzKeyContainer, + [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] byte[] pbKeyBlob, + int cbKeyBlob, + IntPtr ppbSignatureBlob, + IntPtr pcbSignatureBlob); + } + } +} diff --git a/System.Compiler/System.Compiler.csproj b/System.Compiler/System.Compiler.csproj index cb65afd..29123cf 100644 --- a/System.Compiler/System.Compiler.csproj +++ b/System.Compiler/System.Compiler.csproj @@ -124,6 +124,7 @@ Code + Code diff --git a/System.Compiler/Writer.cs b/System.Compiler/Writer.cs index 2237123..9b8afd3 100644 --- a/System.Compiler/Writer.cs +++ b/System.Compiler/Writer.cs @@ -5296,7 +5296,24 @@ public MethodInfo() { } } public class KeyFileNotFoundException : System.ArgumentException{} - public class AssemblyCouldNotBeSignedException : System.ApplicationException{} + + public class AssemblyCouldNotBeSignedException : System.ApplicationException + { + public const string DefaultMessage = "Assembly could not be signed."; + + public AssemblyCouldNotBeSignedException() : base(DefaultMessage) + { + } + + public AssemblyCouldNotBeSignedException(Exception innerException) : base(DefaultMessage, innerException) + { + } + + public AssemblyCouldNotBeSignedException(string message, Exception innerException) : base(message, innerException) + { + } + } + public class DebugSymbolsCouldNotBeWrittenException : System.ApplicationException { } internal class Writer { private Writer(){} @@ -5336,7 +5353,15 @@ private static void WritePE(string/*!*/ location, bool writeDebugSymbols, Module }else keyFileNameDoesNotExist = true; } - assem.PublicKeyOrToken = Writer.GetPublicKey(assem); + try + { + assem.PublicKeyOrToken = Writer.GetPublicKey(assem); + } + catch (ArgumentException ex) + { + throw assem.KeyBlob != null ? new AssemblyCouldNotBeSignedException(ex.Message + " (If you are trying to use a PFX, use the VS_KEY_* key container instead of the key file.)", ex) : + new AssemblyCouldNotBeSignedException(ex); + } } using (FileStream exeFstream = new FileStream(location, FileMode.Create, FileAccess.Write, FileShare.None)){ string debugSymbolsLocation = writeDebugSymbols ? Path.ChangeExtension(location, "pdb") : null; @@ -5349,90 +5374,20 @@ private static void WritePE(string/*!*/ location, bool writeDebugSymbols, Module if (keyFileNameDoesNotExist) throw new KeyFileNotFoundException(); if (delaySign || assem == null) return; if (assem.KeyBlob != null || (assem.KeyContainerName != null && assem.KeyContainerName.Length > 0)){ - try{ - if (!Writer.StrongNameSignatureGeneration(location, keyName, assem.KeyBlob, assem.KeyBlob == null ? 0 : assem.KeyBlob.Length, IntPtr.Zero, IntPtr.Zero)) - throw new AssemblyCouldNotBeSignedException(); - }catch{ - if (!Writer.MscorsnStrongNameSignatureGeneration(location, keyName, assem.KeyBlob, assem.KeyBlob == null ? 0 : assem.KeyBlob.Length, IntPtr.Zero, IntPtr.Zero)) - throw new AssemblyCouldNotBeSignedException(); + try + { + ClrStrongName.SignatureGeneration(location, keyName, assem.KeyBlob); + }catch (Exception ex) { + throw new AssemblyCouldNotBeSignedException(ex); } } } - [DllImport("mscoree.dll", EntryPoint="StrongNameSignatureGeneration", - SetLastError=true, CharSet=CharSet.Unicode, ExactSpelling=true, - CallingConvention=CallingConvention.StdCall)] - private static extern bool StrongNameSignatureGeneration( - string wszFilePath, // [in] valid path to the PE file for the assembly - string wszKeyContainer, // [in] desired key container name - [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 4)] - byte[] pbKeyBlob, // [in] public/private key blob (optional) - int cbKeyBlob, - IntPtr ppbSignatureBlob, // [out] signature blob - IntPtr pcbSignatureBlob); - [DllImport("mscorsn.dll", EntryPoint="StrongNameSignatureGeneration", - SetLastError=true, CharSet=CharSet.Unicode, ExactSpelling=true, - CallingConvention=CallingConvention.StdCall)] - private static extern bool MscorsnStrongNameSignatureGeneration( - string wszFilePath, // [in] valid path to the PE file for the assembly - string wszKeyContainer, // [in] desired key container name - [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 4)] - byte[] pbKeyBlob, // [in] public/private key blob (optional) - int cbKeyBlob, - IntPtr ppbSignatureBlob, // [out] signature blob - IntPtr pcbSignatureBlob); - private unsafe static byte[] GetPublicKey(AssemblyNode/*!*/ assem) { + private static byte[] GetPublicKey(AssemblyNode/*!*/ assem) { Debug.Assert(assem != null); - IntPtr publicKey = IntPtr.Zero; - int size; - try{ - - if (assem.KeyBlob != null){ - Writer.StrongNameGetPublicKey(null, assem.KeyBlob, assem.KeyBlob.Length, out publicKey, out size); - if (publicKey == IntPtr.Zero) return assem.KeyBlob; - }else if (assem.KeyContainerName != null){ - Writer.StrongNameGetPublicKey(assem.KeyContainerName, null, 0, out publicKey, out size); - if (publicKey == IntPtr.Zero) return null; - }else - return assem.PublicKeyOrToken; - byte[] result = new byte[size]; - byte* ptr = (byte*)publicKey; - for (int i = 0; i < size; i++) result[i] = *ptr++; - return result; - }catch{} { - if (assem.KeyBlob != null){ - Writer.MscorsnStrongNameGetPublicKeyUsing(null, assem.KeyBlob, assem.KeyBlob.Length, out publicKey, out size); - if (publicKey == IntPtr.Zero) return assem.KeyBlob; - }else if (assem.KeyContainerName != null){ - Writer.MscorsnStrongNameGetPublicKeyUsing(assem.KeyContainerName, null, 0, out publicKey, out size); - if (publicKey == IntPtr.Zero) return null; - }else - return assem.PublicKeyOrToken; - byte[] result = new byte[size]; - byte* ptr = (byte*)publicKey; - for (int i = 0; i < size; i++) result[i] = *ptr++; - return result; - } + if (assem.KeyContainerName != null) return new Reflection.StrongNameKeyPair(assem.KeyContainerName).PublicKey; + if (assem.KeyBlob != null) return new Reflection.StrongNameKeyPair(assem.KeyBlob).PublicKey; + return assem.PublicKeyOrToken; } - [DllImport("mscoree.dll", EntryPoint="StrongNameGetPublicKey", - SetLastError=true, CharSet=CharSet.Unicode, ExactSpelling=true, - CallingConvention=CallingConvention.StdCall)] - private static extern bool StrongNameGetPublicKey( - string wszKeyContainer, // [in] desired key container name - [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] - byte[] pbKeyBlob, // [in] public/private key blob (optional) - int cbKeyBlob, - [Out] out IntPtr ppbPublicKeyBlob, // [out] public key blob - [Out] out int pcbPublicKeyBlob); - [DllImport("mscorsn.dll", EntryPoint="StrongNameGetPublicKey", - SetLastError=true, CharSet=CharSet.Unicode, ExactSpelling=true, - CallingConvention=CallingConvention.StdCall)] - private static extern bool MscorsnStrongNameGetPublicKeyUsing( - string wszKeyContainer, // [in] desired key container name - [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] - byte[] pbKeyBlob, // [in] public/private key blob (optional) - int cbKeyBlob, - [Out] out IntPtr ppbPublicKeyBlob, // [out] public key blob - [Out] out int pcbPublicKeyBlob); internal static void WritePE(Stream/*!*/ executable, Stream debugSymbols, Module/*!*/ module) { MemoryStream mstream = new MemoryStream();