(Warning: This is semi-advanced stuff. It’s not hard, per se, but it involves editing important project files manually, which, if done wrong, can render them useless making it so that your code won’t compile (and may not even load into Visual Studio). So back up everything or (better still) commit it to your source control repository before doing anything so that in case you mess something up, you can easily recover! You have been warned.)
I’ve been steadily becoming more familiar with MSBuild over the past weeks and months. As my familiarity has grown, so too has my conviction grown firm that MSBuild is the unsung hero of the programming world.
MSBuild, also known as the Microsoft Build Engine, is a program that parses XML files and executes various commands (known as “Tasks” ) contained within them based on the target ( “Targets” ) or targets that are specified. You’ve undoubtedly looked through your project directories over the years and seen files with extensions like .csproj , .vbproj , .vcxproj , .fsproj , and so on. You may even have opened one up in a text editor before and seen a whole large jumble of XML tags with names like <PropertyGroup> and <ItemGroup>. These files are your project files; they’re what tells Visual Studio what files are part of your project, what assembly references your project depends on, and so forth. They also, in part, define what happens when you choose to build your project or solution via the Build menu or via a shortcut key such as F6.
(n.b. Native C++ projects, i.e. .vcxproj files, have their own targets defined in its own special MSBuild files. The rest of this is going to be discussing managed projects, so while it may make interesting reading, remember that Native C++ does its own thing, with its own properties, item groups, and targets that are designed for dealing with the target platform-specific nature of native code).
One thing that’s important to understand is that simply looking at a .csproj (or other .*proj) file will not tell you what is going on. The reason for that is the <Import> tag, which is used to import various other files. One file that’s imported in almost all projects is Microsoft.Common.targets. This file is quite long and consists of more MSBuild XML. What’s important about it is that this is where the default build pipeline is set forth. It defines a number of targets, chief among them being Build, BeforeBuild, CoreBuild, and AfterBuild. It also sets the <Project> tag’s DefaultTargets parameter to be the Build target.
Build itself calls the BeforeBuild, CoreBuild, and AfterBuild targets in that order. CoreBuild is what does all the reference locating, compiling, and linking stuff. It has a number of targets that it runs, but we aren’t going to concern ourselves with that other than to say that the PreBuildEvent and PostBuildEvent targets run within CoreBuild at appropriate times. If you’ve ever viewed your project’s “Properties” in Visual Studio, you may have noticed the “Build Events” tab in which you can specify command line commands to run pre-build and post-build. That’s how these are hooked up, so know that the BeforeBuild target runs before PreBuildEvent and that PostBuildEvent runs before AfterBuild.
Well, so far this has been a dry walkthrough of arcane XML. Where’s the “ruthless productivity” part? It has arrived. We simply needed the dry walkthrough to get to it (and understand it, more importantly). When you build a project, it runs the Build target because that’s what the project (via importing Microsoft.Common.targets) has been setup to do. By default the BeforeBuild and AfterBuild targets do nothing. You, however, can change that. If you edit your .csproj or .vbproj file (Visual Studio’s XML editor works great, but note that the project needs to be closed before you can edit its project file), you’ll find that you can either uncomment the existing, commented out BeforeBuild and AfterBuild targets or else (if they aren’t there or if you feel like making your own tags instead) you can write your own. Do you have anything that you need to do before you build a project? Perhaps for an XNA game you want to copy the latest art assets out of some other directory and move them to the content directory so that your game builds the latest assets. Perhaps you have some files that need to run through a tool or maybe after the build finished you want to copy the new build to a network drive so that you (and other members of your team) can access and run the latest build.
This is where MSBuild shines. Simply open up your .csproj or .vbproj file (or even a .contentproj file) and add some tasks to the BeforeBuild and/or AfterBuild targets. There are many predefined tasks to choose from and, if none fit your needs, you can always write your own custom task. When MSBuild runs a project file, the working directory is generally the directory in which that project file is located; this is handy for when you’re running a Copy task since you can use relative paths (rather than hard-coded ones) so that someone else on a different machine with a different directory structure can still build the project correctly. If you want to run some program, you can make use of the Exec task. You can even check various conditions and abort the build with an Error task if a check fails.
So where does this leave us? Now, rather than have to manually perform various tasks that are necessary to your build process or that you want to perform after the build, you can instead automate them with a few simple lines of XML. Even complex tasks are not outside the read of MSBuild; you just may need to write a custom task for it or perhaps run several different tasks back-to-back (with some error checking thrown in for good measure, of course).
Some final notes. I didn’t really talk much about the ItemGroup and PropertyGroup tags. These are container tags into which you can add various tags with your own unique names. An ItemGroup is normally used to specify one or more files whereas a PropertyGroup is normally used to specify and store values and for evaluating conditions.
A member of an ItemGroup is normally dereferenced via the @(SomeItemTag) syntax, which will by default produce a semicolon separated list of things that are specified in tags in the project’s ItemGroup(s). The above would pull out all items within an ItemGroup that were stored inside <SomeItemTag Include=”text here”></SomeItemTag> pairs. Note that all items must have an Include attribute and that multiple elements within the same Include attribute should be separated with a semicolon. With ItemGroups, you can have the same tag multiple times (and within multiple ItemGroup tag pairs) and it will concatenate them all (provided that you properly escape any special characters using %xx syntax, where xx is the hex code of the ASCII value of the special character ).
Members of a PropertyGroup on the other hand are dereferenced via the $(SomeProperty) syntax. Unlike with ItemGroup, you can only have one instance of a specific tag for PropertyGroup. To the extent that you use the same tag multiple times, only the value of the last instance of the tag will be used. Properties don’t require an Include attribute and normally are specified by including the value within a tag.
As a brief example, here’s a simple MSBuild file that shows some usage of a target, a task type (the Message task), and various ItemGroup and PropertyGroup groups.
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Hello" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Message Importance="high" Text="@(SomeItemTag)" />
<Message Importance="high" Text="$(SomeProperty), $(SomeOtherProperty)" />
<SomeOtherProperty>how's it going</SomeOtherProperty>
If you run a Visual Studio Command Prompt (to setup the environment variables and paths), you can save this to a text file named, e.g. “test.msbuild” and then run it from the command line via:
The file name extension doesn’t matter, btw. I used it just for easy knowledge of the file’s contents. Another thing to know is that MSBuild predefines a large number of properties. If you want to see what all they are, do the same as above only this time run it as:
msbuild /v:diag test.msbuild
That tells msbuild to use a verbosity of diagnostic. To find out other command line parameters, just type:
and it will show you. You can do things like specify properties (and their values) and set a different target than the DefaultTargets target. Say we added a target above called “Goodbye”. Just running MSBuild as is would run the Hello target (and only the Hello target). If you instead used the command line switch, you could tell it to run the Goodbye target or even both targets (in whichever order you specify).
This only scratches the surface of what MSBuild can do. I left out quite a bit that the links do a good job of explaining, so make sure to read them. If you find this post interesting, you might want to bookmark it. I wrote it in part so that I would have something to refer back to whenever I needed, It’s not the kind of thing you’re likely to use every single day or even every week. You might only modify your .csproj file once or twice in the course of an entire project development cycle. So it’s easy to forget quite a bit of this as time passes. If you have any questions, leave comments and I’ll respond to the extent that I can. I may return to this topic again in the future, but for now this should be a good, quick intro to MSBuild.