How to apply build configuration transformations on non-web projects

How to apply build configuration transformations on non-web projects

June 21, 2012 5:44 pm

This is a pretty common request, and the simple answer is available in SO: right after the C# targets import, add the following:

<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="AfterCompile" Condition="exists(\'app.$(Configuration).config\')">
  <!-- Generate transformed app config in the intermediate directory -->
  <TransformXml Source="app.config" Destination="$(IntermediateOutputPath)$(TargetFileName).config" Transform="app.$(Configuration).config" />
  <!-- Force build process to use the transformed configuration file from now on. -->
  <ItemGroup>
    <AppConfigWithTargetPath Remove="app.config" />
    <AppConfigWithTargetPath Include="$(IntermediateOutputPath)$(TargetFileName).config">
      <TargetPath>$(TargetFileName).config</TargetPath>
    </AppConfigWithTargetPath>
  </ItemGroup>
</Target>

This works well for a single project, but there’s a problem: the config file is not copied to the output directory when this project is referenced from another one. Say this is a class library, used by a front-end app: you’ll want the config file copied over, alongside the main dll, pdb and xml documentation file, if any. I found hints at the solution mixed with other questions in SO too, although some of the solutions are one-offs. David found the key to the solution: the task that resolves references relies on a set of allowed extensions to be included in the output directory for references, which is controlled by the following property:

<PropertyGroup>
    <AllowedReferenceRelatedFileExtensions>
        $(AllowedReferenceRelatedFileExtensions);
        .dll.config
    </AllowedReferenceRelatedFileExtensions>
</PropertyGroup>

I also found that for the app.config transformation to work consistently from the command line and for referenced projects, I had to make it run earlier, and make sure it runs before the target that resolves project references. I achieved this with the following complete .targets that you can use:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" />

    <PropertyGroup>
        <AllowedReferenceRelatedFileExtensions>
            $(AllowedReferenceRelatedFileExtensions);
            .dll.config
        </AllowedReferenceRelatedFileExtensions>
    </PropertyGroup>

    <PropertyGroup>
        <ResolveReferencesDependsOn>
            TransformConfig;
            $(ResolveReferencesDependsOn)
        </ResolveReferencesDependsOn>
    </PropertyGroup>

    <Target Name="TransformConfig" BeforeTargets="_CopyAppConfigFile" Condition="Exists(\'App.$(Configuration).config\')">
        <!--Generate transformed app config in the intermediate directory-->
        <TransformXml Source="App.config" Destination="$(IntermediateOutputPath)$(TargetFileName).config" Transform="App.$(Configuration).config" />
        <!--Force build process to use the transformed configuration file from now on.-->
        <ItemGroup>
            <AppConfigWithTargetPath Remove="App.config" />
            <AppConfigWithTargetPath Include="$(IntermediateOutputPath)$(TargetFileName).config">
                <TargetPath>$(TargetFileName).config</TargetPath>
            </AppConfigWithTargetPath>
        </ItemGroup>
    </Target>
</Project>

By simply adding this as an import right after the C# imports, you get automatic transformation of your own project App.config, but also inclusion in the output directory of the transformed configs of referenced projects:

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\ConfigTransform.targets" />

So this is a combination of different pieces that I found on SO, which is an amazingly comprehensive Q&A site where you can find the answer to pretty much anything Sonrisa.

There are two extensions in the VS gallery that claim to achieve this:

  • SlowCheetah: this one contains a complete rewrite of the XDT transforms language. I’d rather not depend on the proper cloning of built-in VS behaviors.
  • Configuration Transform: this is exactly what I wanted. It just adds the MSBuild configuration shown at the beginning. It doesn’t do the second part for referenced projects, but it’s very simple and has no external dependencies whatesoever.

Of course, a simple .targets import does the trick, but the tools help automate adding the dependent files for each build configuration, so they can come in handy too.

Enjoy!

/kzu

/kzu dev↻d