Friday, June 10, 2011

Frankenstein's monster build configurations

What follows will probably prove in hindsight to have been a bad idea. It was however a working solution to a problem I faced with a code base.

The problem - Two class library projects with a 90%+ common code base

I've been working on a Visual Studio 2010 solution that can produce two separate class libraries. Lets call them A and B. The key differences between the two projects are:

  • The namespaces for A and B have different prefixes.
    A has CompanyX.ProductA.FooBar while B has CompanyX.ProductB.FooBar.
  • A small number of code changes that significantly change the functionality of each output library.
  • The output Assembly Name (DLL name)
    A has CompanyX.ProductA.dll while B has CompanyX.ProductB.dll

The Frankenstien's monster solution

By using Conditional Compilation directives and multiple build configurations that define different Conditional compilation symbols for A and B the first two differences above can be handled.

At the start of each class the namespace and using statements can be toggled based on the defined symbols. This can also be used to make the code changes between the two output libraries.

using System;

#if A
namespace CompanyX.ProjectA
#elif B
namespace CompanyX.ProjectB
#else
namespace CompanyX.ProjectX
#endif
{
 public class ExampleClass
 {
  public ExampleClass()
  {
#if A
   EnableProjectAFeatureSet();
#elif B
   EnalbeProjectBFeatureSet();
#else
   
#endif
  }

  private void EnableProjectAFeatureSet()
  {
   throw new NotImplementedException();
  }

  private void EnalbeProjectBFeatureSet()
  {
   throw new NotImplementedException();
  }

 }
}

The final requirement for differing assembly names can be handled by copying the <AssemblyName>ClassLibrary1<:/AssemblyName> element in the csproj file into the configuration conditional PropertyGroups. See Change assembly name based on configuration (Visual Studio 2005/2008)