Reference paths to the rescue
On a regular basis I have to compile code against assemblies produced by separate scrum teams. These teams share the same solution structure but add new functionality to projects as they go. Within the solutions we abandoned the use of the global assembly cache in favor of easier deployment and most assembly’s referenced lack a hint path to aid in assembly resolving.
All comes down to having to build the complete feature team solution in order to get the right assemblies. Most of the time my development machine resulted in being able to build for a specific team after quite some effort, but everything had to be redone for another team.
In search of a better solution I remembered the use of the reference paths in the project properties.
Any reference path added here ends up in a file (.user) that is local to the developer and should be ignored by versioning systems. Resolving assemblies during compilation is done by a lookup of the assembly in the first reference path, followed by the next etcetera.
Using this developer local file, I copied the TFS build output to a local directory and referenced that directory as a reference path in the projects. Switching teams meant deleting the contents of the local directory and copying the new assemblies from the build output, something I automated as well.
The reason for this article is twofold; first of all I want to share the use of this little gem called reference paths. Furthermore, there is one inconvenience with this solution if there are many projects and solutions. Adding reference paths to all of them is bit of a bore.
My solution is a small command line application that recursively searches for projects and adds the reference path to the .user file auto-magically, leaving intact any settings already in place or adding a .user file if nonexistent.
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml.Linq; namespace SetLibPathInUserFile { class Program { /// <summary> /// The namespace to which the elements belong in the .user xml file /// </summary> private static readonly XNamespace MsBuild2003 = XNamespace.Get("http://schemas.microsoft.com/developer/msbuild/2003"); /// <summary> /// The main program entry /// </summary> /// <param name="args">The args.</param> static void Main(string[] args) { if (args.Length < 2) { Console.WriteLine("SetLibPathInUserFile [RootPath] [ReferencePathsToAdd]"); Console.WriteLine(); Console.WriteLine("RootPath : File path from which to search for .csproj and related .user files"); Console.WriteLine("ReferencePathsToAdd : a ';' separated line with the paths to add to the refences"); return; } var rootPathParameter = args[0].Trim('"'); var referencesParameter = args[1].Trim('"'); var referencesToAdd = new List<string>(referencesParameter.Split(';')); var count = 0; var processed = 0; foreach (var file in new DirectoryInfo(rootPathParameter).GetFiles("*.csproj", SearchOption.AllDirectories)) { count++; var isDirty = false; var userfile = file.FullName + ".user"; XElement xml; if (!File.Exists(userfile)) { xml = new XElement(MsBuild2003 + "Project", new XAttribute("ToolsVersion", "4.0"), CreatePropertyGroup(referencesToAdd)); isDirty = true; } else { xml = XElement.Load(userfile); var project = xml.Element(MsBuild2003 + "Project"); if (project != null) { foreach (var propertyGroupElement in project.Elements(MsBuild2003 + "PropertyGroup")) { var referenceElement = propertyGroupElement.Element(MsBuild2003 + "ReferencePath"); if (referenceElement != null) { var references = string.IsNullOrEmpty(referenceElement.Value) ? new List<string>() : new List<string>(referenceElement.Value.Split(';')); var joinedReferences = references.Union(referencesToAdd).ToList(); if (joinedReferences.Count > references.Count) { referenceElement.Value = string.Join(";", joinedReferences); isDirty = true; } break; } } if (!isDirty) { project.Add(CreatePropertyGroup(referencesToAdd)); isDirty = true; } } } if (isDirty) { xml.Save(userfile); Console.WriteLine("saved {0}", Path.GetFileName(userfile)); processed++; } } Console.WriteLine("C# projects located: {0}", count); Console.WriteLine("User files modified: {0}", processed); } /// <summary> /// Creates a property group containing the References element /// </summary> /// <param name="referencesToAdd">The references to add to the references element</param> /// <returns></returns> private static XElement CreatePropertyGroup(IEnumerable<string> referencesToAdd) { return new XElement(MsBuild2003 + "PropertyGroup", new XElement(MsBuild2003 + "ReferencePath", string.Join(";", referencesToAdd))); } } }