Automating your build process is very important. An automated build process can detect new issues in a timely manner which means there is a lot less code to have to look at to figure out why it broke (read more on my blog, Making the Build). However, it can be time consuming to put together a build process and, if it's not automated enough, can become a source of bugs as well.
So what do you do if you have more projects in your application that can reasonably be put into a solution? How do you run your automated build? Wouldn't it be nice to be able to just point to a directory and compile all of the projects under it in the correct order based on the dependencies already defined in your projects?
The source that is included with this article will do this! Just give it a root directory or text file and it will build an MSBuild project that you can use to compile your projects in the correct order.
There are a lot of different build systems out there (we use FinalBuilder at my work), so the included code is a simple executable that will generate a MSBuild file that can then be passed to MSBuild to compile your projects. If you have a build system that can accommodate custom activities, you should be able to adapt the code easily enough.
In order to make use of the MSBuild project file, you should probably be familiar with MSBuild. MSBuild was introduced with Visual Studio 2005 and is the tool used by Visual Studio to build your project. It is an extensible system that allows a lot of flexibility. However, it is also very complicated and difficult to configure. Personally I'm hoping somebody builds a visual tools around MSBuild, though, I'm happy with FinalBuilder.
If you want to learn more about MSBuild, you can try starting with the MSBuild Overview. Don't worry, this article will explain everything you need to know about MSBuild in order to run the MSBuild project that is created by the included code.
Using the Code
The code is comprised of two classes and one module. The classes are
ProjectList is essentially a list of projects. It also manages the interactions between the projects and contains the code to create the MSBuild file. The module creates the
ProjectList, initializes it based on the command-line, and calls upon the
ProjectList to save the MSBuild project file.
The application only accepts two command-line parameters. The first parameter must be the path to the directory or text file that contains the VB.NET or C# project files that you want to include and the second parameter must be the path where you want to write the MSBuild project that will be created (the extension should be .proj).
If you choose to use a text file, the text file can contain any combination of root directories, vbproj or csproj files, or more text files. Just add them one-per-line. You can add comments to the text file by placing a single quote (') at the start of the line. You can also include empty lines.
ProjectList is very easy (as you should be able to see in
Main()). To create a MSBuild project, all you need is the following code:
Dim projects As New ProjectList()
This code instantiates
ProjectList, loads the projects from the given source path (root directory or text file), and then saves the MSBuild project.
Now if you want to compile the MSBuild project file that you created above, you will need to open up the Visual Studio command prompt (for VS 2005
goto Start -> All Programs -> Microsoft Visual Studio 2005 -> Visual Studio Tools -> Visual Studio 2005 Command Prompt). You can then type in:
Project dependencies are based strictly on the project name. The following is an example of references within a .vbproj file (non-system assemblies look similar, though they may contain more information):
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Xml" />
The project dependency engine basically gets the list of references from the project file and then, once the
ProjectList is fully loaded, iterates through each reference and checks to see if there is a matching project in the
ProjectList. To view this code, look for the
Project.InitializeReferences method (most of the work is done in
Project.InitializeReferences - the code is below).
Public Sub InitializeReferences(ByVal projects() As Project)
mProjectReferences = New List(Of Project)
For Each refAsmName As String In mAllProjectReferences
' we only consider references for projects in our list
' not to external references (such as System.dll)
For Each proj As Project In projects
' find the referenced project based on the assembly name
If proj.AssemblyName.Equals(refAsmName, _
' this reference is in our project list, so we add it
' to this projects reference list
' exits the inner loop, but still goes through the rest of
' the references
One thing to watch for is circular references. If two projects reference each other, they will not compile correctly.
ProjectList will ignore circular references (a command-line message will be displayed). What this means is that the compilation is likely to fail (we continue because there is a non-zero chance that it will succeed so we might as well try). Take a look at
ProjectList.AddProject to see how the projects are sorted.
Private Sub SortByReference()
Dim sortedProjects As New List(Of Project)
For Each proj As Project In Me
AddProject(proj, sortedProjects, New Stack(Of Project))
' clear the list and add the projects back in their sorted order
Private Sub AddProject( _
ByVal proj As Project, _
ByVal projects As List(Of Project), _
ByVal refStack As Stack(Of Project))
If projects.Contains(proj) Then Return
For Each ref As Project In proj.ProjectReferences
If refStack.Contains(ref) Then
' circular reference found
' circular references cannot be compiled in a clean
' 'environment (when no assemblies are built)
Dim circRefList As New List(Of String)
For Each circRef As Project In refStack
Console.WriteLine("Circular reference found. " _
& String.Join(" -> ", circRefList.ToArray()))
' This has to be treated as a non-reference otherwise
' we will get into an infinite loop
AddProject(ref, projects, refStack)
I plan on writing another blog post that includes more advice on creating a build within the next couple of weeks, so if you are interested, subscribe to my blog. You can find it at Brian Online.
- June 23rd, 2007 - Created article