Introduction
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.
Background
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
and Project
. 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.
Using 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()
projects.LoadProjects("C:\MyProjects")
projects.SaveMSBuildProject("C:\MyProjects.proj")
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:
MSBuild C:\MyProjects.proj
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):
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Xml" />
</ItemGroup>
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 ProjectList.InitializeReferences
and 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
For Each proj As Project In projects
If proj.AssemblyName.Equals(refAsmName, _
StringComparison.InvariantCultureIgnoreCase) Then
mProjectReferences.Add(proj)
Exit For
End If
Next
Next
End Sub
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.SortByReference
and 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))
Next
Me.Clear()
Me.AddRange(sortedProjects)
End Sub
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
refStack.Push(proj)
For Each ref As Project In proj.ProjectReferences
If refStack.Contains(ref) Then
Dim circRefList As New List(Of String)
For Each circRef As Project In refStack
circRefList.Insert(0, circRef.Name)
Next
circRefList.Add(ref.Name)
Console.WriteLine("Circular reference found. " _
& String.Join(" -> ", circRefList.ToArray()))
Continue For
End If
AddProject(ref, projects, refStack)
Next
refStack.Pop()
projects.Add(proj)
End Sub
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.
History
- June 23rd, 2007 - Created article