Tuesday, August 14, 2012

Assemblies Merging With ILMerge and MSBuild

ILMerge is a useful utility to merge multiple .NET assemblies into a single assembly. We can use it together with MSBuild to automate the merging process when building our projects.

There is another method which is more basic and has less features. Please refer Assemblies Merging With Post-build Event.

Thanks to some great online references I found here, here and here, this is a sample MSBuild script which has the following features:
  • Runs only when building your project in "Release" mode.
  • Automatically merge all "Copy Local" referenced assemblies, instead of having to define every assemblies to be merged.
  • Option to define assemblies to be excluded from merging in the "ilmerge.assembly.exclude" file, for all projects (global) and/or each individual project.
  • Option to define the ILMerge's internalized: excludeFile list in the "ilmerge.internalize.exclude" file, for all projects (global) and/or each individual project.
  • Output to the bin\Release folder and the merged referenced assemblies will be deleted.

Setup ILMerge and Ilmerge.CSharp.targets (only set once):

1. Download and install the ILMerge utility.
  • Note: You may copy the "ILMerge.exe" and put it into any custom folder, as long as you change to the correct path in the <ILMergeExeDir> property in the "Ilmerge.CSharp.targets" file below.

2. Create a file named as "Ilmerge.CSharp.targets" with the following contents:
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

 <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />

 <PropertyGroup>
    <ILMergeExeDir>$(ProgramFiles)\Microsoft\ILMerge\</ILMergeExeDir>
 </PropertyGroup>

 <PropertyGroup>
    <ILMergeTargetPlatformArg>/targetplatform:v2,&quot;$(MSBuildBinPath)&quot;</ILMergeTargetPlatformArg>
 </PropertyGroup>

 <PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v4.0' ">
    <ILMergeTargetPlatformArg>/targetplatform:v4,&quot;$(MSBuildToolsPath)&quot;</ILMergeTargetPlatformArg>
 </PropertyGroup>

 <PropertyGroup Condition=" '$(AssemblyOriginatorKeyFile)' != '' ">
    <ILMergeKeyFileArg>/keyfile:$(AssemblyOriginatorKeyFile)</ILMergeKeyFileArg>
 </PropertyGroup>

 <PropertyGroup Condition=" Exists('$(ILMergeExeDir)ilmerge.internalize.exclude') ">
    <ILMergeInternalizeExcludeArg>:&quot;$(ILMergeExeDir)ilmerge.internalize.exclude&quot;</ILMergeInternalizeExcludeArg>
 </PropertyGroup>
 <PropertyGroup Condition=" Exists('$(ProjectDir)ilmerge.internalize.exclude') ">
    <ILMergeInternalizeExcludeArg>:&quot;$(ProjectDir)ilmerge.internalize.exclude&quot;</ILMergeInternalizeExcludeArg>
 </PropertyGroup>

 <ItemGroup>
    <ILMergeAsmGlobalExcludeFile Include="$(ILMergeExeDir)ilmerge.assembly.exclude"/>
    <ILMergeAsmExcludeFile Include="$(ProjectDir)ilmerge.assembly.exclude"/>
 </ItemGroup>

 <Target Name="AfterBuild" Condition=" '$(Configuration)' == 'Release' ">
    <ReadLinesFromFile File="@(ILMergeAsmGlobalExcludeFile)" >
        <Output TaskParameter="Lines" ItemName="ILMergeAsmExclude"/>
    </ReadLinesFromFile>
    <ReadLinesFromFile File="@(ILMergeAsmExcludeFile)" >
        <Output TaskParameter="Lines" ItemName="ILMergeAsmExclude"/>
    </ReadLinesFromFile>

    <CreateItem Include="@(ReferenceCopyLocalPaths)" Condition="'%(Extension)'=='.dll' And '%(Filename)%(Extension)'!='@(ILMergeAsmExclude->'%(Filename)%(Extension)')'">
        <Output TaskParameter="Include" ItemName="AssembliesToMerge"/>
    </CreateItem>

    <Message Text="MERGING: @(AssembliesToMerge->'%(Filename)')" Importance="High" />

    <Exec Command="&quot;$(ILMergeExeDir)ILMerge.exe&quot; /internalize$(ILMergeInternalizeExcludeArg) /ndebug /allowDup $(ILMergeKeyFileArg) /out:@(MainAssembly->'&quot;%(FullPath)&quot;') $(ILMergeTargetPlatformArg) @(IntermediateAssembly->'&quot;%(FullPath)&quot;') @(AssembliesToMerge->'&quot;%(FullPath)&quot;', ' ')" />

    <CreateItem Include="@(ReferenceCopyLocalPaths->'$(OutDir)%(DestinationSubDirectory)%(Filename)%(Extension)')" Condition=" '%(Filename)%(Extension)'!='@(ILMergeAsmExclude->'%(Filename)%(Extension)')' ">
          <Output TaskParameter="Include" ItemName="AssembliesToDelete" />
    </CreateItem>
    <Delete Files="@(AssembliesToDelete)" />
 </Target>

 <Target Name="_CopyFilesMarkedCopyLocal"/>

</Project>
3. Save this file into the "C:\Program Files\MSBuild\ILMerge" folder.
  • Note: You can put this file into any other folder, as long as you change to the correct path in the <Import Project> property in .csproj below.

Setup your project:

1. Close any opening project and solution in Visual Studio.
2. Open the main project's *.csproj with a text editor (e.g. Notepad).
3. Search for "Import", replace the <Import ...> line with the following:
<Import Project="$(MSBuildExtensionsPath)\ILMerge\Ilmerge.CSharp.targets" />
4. Save the *.csproj file.
5. Open the project/solution in Visual Studio. When prompted, select "Load Project Normally".
6. Build the project.


Exlcude file sample:

1. If you would like to exclude certain "Copy Local" assemblies from being merged, just simply create an ASCII file named as "ilmerge.assembly.exclude", then list down the assembly names (including extension and is case sensitive) on each line, as below:
System.Data.SQLite.DLL
MyAssembly.Data.dll
MyAssembly.Special.dll
2. Put this file in the same folder as ILMerge.exe to apply for all projects (global). Put this file in the project folder to only apply for that particular project. Either or both can exist.

3. The same goes for the "ilmerge.internalize.exclude" file.


Additional Resources:
ILMerge and ConfigurationSection


If you find this post helpful, would you buy me a coffee?


No comments:

Post a Comment