From 5f1316841944198037d6463d3ddd12efcfae45b3 Mon Sep 17 00:00:00 2001
From: Basil Fierz <basil.fierz@hotmail.com>
Date: Sat, 19 May 2018 13:23:21 +0200
Subject: VS: Add option to select the version of the toolset used by VS 2017

Add new `version=` parameter in the toolset setting to select the
version.  Add variable `CMAKE_VS_PLATFORM_TOOLSET_VERSION` to hold the
version, if one is set (blank indicates default).

Fixes: #17549
---
 Help/manual/cmake-variables.7.rst                  |  1 +
 Help/release/dev/vs-toolset-version.rst            |  7 ++
 Help/variable/CMAKE_GENERATOR_TOOLSET.rst          |  5 ++
 .../variable/CMAKE_VS_PLATFORM_TOOLSET_VERSION.rst | 11 +++
 Modules/CMakeDetermineCompilerId.cmake             |  4 +
 Modules/CompilerId/VS-10.vcxproj.in                |  1 +
 Source/cmGlobalVisualStudio10Generator.cxx         | 85 ++++++++++++++++++++++
 Source/cmGlobalVisualStudio10Generator.h           |  7 ++
 Source/cmGlobalVisualStudio15Generator.cxx         | 19 +++++
 Source/cmGlobalVisualStudio15Generator.h           |  2 +
 Source/cmVisualStudio10TargetGenerator.cxx         |  5 ++
 .../GeneratorToolset/BadToolsetVersion-result.txt  |  1 +
 .../GeneratorToolset/BadToolsetVersion-stderr.txt  | 10 +++
 .../GeneratorToolset/BadToolsetVersion.cmake       |  1 +
 .../BadToolsetVersionTwice-result.txt              |  1 +
 .../BadToolsetVersionTwice-stderr.txt              | 10 +++
 .../GeneratorToolset/BadToolsetVersionTwice.cmake  |  1 +
 Tests/RunCMake/GeneratorToolset/RunCMakeTest.cmake | 12 +++
 .../TestToolsetVersionBoth-stdout.txt              |  2 +
 .../GeneratorToolset/TestToolsetVersionBoth.cmake  |  2 +
 .../TestToolsetVersionOnly-stdout.txt              |  2 +
 .../GeneratorToolset/TestToolsetVersionOnly.cmake  |  2 +
 22 files changed, 191 insertions(+)
 create mode 100644 Help/release/dev/vs-toolset-version.rst
 create mode 100644 Help/variable/CMAKE_VS_PLATFORM_TOOLSET_VERSION.rst
 create mode 100644 Tests/RunCMake/GeneratorToolset/BadToolsetVersion-result.txt
 create mode 100644 Tests/RunCMake/GeneratorToolset/BadToolsetVersion-stderr.txt
 create mode 100644 Tests/RunCMake/GeneratorToolset/BadToolsetVersion.cmake
 create mode 100644 Tests/RunCMake/GeneratorToolset/BadToolsetVersionTwice-result.txt
 create mode 100644 Tests/RunCMake/GeneratorToolset/BadToolsetVersionTwice-stderr.txt
 create mode 100644 Tests/RunCMake/GeneratorToolset/BadToolsetVersionTwice.cmake
 create mode 100644 Tests/RunCMake/GeneratorToolset/TestToolsetVersionBoth-stdout.txt
 create mode 100644 Tests/RunCMake/GeneratorToolset/TestToolsetVersionBoth.cmake
 create mode 100644 Tests/RunCMake/GeneratorToolset/TestToolsetVersionOnly-stdout.txt
 create mode 100644 Tests/RunCMake/GeneratorToolset/TestToolsetVersionOnly.cmake

diff --git a/Help/manual/cmake-variables.7.rst b/Help/manual/cmake-variables.7.rst
index 0f8502c..edfff6c 100644
--- a/Help/manual/cmake-variables.7.rst
+++ b/Help/manual/cmake-variables.7.rst
@@ -100,6 +100,7 @@ Variables that Provide Information
    /variable/CMAKE_VS_PLATFORM_TOOLSET
    /variable/CMAKE_VS_PLATFORM_TOOLSET_CUDA
    /variable/CMAKE_VS_PLATFORM_TOOLSET_HOST_ARCHITECTURE
+   /variable/CMAKE_VS_PLATFORM_TOOLSET_VERSION
    /variable/CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION
    /variable/CMAKE_XCODE_GENERATE_SCHEME
    /variable/CMAKE_XCODE_PLATFORM_TOOLSET
diff --git a/Help/release/dev/vs-toolset-version.rst b/Help/release/dev/vs-toolset-version.rst
new file mode 100644
index 0000000..239917e
--- /dev/null
+++ b/Help/release/dev/vs-toolset-version.rst
@@ -0,0 +1,7 @@
+vs-toolset-version
+------------------
+
+* The :ref:`Visual Studio Generators` for VS 2017 learned to support a
+  ``version=14.##`` option in the :variable:`CMAKE_GENERATOR_TOOLSET`
+  value (e.g. via the :manual:`cmake(1)` ``-T`` option) to specify a
+  toolset version number.
diff --git a/Help/variable/CMAKE_GENERATOR_TOOLSET.rst b/Help/variable/CMAKE_GENERATOR_TOOLSET.rst
index 11c37d7..3220244 100644
--- a/Help/variable/CMAKE_GENERATOR_TOOLSET.rst
+++ b/Help/variable/CMAKE_GENERATOR_TOOLSET.rst
@@ -48,3 +48,8 @@ Supported pairs are:
   Supported by VS 2013 and above.
   See the :variable:`CMAKE_VS_PLATFORM_TOOLSET_HOST_ARCHITECTURE`
   variable.
+
+``version=<version>``
+  Specify the toolset version to use.  Supported by VS 2017
+  and above with the specified toolset installed.
+  See the :variable:`CMAKE_VS_PLATFORM_TOOLSET_VERSION` variable.
diff --git a/Help/variable/CMAKE_VS_PLATFORM_TOOLSET_VERSION.rst b/Help/variable/CMAKE_VS_PLATFORM_TOOLSET_VERSION.rst
new file mode 100644
index 0000000..4d9b978
--- /dev/null
+++ b/Help/variable/CMAKE_VS_PLATFORM_TOOLSET_VERSION.rst
@@ -0,0 +1,11 @@
+CMAKE_VS_PLATFORM_TOOLSET_VERSION
+---------------------------------
+
+Visual Studio Platform Toolset version.
+
+The :ref:`Visual Studio Generators` for VS 2017 and above allow to
+select minor versions of the same toolset. The toolset version number
+may be specified by a field in :variable:`CMAKE_GENERATOR_TOOLSET` of
+the form ``version=14.11``. If none is specified CMake will choose a default
+toolset. The value may be empty if no minor version was selected and the
+default is used.
diff --git a/Modules/CMakeDetermineCompilerId.cmake b/Modules/CMakeDetermineCompilerId.cmake
index ea5465a..1628203 100644
--- a/Modules/CMakeDetermineCompilerId.cmake
+++ b/Modules/CMakeDetermineCompilerId.cmake
@@ -213,9 +213,13 @@ Id flags: ${testflags} ${CMAKE_${lang}_COMPILER_ID_FLAGS_ALWAYS}
         if(CMAKE_VS_PLATFORM_TOOLSET MATCHES "Intel")
           set(id_cl icl.exe)
         endif()
+        if(CMAKE_VS_PLATFORM_TOOLSET_VERSION)
+          set(id_toolset_version_props "<Import Project=\"${CMAKE_GENERATOR_INSTANCE}\\VC\\Auxiliary\\Build\\${CMAKE_VS_PLATFORM_TOOLSET_VERSION}\\Microsoft.VCToolsVersion.${CMAKE_VS_PLATFORM_TOOLSET_VERSION}.props\" />")
+        endif()
       endif()
     else()
       set(id_toolset "")
+      set(id_toolset_version_props "")
     endif()
     if(CMAKE_VS_PLATFORM_TOOLSET_HOST_ARCHITECTURE)
       set(id_PreferredToolArchitecture "<PreferredToolArchitecture>${CMAKE_VS_PLATFORM_TOOLSET_HOST_ARCHITECTURE}</PreferredToolArchitecture>")
diff --git a/Modules/CompilerId/VS-10.vcxproj.in b/Modules/CompilerId/VS-10.vcxproj.in
index 6b9b361..32c4ffc 100644
--- a/Modules/CompilerId/VS-10.vcxproj.in
+++ b/Modules/CompilerId/VS-10.vcxproj.in
@@ -15,6 +15,7 @@
     @id_WindowsTargetPlatformVersion@
     @id_WindowsSDKDesktopARMSupport@
   </PropertyGroup>
+  @id_toolset_version_props@
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
   <PropertyGroup>
     @id_PreferredToolArchitecture@
diff --git a/Source/cmGlobalVisualStudio10Generator.cxx b/Source/cmGlobalVisualStudio10Generator.cxx
index 205e0d0..eafddd6 100644
--- a/Source/cmGlobalVisualStudio10Generator.cxx
+++ b/Source/cmGlobalVisualStudio10Generator.cxx
@@ -231,9 +231,66 @@ bool cmGlobalVisualStudio10Generator::SetGeneratorToolset(
     }
   }
 
+  if (!this->GeneratorToolsetVersion.empty() &&
+      this->GeneratorToolsetVersion != "Test Toolset Version") {
+    // If a specific minor version of the toolset was requested, verify that it
+    // is compatible to the major version and that is exists on disk.
+    // If not clear the value.
+    std::string version = this->GeneratorToolsetVersion;
+    cmsys::RegularExpression regex("[0-9][0-9]\\.[0-9][0-9]");
+    if (regex.find(version)) {
+      version = "v" + version.erase(2, 1);
+    } else {
+      // Version not recognized. Clear it.
+      version.clear();
+    }
+
+    if (version.find(this->GetPlatformToolsetString()) != 0) {
+      std::ostringstream e;
+      /* clang-format off */
+      e <<
+        "Generator\n"
+        "  " << this->GetName() << "\n"
+        "given toolset and version specification\n"
+        "  " << this->GetPlatformToolsetString() << ",version=" <<
+        this->GeneratorToolsetVersion << "\n"
+        "contains an invalid version specification."
+      ;
+      /* clang-format on */
+      mf->IssueMessage(cmake::FATAL_ERROR, e.str());
+
+      // Clear the configured tool-set
+      this->GeneratorToolsetVersion.clear();
+    }
+
+    std::string const toolsetPath = this->GetAuxiliaryToolset();
+    if (!toolsetPath.empty() && !cmSystemTools::FileExists(toolsetPath)) {
+
+      std::ostringstream e;
+      /* clang-format off */
+      e <<
+        "Generator\n"
+        "  " << this->GetName() << "\n"
+        "given toolset and version specification\n"
+        "  " << this->GetPlatformToolsetString() << ",version=" <<
+        this->GeneratorToolsetVersion << "\n"
+        "does not seem to be installed at\n" <<
+        "  " << toolsetPath;
+      ;
+      /* clang-format on */
+      mf->IssueMessage(cmake::FATAL_ERROR, e.str());
+
+      // Clear the configured tool-set
+      this->GeneratorToolsetVersion.clear();
+    }
+  }
+
   if (const char* toolset = this->GetPlatformToolset()) {
     mf->AddDefinition("CMAKE_VS_PLATFORM_TOOLSET", toolset);
   }
+  if (const char* version = this->GetPlatformToolsetVersion()) {
+    mf->AddDefinition("CMAKE_VS_PLATFORM_TOOLSET_VERSION", version);
+  }
   if (const char* hostArch = this->GetPlatformToolsetHostArchitecture()) {
     mf->AddDefinition("CMAKE_VS_PLATFORM_TOOLSET_HOST_ARCHITECTURE", hostArch);
   }
@@ -319,6 +376,10 @@ bool cmGlobalVisualStudio10Generator::ProcessGeneratorToolsetField(
     this->GeneratorToolsetCuda = value;
     return true;
   }
+  if (key == "version") {
+    this->GeneratorToolsetVersion = value;
+    return true;
+  }
   return false;
 }
 
@@ -512,6 +573,25 @@ std::string const& cmGlobalVisualStudio10Generator::GetPlatformToolsetString()
   return empty;
 }
 
+const char* cmGlobalVisualStudio10Generator::GetPlatformToolsetVersion() const
+{
+  std::string const& version = this->GetPlatformToolsetVersionString();
+  if (version.empty()) {
+    return nullptr;
+  }
+  return version.c_str();
+}
+
+std::string const&
+cmGlobalVisualStudio10Generator::GetPlatformToolsetVersionString() const
+{
+  if (!this->GeneratorToolsetVersion.empty()) {
+    return this->GeneratorToolsetVersion;
+  }
+  static std::string const empty;
+  return empty;
+}
+
 const char*
 cmGlobalVisualStudio10Generator::GetPlatformToolsetHostArchitecture() const
 {
@@ -535,6 +615,11 @@ cmGlobalVisualStudio10Generator::GetPlatformToolsetCudaString() const
   return this->GeneratorToolsetCuda;
 }
 
+std::string cmGlobalVisualStudio10Generator::GetAuxiliaryToolset() const
+{
+  return {};
+}
+
 bool cmGlobalVisualStudio10Generator::FindMakeProgram(cmMakefile* mf)
 {
   if (!this->cmGlobalVisualStudio8Generator::FindMakeProgram(mf)) {
diff --git a/Source/cmGlobalVisualStudio10Generator.h b/Source/cmGlobalVisualStudio10Generator.h
index f2501c2..dcdab09 100644
--- a/Source/cmGlobalVisualStudio10Generator.h
+++ b/Source/cmGlobalVisualStudio10Generator.h
@@ -54,6 +54,10 @@ public:
   const char* GetPlatformToolset() const;
   std::string const& GetPlatformToolsetString() const;
 
+  /** The toolset version.  */
+  const char* GetPlatformToolsetVersion() const;
+  std::string const& GetPlatformToolsetVersionString() const;
+
   /** The toolset host architecture name (e.g. x64 for 64-bit host tools).  */
   const char* GetPlatformToolsetHostArchitecture() const;
 
@@ -101,6 +105,8 @@ public:
   std::string Encoding() override;
   virtual const char* GetToolsVersion() { return "4.0"; }
 
+  virtual std::string GetAuxiliaryToolset() const;
+
   bool FindMakeProgram(cmMakefile* mf) override;
 
   static std::string GetInstalledNsightTegraVersion();
@@ -135,6 +141,7 @@ protected:
   std::string const& GetMSBuildCommand();
 
   std::string GeneratorToolset;
+  std::string GeneratorToolsetVersion;
   std::string GeneratorToolsetHostArchitecture;
   std::string GeneratorToolsetCuda;
   std::string DefaultPlatformToolset;
diff --git a/Source/cmGlobalVisualStudio15Generator.cxx b/Source/cmGlobalVisualStudio15Generator.cxx
index 014d93d..6af5793 100644
--- a/Source/cmGlobalVisualStudio15Generator.cxx
+++ b/Source/cmGlobalVisualStudio15Generator.cxx
@@ -158,6 +158,25 @@ bool cmGlobalVisualStudio15Generator::GetVSInstance(std::string& dir) const
   return vsSetupAPIHelper.GetVSInstanceInfo(dir);
 }
 
+std::string cmGlobalVisualStudio15Generator::GetAuxiliaryToolset() const
+{
+  const char* version = this->GetPlatformToolsetVersion();
+  if (version) {
+    std::string instancePath;
+    GetVSInstance(instancePath);
+    std::stringstream path;
+    path << instancePath;
+    path << "/VC/Auxiliary/Build/";
+    path << version;
+    path << "/Microsoft.VCToolsVersion." << version << ".props";
+
+    std::string toolsetPath = path.str();
+    cmSystemTools::ConvertToUnixSlashes(toolsetPath);
+    return toolsetPath;
+  }
+  return {};
+}
+
 bool cmGlobalVisualStudio15Generator::InitializeWindows(cmMakefile* mf)
 {
   // If the Win 8.1 SDK is installed then we can select a SDK matching
diff --git a/Source/cmGlobalVisualStudio15Generator.h b/Source/cmGlobalVisualStudio15Generator.h
index 4f4e0b9..3b9cfc7 100644
--- a/Source/cmGlobalVisualStudio15Generator.h
+++ b/Source/cmGlobalVisualStudio15Generator.h
@@ -32,6 +32,8 @@ public:
 
   bool GetVSInstance(std::string& dir) const;
 
+  std::string GetAuxiliaryToolset() const override;
+
 protected:
   bool InitializeWindows(cmMakefile* mf) override;
   bool SelectWindowsStoreToolset(std::string& toolset) const override;
diff --git a/Source/cmVisualStudio10TargetGenerator.cxx b/Source/cmVisualStudio10TargetGenerator.cxx
index f3b9c84..f16db76 100644
--- a/Source/cmVisualStudio10TargetGenerator.cxx
+++ b/Source/cmVisualStudio10TargetGenerator.cxx
@@ -557,6 +557,11 @@ void cmVisualStudio10TargetGenerator::Generate()
 
     switch (this->ProjectType) {
       case vcxproj:
+        if (this->GlobalGenerator->GetPlatformToolsetVersion()) {
+          Elem(e0, "Import")
+            .Attribute("Project",
+                       this->GlobalGenerator->GetAuxiliaryToolset());
+        }
         Elem(e0, "Import").Attribute("Project", VS10_CXX_DEFAULT_PROPS);
         break;
       case csproj:
diff --git a/Tests/RunCMake/GeneratorToolset/BadToolsetVersion-result.txt b/Tests/RunCMake/GeneratorToolset/BadToolsetVersion-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GeneratorToolset/BadToolsetVersion-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GeneratorToolset/BadToolsetVersion-stderr.txt b/Tests/RunCMake/GeneratorToolset/BadToolsetVersion-stderr.txt
new file mode 100644
index 0000000..c976f59
--- /dev/null
+++ b/Tests/RunCMake/GeneratorToolset/BadToolsetVersion-stderr.txt
@@ -0,0 +1,10 @@
+CMake Error at CMakeLists.txt:[0-9]+ \(project\):
+  Generator
+
+    .*
+
+  given toolset and version specification
+
+    Test Toolset,version=Bad Toolset Version
+
+  contains an invalid version specification.
diff --git a/Tests/RunCMake/GeneratorToolset/BadToolsetVersion.cmake b/Tests/RunCMake/GeneratorToolset/BadToolsetVersion.cmake
new file mode 100644
index 0000000..2fc38e5
--- /dev/null
+++ b/Tests/RunCMake/GeneratorToolset/BadToolsetVersion.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached!")
diff --git a/Tests/RunCMake/GeneratorToolset/BadToolsetVersionTwice-result.txt b/Tests/RunCMake/GeneratorToolset/BadToolsetVersionTwice-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GeneratorToolset/BadToolsetVersionTwice-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GeneratorToolset/BadToolsetVersionTwice-stderr.txt b/Tests/RunCMake/GeneratorToolset/BadToolsetVersionTwice-stderr.txt
new file mode 100644
index 0000000..b780dad
--- /dev/null
+++ b/Tests/RunCMake/GeneratorToolset/BadToolsetVersionTwice-stderr.txt
@@ -0,0 +1,10 @@
+CMake Error at CMakeLists.txt:[0-9]+ \(project\):
+  Generator
+
+    .*
+
+  given toolset specification
+
+    Test Toolset,version=Test Toolset Version,version=Test Toolset Version
+
+  that contains duplicate field key 'version'\.$
diff --git a/Tests/RunCMake/GeneratorToolset/BadToolsetVersionTwice.cmake b/Tests/RunCMake/GeneratorToolset/BadToolsetVersionTwice.cmake
new file mode 100644
index 0000000..2fc38e5
--- /dev/null
+++ b/Tests/RunCMake/GeneratorToolset/BadToolsetVersionTwice.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached!")
diff --git a/Tests/RunCMake/GeneratorToolset/RunCMakeTest.cmake b/Tests/RunCMake/GeneratorToolset/RunCMakeTest.cmake
index f89100e..d1738a0 100644
--- a/Tests/RunCMake/GeneratorToolset/RunCMakeTest.cmake
+++ b/Tests/RunCMake/GeneratorToolset/RunCMakeTest.cmake
@@ -25,6 +25,18 @@ if("${RunCMake_GENERATOR}" MATCHES "Visual Studio 1[01245]")
     run_cmake(BadToolsetHostArch)
     set(RunCMake_GENERATOR_TOOLSET "Test Toolset,host=x64,host=x64")
     run_cmake(BadToolsetHostArchTwice)
+    if("${RunCMake_GENERATOR}" MATCHES "Visual Studio 15")
+      set(RunCMake_GENERATOR_TOOLSET "Test Toolset,version=Test Toolset Version")
+      run_cmake(TestToolsetVersionBoth)
+      set(RunCMake_GENERATOR_TOOLSET ",version=Test Toolset Version")
+      run_cmake(TestToolsetVersionOnly)
+      set(RunCMake_GENERATOR_TOOLSET "version=Test Toolset Version")
+      run_cmake(TestToolsetVersionOnly)
+      set(RunCMake_GENERATOR_TOOLSET "Test Toolset,version=Bad Toolset Version")
+      run_cmake(BadToolsetVersion)
+      set(RunCMake_GENERATOR_TOOLSET "Test Toolset,version=Test Toolset Version,version=Test Toolset Version")
+      run_cmake(BadToolsetVersionTwice)
+    endif()
   else()
     set(RunCMake_GENERATOR_TOOLSET "Test Toolset,host=x64")
     run_cmake(BadToolsetHostArch)
diff --git a/Tests/RunCMake/GeneratorToolset/TestToolsetVersionBoth-stdout.txt b/Tests/RunCMake/GeneratorToolset/TestToolsetVersionBoth-stdout.txt
new file mode 100644
index 0000000..9b91fde
--- /dev/null
+++ b/Tests/RunCMake/GeneratorToolset/TestToolsetVersionBoth-stdout.txt
@@ -0,0 +1,2 @@
+-- CMAKE_VS_PLATFORM_TOOLSET='Test Toolset'
+-- CMAKE_VS_PLATFORM_TOOLSET_VERSION='Test Toolset Version'
diff --git a/Tests/RunCMake/GeneratorToolset/TestToolsetVersionBoth.cmake b/Tests/RunCMake/GeneratorToolset/TestToolsetVersionBoth.cmake
new file mode 100644
index 0000000..fbc75be
--- /dev/null
+++ b/Tests/RunCMake/GeneratorToolset/TestToolsetVersionBoth.cmake
@@ -0,0 +1,2 @@
+message(STATUS "CMAKE_VS_PLATFORM_TOOLSET='${CMAKE_VS_PLATFORM_TOOLSET}'")
+message(STATUS "CMAKE_VS_PLATFORM_TOOLSET_VERSION='${CMAKE_VS_PLATFORM_TOOLSET_VERSION}'")
diff --git a/Tests/RunCMake/GeneratorToolset/TestToolsetVersionOnly-stdout.txt b/Tests/RunCMake/GeneratorToolset/TestToolsetVersionOnly-stdout.txt
new file mode 100644
index 0000000..25c4163
--- /dev/null
+++ b/Tests/RunCMake/GeneratorToolset/TestToolsetVersionOnly-stdout.txt
@@ -0,0 +1,2 @@
+-- CMAKE_VS_PLATFORM_TOOLSET='v[0-9]+'
+-- CMAKE_VS_PLATFORM_TOOLSET_VERSION='Test Toolset Version'
diff --git a/Tests/RunCMake/GeneratorToolset/TestToolsetVersionOnly.cmake b/Tests/RunCMake/GeneratorToolset/TestToolsetVersionOnly.cmake
new file mode 100644
index 0000000..fbc75be
--- /dev/null
+++ b/Tests/RunCMake/GeneratorToolset/TestToolsetVersionOnly.cmake
@@ -0,0 +1,2 @@
+message(STATUS "CMAKE_VS_PLATFORM_TOOLSET='${CMAKE_VS_PLATFORM_TOOLSET}'")
+message(STATUS "CMAKE_VS_PLATFORM_TOOLSET_VERSION='${CMAKE_VS_PLATFORM_TOOLSET_VERSION}'")
-- 
cgit v0.12