ILMerge is a utility from Microsoft Research that combines multiple .NET assemblies into a single assembly. This is convenient when you want to combine your application and its dependencies into a single DLL file, for example, to make deployment and versioning easier.
ILMerge is released as a console application but also exposes an API to allow you to use it in other applications. For example, I see there are some GUI applications to ease the burden of typing in all those command line switches. ILMerge is mysteriously missing from the community collections of MSBuild tasks, such as the SDC Tasks Library and MSBuild Extended Tasks, probably because it is perfectly feasible to invoke the ILMerge executable using the Exec task that is provided with MSBuild.
The goal is to integrate ILMerge into MSBuild, such that it runs automagically every time the project is built (either within Visual Studio, or with MSBuild from the command line).
Unfortunately there are some interesting details to integrate smoothly into the build, such as making sure the task handles incremental builds properly (so that adding ILMerge to one project in a solution doesn’t force a re-build of that entire sub-tree every time you build!)
I’ve not been able to find an adequate pre-canned way to achieve this, but I’ve hacked something together starting from Jomo Fisher’s solution and addressing some of the shortcomings I found along the way.
Hand-edit your MSBuild project (e.g. *.csproj) file to tag the referenced assemblies you’d like to merge with the ILMerge=True metadata, like this:
<Reference Include="DependencyLibrary, Version=184.108.40.206, Culture=neutral, processorArchitecture=MSIL">
(Note that it is not necessary to set CopyLocal=True for the target assemblies.)
Then, define the following targets and properties at the bottom of your MSBuild project (just above the </Project> tag):
<Target Name="AfterBuild" DependsOnTargets="ILMerge" />
<Target Name="ILMerge" Inputs="@(IntermediateAssembly)"
Outputs="@(MainAssembly -> '%(RelativeDir)%(Filename).ILMergeTrigger%(Extension)')">
<CreateItem Include="@(ReferencePath)" Condition="'%(ReferencePath.ILMerge)'=='True'">
<Output TaskParameter="Include" ItemName="ILMergeAssemblies" />
<Exec Command="$(ILMergeExecutable) /Closed /Internalize /Lib:$(OutputPath) /keyfile:$(KeyFile) /out:@(MainAssembly) "@(IntermediateAssembly)" @(ILMergeAssemblies->'"%(FullPath)"', ' ')" />
<!-- Make a copy of the merged output DLL to use as a trigger for incremental builds -->
DestinationFiles="@(MainAssembly -> '%(RelativeDir)%(Filename).ILMergeTrigger%(Extension)')" />
Here's the full wolking solution: ILMergeExperiments
There are a couple of hacks here to deal with the fact that we want our ILMerged assembly to have the same name as the original:
This is somewhat hacky, and I’m sure there must be a more cunning way to integrate into MSBuild; I’ll have to revisit this once I’ve read the book Inside the Microsoft Build Engine: Using MSBuild and Team Foundation Build.
written on 09-Feb-2010
Hey, I remember ILMerge... from a job that now seems many, many years ago!
written on 03-Mar-2010
Mario Klebsch says:
Have you ever tried to include the primary project output of your ilmerged project in a setup project? I am using .NET Reactor to merge some libs and ran into lots of trouble. :-(
I want to build an .MSI from post processed assemblies in $(OutDir) and it does not work. After days of trying, I found a hint on the web, that setup projects do not take the files from $(OutDir) but from $(IntermediateOutputPath). So, any modification to the assembly must be applied to the files in @(IntermediateAssembly).
Modifying files in $(IntermediateOutputPath) must be done prior to executing the CopyFilesToOutputDirectory target. Even AfterBuild is run too late. Unfortunately, CopyFilesToOutputDirectory does not have a changeable DependsOnTargets value (at least for .NET 2.0). At least, additional targets could be prepended to PrepareForRunDependsOn, which currently only includes CopyFilesToOutputDirectory.
I also ran into the problem, that I cannot process the assembly in place and that there is no output file generated, that can be used to track, wether a rebuild is required. I have not found a working solution right now. I currently copy my modified assemblies from $(OutDir) back to $(IntermediateOutputPath), but this seems like a bad hack to me. :-(
I also have problems with library dependencies. After merging some .DLLs, they still are included in the .MSI. I can set the local copy (Lokale Kopie in german) property to false to prevent MSBuild to copy these libs to $(OutDir), but the setup project does not care about. :-(