Pages

Friday, February 21, 2014

Automatically Embed Copy Local Assemblies with Symbols in MSBuild

Many have written about how to automatically embed assemblies into an executable, such as:
Lots of questions about this on stackoverflow as well, such as:
However, none of these show how pdb-files can be embedded as well to ensure symbols are also loaded when resolving embedded assemblies as detailed in Embedded Assembly Loading with support for Symbols and Portable Class Libraries in C#.

Automatically embed all dll- and pdb-files exclude xml-files

The solution, shown below, is a simple extension of what Daniel Chambers has described, but also includes pdb-files and exclude copying of xml-files to the output directory since many libraries often include these documentation files.
<Target Name="EmbedReferencedAssemblies" AfterTargets="ResolveAssemblyReferences">
  <ItemGroup>
    <!-- get list of assemblies marked as CopyToLocal -->
    <FilesToEmbed Include="@(ReferenceCopyLocalPaths)" 
                  Condition="('%(ReferenceCopyLocalPaths.Extension)' == '.dll' Or '%(ReferenceCopyLocalPaths.Extension)' == '.pdb')" />
    <FilesToExclude Include="@(ReferenceCopyLocalPaths)" 
                  Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.xml'" />

    <!-- add these assemblies to the list of embedded resources -->
    <EmbeddedResource Include="@(FilesToEmbed)">
      <LogicalName>%(FilesToEmbed.DestinationSubDirectory)%(FilesToEmbed.Filename)%(FilesToEmbed.Extension)</LogicalName>
    </EmbeddedResource>

    <!-- no need to copy the assemblies locally anymore -->
    <ReferenceCopyLocalPaths Remove="@(FilesToEmbed)" />
    <ReferenceCopyLocalPaths Remove="@(FilesToExclude)" />
  </ItemGroup>

  <Message Importance="high" Text="Embedding: @(FilesToEmbed->'%(Filename)%(Extension)', ', ')" />
</Target>
To use this simply copy and paste this into the executable project file (e.g. *.csproj) right after:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

Automatically embed all dll- and pdb-files exclude xml-files and mixed mode assemblies

However, unfortunately as far as I know embedding mixed mode assemblies (e.g. with both managed and native code from for example a C++/CLI project) does not work. So these still have to be copied to the build output. At least, if you do not want to extract the embedded file, as detailed in Single Assembly Deployment of Managed and Unmanaged Code.

One solution to this is to simply exclude these files by adding exclude conditions to the above xml. For example:
<Target Name="EmbedReferencedAssemblies" AfterTargets="ResolveAssemblyReferences">
  <ItemGroup>
    <!-- get list of assemblies marked as CopyToLocal -->
    <FilesToEmbed Include="@(ReferenceCopyLocalPaths)" 
                  Condition="('%(ReferenceCopyLocalPaths.Extension)' == '.dll' Or '%(ReferenceCopyLocalPaths.Extension)' == '.pdb') And '%(Filename)'!='MixedModeAssemblyA' And '%(Filename)'!='MixedModeAssemblyB'" />
    <FilesToExclude Include="@(ReferenceCopyLocalPaths)" 
                  Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.xml'" />

    <!-- add these assemblies to the list of embedded resources -->
    <EmbeddedResource Include="@(FilesToEmbed)">
      <LogicalName>%(FilesToEmbed.DestinationSubDirectory)%(FilesToEmbed.Filename)%(FilesToEmbed.Extension)</LogicalName>
    </EmbeddedResource>

    <!-- no need to copy the assemblies locally anymore -->
    <ReferenceCopyLocalPaths Remove="@(FilesToEmbed)" />
    <ReferenceCopyLocalPaths Remove="@(FilesToExclude)" />
  </ItemGroup>

  <Message Importance="high" Text="Embedding: @(FilesToEmbed->'%(Filename)%(Extension)', ', ')" />
</Target>
I would love to have a solution that actually checks whether an assembly is mixed mode (i.e. not pure) before embedding it. Or at least create a list of assembly names to exclude instead of the crude condition hack above.

One could also imagine checking the path of the assembly and whether this has AnyCPU, x86, x64 in the path or similarly as a convention for embedding or not embedding the given assembly. Lots of other improvements should be possible...

There is also a complete solution out there in the form of Costura.Fody, which exists as a convenient nuget package as well, see http://www.nuget.org/packages/Costura.Fody. This does, however, rely on IL rewriting which may be a problem for some. It does look as if it handles all possible issues via configuration, though.

No comments:

Post a Comment