Issue Tracker part IV: The Build Enviroment using MSBuild (or NAnt)

by brunofig 27. July 2007 22:02

Since that in the last post we created the solutions for this application, it's now time to setup our build enviroment. This automated build will help us in the proccess of not performing those tedious tasks of copying files from one side to another, zipping, etc, tasks that some times leads us to errors.

For this job we will use MSBuild. Since both MSBuild and NAnt use, most of the times, the same syntax, converting will be easy for the NAnt lovers.

Our build proccess will:

  • Clean up the Release folder;
  • Run Code Coverega Reports and Unit tests;
  • Build the Marvin.Core project:
    • Generate the build version;
    • using the previous generated build version we will update the AssemblyInfo.cs file;
    • Deploy the generated assembly to the Dependecies folder;
  • Build the Marvin.Website project:
    • First we need to copy the Marvin.Core generated assemply to the Bin folder, this because the aspcompiler dosen't update the references (for what I know. If you know a way, please tell me :) ).
    • The build will deploy the website into the Release\Application folder;
  • Generate Documentation;
  • Zip the Application into the Maintenance folder:
    • the zip will contain both deploy and source code;

In both applications, the build proccess is defined using a xml structure file. Each step of the build is defined using the Task element in both MSBuild and NAnt. Tasks are grouped by Targets in MSBuild and NAnt.

A Project (NAnt and MSBuild) is a set of Targets.

When executing a build proccess, in both NAnt and MSBuild, we can tell wich of the target to execute. If none is suplied, the default target, defined in the Project element, is called. Targets can depend on other targets, so "You might have a target for compiling, for example, and a target for creating a distributable.  You can only build a distributable when you have compiled first, so the distribute target depends on the compile target." (NAnt definition).

This said, the build proccess will be structured like this:

  • Project (the Release is the default target)
    • Clean target
      • Delete task
    • Code Coverage and Unit Tests
      • NCover task
    • Core target
      • Version task
      • Assembly task
      • Build task
      • Copy task
    • Website Target (depends on Clean and Core targets)
      • Build Task
    • Documentation
      • NDoc task;
    • Zip Target
      • Create Version Folder task
      • Zip task
    • Release Target (Depends on the website and zip targets)

The Release target dosen't have any task, but we will use it only when we need to release a new version of our application, so it will have addictional dependencies such as code coverage, unit tests and documentation.

NOTE: We will not cover in this topic the proccess of Continuous Integration with such tools as the CruiseControl.Net. For more on this please visit the CruiseControl.Net website, and check out both the  Using CruiseControl.NET with MSBuild and Part 1: Continuous Integration using MSBuild, CruiseControl.NET, FxCop, NUnit, NCover + Subversion articles. 

Building with MSBuild

MSBuild is very similar to NAnt, so if you already have worked with NAnt, changing to MSBuild will be a easy task.

Has said before, the build proccess is defined using a xml document. So in our Scripts folder we create a xml file named marvin.msbuild:

<?xml version="1.0" encoding="utf-8"?>

A build proccess need a Project element, where we set the default target if none is supplied to the MSBuild executable, so:

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Release" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

If we have values that can be duplicated along the script, we can create define them on a PropertyGroup element, and them reference them later on.

We call a property be using the $(PropertyName) syntax.

<PropertyGroup>
    <Property1>false</Property1>
    <Property2>c:</Property2>
    <Property3>$(Property2)\debug</Property3>
</PropertyGroup>

For our project we need values for:

  • the tools directory;
  • Project name;
  • Release directory;
  • Source directory;
  • Dependencies directory;
  • Core directory;
  • Core Project file;
  • Website directory;
  • Website deploy directory;
<PropertyGroup>
	<ToolsDirectory>..\..\..\Tools</ToolsDirectory>
	<ProjectName>Marvin</ProjectName> 
	<ReleaseDirectory>..\Release</ReleaseDirectory>
	<SourceDirectory>..\Source</SourceDirectory>
	<DependenciesDirectory>..\Dependencies</DependenciesDirectory>
	<CoreDirectory>$(SourceDirectory)\Marvin.Core\Marvin.Core</CoreDirectory>
	<CoreProjectFile>$(CoreDirectory)\Marvin.Core.csproj</CoreProjectFile>
	<WebSiteDirectory>$(SourceDirectory)\Marvin.Website</WebSiteDirectory>
	<WebSiteReleaseDirectory>$(ReleaseDirectory)\Application</WebSiteReleaseDirectory>
</PropertyGroup>

Although the properties are defined in our application build file, generic cross-application properties (and all other elements allowed) can be set in a external file, and referenced in.

Properties like company name and tasks like sending emails, that don't depend on the project, can be placed in this external file. All we have to do is Import the file:

<Import  Project="$(ToolsDirectory)\default.targets"/>

In our case the default.targets will not only define the company name and email, but also import custom tasks, this because while MSBuild delivers a large number of common tasks, not always they do what we want, so  when the existing ones don't do what we need, we can always write one, or check the community, and particullary, the MSBuild Community Tasks Project, that we will use in our build proccess.

The default.targets inside the Tools directory looks (for now) like this:

<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
	<PropertyGroup>
		<MSBuildCommunityTasksPath>.\</MSBuildCommunityTasksPath>
		<CompanyName>BrunoFigueiredo.com</CompanyName>
		<CompanyEmail>notreallymyemail@someboguscompany.com</CompanyEmail>
		<Copyright>Copyright (c) $(CompanyName) 2007</Copyright>
	</PropertyGroup>
	<Import Project=".\MSBuild.Community.Tasks\Build\MSBuild.Community.Tasks.Targets"/>
</Project>

For more details on the MSBuild Community Tasks Project, please visit their site.

Now that we have setup the properties to use, we will write our first target: Clean the release folder.

Every time we want to release a new version of our application, the Release folder must first be clean up. Since the Delete task dosen't accepts wildcards it its declaration, we can:

  • Create a ItemGroup containing the list of files/folders to delete, or...
  • ... execute a command-line delete.

We will use a ItemGroup with all the folders we want to remove . This allows us to move this target to the default.targets while mantaining the ItemGroup on the project script file.

So in the default.targets we will have:

<Target Name="CleanUp">
	<RemoveDir Directories="@(RemoveFolders)" />
</Target>

While the marvin.msbuild inside the Scripts folder of our application has, for now:

<ItemGroup>
	<RemoveFolders Include="$(WebSiteReleaseDirectory)" />
</ItemGroup>

Our first target is done. Easy right? Let's move on then. Next we need to build the Marvin.Core project. We have now several tasks to perform:

The first one is to update the build version on the AssemblyInfo.cs file. For this we will use two MSBuild Community Tasks Project tasks: the Version and the AssemblyInfo taks.

The Version task updates a textfile containing the version, while the AssemblyInfo task recreates the assemblyinfo.cs file in the Marvin.Core project with the new generated version:

<Target Name="Version">
	<Version VersionFile="version.txt" BuildType="Increment" RevisionType="None">
		<Output TaskParameter="Major" PropertyName="Major" />
		<Output TaskParameter="Minor" PropertyName="Minor" />
		<Output TaskParameter="Build" PropertyName="Build" />
		<Output TaskParameter="Revision" PropertyName="Revision" />
	</Version>
	<Message Text="Version: $(Major).$(Minor).$(Build).$(Revision)"/>
	<AssemblyInfo CodeLanguage="CS"  
			OutputFile="$(CoreDirectory)\Properties\AssemblyInfo.cs" 
			AssemblyTitle="$(ProjectName)" 
			AssemblyDescription="$(ProjectDescription)"
			AssemblyConfiguration=""
			AssemblyCompany="$(CompanyName)"
			AssemblyProduct="$(ProjectName)"
			AssemblyCopyright="$(Copyright)"
			AssemblyTrademark=""
			ComVisible="false"
			CLSCompliant="true"
			Guid="$(ProjectGuid)"
			AssemblyVersion="$(Major).$(Minor).$(Build).$(Revision)" 
			AssemblyFileVersion="$(Major).$(Minor).$(Build).$(Revision)" />
</Target>

Now that we have updated the version and the assemblyinfo file, it now time to compile.  

<Target Name="Core" DependsOnTargets="Version">
	<MSBuild Projects="$(CoreProjectFile)" Targets="ReBuild" Properties="Configuration=Release">
		<Output ItemName="Outputs" TaskParameter="TargetOutputs"/>
	</MSBuild>
	<Copy
		SourceFiles="@(Outputs)"
		DestinationFolder="$(DependenciesDirectory)"
	/>
</Target>

 The Core target depends on the Version target, so it runs this first. After that the MSBuild task compiles our assembly. We use the Output element to create a new property named Outputs containing all the files generated by the MSBuild task. With this property we can next Copy them to the Dependencies folder.

Alright, we have cleaned up the Release folder, generated a new version, updated the AssemblyInfo file, compiled and copied the Marvin.Core assembly.

That just leaves us with the Application target (for now).

For compiling the Marvin.Website we will use the AspNetCompiler task. Since we have a reference to the Marvin.Core in the Marvin.WebSite project, we first need to copy the core assembly to the Bin directory and then compile:

<Target Name="Application" DependsOnTargets="Core">
	<Copy
		SourceFiles="@(Outputs)"
		DestinationFolder="$(WebSiteBinDirectory)"
	/>
	<AspNetCompiler 
		VirtualPath="/$(ProjectName)"
		PhysicalPath="$(WebSiteDirectory)"
		TargetPath="$(WebSiteReleaseDirectory)"
		Force="true"
		Debug="false"
		Updateable="true"
	/>
</Target>

Thats it. We are done, at least for now.

I leave you with the full default.targets and marvin.msbuild:

Default.Targets
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
	<PropertyGroup>
		<MSBuildCommunityTasksPath>.\</MSBuildCommunityTasksPath>
		<CompanyName>BrunoFigueiredo.com</CompanyName>
		<CompanyEmail>notrealymyemail@someboguscompany.com</CompanyEmail>
		<Copyrights>Copyright (c) $(CompanyName) 2007</Copyrights>
	</PropertyGroup>
	<Import Project=".\MSBuild.Community.Tasks\Build\MSBuild.Community.Tasks.Targets"/>
	
	<Target Name="CleanUp">
		<RemoveDir Directories="@(RemoveFolders)" />
	</Target>
</Project>
Marvin.msbuild
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Release" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
	<PropertyGroup>
		<ToolsDirectory>..\..\..\Tools</ToolsDirectory>
		<ProjectName>Marvin</ProjectName> 
		<ProjectDescription>$(ProjectName) Issue Tracker</ProjectDescription>
		<ProjectGuid>d038566a-1937-478a-b5c5-b79c4afb253d</ProjectGuid>
		<ReleaseDirectory>..\Release</ReleaseDirectory>
		<SourceDirectory>..\Source</SourceDirectory>
		<DependenciesDirectory>..\Dependencies</DependenciesDirectory>
		<CoreDirectory>$(SourceDirectory)\Marvin.Core\Marvin.Core</CoreDirectory>
		<CoreProjectFile>$(CoreDirectory)\Marvin.Core.csproj</CoreProjectFile>
		<WebSiteDirectory>$(SourceDirectory)\Marvin.Website</WebSiteDirectory>
		<WebSiteBinDirectory>$(SourceDirectory)\Marvin.Website\Bin</WebSiteBinDirectory>
		<WebSiteReleaseDirectory>$(ReleaseDirectory)\Application</WebSiteReleaseDirectory>
	</PropertyGroup>
	
	<ItemGroup>
		<RemoveFolders Include="$(WebSiteReleaseDirectory)" />
	</ItemGroup>
	
	<Import  Project="$(ToolsDirectory)\default.targets"/>
			
	<Target Name="Release" DependsOnTargets="CleanUp;TestsCoverage;Application;Documentation;Maintenance" />
		
	<Target Name="Version">
		<Version VersionFile="version.txt" BuildType="Increment" RevisionType="None">
			<Output TaskParameter="Major" PropertyName="Major" />
			<Output TaskParameter="Minor" PropertyName="Minor" />
			<Output TaskParameter="Build" PropertyName="Build" />
			<Output TaskParameter="Revision" PropertyName="Revision" />
		</Version>
		<Message Text="Version: $(Major).$(Minor).$(Build).$(Revision)"/>
		<AssemblyInfo CodeLanguage="CS"  
				OutputFile="$(CoreDirectory)\Properties\AssemblyInfo.cs" 
				AssemblyTitle="$(ProjectName)" 
				AssemblyDescription="$(ProjectDescription)"
				AssemblyConfiguration=""
				AssemblyCompany="$(CompanyName)"
				AssemblyProduct="$(ProjectName)"
				AssemblyCopyright="$(Copyright)"
				AssemblyTrademark=""
				ComVisible="false"
				CLSCompliant="true"
				Guid="$(ProjectGuid)"
				AssemblyVersion="$(Major).$(Minor).$(Build).$(Revision)" 
				AssemblyFileVersion="$(Major).$(Minor).$(Build).$(Revision)" />
	</Target>
	
	<Target Name="TestsCoverage">
	</Target>
	
	<Target Name="Core" DependsOnTargets="Version">
		<MSBuild Projects="$(CoreProjectFile)" Targets="ReBuild" Properties="Configuration=Release">
			<Output ItemName="Outputs" TaskParameter="TargetOutputs"/>
		</MSBuild>
		<Copy
			SourceFiles="@(Outputs)"
			DestinationFolder="$(DependenciesDirectory)"
		/>
	</Target>
	
	<Target Name="Application" DependsOnTargets="Core">
		<Copy
			SourceFiles="@(Outputs)"
			DestinationFolder="$(WebSiteBinDirectory)"
		/>
		<AspNetCompiler 
			VirtualPath="/$(ProjectName)"
			PhysicalPath="$(WebSiteDirectory)"
			TargetPath="$(WebSiteReleaseDirectory)"
			Force="true"
			Debug="false"
			Updateable="true"
		/>
	</Target>
	
	<Target Name="Documentation">
	</Target>
	
	<Target Name="Maintenance">
	</Target>
</Project>

Currently rated 4.5 by 2 people

  • Currently 4.5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , ,

Add comment


(Will show your Gravatar icon)

  Country flag

biuquote
  • Comment
  • Preview
Loading



Powered by BlogEngine.NET 1.4.5.7
Theme by Mads Kristensen

About the author

Hi!

My name is Bruno and I'am a senior consultant. When I'm not working, you can catch me playing with my daugther, taking photos and hanging out with my wife and friends. :)

You can also check my dark side or have fun with my vision of the world.

View Bruno Figueiredo's profile on LinkedIn

TwitterCounter for @brunoshine

Ads

    Page List

      Most comments

      ricos ricos
      1 comments
      be Belgium
      Root Server Root Server
      1 comments
      de Germany
      steph steph
      1 comments
      sg Singapore

      RecentComments

      Comment RSS

      Now Reading

      Professional WCF Programming: .NET Development with the Windows Communication Foundation (Programmer to Programmer) by Scott Klein
      Professional Windows Workflow Foundation by Todd Kitta

      Popuri.us

      My Popularity (by popuri.us)