Hello to all readers of Habr!
Not so long ago, I started using some NAnt projects for continuous integration with the server along with the already mastered MSBuild. As always, in the process of work, bonuses with different signs are detected (both plus and minus). Those who are interested in the details of the assembly of different engines (MSBuild, NAnt) in the context of the CI server, I am pleased to invite under the cat.
')
Assembly process
First, briefly consider the build process, that is, the list of phases that pass the source files to become the final product:
- cleaning the build folder from the results of the previous build
- initialization of the build process (for example, installing the required version in the code files)
- compile files with code
- test run
- creation of special-purpose files based on binary files (msi installers, NuGet packages, etc.)
- Publish build results (FTP, nuget push)
This assembly model is fairly simplified so as not to clutter the discussion with irrelevant details. So the compilation phase can include loading packages from the repository, the testing phase can be broken into modular and integration test runs. The ability to adapt the assembly tool to the desired process, in my opinion, is quite a significant factor in the selection.
Support for the build process in MSBuild
Since MSBuild is a full-time builder for IDE (Visual Studio, SharpDevelop), we initially worked with it.
To build the project entirely a couple of additional scripts were used. Why a couple, not one, you ask. Everything is quite simple: MSBuild does not know how to load targets-files during the execution of the script. He first imports all the files specified in the script, and then starts the build process. Therefore, the first script was responsible only for downloading additional targets-files from the NuGet repository, and the second, using all the wealth that fell on it, executed the build script.
To build individual projects, generated IDE-csproj files were used with a slight enrichment of them with their variables. It turned out that MSBuild’s project inheritance is tight. For a proper run, the parent script had to completely pass the entire set of parameters inside the child, simple property definitions in the body of the parent were not enough. This, of course, is not fatal, but it forces us to do extra work.
If there are no special problems with the first three phases, then the implementation of the remaining ones requires some skill. Since csproj files are used by IDE to manage the project, they cannot be changed much. The solution was to write the script for all the remaining phases in the main build script. The advantages of such a decision are that everything is described in one place. However, when the number of production and paired unit-test projects increases to a dozen, it becomes difficult to maintain such an build script.
IDEs like to generate separate output folders for each Platform / Configuration combination, so it’s not always a trivial task to determine which folder the dlls from the Reference project are in your folder.
If we decide to use the NuGet package repository in our project, we immediately get duplication. The link to the dll from the package will be present in two places, namely in the files csproj and packages.config. The Nuget client has known issues while maintaining the synchronization of these two files. In particular, updating packages does not always lead to the desired result.
NAnt build support
NAnt today has the current version 0.92 (release of the summer of 2012), so in my research I relied on it.
Here we, perhaps, will begin with the revealed lacks.
The main IDEs (Visual Studio, SharpDevelop) do not like NAnt and do not support the “out of the box” format of its projects. Although, for the sake of completeness, it can be noted that one of the older versions of SharpDevelop NAnt supported. Why in the current development branch of SharpDevelop, NAnt support is minimized for me, while it remains a mystery.
NAnt does not have some proprietary bonuses that allow it to collect certain types of projects without the help of MSBuild (for example, WPF). On minimizing the effects of working with MSBuild, let's say a little later.
What do we have from nice bonuses?
Working with files by masks allows us to minimize the amount of work required to support once-written scripts.
Inheritance of properties between projects allows us not to worry about the explicit transfer of all values ​​between projects at different levels. For example, it is easy to organize the collection of all the compilation results in one folder at the Solution level instead of duplicating the bin, obj folders in each individual project.
The imperativeness of the work of NAnt allows you to easily modify individual project targets at your discretion. Implementing a typical task: copying in addition to libraries a couple of self-made configs using BeforeBuild and AfterBuild events (MSBuild tasks are not used inside them, but cmd or Powershell scripts are usually used) looks like a crutch compared to the explicit use of the NAnt copy native task.
The output system of the regular Java Ant collector has long and well been parsed on assembly servers, so there are no special problems with NAnt. For Jenkins build server (Hudson) there is a separate plugin for NAnt.
NAnt, unlike MSBuild, is able to dynamically load sets of additional tasks with explicit instructions inside scripts or loads them automatically if they are appropriately located next to NAnt.exe. Thus, the collector itself can be distributed using the “xcopy” method, which greatly facilitates the use of a single configuration of the collector in parallel on several machines. From the useful libraries with additions to the regular set of tasks I would like to note:
- NAnt Contrib is a large set of add-ons for NAnt, including interoperability with MSBuild and Subversion
- Wix allows you to build MSI installers
- Nuget bonuses for working with the package manager
As a result of the above, for me the optimal combination was the following: building MSBuild when working in IDE, launching NAnt when running the CI server.
Build WPF Project
In conclusion, I wanted to tell about the spoon of honey in a tar barrel, namely about the assembly of the WPF project with the help of NAnt. I will say right away that there will be no magic to fully overcome double compilation (xaml -> cs -> dll). However, it is still possible to teach MSBuild to go to the tray under the watchful supervision of NAnt.
When running MSBuild in the context of the NAnt build, we have two key problems:
- MSBuild mistakenly thinks that dependency projects need to be built.
- MSBuild looks for dll with dependencies where they should be after building dependency projects
The first problem is solved by setting the build key
/ p: BuildProjectReferences = false .
The second in the forehead is not solved, so let's go a little from the other side. It is unprofitable to contain a static version of the csproj file for running the assembly through NAnt, since support will have both versions. So, you need to generate such a version on the fly. Take and write a small XSL transform, run it through the style NAnt task and feed MSBuild the adjusted project. When you run the style task, one parameter is passed that contains the path to the already compiled DLL files.
XSL conversion<?xml version="1.0"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msbuild="http://schemas.microsoft.com/developer/msbuild/2003"> <xsl:output indent="yes" method="xml"/> <xsl:param name="dllPath"/> <xsl:template match="/"> <xsl:apply-templates /> </xsl:template> <xsl:template match="//msbuild:ItemGroup/msbuild:ProjectReference"> <xsl:element name="Reference" namespace="http://schemas.microsoft.com/developer/msbuild/2003"> <xsl:attribute name="Include"> <xsl:value-of select="msbuild:Name/text()"/> </xsl:attribute> <xsl:element name="HintPath" namespace="http://schemas.microsoft.com/developer/msbuild/2003"> <xsl:value-of select="$dllPath"/> <xsl:value-of select="msbuild:Name/text()"/>.dll</xsl:element> </xsl:element> </xsl:template> <xsl:template match="*|@*"> <xsl:copy> <xsl:apply-templates select="node()|@*"/> </xsl:copy> </xsl:template> </xsl:stylesheet>
Thus, we manage to ensure that MSBuild builds its project in isolation, not knowing anything about other projects. As people say, “you know less, sleep better”, which is very important for managing MSBuild.
Place (instead of) conclusions
How and what to use should always be decided by oneself or within a team, carefully weighing all the pros and cons.
Best start with MSBuild, because it is usually already installed on the developer's machine and does not require unnecessary gestures on the basic setup.
Working with NAnt is also not too difficult, but it requires a certain “turn” of the brain from the developer.