Latest Replies
Sunday
Jan042009

How to Target Multiple .NET Frameworks

Here are the tricks that are known to help in sharing codebase between different Microsoft .NET Frameworks (i.e.: Silverlight 2.0, .NET 3.5, .NET 2.0):

  • Everything is hooked up to the Profile parameter (e.g.: Silverlight, NET35 or NET20) being passed to the external build script. For example, that's how different Autofac profiles are executed from the command-line (go.cmd is just a helper shortcut that calls NAnt with the arguments provided):

    go integrate
    go net20 integrate
    go silverlight integrate
    
  • Use this profile parameter to set up build properties like SolutionFile, MSBuildTargets and BuildConstants and then pass them to the MSBuild process that actually builds everything. That's how, for example, these properties are passed from Autofac.build (NAnt file) to the MSBuild script:

    <msbuild project="${SolutionFile}">
      <property name="Configuration" value="${ProjectConfig}"/>
      <property name="Platform" value="${Platform}"/>
      <property name="TargetFrameworkVersion" value="${TargetFrameworkVersion}"/>
      <property name="MSBuildTargets" value="${MSBuildTargets}"/>
      <property name="BuildConstants" value="${BuildConstants}"/>
    </msbuild>
    

    Let's have a closer look at these properties:

    • SolutionFile (i.e.: Autofac.Full.sln, Autofac.Core.sln) points to collection of projects being build under the current profile. MSBuild task will actually be executed against this solution.

    • BuildConstants (e.g.: SILVERLIGT20, NET20) that are passed to MSBuild and are appended to the list of switches for every project. That's the piece of Autofac.csproj:

      <DefineConstants>
        DEBUG;TRACE;$(BuildConstants)
      </DefineConstants>
      
    • MSBuildTargets (i.e.: CSharp or Silverlight 2.0) is passed to the MSBuild script to let it pick the primary building target file for MSBuild.

That's how Autofac.csproj looks like (we are replacing initial import of Microsoft.CSharp.targets):

<Import 
  Project="$(MSBuildBinPath)\Microsoft.CSharp.targets"
  Condition="($(MSBuildTargets) == '') Or ($(MSBuildTargets) == 'CSharp')" />
<Import 
  Project="$(MSBuildExtensionsPath)\Microsoft\Silverlight\v2.0\Microsoft.Silverlight.CSharp.targets" 
  Condition="$(MSBuildTargets) == 'Silverlight 2.0'" />

Note, that when we open the project in Visual Studio 2008, we are working against .NET 3.5 by default.

  • Use compiler pre-processor directives (matching build constants) to enable or disable certain code blocks or entire classes. For example:

    #if SILVERLIGHT2        
    namespace System
    {
      /// <summary>
      /// Marker attribute to make code compilable to Silverlight
      /// </summary>
      [AttributeUsage(AttributeTargets.Delegate | AttributeTargets.Enum
        | AttributeTargets.Struct | AttributeTargets.Class,
        Inherited = false)]
      public sealed class SerializableAttribute : Attribute {}
    }        
    #endif
    
  • Add stub classes for non-critical attributes that are missing from the target framework. For example, Silverlight 2.0 is missing:

    • SerializableAttribute
    • AllowPartiallyTrustedCallerAttribute
  • Add implementation classes for pieces that are missing from the target framework and are critical (i.e.: low-precision implementation of the StopWatch).

  • Check "Do not reference mscorlib.dll" in the project Build properties (behind this Advanced... button) for all configurations and then manually add "mscorlib.dll" to the references (so that the primary targets file will be used to pick the proper runtime).

Advanced Build Settings

  • Set CopyLocal for every single reference in the UnitTest projects, so that NUnit will not have to worry about resolving the references.
  • Hook up continuous integration to always run compile-and-test routine against every single framework being targeted.
  • Be prepared to discover methods that are missing from some frameworks (i.e.: Module.ResolveField in Silverlight 2.0).
  • Be prepared to have even some more fun if your development or integration machine is a 64bit OS.

You can check out the Autofac codebase for a sample of solution that targets .NET 2.0, .NET 3.5 and Silverlight 2.0.

I'll be updating this post with additional details, if there is interest or any questions.

PS: I'm planning to add some helpers to Lokad.Shared.dll in Silverlight profile to aid in managing .NET code that targets multiple frameworks. Just need to come up with Silverlight version of strongly-typed reflection for discovering variable/argument names.

« Performance Improvements of ReSharper 4.5 (R#) | Main | Strongly Typed Reflection in Lokad Shared »

References (2)

References allow you to track sources for this article, as well as articles that were written in response to this article.