Wednesday, June 20, 2012

MSBuild Task Build Projects With Different Properties

I built an MSBuild Script a couple months ago and stumbled across a couple interesting features that MSBuild gives you a way to control a project file's build in a fine tuned way. In my build scenario, I have 3 SharePoint 2010 projects and a WCF service to build (preferably without a lot of source). Since I want to generate the SharePoint solutions and have separate have the WCF service output placed in a different place, normally the path of least resistance would be to have two MSBuild tasks, one for the SharePoint projects and one for the WCF service. This works, but there is some refactoring that can be accomplished. Thus, the following.

Suppose a script reads in several properties we want to use on the various projects. In this example we will hardcode them in a PropertyGroup node as follows:
<PropertyGroup Label="Build options" Condition="'$(GenerateSerializationAssemblies)' == ''">
    <GenerateSerializationAssemblies>Auto</GenerateSerializationAssemblies><!-- Auto|On|Off -->
    <Configuration>Release</Configuration>
</PropertyGroup>
Now we define the ItemGroup with all of our projects. The important part each node is that we define the Targets and Properties nodes that we would otherwise define in the MSBuild task node. This example shows that we set the same Configuration setting for all projects and only set the GenerateSerializationAssemblies on the DemoWeb project and then set it to off for the other projects.
<ItemGroup>
<ProjectsToBuild Include="$(SourceFolder)\$(DemoWeb)\$(DemoWeb).csproj">
    <Targets>Package</Targets>
    <Properties>BasePackagePath=.\$(WSPOutputFolder)\;IsPackaging=True;Configuration=$(Configuration);GenerateSerializationAssemblies=$(GenerateSerializationAssemblies)</Properties>
</ProjectsToBuild>
<ProjectsToBuild Include="$(SourceFolder)\$(DemoConfiguration)\$(DemoConfiguration).csproj">
    <Targets>Package</Targets>
    <Properties>BasePackagePath=.\$(WSPOutputFolder)\;IsPackaging=True;Configuration=$(Configuration);GenerateSerializationAssemblies=Off</Properties>
</ProjectsToBuild>
<ProjectsToBuild Include="$(SourceFolder)\$(DemoBusinessData)\$(DemoBusinessData).csproj">
    <Targets>Package</Targets>
    <Properties>BasePackagePath=.\$(WSPOutputFolder)\;IsPackaging=True;Configuration=$(Configuration);GenerateSerializationAssemblies=Off</Properties>
</ProjectsToBuild>
<ProjectsToBuild Include="$(WCFSourceFolder)\$(DemoSvc)\$(DemoSvc).csproj">
    <Targets>ResolveReferences;_CopyWebApplication</Targets>
    <Properties>WebProjectOutputDir=$(WCFOutputFolder)\;OutDir=$(WCFOutputFolder)\bin\;Configuration=$(Configuration)</Properties>
</ProjectsToBuild>
</ItemGroup>
Now the projects and settings are set as needed. The compilation is done as normal, just with some of the nodes refactored. This is great, one MSBuild task, and finer grain control over each project.
<Target Name="Compile" DependsOnTargets="Clean">
    <!-- ... Other tasks ... -->
    
    <MSBuild Projects="@(ProjectsToBuild)">
        <Output ItemName="BuildOutput" TaskParameter="TargetOutputs"/>
    </MSBuild>
    <Message Text="!%0D%0A@(BuildOutput)" />

    <!-- ... Other tasks ... -->
</Target>
As it turns out, it appears that the properties/targets from the current project are applied onto the result of the other project's properties/targets (respectively applied onto the previous.

I have not found this documented previously, but it works for me. I hope it will help someone else.

No comments: