Welcome to part three of the MSBuild tutorial; please take a look at the previous parts if you haven’t already.
At this point there are a couple of minor issues with our build script that need to be resolved before pressing on with any new targets.
Usability
The first of these is usability. Right now there is no easy way to run all of the targets in one go. This is not very helpful if someone wants to run the full build process from start to finish. To remedy this, I’ve introduced a new target called Run, below
<Target Name="Run"> <CallTarget Targets="Compile" /> <CallTarget Targets="Publish" /> <CallTarget Targets="SetConfig" /> </Target>
This simply uses the CallTarget task to hand the work off to the targets we created in the last two posts. Notice that it isn’t necessary to call Clean and GetConfig as they are dependencies of Compile and SetConfig respectively. MSBuild will automatically run them, in the correct order.
The finishing touch is applied by adding the DefaultTargets attribute to the root Project node, with a value of Run. This tells MSBuild to call the Run target if no targets are explicitly passed in from the command line. Having added the attribute, the whole script can be executed with this simple command
msbuild Build.xml
As you can see the /t switch has gone. Much nicer!
DRYing out the XML
Our second issue involves refactoring. It is important to treat our build script just like any other code and apply good practices to it. Some repetition has crept in, which violates the DRY principle, specifically
- Each XPath to a config setting occurs twice
- The Output folder is referred to numerous times
The best solution is to extract these strings into properties and then use the $(PropertyName) syntax to refer to them. It doesn’t take long and makes the script much easier to maintain in future. You can see the end result here.
Deploying the web site
Now the housekeeping is out of the way, we can start work on the last target, which is to deploy the web site to the correct location and configure IIS accordingly. This means copying the contents of the Output folder to another computer and either adding or updating a virtual directory to run the site.
The first part is pretty simple, and just a case of using the RemoveDir and Copy tasks in a similar way to the Publish target in part two. The following XML illustrates this
<Target Name="Deploy"> <RemoveDir Directories="$(DeploymentFolder)" ContinueOnError="true" /> <ItemGroup> <DeploymentFiles Include="$(OutputFolder)\**\*.*" /> </ItemGroup> <Copy SourceFiles="@(DeploymentFiles)" DestinationFolder="$(DeploymentFolder)\%(RecursiveDir)" /> </Target>
Nothing new to see there, so we’ll move on quickly to configuring the IIS virtual directory. There is no built-in task to do this, however the MSBuild Community Tasks Project comes to the rescue again with the WebDirectoryDelete and WebDirectoryCreate tasks. It is important to recreate the virtual directory each time to ensure we have a clean deployment. This is accomplished by adding the following XML to our Deploy target
<WebDirectoryDelete VirtualDirectoryName="$(VirtualDirectory)" ContinueOnError="true" /> <WebDirectoryCreate VirtualDirectoryName="$(VirtualDirectory)" VirtualDirectoryPhysicalPath="$(DeploymentFolder)" />
Something worth noting at this point is that this only works for the local machine. If you are deploying to another box, which is highly likely, then you would need to supply the ServerName, Username and Password attributes as well.
The final piece of the puzzle is to amend the Run target to ensure it calls our new Deploy target at the end. At this point it is possible to run the entire script and get a newly built and deployed web site in a matter of seconds. Very useful indeed.
Environment-specific deployment
At this point we need to revisit the Environment property which was defined back in part two. This is still hard-wired to Test, as are the new DeploymentFolder and VirtualDirectory properties. As a consequence, users of the build script will have to edit it if they want to build for, say, a live environment.
To prevent this we can introduce a PropertyGroup for each of the environments, and move the DeploymentFolder and VirtualDirectory properties into each of them. Then, by adding the Condition attribute to each group, it is possible to test which environment is being used and bring its PropertyGroup into play. The following snippet shows how to do so
<PropertyGroup Condition="$(Environment) == 'Test'"> <DeploymentFolder>C:\Temp\BuildDemoSite\Test</DeploymentFolder> <VirtualDirectory>BuildDemoTest</VirtualDirectory> </PropertyGroup> <PropertyGroup Condition="$(Environment) == 'Live'"> <DeploymentFolder>C:\Temp\BuildDemoSite\Live</DeploymentFolder> <VirtualDirectory>BuildDemoLive</VirtualDirectory> </PropertyGroup>
As you can see, the value of the Condition attribute is an expression that evaluates to true or false. In our case, we test the value of the Environment setting. In future, if further deployment environments are required, it is simple to add a new PropertyGroup.
Despite these changes, we still have that pesky Environment property – let’s delete it. Instead, it can specified via the command line when the script is run, as follows
msbuild Build.xml /p:Environment=Live
By using the /p switch it is possible to specify a value for the property, in a similar way to calling a function and passing in arguments. However, there is always a chance that someone may forget to do this, in which case you may wish to restore the Environment property and have it act as a default.
Summary
That’s the end of the tutorial. Although the final script (available on CodePlex) achieves all of the aims set out in part one there are still a couple of areas for improvement, specifically
- There is no MSBuild task which provides the same behaviour as Visual Studio’s Publish feature
- A certain amount of repetition remains when merging config settings. Ideally all settings would be merged automatically, perhaps via a custom MSBuild task (something for another post perhaps)
Overall however the end result is still very useful, and only scratches the surface of what MSBuild can do. If you are interested in learning more, the following links may be of use
- MSBuild Team Blog
- MSBuild Task Reference
- MSBuild Command Line Reference
- MSBuild Community Tasks Project
- For an alternative build engine, try NAnt. This open source tool precedes MSBuild and is still widely used
All of the source code for the tutorial is available from CodePlex. I hope you’ve found it useful and welcome any feedback.
Technorati tags: msbuild msbuild tutorial build script .NET