289 lines
12 KiB
C#
289 lines
12 KiB
C#
// Copyright (c) 2010-2014 SharpDX - Alexandre Mutel
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Reflection;
|
|
using System.Text.RegularExpressions;
|
|
using ILMerging;
|
|
using Microsoft.Win32;
|
|
using Mono.Cecil;
|
|
using Mono.Linker;
|
|
using Mono.Linker.Steps;
|
|
using Mono.Options;
|
|
|
|
namespace SharpPak
|
|
{
|
|
/// <summary>
|
|
/// SharpPak application is able to pack/link into a single executable a SharpDX application
|
|
/// without requiring additional dependencies.
|
|
/// </summary>
|
|
public class SharpPakApp
|
|
{
|
|
public SharpPakApp()
|
|
{
|
|
OutputDirectory = "Output";
|
|
AssembliesToLink = new List<string>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the output directory.
|
|
/// </summary>
|
|
/// <value>The output directory.</value>
|
|
public string OutputDirectory { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the path to the main assembly to pack.
|
|
/// </summary>
|
|
/// <value>The main assembly to pack.</value>
|
|
public string MainAssembly { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the paths to the assemblies to merge into the main assembly.
|
|
/// </summary>
|
|
/// <value>The assemblies to merge into the main assembly.</value>
|
|
public List<string> AssembliesToLink { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether [auto references].
|
|
/// </summary>
|
|
/// <value><c>true</c> if [auto references]; otherwise, <c>false</c>.</value>
|
|
public bool AutoReferences { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether [no linker].
|
|
/// </summary>
|
|
/// <value><c>true</c> if [no linker]; otherwise, <c>false</c>.</value>
|
|
public bool NoLinker { get; set; }
|
|
|
|
/// <summary>
|
|
/// Print usages the error.
|
|
/// </summary>
|
|
/// <param name="error">The error.</param>
|
|
private static void UsageError(string error)
|
|
{
|
|
var exeName = Path.GetFileName(Assembly.GetEntryAssembly().Location);
|
|
Console.Write("{0}: ", exeName);
|
|
Console.WriteLine(error);
|
|
Console.WriteLine("Use {0} --help' for more information.", exeName);
|
|
Environment.Exit(1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses the command line arguments.
|
|
/// </summary>
|
|
/// <param name="args">The args.</param>
|
|
public void ParseArguments(string[] args)
|
|
{
|
|
var showHelp = false;
|
|
|
|
var options = new OptionSet()
|
|
{
|
|
"Copyright (c) 2010-2014 SharpDX - Alexandre Mutel",
|
|
"Usage: SharpPak [options]* MainAssembly.exe/dll [ Assembly1.dll...]*",
|
|
"Assembly linker for SharpDX applications",
|
|
"",
|
|
"options:",
|
|
{"a|auto", "Embed automatically all referenced assemblies [default: false]", opt => AutoReferences = opt != null},
|
|
{"n|nolinker", "Perform no linker [default: false]", opt => NoLinker = opt != null},
|
|
{"o|output=", "Specify the output directory [default: Output]", opt => OutputDirectory = opt},
|
|
{"h|help", "Show this message and exit", opt => showHelp = opt != null},
|
|
// default
|
|
{"<>", opt => AssembliesToLink.AddRange(opt.Split(' ', '\t')) },
|
|
};
|
|
try
|
|
{
|
|
options.Parse(args);
|
|
}
|
|
catch (OptionException e)
|
|
{
|
|
UsageError(e.Message);
|
|
}
|
|
|
|
if (showHelp)
|
|
{
|
|
options.WriteOptionDescriptions(Console.Out);
|
|
Environment.Exit(0);
|
|
}
|
|
|
|
if (AssembliesToLink.Count == 0)
|
|
UsageError("MainAssembly file to pack is missing");
|
|
|
|
MainAssembly = AssembliesToLink[0];
|
|
AssembliesToLink.RemoveAt(0);
|
|
}
|
|
|
|
private void AddAssemblies(AssemblyDefinition assembly,
|
|
List<string> paths,
|
|
string fromDirectory,
|
|
string[] includeMergeListRegex)
|
|
{
|
|
var hashSet = new HashSet<AssemblyDefinition>();
|
|
AddAssemblies(assembly, paths, fromDirectory, includeMergeListRegex, hashSet);
|
|
}
|
|
|
|
|
|
private void AddAssemblies(AssemblyDefinition assembly, List<string> paths, string fromDirectory, string[] includeMergeListRegex, HashSet<AssemblyDefinition> added)
|
|
{
|
|
if(added.Contains(assembly))
|
|
return;
|
|
|
|
added.Add(assembly);
|
|
var directoryOfAssembly = Path.GetDirectoryName(assembly.MainModule.FullyQualifiedName);
|
|
if (fromDirectory == directoryOfAssembly)
|
|
{
|
|
if(!paths.Contains(assembly.MainModule.FullyQualifiedName))
|
|
{
|
|
paths.Add(assembly.MainModule.FullyQualifiedName);
|
|
}
|
|
}
|
|
|
|
// Load SharpDX assemblies
|
|
foreach (var assemblyRef in assembly.MainModule.AssemblyReferences)
|
|
{
|
|
bool isAssemblyAdded = false;
|
|
|
|
foreach (var regexIncludeStr in includeMergeListRegex)
|
|
{
|
|
var regexInclude = new Regex(regexIncludeStr);
|
|
if (regexInclude.Match(assemblyRef.Name).Success)
|
|
{
|
|
var assemblyDefRef = assembly.MainModule.AssemblyResolver.Resolve(assemblyRef);
|
|
AddAssemblies(assemblyDefRef, paths, fromDirectory, includeMergeListRegex, added);
|
|
isAssemblyAdded = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!isAssemblyAdded && AutoReferences)
|
|
{
|
|
var assemblyDefRef = assembly.MainModule.AssemblyResolver.Resolve(assemblyRef);
|
|
AddAssemblies(assemblyDefRef, paths, fromDirectory, includeMergeListRegex, added);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Performs packing.
|
|
/// </summary>
|
|
public void Run()
|
|
{
|
|
// Steps
|
|
// 1) Mono.Cecil: Determine assembly dependencies
|
|
// 2) ILMerge: Merge exe into a single assembly
|
|
// 3) Mono.Linker
|
|
var includeMergeListRegex = new string[] { @"SharpDX\..*" };
|
|
|
|
// Step 1 : Mono.Cecil: Determine assembly dependencies
|
|
var assembly = AssemblyDefinition.ReadAssembly(MainAssembly);
|
|
var corlib = (AssemblyNameReference)assembly.MainModule.TypeSystem.Corlib;
|
|
bool isNet40 = corlib.Version.Major == 4;
|
|
|
|
var paths = new List<string>();
|
|
|
|
var fromDirectory = Path.GetDirectoryName(assembly.MainModule.FullyQualifiedName);
|
|
|
|
// Load SharpDX assemblies
|
|
AddAssemblies(assembly, paths, fromDirectory, includeMergeListRegex);
|
|
|
|
// Load assemblies to link
|
|
foreach (var assemblyToLinkName in AssembliesToLink)
|
|
{
|
|
var assemblyToLink = AssemblyDefinition.ReadAssembly(assemblyToLinkName);
|
|
paths.Add(assemblyToLink.MainModule.FullyQualifiedName);
|
|
}
|
|
|
|
|
|
// Step 2: ILMerge: Merge exe into a single assembly
|
|
var merge = new ILMerge();
|
|
|
|
String[] files = paths.ToArray();
|
|
|
|
if (!Directory.Exists(OutputDirectory))
|
|
Directory.CreateDirectory(OutputDirectory);
|
|
|
|
//Here we get the first file name (which was the .exe file) and use that
|
|
// as the output
|
|
String strOutputFile = System.IO.Path.GetFileName(files[0]);
|
|
|
|
merge.OutputFile = OutputDirectory + "\\" + strOutputFile;
|
|
merge.SetInputAssemblies(files);
|
|
merge.DebugInfo = false;
|
|
merge.CopyAttributes = true;
|
|
merge.AllowMultipleAssemblyLevelAttributes = true;
|
|
merge.XmlDocumentation = false;
|
|
|
|
// Special case for v4 framework
|
|
// See http://research.microsoft.com/en-us/people/mbarnett/ilmerge.aspx
|
|
if (isNet40)
|
|
{
|
|
// Retrieve the install root path for the framework
|
|
string installRoot = Registry.LocalMachine.OpenSubKey(@"Software\Microsoft\.NetFramework", false).GetValue("InstallRoot").ToString();
|
|
var directorties = Directory.GetDirectories(installRoot, "v4.*");
|
|
if (directorties.Length == 0)
|
|
UsageError(string.Format(System.Globalization.CultureInfo.InvariantCulture, "Cannot found any .Net 4.0 directory from [{0}] ", installRoot));
|
|
merge.SetTargetPlatform("v4", directorties[0]);
|
|
}
|
|
|
|
merge.Merge();
|
|
|
|
// Step 3: Mono.Linker
|
|
if (!NoLinker)
|
|
{
|
|
var pipeline = GetStandardPipeline();
|
|
var context = new LinkContext(pipeline) {CoreAction = AssemblyAction.Skip, OutputDirectory = OutputDirectory};
|
|
context.OutputDirectory = OutputDirectory;
|
|
|
|
var mainAssemblyDirectory = new DirectoryInfo(Path.GetDirectoryName(Path.GetFullPath(MainAssembly)));
|
|
context.Resolver.AddSearchDirectory(mainAssemblyDirectory.FullName);
|
|
|
|
// Load assembly merged previously by ILMerge
|
|
var mergedAssemblyDefinition = context.Resolve(merge.OutputFile);
|
|
|
|
// Create Mono.Linker default pipeline
|
|
pipeline = GetStandardPipeline();
|
|
pipeline.PrependStep(new ResolveFromAssemblyStep(mergedAssemblyDefinition));
|
|
|
|
// Add custom step for ComObject constructors
|
|
pipeline.AddStepBefore(typeof (SweepStep), new ComObjectStep());
|
|
|
|
pipeline.Process(context);
|
|
}
|
|
|
|
Console.WriteLine("Assembly successfully packed to [{0}]", merge.OutputFile);
|
|
}
|
|
|
|
static Pipeline GetStandardPipeline()
|
|
{
|
|
var pipeline = new Pipeline();
|
|
pipeline.AppendStep(new LoadReferencesStep());
|
|
pipeline.AppendStep(new BlacklistStep());
|
|
pipeline.AppendStep(new TypeMapStep());
|
|
pipeline.AppendStep(new MarkStep());
|
|
pipeline.AppendStep(new SweepStep());
|
|
pipeline.AppendStep(new CleanStep());
|
|
pipeline.AppendStep(new RegenerateGuidStep());
|
|
pipeline.AppendStep(new OutputStep());
|
|
return pipeline;
|
|
}
|
|
}
|
|
} |