Visual Studio : Building Multiple Toolsets in a Single Solution

When you develop a framework or a library that is consumed by more than one client chances are that you will need to build binaries for different Visual Studio versions: one client will use (for example) Visual Studio 2013 and another will use maybe Visual Studio 2015.

One approach is to have a different solution for every Visual Studio version. That can be tedious to work with - when you add a source file in one solution you have to remember to add it in the other solutions too. Also you always have to open and work with every Visual Studio IDE that you need to support. Trust me this approach is no fun!

So what can you do about it? Well... you can start using a very nice Visual Studio feature called native multi-targeting (read more about this here).

In this post I will write how I use this feature with a custom naming scheme for project configuration. In my example project I will use Visual Studio 2017, Visual Studio 2015 and Visual Studio 2013. My IDE of choice will be Visual Studio 2017. The steps to add support for multi targeting are:

  1. start with a new solution or load an existing solution
  2. prefix the Debug and Release configuration names with VS2017_, VS2015_, VS2013_, VS2012_, VS2010_ or VS2008_ depending  on your default Visual Studio version - for me it will be VS2017_; after this the configuration will be named VS2017_Debug and VS2017_Release
  3. add new configuration from existing ones for every toolset you want to support - in my case I will create VS2013_Debug and VS2015_Debug from VS2017_Debug and VS2013_Release from VS2017
  4. change the platform toolset version for every new configuration added
  5. done!

If you want to have a more structured output for you solution without manually editing the output and intermediate paths for every configuration you can use a custom property sheet:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ImportGroup Label="PropertySheets" />
  <PropertyGroup Label="UserMacros">

    <MVS_Version Condition="'$(PlatformToolset)' == 'v90'">VS2008</MVS_Version>
    <MVS_Version Condition="'$(PlatformToolset)' == 'v90_xp'">VS2008XP</MVS_Version>
    <MVS_Version Condition="'$(PlatformToolset)' == 'v100'">VS2010</MVS_Version>
    <MVS_Version Condition="'$(PlatformToolset)' == 'v100_xp'">VS2010XP</MVS_Version>
    <MVS_Version Condition="'$(PlatformToolset)' == 'v110'">VS2012</MVS_Version>
    <MVS_Version Condition="'$(PlatformToolset)' == 'v110_xp'">VS2012XP</MVS_Version>
    <MVS_Version Condition="'$(PlatformToolset)' == 'v120'">VS2013</MVS_Version>
    <MVS_Version Condition="'$(PlatformToolset)' == 'v120_xp'">VS2013XP</MVS_Version>
    <MVS_Version Condition="'$(PlatformToolset)' == 'v140'">VS2015</MVS_Version>
    <MVS_Version Condition="'$(PlatformToolset)' == 'v140_xp'">VS2015XP</MVS_Version>
    <MVS_Version Condition="'$(PlatformToolset)' == 'v141'">VS2017</MVS_Version>
    <MVS_Version Condition="'$(PlatformToolset)' == 'v141_xp'">VS2017XP</MVS_Version>

    <MVS_Configuration Condition="$([System.Text.RegularExpressions.Regex]::IsMatch($(Configuration), 'VS2008_(.*)'))">$(Configuration.Replace('VS2008_', ''))</MVS_Configuration>
    <MVS_Configuration Condition="$([System.Text.RegularExpressions.Regex]::IsMatch($(Configuration), 'VS2008XP_(.*)'))">$(Configuration.Replace('VS2008XP_', ''))</MVS_Configuration>
    <MVS_Configuration Condition="$([System.Text.RegularExpressions.Regex]::IsMatch($(Configuration), 'VS2010_(.*)'))">$(Configuration.Replace('VS2010_', ''))</MVS_Configuration>
    <MVS_Configuration Condition="$([System.Text.RegularExpressions.Regex]::IsMatch($(Configuration), 'VS2010XP_(.*)'))">$(Configuration.Replace('VS2010XP_', ''))</MVS_Configuration>
    <MVS_Configuration Condition="$([System.Text.RegularExpressions.Regex]::IsMatch($(Configuration), 'VS2012_(.*)'))">$(Configuration.Replace('VS2012_', ''))</MVS_Configuration>
    <MVS_Configuration Condition="$([System.Text.RegularExpressions.Regex]::IsMatch($(Configuration), 'VS2012XP_(.*)'))">$(Configuration.Replace('VS2012XP_', ''))</MVS_Configuration>
    <MVS_Configuration Condition="$([System.Text.RegularExpressions.Regex]::IsMatch($(Configuration), 'VS2013_(.*)'))">$(Configuration.Replace('VS2013_', ''))</MVS_Configuration>
    <MVS_Configuration Condition="$([System.Text.RegularExpressions.Regex]::IsMatch($(Configuration), 'VS2013XP_(.*)'))">$(Configuration.Replace('VS2013XP_', ''))</MVS_Configuration>
    <MVS_Configuration Condition="$([System.Text.RegularExpressions.Regex]::IsMatch($(Configuration), 'VS2015_(.*)'))">$(Configuration.Replace('VS2015_', ''))</MVS_Configuration>
    <MVS_Configuration Condition="$([System.Text.RegularExpressions.Regex]::IsMatch($(Configuration), 'VS2015XP_(.*)'))">$(Configuration.Replace('VS2015XP_', ''))</MVS_Configuration>
    <MVS_Configuration Condition="$([System.Text.RegularExpressions.Regex]::IsMatch($(Configuration), 'VS2017_(.*)'))">$(Configuration.Replace('VS2017_', ''))</MVS_Configuration>
    <MVS_Configuration Condition="$([System.Text.RegularExpressions.Regex]::IsMatch($(Configuration), 'VS2017XP_(.*)'))">$(Configuration.Replace('VS2017XP_', ''))</MVS_Configuration>

  </PropertyGroup>
  <PropertyGroup />
  <ItemDefinitionGroup />
  <ItemGroup>
    <BuildMacro Include="MVS_Version">
      <Value>$(MVS_Version)</Value>
      <EnvironmentVariable>true</EnvironmentVariable>
    </BuildMacro>
    <BuildMacro Include="MVS_Configuration">
      <Value>$(MVS_Configuration)</Value>
      <EnvironmentVariable>true</EnvironmentVariable>
    </BuildMacro>
  </ItemGroup>
</Project>

Previous Post