Daniel Fortunov


 

 Daniel Fortunov's Adventures in Software Development » ILMerge in MSBuild

 2 Comments- Add comment | Back to Software Development Blog Written on 09-Feb-2010 by asqui

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

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.

The Solution

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=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
  <SpecificVersion>False</SpecificVersion>
  <HintPath>Referenced Assemblies\DependencyLibrary.dll</HintPath>
  <ILMerge>True</ILMerge>
  <Private>False</Private>
</Reference>

(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" />
<PropertyGroup>
  <ILMergeExecutable>"..\BuildTools\ILMerge\ILMerge.exe"</ILMergeExecutable>
  <KeyFile>"$(ProjectDir)MyApplication.snk"</KeyFile>
</PropertyGroup>
<Target Name="ILMerge" Inputs="@(IntermediateAssembly)"
        Outputs="@(MainAssembly -> '%(RelativeDir)%(Filename).ILMergeTrigger%(Extension)')">
  <CreateItem Include="@(ReferencePath)" Condition="'%(ReferencePath.ILMerge)'=='True'">
    <Output TaskParameter="Include" ItemName="ILMergeAssemblies" />
  </CreateItem>
  <Exec Command="$(ILMergeExecutable) /Closed /Internalize /Lib:$(OutputPath) /keyfile:$(KeyFile) /out:@(MainAssembly) &quot;@(IntermediateAssembly)&quot; @(ILMergeAssemblies->'&quot;%(FullPath)&quot;', ' ')" />
  <!-- Make a copy of the merged output DLL to use as a trigger for incremental builds -->
  <Copy
    SourceFiles="@(MainAssembly)"
    DestinationFiles="@(MainAssembly -> '%(RelativeDir)%(Filename).ILMergeTrigger%(Extension)')" />
</Target>

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:

  1. We are referencing the intermediate assembly from the ‘obj’ directory as input (because you can’t have the same file as both input and output to ILMerge)
  2. A copy of the output file is saved to Foo.ILMergeTrigger.dll, and it is this file that is named as the output of the build target. This is in order to correctly participate in the dependency analysis that is used during incremental builds (because if your merged output assembly has the same name as the unmerged output assembly, then the standard build will overwrite your merged assembly and make it look ‘up to date’, and your ILMerge task will not be executed because its outputs are up to date!)

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.

Send to a friend

Comments

  • written on 09-Feb-2010

    David says:

    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. :-(

Leave a Comment









Loading …
  • Server: web1.webjam.com
  • Total queries:
  • Serialization time: 109ms
  • Execution time: 141ms
  • XSLT time: $$$XSLT$$$ms