Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Good-practices: How to reuse .csproj and .sln files to create your MSBuild script for CI?

What is the painless/maintainable way of using MSBuild as your build runner ? (Forgive the length of this post)

I was just trying my hand at TeamCity (which I must say is awesome w.r.t. learning curve and out of the box functionality). I got an SVN > MSBuild > NUnit > NCover combo working.

I was curious as to how moderate to large projects are using MSBuild - I've just pointed MSBuild to my Main sln file. I've spent some time with NAnt some years ago and I found MSBuild to be a bit obtuse. The docs are too dense/detailed for a beginner.

MSBuild seems to have some special magic to handle .sln files ; I tried my hand at writing a custom build script by hand, linking/including .csproj files in order (such that I could have custom pre-post build tasks). However it threw up (citing duplicate target imports). I'm assuming most devs wouldn't want to go messing around with msbuild proj files - they'd be making changes to the .csproj and .sln files. Is there some tool / MSBuild task that reverse-engineers a new script from an existing .sln + its .csproj files that I'm unaware of ?

If I'm using MSBuild just to do the compile step, I might as well use Nant with an exec task to MSBuild for compiling the solution ? I've this nagging feeling that I'm missing something obvious.

My end-goal here is to have a MSBuild build script

  • which builds the solution
  • that acts as a build script instead of a compile step. Allows custom pre/post tasks. (e.g. call nunit to run a nunit project (which seems to be not yet supported via the teamcity web UI))
  • stays out of the way of the developers making changes to the solution. No redundancy ; shouldn't require devs to make the same change in 2 places
like image 524
Gishu Avatar asked Jun 18 '10 05:06

Gishu


1 Answers

I didn't try TeamCity yet but did set up a Build environment for our new BizTalk project.

Following the excellent advice of Sayed Ibrahim Hashimi on my own question before starting out, I created a set of MSBuild .proj and .targets scripts.

The Core

A central .targets script for the actual build steps you want to perform:

<Project DefaultTargets="Deploy" xmlns="...">
    <!-- omitted validation steps, see referenced post for details -->
    <PropertyGroup>
        <PullDependsOn>
            $(ValidateDependsOn);
            Validate;
        </PullDependsOn>
    </PropertyGroup>

    <PropertyGroup>
        <BuildDependsOn>
            $(PullDependsOn);
            PullFromVersionControl;
        </BuildDependsOn>
    </PropertyGroup>

    <PropertyGroup>
        <DeployDependsOn>
            $(BuildDependsOn);
            Build;
        </DeployDependsOn>
    </PropertyGroup>

    <Target Name="PullFromVersionControl" DependsOnTargets="$(PullDependsOn)">
        <Exec Command="..." />
    </Target>

    <Target Name="Build" DependsOnTargets="$(BuildDependsOn)">
        <MSBuild Projects="@(ProjectsToBuild)" />
    </Target>

    <Target Name="Deploy" DependsOnTargets="$(DeployDependsOn)">
        <Exec Command="..." />
    </Target>
</Project>

The second core part are the configuration targets like you find them in your .csproj files

<Project xmlns="...">
    <PropertyGroup Condition=" '$(Environment)' == 'DEV' ">
        <SomeConfigKey Condition=" '$(SomeConfigKey)' == '' ">Foo</SomeConfigKey>
    </PropertyGroup>

    <PropertyGroup Condition=" '$(Environment)' == 'TEST' ">
        <SomeConfigKey Condition=" '$(SomeConfigKey)' == '' ">Bar</SomeConfigKey>
    </PropertyGroup>
</Project>

The Projects

The single .csproj itself is represented by a.targets file with just a collection of ItemGroups you need for building.

<Project xmlns="...">
    <ItemGroup>
        <!-- this group contains the list of items to pull from version control -->
        <Sources Include="@(Sources)" />
        <Sources Include="MyProjectRootDir" />
        <Sources Include="MyDependentProjectRootDir" />
    </ItemGroup>

    <ItemGroup>
        <ProjectsToBuild Include="@(ProjectsToBuild)" />
        <ProjectsToBuild Include="MyProject.csproj" />
    </ItemGroup>
</Project>

Putting it together

The .proj you are actually going to execute with MSBuild will import your Configuration, your Project (source code files) and the Core (Pull, Build and Deployment commands)

<Project DefaultTargets="Deploy" xmlns="...">
    <Import Project="Config.targets"/>

    <Import Project="Project.targets"/>

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

Using this approach I was able to reuse the .targets containing the sources to build my some 50 projects in many different combinations instead of creating VS solutions to group them.

I hope you'll find this useful - I can add more details if you're interested.

like image 127
Filburt Avatar answered Oct 04 '22 11:10

Filburt