As previously discussed by Simon,
there are issues with pointing MSBuild at a Visual Studio solution
file to build your project assemblies - the alternatives are to either
build each of the projects individually (using <MSBuild/> tasks) or to call VS itself to build the solution (the SDC Build Tools project has a custom task to easily do this).
The former approach causes some maintenance headaches by requiring
the MSBuild script to be updated anytime a project is added or removed
from the solution - whereas the latter option means you must have
Visual Studio installed (which was supposed to be one of the benefits
of using MSBuild) and can also result in some referencing issues the
first time you build a solution after project changes.
In order to try and get the best of boths worlds, I started thinking
about dynamically generating an MSBuild script with all the tasks
needed to build each VS project referenced by the targetted .sln
file (i.e. the first of the above approaches, but with all the leg-work
automated). My preferred tool for such things (since being
introduced to it by Howard) is the excellent CodeSmith .
By running the template below prior to calling
MSBuild, the tasks needed to build all the Solution's
projects are written to a .targets file that can be <Import'd
by the main MSBuild script - in my case this 2-step process is
wrapped by a simple batch file that gets called by CruiseControl.
<%--
Description: Generates an MSBuild script to build each of the Visual Studio
projects found within the specified Visual Studio Solution file.
%>
<%@
CodeTemplate Language="C#" TargetLanguage="Text" Src="" Inherits=""
Debug="False" Description="Generate MSBuild actions from Solution file"
%>
<%@ Property
Name="SolutionFile" Type="System.String" Default="" Optional="False"
Category="Strings" Description="VS.NET Solution file to process" %>
<%@ Import Namespace="System.Diagnostics" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Text.RegularExpressions" %>
<%
if ( !File.Exists(this.SolutionFile) )
{
throw new ArgumentException("ERROR:\nUnable to find the VS.NET Solution file:\n" + this.SolutionFile);
}
else
{
this.SolutionFile = Path.GetFullPath(this.SolutionFile);
}
%>
<?xml version="1.0" encoding="utf-8" ?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="BuildProjects">
<%
string contents;
using ( StreamReader sr = new StreamReader(this.SolutionFile) )
{
contents = sr.ReadToEnd();
}
// The regular expression below extracts the project information from
// the relevant part of the solution file - the format of which seems to
// be the same in VS 2003 & 2005
Regex regex = new Regex("^Project\\(.*\\s=\\s\"(?<projectName>.*)\",\\s\"(?<projectPath>.*)\",");
MatchCollection matches = regex.Matches(contents);
if ( matches.Count > 0 )
{
foreach ( Match match in matches )
{
string projectName = match.Groups["projectName"].Value;
string projectPath = match.Groups["projectPath"].Value;
Trace.WriteLine( string.Format("ProjectName: {0}\nProjectPath: {1}",
projectName, projectPath) );
// Make sure that the reference in the .sln file is really a project
// and not just a Solution Folder
if ( projectName != projectPath && projectPath.EndsWith("proj") )
{
%>
<MSBuild
Projects="<%=Path.Combine(Path.GetDirectoryName(this.SolutionFile),projectPath)%>"
Targets="Clean;Rebuild" />
<%
}
}
}
else
{
throw new ApplicationException( string.Format("ERROR:\nThe specified
Solution file contains no projects:\n{0}", this.SolutionFile) );
}
%>
</Target>
</Project>
<script runat="template">
</script>