Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VB

Compiling Multiple Projects Without a Solution

4.33/5 (6 votes)
23 Jun 20074 min read 2   410  
An article on creating a build process to compile multiple .NET projects using MSBuild without using a solution file.

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:

VB.NET
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):

XML
<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).

VB.NET
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, _
        StringComparison.InvariantCultureIgnoreCase) Then
                ' this reference is in our project list, so we add it
                ' to this projects reference list
                mProjectReferences.Add(proj)

                ' exits the inner loop, but still goes through the rest of
                ' the references
                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.

VB.NET
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

    ' clear the list and add the projects back in their sorted order
    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
            ' 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
                circRefList.Insert(0, circRef.Name)
            Next
            circRefList.Add(ref.Name)
            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
            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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here