Alan Dean

CTO, Developer, Agile Practitioner

Photograph of Alan Dean

Sunday, February 20, 2011

Framework version targeting with MSBUILD

I faced a question this weekend: how best should I support conditional compilation by framework? The proximate reason for asking this was a need to use BigInteger in the Cavity project in order to be able to support 128-bit base-36 notation with a minimum of fuss. This is a new value type in Framework 4.0 and up until now Framework 3.5 have been the target for Cavity assemblies. As 3.5 is going to be around for a good while yet (heck, there is still plenty of 2.0 in production today) I didn’t want to orphan support for that version. I also didn’t want to add complexity to my build or maintenance activities. This ruled out brute force options such as creating a branch for 3.5 or creating a new solution and project files for one or other of the framework versions.

A search yielded a varied collection of advice. Some was plainly potty but a couple of StackOverflow answers [1] [2] looked promising. Taking the answers and combining a bit of common sense, I put together a quick spike to verify that I had the gist of it. Here is what I learnt:

  1. The best way to control targeting is to use property parameters with a build file. Here is a .bat file to build release versions targeting each major framework version:
    MSBUILD build.xml /p:Configuration=Release /p:TargetFrameworkVersion=v2.0
    MSBUILD build.xml /p:Configuration=Release /p:TargetFrameworkVersion=v3.5
    MSBUILD build.xml /p:Configuration=Release /p:TargetFrameworkVersion=v4.0

  2. You don’t need to do anything in the build.xml or the .sln file for this to work. You do, however, need to do a little work in the .csproj files:
    • First, you need to decide which framework version you want to work with in Visual Studio (typically, this will be the latest version) and configure TargetFrameworkVersion accordingly:

      <TargetFrameworkVersion Condition=" '$(TargetFrameworkVersion)' == '' ">v4.0</TargetFrameworkVersion>

    • To make the build output obvious, set the OutputPath to use properties:

      <OutputPath>bin\$(Configuration) $(TargetFrameworkVersion)\</OutputPath>

    • Next, you should set up some conditional property groups:
      <PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v2.0' ">
          <DefineConstants>NET20</DefineConstants>
          <TargetFrameworkVersionNumber>2.0</TargetFrameworkVersionNumber>
      </PropertyGroup>
      <PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v3.5' ">
          <DefineConstants>NET35</DefineConstants>
          <TargetFrameworkVersionNumber>3.5</TargetFrameworkVersionNumber>
      </PropertyGroup>
      <PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v4.0' ">
          <DefineConstants>NET40</DefineConstants>
          <TargetFrameworkVersionNumber>4.0</TargetFrameworkVersionNumber>
      </PropertyGroup>

    • This allows you to configure framework-specific assembly references, such as System.Core and Microsoft.CSharp:

      <Reference Include="System.Core" Condition=" '$(TargetFrameworkVersionNumber)' >= '3.5' " />
      <Reference Include="Microsoft.CSharp" Condition=" '$(TargetFrameworkVersionNumber)' >= '4.0' " />

    • You should now be able to target framework versions in a straightforward manner by running the batch file above.

  3. If you want to conditionally include classes, simply apply a framework condition:
    <Compile Include="Class20.cs" Condition=" '$(TargetFrameworkVersion)' == 'v2.0' " />

  4. I also defined constants above to allow conditional compilation at code level:
    namespace Example
    {
        public sealed class Class1
        {
    #if NET20
            public void Net20()
            {
            }
    #endif
    
    #if NET35
            public void Net35()
            {
            }
    #endif
    
    #if NET40
            public void Net40()
            {
            }
    #endif
        }
    }



The only pain I have really encountered so far is getting MVC web applications to play nicely.

2 comments:

Mauricio Scheffer said...

Nice article! I haven't been able to use >= conditions though... I get this error:

error MSB4086: A numeric comparison was attempted on "$(TargetFrameworkVersion)" that evaluates to "v4.0.30319" instead of a number, in condition "'$(TargetFrameworkVersion)' >= 'v4.0'".

Any ideas?

Alan Dean said...

You have to character encode the greater than symbol, i.e &gt;