views:

446

answers:

1

I'd like to create a MSBuild project that reflects the project dependencies in a solution and wraps the VS projects inside reusable targets.

The problem I like solve doing this is to svn-export, build and deploy a specific assembly (and its dependencies) in an BizTalk application.

My question is: How can I make the targets for svn-exporting, building and deploying reusable and also reuse the wrapped projects when they are built for different dependencies?

I know it would be simpler to just build the solution and deploy only the assemblies needed but I'd like to reuse the targets as much as possible.

The parts

The project I like to deploy

<Project DefaultTargets="Deploy" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"&gt;
    <PropertyGroup>
        <ExportRoot Condition="'$(Export)'==''">Export</ExportRoot>
    </PropertyGroup>

    <Target Name="Clean_Export">
        <RemoveDir Directories="$(ExportRoot)\My.Project.Dir" />
    </Target>

    <Target Name="Export_MyProject">
        <Exec Command="svn export svn://xxx/trunk/Biztalk2009/MyProject.btproj --force" WorkingDirectory="$(ExportRoot)" />
    </Target>

    <Target Name="Build_MyProject" DependsOnTargets="Export_MyProject">
        <MSBuild Projects="$(ExportRoot)\My.Project.Dir\MyProject.btproj" Targets="Build" Properties="Configuration=Release"></MSBuild>
    </Target>

    <Target Name="Deploy_MyProject" DependsOnTargets="Build_MyProject">
        <Exec Command="BTSTask AddResource -ApplicationName:CORE -Source:MyProject.dll" />
    </Target>
</Project>

The projects it depends upon look almost exactly like this (other .btproj and .csproj).

+8  A: 

Wow, this is a loaded question for a forum post. I wrote about 20 pages on creating reusable .targets files in my book, but I'll get you started here with the basics here. I believe that the key to creating reusable build scripts (i.e. .targets files) is three elements:

  • Place behavior (i.e. targets) into separate files
  • Place data (i.e. properties and items, these are called .proj files) into their own files
  • Extensibility
  • .targets files should validate assumptions

The idea is that you want to place all of your targets into separate files and then these files will be imported by the files which will be driving the build process. These are the files which contain the data. Since you import the .targets files you get all the targets as if they had been defined inline. There will be a silent contract between the .proj and .targets files. This contract is defined in properties and items which both use. This is what needs to be validated.

The idea here is not new. This pattern is followed by .csproj (and other projects generated by Visual Studio). If you take a look your .csproj file you will not find a single target, just properties and items. Then towards the bottom of the file it imports Microsoft.csharp.targets (may differ depending on project type). This project file (along with others that it imports) contains all the targets which actually perform the build.

So it's layed out like this:

  • SharedBuild.targets
  • MyProduct.proj

Where MyProdcut.proj might look like:

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"&gt;
  <!-- This uses a .targets file to off load performing the build -->
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)'=='' ">Release</Configuration>
    <OutputPath Condition=" '$(OutputPath)'=='' ">$(MSBuildProjectDirectory)\BuildArtifacts\bin\</OutputPath>
  </PropertyGroup>

  <ItemGroup>
    <Projects Include="$(MSBuildProjectDirectory)\..\ClassLibrary1\ClassLibrary1.csproj"/>
    <Projects Include="$(MSBuildProjectDirectory)\..\ClassLibrary2\ClassLibrary2.csproj"/>
    <Projects Include="$(MSBuildProjectDirectory)\..\ClassLibrary3\ClassLibrary3.csproj"/>
    <Projects Include="$(MSBuildProjectDirectory)\..\WindowsFormsApplication1\WindowsFormsApplication1.csproj"/>
  </ItemGroup>

  <Import Project="SharedBuild.targets"/>
</Project>

And SharedBuild.targets might look like:

<Project  DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"&gt;
  <!-- This represents a re-usable build file -->
  <Target Name="SharedBuild_Validate">
    <!-- See http://sedodream.com/2009/06/30/ElementsOfReusableMSBuildScriptsValidation.aspx for more info
         about this validation pattern
    -->
    <ItemGroup>
      <_RequiredProperties Include ="Configuration">
          <Value>$(Configuration)</Value>
      </_RequiredProperties>    
      <_RequiredProperties Include ="OutputPath">
          <Value>$(OutputPath)</Value>
      </_RequiredProperties>

      <_RequiredItems Include="Projects">
        <RequiredValue>%(Projects.Identity)</RequiredValue>
        <RequiredFilePath>%(Projects.Identity)</RequiredFilePath>
      </_RequiredItems>
    </ItemGroup>

    <!-- Raise an error if any value in _RequiredProperties is missing -->
    <Error Condition="'%(_RequiredProperties.Value)'==''"
           Text="Missing required property [%(_RequiredProperties.Identity)]"/>

    <!-- Raise an error if any value in _RequiredItems is empty -->
    <Error Condition="'%(_RequiredItems.RequiredValue)'==''"
           Text="Missing required item value [%(_RequiredItems.Identity)]" />

    <!-- Validate any file/directory that should exist -->
    <Error Condition="'%(_RequiredItems.RequiredFilePath)' != '' and !Exists('%(_RequiredItems.RequiredFilePath)')"
           Text="Unable to find expeceted path [%(_RequiredItems.RequiredFilePath)] on item [%(_RequiredItems.Identity)]" />
  </Target>

  <PropertyGroup>
    <BuildDependsOn>
      SharedBuild_Validate;
      BeforeBuild;
      CoreBuild;
      AfterBuild;
    </BuildDependsOn>
  </PropertyGroup>
  <Target Name="Build" DependsOnTargets="$(BuildDependsOn)"/>
  <Target Name="BeforeBuild"/>
  <Target Name="AfterBuild"/>
  <Target Name="CoreBuild">
    <!-- Make sure output folder exists -->
    <PropertyGroup>
      <_FullOutputPath>$(OutputPath)$(Configuration)\</_FullOutputPath>
    </PropertyGroup>
    <MakeDir Directories="$(_FullOutputPath)"/>
    <MSBuild Projects="@(Projects)"
             BuildInParallel="true"
             Properties="OutputPath=$(_FullOutputPath)"/>
  </Target>
</Project>

Don't look too much at the SharedBuild_Validate target yet. I put that there for completeness but don't focus on it. You can find more info on that at my blog at http://sedodream.com/2009/06/30/ElementsOfReusableMSBuildScriptsValidation.aspx.

The important parts to notice are the extensibility points. Even though this is a very basic file, it has all the components of a reusable .targets file. You can customize it's behavior by passing in different properties and items to build. You can extend it's behavior by overriding a target (BeforeBuild, AfterBuild or even CoreBuild) and you can inject your own targets into the build with:

<Project ...>
   ...
  <Import Project="SharedBuild.targets"/>
  <PropertyGroup>
    <BuildDependsOn>
      $(BuildDependsOn);
      CustomAfterBuild
    </BuildDependsOn>
  </PropertyGroup>
  <Target Name="CustomAfterBuild">
    <!-- Insert stuff here -->
  </Target>
</Project>

In your case I would create an SvnExport.targets file which uses the required properties:

  • SvnExportRoot
  • SvnUrl
  • SvnWorkingDirectory You will use these properties to do the Export.

Then create another one for Biztalk build and deploy. You could split this up into 2 if necessary.

Then inside of your .proj file you just import both and setup the targets to build in the right order, and your off.

This is only really the beginning of creating reusable build elements, but this should get the wheels turning in your head. I am going to post all of this to my blog as well as download links for all files.

UPDATE:

Posted to blog at http://sedodream.com/2010/03/19/ReplacingSolutionFilesWithMSBuildFiles.aspx

Sayed Ibrahim Hashimi
Thanks for this comprehensive primer on reusable Targets! This will give me a good starting point. I think the problem with my attempts was that I tried to mix the .proj driving the build and the reusable .targets.
Filburt