From 314613d1afd069dd896aa0d5112dfbf2b82af3c3 Mon Sep 17 00:00:00 2001
From: Brad King <brad.king@kitware.com>
Date: Tue, 3 Oct 2017 14:53:34 -0400
Subject: Add infrastructure for generators to select a build tool instance

Add cache entry `CMAKE_GENERATOR_INSTANCE` to hold the instance location
persistently across re-runs of CMake in a given build tree.

For now we reject the option by default if explicitly set.  It will be
implemented on a per-generator basis.  Pass the setting into try_compile
project generation.  Add a RunCMake.GeneratorInstance test to cover
basic use cases for the option.  Verify that `CMAKE_GENERATOR_INSTANCE`
is empty by default, and that it is rejected when the generator does not
support a user setting.

Issue: #17268
---
 Help/manual/cmake-variables.7.rst                  |  1 +
 Help/release/dev/generator-instance.rst            |  7 ++++++
 Help/variable/CMAKE_GENERATOR_INSTANCE.rst         | 22 ++++++++++++++++++
 Source/cmGlobalGenerator.cxx                       | 27 ++++++++++++++++++++++
 Source/cmGlobalGenerator.h                         |  3 +++
 Source/cmMakefile.cxx                              |  1 +
 Source/cmake.cxx                                   | 27 ++++++++++++++++++++++
 Source/cmake.h                                     |  7 ++++++
 Tests/RunCMake/CMakeLists.txt                      |  3 +++
 .../GeneratorInstance/BadInstance-result.txt       |  1 +
 .../GeneratorInstance/BadInstance-stderr.txt       | 10 ++++++++
 .../GeneratorInstance/BadInstance-toolchain.cmake  |  1 +
 Tests/RunCMake/GeneratorInstance/BadInstance.cmake |  1 +
 .../BadInstanceToolchain-result.txt                |  1 +
 .../BadInstanceToolchain-stderr.txt                | 10 ++++++++
 .../GeneratorInstance/BadInstanceToolchain.cmake   |  1 +
 Tests/RunCMake/GeneratorInstance/CMakeLists.txt    |  3 +++
 .../GeneratorInstance/NoInstance-result.txt        |  1 +
 .../GeneratorInstance/NoInstance-stderr.txt        |  4 ++++
 Tests/RunCMake/GeneratorInstance/NoInstance.cmake  |  7 ++++++
 .../RunCMake/GeneratorInstance/RunCMakeTest.cmake  | 11 +++++++++
 Tests/RunCMake/RunCMake.cmake                      |  6 +++++
 22 files changed, 155 insertions(+)
 create mode 100644 Help/release/dev/generator-instance.rst
 create mode 100644 Help/variable/CMAKE_GENERATOR_INSTANCE.rst
 create mode 100644 Tests/RunCMake/GeneratorInstance/BadInstance-result.txt
 create mode 100644 Tests/RunCMake/GeneratorInstance/BadInstance-stderr.txt
 create mode 100644 Tests/RunCMake/GeneratorInstance/BadInstance-toolchain.cmake
 create mode 100644 Tests/RunCMake/GeneratorInstance/BadInstance.cmake
 create mode 100644 Tests/RunCMake/GeneratorInstance/BadInstanceToolchain-result.txt
 create mode 100644 Tests/RunCMake/GeneratorInstance/BadInstanceToolchain-stderr.txt
 create mode 100644 Tests/RunCMake/GeneratorInstance/BadInstanceToolchain.cmake
 create mode 100644 Tests/RunCMake/GeneratorInstance/CMakeLists.txt
 create mode 100644 Tests/RunCMake/GeneratorInstance/NoInstance-result.txt
 create mode 100644 Tests/RunCMake/GeneratorInstance/NoInstance-stderr.txt
 create mode 100644 Tests/RunCMake/GeneratorInstance/NoInstance.cmake
 create mode 100644 Tests/RunCMake/GeneratorInstance/RunCMakeTest.cmake

diff --git a/Help/manual/cmake-variables.7.rst b/Help/manual/cmake-variables.7.rst
index 3880bcf..b37d473 100644
--- a/Help/manual/cmake-variables.7.rst
+++ b/Help/manual/cmake-variables.7.rst
@@ -42,6 +42,7 @@ Variables that Provide Information
    /variable/CMAKE_FIND_PACKAGE_SORT_DIRECTION
    /variable/CMAKE_FIND_PACKAGE_SORT_ORDER
    /variable/CMAKE_GENERATOR
+   /variable/CMAKE_GENERATOR_INSTANCE
    /variable/CMAKE_GENERATOR_PLATFORM
    /variable/CMAKE_GENERATOR_TOOLSET
    /variable/CMAKE_HOME_DIRECTORY
diff --git a/Help/release/dev/generator-instance.rst b/Help/release/dev/generator-instance.rst
new file mode 100644
index 0000000..5e92f7d
--- /dev/null
+++ b/Help/release/dev/generator-instance.rst
@@ -0,0 +1,7 @@
+generator-instance
+------------------
+
+* A :variable:`CMAKE_GENERATOR_INSTANCE` variable was introduced
+  to hold the selected instance of the generator's corresponding
+  native tools if multiple are available.  Currently no generators
+  actually use this, but the infrastructure is in place.
diff --git a/Help/variable/CMAKE_GENERATOR_INSTANCE.rst b/Help/variable/CMAKE_GENERATOR_INSTANCE.rst
new file mode 100644
index 0000000..7c2c280
--- /dev/null
+++ b/Help/variable/CMAKE_GENERATOR_INSTANCE.rst
@@ -0,0 +1,22 @@
+CMAKE_GENERATOR_INSTANCE
+------------------------
+
+Generator-specific instance specification provided by user.
+
+Some CMake generators support selection of an instance of the native build
+system when multiple instances are available.  If the user specifies an
+instance (e.g. by setting this cache entry), or after a default instance is
+chosen when a build tree is first configured, the value will be available in
+this variable.
+
+The value of this variable should never be modified by project code.
+A toolchain file specified by the :variable:`CMAKE_TOOLCHAIN_FILE`
+variable may initialize ``CMAKE_GENERATOR_INSTANCE`` as a cache entry.
+Once a given build tree has been initialized with a particular value
+for this variable, changing the value has undefined behavior.
+
+Instance specification is supported only on specific generators:
+
+* None
+
+See native build system documentation for allowed instance values.
diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx
index 38669c9..e6d389e 100644
--- a/Source/cmGlobalGenerator.cxx
+++ b/Source/cmGlobalGenerator.cxx
@@ -111,6 +111,26 @@ cmGlobalGenerator::~cmGlobalGenerator()
   delete this->ExtraGenerator;
 }
 
+bool cmGlobalGenerator::SetGeneratorInstance(std::string const& i,
+                                             cmMakefile* mf)
+{
+  if (i.empty()) {
+    return true;
+  }
+
+  std::ostringstream e;
+  /* clang-format off */
+  e <<
+    "Generator\n"
+    "  " << this->GetName() << "\n"
+    "does not support instance specification, but instance\n"
+    "  " << i << "\n"
+    "was specified.";
+  /* clang-format on */
+  mf->IssueMessage(cmake::FATAL_ERROR, e.str());
+  return false;
+}
+
 bool cmGlobalGenerator::SetGeneratorPlatform(std::string const& p,
                                              cmMakefile* mf)
 {
@@ -491,6 +511,13 @@ void cmGlobalGenerator::EnableLanguage(
   }
 
   if (readCMakeSystem) {
+    // Tell the generator about the instance, if any.
+    std::string instance = mf->GetSafeDefinition("CMAKE_GENERATOR_INSTANCE");
+    if (!this->SetGeneratorInstance(instance, mf)) {
+      cmSystemTools::SetFatalErrorOccured();
+      return;
+    }
+
     // Find the native build tool for this generator.
     if (!this->FindMakeProgram(mf)) {
       return;
diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h
index 04e9dc1..abbc001 100644
--- a/Source/cmGlobalGenerator.h
+++ b/Source/cmGlobalGenerator.h
@@ -70,6 +70,9 @@ public:
   /** Tell the generator about the target system.  */
   virtual bool SetSystemName(std::string const&, cmMakefile*) { return true; }
 
+  /** Set the generator-specific instance.  Returns true if supported.  */
+  virtual bool SetGeneratorInstance(std::string const& i, cmMakefile* mf);
+
   /** Set the generator-specific platform name.  Returns true if platform
       is supported and false otherwise.  */
   virtual bool SetGeneratorPlatform(std::string const& p, cmMakefile* mf);
diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx
index 5643c97..b2641d2 100644
--- a/Source/cmMakefile.cxx
+++ b/Source/cmMakefile.cxx
@@ -3186,6 +3186,7 @@ int cmMakefile::TryCompile(const std::string& srcdir,
   // do a configure
   cm.SetHomeDirectory(srcdir);
   cm.SetHomeOutputDirectory(bindir);
+  cm.SetGeneratorInstance(this->GetSafeDefinition("CMAKE_GENERATOR_INSTANCE"));
   cm.SetGeneratorPlatform(this->GetSafeDefinition("CMAKE_GENERATOR_PLATFORM"));
   cm.SetGeneratorToolset(this->GetSafeDefinition("CMAKE_GENERATOR_TOOLSET"));
   cm.LoadCache();
diff --git a/Source/cmake.cxx b/Source/cmake.cxx
index d7ed772..cd714c6 100644
--- a/Source/cmake.cxx
+++ b/Source/cmake.cxx
@@ -1326,6 +1326,25 @@ int cmake::ActualConfigure()
                         cmStateEnums::INTERNAL);
   }
 
+  if (const char* instance =
+        this->State->GetInitializedCacheValue("CMAKE_GENERATOR_INSTANCE")) {
+    if (!this->GeneratorInstance.empty() &&
+        this->GeneratorInstance != instance) {
+      std::string message = "Error: generator instance: ";
+      message += this->GeneratorInstance;
+      message += "\nDoes not match the instance used previously: ";
+      message += instance;
+      message += "\nEither remove the CMakeCache.txt file and CMakeFiles "
+                 "directory or choose a different binary directory.";
+      cmSystemTools::Error(message.c_str());
+      return -2;
+    }
+  } else {
+    this->AddCacheEntry(
+      "CMAKE_GENERATOR_INSTANCE", this->GeneratorInstance.c_str(),
+      "Generator instance identifier.", cmStateEnums::INTERNAL);
+  }
+
   if (const char* platformName =
         this->State->GetInitializedCacheValue("CMAKE_GENERATOR_PLATFORM")) {
     if (!this->GeneratorPlatform.empty() &&
@@ -2360,6 +2379,14 @@ int cmake::Build(const std::string& dir, const std::string& target,
               << "\"\n";
     return 1;
   }
+  const char* cachedGeneratorInstance =
+    this->State->GetCacheEntryValue("CMAKE_GENERATOR_INSTANCE");
+  if (cachedGeneratorInstance) {
+    cmMakefile mf(gen.get(), this->GetCurrentSnapshot());
+    if (!gen->SetGeneratorInstance(cachedGeneratorInstance, &mf)) {
+      return 1;
+    }
+  }
   std::string output;
   std::string projName;
   const char* cachedProjectName =
diff --git a/Source/cmake.h b/Source/cmake.h
index ed3ebe0..5c5a90d 100644
--- a/Source/cmake.h
+++ b/Source/cmake.h
@@ -203,6 +203,12 @@ public:
   ///! Get the names of the current registered generators
   void GetRegisteredGenerators(std::vector<GeneratorInfo>& generators) const;
 
+  ///! Set the name of the selected generator-specific instance.
+  void SetGeneratorInstance(std::string const& instance)
+  {
+    this->GeneratorInstance = instance;
+  }
+
   ///! Set the name of the selected generator-specific platform.
   void SetGeneratorPlatform(std::string const& ts)
   {
@@ -431,6 +437,7 @@ protected:
 
   cmGlobalGenerator* GlobalGenerator;
   std::map<std::string, DiagLevel> DiagLevels;
+  std::string GeneratorInstance;
   std::string GeneratorPlatform;
   std::string GeneratorToolset;
 
diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt
index 7eafa06..b70aeb9 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/Tests/RunCMake/CMakeLists.txt
@@ -15,6 +15,7 @@ macro(add_RunCMake_test test)
   add_test(NAME RunCMake.${test} COMMAND ${CMAKE_CMAKE_COMMAND}
     -DCMAKE_MODULE_PATH=${CMAKE_CURRENT_SOURCE_DIR}
     -DRunCMake_GENERATOR=${CMAKE_GENERATOR}
+    -DRunCMake_GENERATOR_INSTANCE=${CMAKE_GENERATOR_INSTANCE}
     -DRunCMake_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM}
     -DRunCMake_GENERATOR_TOOLSET=${CMAKE_GENERATOR_TOOLSET}
     -DRunCMake_MAKE_PROGRAM=${CMake_TEST_EXPLICIT_MAKE_PROGRAM}
@@ -47,6 +48,7 @@ function(add_RunCMake_test_group test types)
         -DTEST_TYPE=${type}
         -DCMAKE_MODULE_PATH=${CMAKE_CURRENT_SOURCE_DIR}
         -DRunCMake_GENERATOR=${CMAKE_GENERATOR}
+        -DRunCMake_GENERATOR_INSTANCE=${CMAKE_GENERATOR_INSTANCE}
         -DRunCMake_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM}
         -DRunCMake_GENERATOR_TOOLSET=${CMAKE_GENERATOR_TOOLSET}
         -DRunCMake_MAKE_PROGRAM=${CMake_TEST_EXPLICIT_MAKE_PROGRAM}
@@ -146,6 +148,7 @@ if(NOT CMAKE_C_COMPILER_ID MATCHES "Watcom")
   add_RunCMake_test(GenerateExportHeader)
 endif()
 add_RunCMake_test(GeneratorExpression)
+add_RunCMake_test(GeneratorInstance)
 add_RunCMake_test(GeneratorPlatform)
 add_RunCMake_test(GeneratorToolset)
 add_RunCMake_test(GetPrerequisites)
diff --git a/Tests/RunCMake/GeneratorInstance/BadInstance-result.txt b/Tests/RunCMake/GeneratorInstance/BadInstance-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/BadInstance-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GeneratorInstance/BadInstance-stderr.txt b/Tests/RunCMake/GeneratorInstance/BadInstance-stderr.txt
new file mode 100644
index 0000000..5d01c4f
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/BadInstance-stderr.txt
@@ -0,0 +1,10 @@
+CMake Error at CMakeLists.txt:[0-9]+ \(project\):
+  Generator
+
+    .*
+
+  does not support instance specification, but instance
+
+    Bad Instance
+
+  was specified.$
diff --git a/Tests/RunCMake/GeneratorInstance/BadInstance-toolchain.cmake b/Tests/RunCMake/GeneratorInstance/BadInstance-toolchain.cmake
new file mode 100644
index 0000000..1d99259
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/BadInstance-toolchain.cmake
@@ -0,0 +1 @@
+set(CMAKE_GENERATOR_INSTANCE "Bad Instance")
diff --git a/Tests/RunCMake/GeneratorInstance/BadInstance.cmake b/Tests/RunCMake/GeneratorInstance/BadInstance.cmake
new file mode 100644
index 0000000..2fc38e5
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/BadInstance.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached!")
diff --git a/Tests/RunCMake/GeneratorInstance/BadInstanceToolchain-result.txt b/Tests/RunCMake/GeneratorInstance/BadInstanceToolchain-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/BadInstanceToolchain-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GeneratorInstance/BadInstanceToolchain-stderr.txt b/Tests/RunCMake/GeneratorInstance/BadInstanceToolchain-stderr.txt
new file mode 100644
index 0000000..5d01c4f
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/BadInstanceToolchain-stderr.txt
@@ -0,0 +1,10 @@
+CMake Error at CMakeLists.txt:[0-9]+ \(project\):
+  Generator
+
+    .*
+
+  does not support instance specification, but instance
+
+    Bad Instance
+
+  was specified.$
diff --git a/Tests/RunCMake/GeneratorInstance/BadInstanceToolchain.cmake b/Tests/RunCMake/GeneratorInstance/BadInstanceToolchain.cmake
new file mode 100644
index 0000000..2fc38e5
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/BadInstanceToolchain.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached!")
diff --git a/Tests/RunCMake/GeneratorInstance/CMakeLists.txt b/Tests/RunCMake/GeneratorInstance/CMakeLists.txt
new file mode 100644
index 0000000..d3137f6
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/CMakeLists.txt
@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.9)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/GeneratorInstance/NoInstance-result.txt b/Tests/RunCMake/GeneratorInstance/NoInstance-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/NoInstance-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GeneratorInstance/NoInstance-stderr.txt b/Tests/RunCMake/GeneratorInstance/NoInstance-stderr.txt
new file mode 100644
index 0000000..e7b52fd
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/NoInstance-stderr.txt
@@ -0,0 +1,4 @@
+CMake Error at NoInstance.cmake:2 \(message\):
+  CMAKE_GENERATOR_INSTANCE is empty as expected.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
diff --git a/Tests/RunCMake/GeneratorInstance/NoInstance.cmake b/Tests/RunCMake/GeneratorInstance/NoInstance.cmake
new file mode 100644
index 0000000..2e6782e
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/NoInstance.cmake
@@ -0,0 +1,7 @@
+if("x${CMAKE_GENERATOR_INSTANCE}" STREQUAL "x")
+  message(FATAL_ERROR "CMAKE_GENERATOR_INSTANCE is empty as expected.")
+else()
+  message(FATAL_ERROR
+    "CMAKE_GENERATOR_INSTANCE is \"${CMAKE_GENERATOR_INSTANCE}\" "
+    "but should be empty!")
+endif()
diff --git a/Tests/RunCMake/GeneratorInstance/RunCMakeTest.cmake b/Tests/RunCMake/GeneratorInstance/RunCMakeTest.cmake
new file mode 100644
index 0000000..f66da10
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/RunCMakeTest.cmake
@@ -0,0 +1,11 @@
+include(RunCMake)
+
+set(RunCMake_GENERATOR_INSTANCE "")
+run_cmake(NoInstance)
+
+set(RunCMake_GENERATOR_INSTANCE "Bad Instance")
+run_cmake(BadInstance)
+
+set(RunCMake_TEST_OPTIONS -DCMAKE_TOOLCHAIN_FILE=${RunCMake_SOURCE_DIR}/BadInstance-toolchain.cmake)
+run_cmake(BadInstanceToolchain)
+unset(RunCMake_TEST_OPTIONS)
diff --git a/Tests/RunCMake/RunCMake.cmake b/Tests/RunCMake/RunCMake.cmake
index 4fd816e..e688830 100644
--- a/Tests/RunCMake/RunCMake.cmake
+++ b/Tests/RunCMake/RunCMake.cmake
@@ -79,11 +79,17 @@ function(run_cmake test)
       ${maybe_timeout}
       )
   else()
+    if(RunCMake_GENERATOR_INSTANCE)
+      set(_D_CMAKE_GENERATOR_INSTANCE "-DCMAKE_GENERATOR_INSTANCE=${RunCMake_GENERATOR_INSTANCE}")
+    else()
+      set(_D_CMAKE_GENERATOR_INSTANCE "")
+    endif()
     execute_process(
       COMMAND ${CMAKE_COMMAND} "${RunCMake_TEST_SOURCE_DIR}"
                 -G "${RunCMake_GENERATOR}"
                 -A "${RunCMake_GENERATOR_PLATFORM}"
                 -T "${RunCMake_GENERATOR_TOOLSET}"
+                ${_D_CMAKE_GENERATOR_INSTANCE}
                 -DRunCMake_TEST=${test}
                 --no-warn-unused-cli
                 ${RunCMake_TEST_OPTIONS}
-- 
cgit v0.12