Continuous Integration (Part 1 - MSBuild)

by Derek Pinkerton 1. June 2011 21:22

 

In this two part post I will be discussing setting up a Continuous Integration and nightly build system for an ASP.Net web application using MSBuild build scripts and Jenkins CI Server. I will be using Subversion (SVN) for source control.

I recently setup a continuous integration server for Qualtrax using the same methods I’ll describe here. To follow along you will need to

(Visual Studio 2010 solution, Asp.Net 4.0.) I do not believe the unit test project included will load in Visual Studio Express Edition. You will also need to download and install the MSBuild Community Tasks library. The sample project is a very basic web application that utilizes Entity Framework and the Adventure Works SQL Server sample database.

 

image

Unzip the sample project and open the CI_Sample.sln solution file. Once you have opened the sample project in Visual Studio you will see the following project structure within Solution Explorer.

  • BuildActions – Custom tasks to be run during the build.
  • Core – The data layer for the web application (uses Entity Framework and Adventure Works sample database.
  • Solution Items – Contains the MainBuild.proj MSBuild file that defines the build, GlobalAssemblyInfo.cs file that is used by all projects for version numbering, as well as some files used in unit testing.
  • Tests – Unit tests to be run for the integration build
  • CI.Web – The sample web application

Build Actions

The build actions project defines two custom actions that can be used within your MSBuild build script. Each task that will be used from within an MSBuild script will need to inherit from Microsoft.Build.Utilities.Task. See Task Writingon MSDN for more information on creating custom tasks.

  • MinifyCss – a C# implementation of the YUI Compressor. This task can be used to combine multiple CSS files into a single file and compress that file by removing blank lines and shortening various attributes.
  • UpdateAssemblyInfo – a custom task that can be used to increment the build number of your application each time a build is run.


    Core

    The data layer for this sample application was created using Entity Framework. The Asp.net website has a good exampleof creating an ASP.Net website with an Entity Framework backend. I won’t cover this in any more detail as this tutorial is meant to assist in creating the MSBuild script to build the completed project.

    Solution Items

    The MainBuild.proj file is an XML formatted document that will be used by MSBuild to perform the build. The MSDN websiteis a good reference for getting to know the format and concepts of a MSBuild project file.

    Property Groups
    A property group is used to define one or more properties (variables) that can be used throughout the build script. A property group can have a condition which must be met in order to be applied. Take the following example:

      <PropertyGroup Condition="$(Configuration) == ''">
        <Configuration>Release</Configuration>
      </PropertyGroup>
    

    This property group has a condition that the $(Configuration) property must be empty. If this condition is met, the same property is set to “Release”. The reason that the condition is specified on this property group is that properties can be specified on the command line and we do not want to overwrite command line arguments.

    Any number of properties can be defined within the same property group. The values for properties can use other properties within them. For example:

      <PropertyGroup>
        <MSBuildCommunityTasksPath>$(MSBuildExtensionsPath32)\
    MSBuildCommunityTasks</MSBuildCommunityTasksPath> <CIBuildActionsTasksLib>$(MSBuildProjectDirectory)\BuildActions\bin\
    $(Configuration)\CI.BuildActions.dll</CIBuildActionsTasksLib> </PropertyGroup>

    As you can see, two properties are defined within this property group. The second of which uses the $(Configuration) property as part of the path to the CI.BuildActions.dll file. Line breaks have been added within the properties for readability here.

    UsingTask
    UsingTask tags can be added to the project file to include custom tasks that you create. The following statements are used to include the custom tasks from the BuildActions project.

      <UsingTask AssemblyFile="$(CIBuildActionsTasksLib)" 
    TaskName="CI.BuildActions.UpdateAssemblyInfo" /> <UsingTask AssemblyFile="$(CIBuildActionsTasksLib)"
    TaskName="CI.BuildActions.MinifyCss" />

    Again we see that properties defined earlier are used within this statement. Each task that you define will need to have a separate UsingTask tag. You can also group these together in a separate file and import the entire file. The Import tag is used to import these separate files. In the example below we import the MSBuild Community Tasks project, which is an open source package containing numerous tasks you can use. We will use the Zip task from this library later on.

    <Import Project="$(MSBuildCommunityTasksPath)\MSBuild.Community.Tasks.Targets" />
    

    Targets
    Each target defines a list of tasks to be run. A build may consist of one or more targets to run. The sample target below displays the message “Building the entire solution” in the build output and then calls MSBuild to build the entire CI_Sample.sln solution. This is the target we will be using for the Continuous Integration build in part two of this article.

      <Target Name="IntegrationBuild">
        <Message Text="Building the entire solution" Importance="high"></Message>
        <MSBuild BuildInParallel="true" Projects="$(MSBuildProjectDirectory)\CI_Sample.sln" 
                 Targets="Rebuild"/>
      </Target>
    

    You may have noticed that the project file reference includes $(MSBuildProjectDirectory). This is a property/variable that is included in MSBuild and is automatically defined based on the directory in which your project file is located. Since the MainBuild.proj file and the CI_Sample.sln file are both located in the same directory we define the path as $(MSBuildProjectDirectory)\CI_Sample.sln. Additional properties are available (see MSDN site for more info) and you can also define your own properties through the use of ProperyGroup tags as described above. We also tell MSBuild that it can build the projects within this solution in parallel (if possible based on references) and that it will preform a full rebuild.

    Targets defined in the MainBuild.Proj file and their uses:

    • RebuildBuildActions – This target will rebuild just the BuildActions project. This is done as a separate task because the BuildActions dll is used within other targets and as such cannot be modified during a build. It is important to note that this target uses the Exec Command tag to build rather than using the MSBuild tag. Exec Command can be used to execute any command line. In this case we are executing DevEnv, which is the Visual Studio development environment. This will require Visual Studio to be installed on the build machine. This can be modified to use the MSBuild tag instead and was only done in this manner for illustrative purposes.
    • IncrementBuildNumber – As its name implies this task will increment the build number within the GlobalAssemblyInfo.cs file. We will use this target during the nightly full rebuild, but not during the integration build.
    • IntegrationBuild – This target will define the tasks that will be completed during the continuous integration build. This build will occur each time new code is checked into the source code repository.
    • MainBuild – This task will be used for the nightly rebuild. This will perform all necessary tasks to complete a full build of all projects, publishing the web application and zipping up the resulting files.

    It is also important to note that many of the tags used in this project will not be recognized by Visual Studio’s IntelliSense. As such they will be underlined as errors. Do not be alarmed as these will still function correctly.

    ItemGroup
    An Item Group is a way of defining a property that has multiple values. You can think of this as an array. We use an Item Group to define the list of files to include in the zip file that we create at the end of the main build.

        <ItemGroup>
          <ZipFiles Include="$(WebPublishDirectory)\**\*.*" Exclude="*.zip" />
        </ItemGroup>
    

    In the example above “\**\*.*” is used to recursively (\**\) include all files (*.*). Instead of using a recursive path such as this, you could specify each file individually by adding the same inner tag multiple times with different values such as:

        <ItemGroup>
          <ZipFiles Include="$(WebPublishDirectory)\*.*" Exclude="*.zip" />
    <ZipFiles Include="$(WebPublishDirectory)\bin\*.*" Exclude="*.zip" />
    <ZipFiles Include="$(WebPublishDirectory)\Styles\*.*" Exclude="*.zip" />

    </
    ItemGroup>

    When using an Item Group variable within a statement, you will need to use the
    @(VariableName) syntax instead of the $(VariableName) syntax.

    <Zip Files="@(ZipFiles)" ZipFileName="Website.zip" 
    WorkingDirectory="$(WebPublishDirectory)\" />

    I hope this example has helped you to create a MSBuild project file. In the next post I will use the MSBuild project file in this example to setup a continuous integration system.

     

  •  

    Tags: , , , , ,

    c# | Continuous Integration | MSBuild | Qualtrax

    Comments

    Comments are closed