From 152f9978dde5ff085db28835c797827870978e9c Mon Sep 17 00:00:00 2001
From: Brad King <brad.king@kitware.com>
Date: Fri, 29 Oct 2021 10:19:48 -0400
Subject: Help: De-duplicate VS instance selection documentation

Add a section to `CMAKE_GENERATOR_INSTANCE` for VS instance selection,
and reference it from the corresponding sections of each VS generator.
---
 Help/generator/Visual Studio 15 2017.rst   | 14 ++------------
 Help/generator/Visual Studio 16 2019.rst   | 14 ++------------
 Help/generator/Visual Studio 17 2022.rst   | 14 ++------------
 Help/variable/CMAKE_GENERATOR_INSTANCE.rst | 21 ++++++++++++++++-----
 4 files changed, 22 insertions(+), 41 deletions(-)

diff --git a/Help/generator/Visual Studio 15 2017.rst b/Help/generator/Visual Studio 15 2017.rst
index b4d6f6d..912afad 100644
--- a/Help/generator/Visual Studio 15 2017.rst	
+++ b/Help/generator/Visual Studio 15 2017.rst	
@@ -17,18 +17,8 @@ Instance Selection
 
 .. versionadded:: 3.11
 
-VS 2017 supports multiple installations on the same machine.
-The :variable:`CMAKE_GENERATOR_INSTANCE` variable may be set as a
-cache entry containing the absolute path to a Visual Studio instance.
-If the value is not specified explicitly by the user or a toolchain file,
-CMake queries the Visual Studio Installer to locate VS instances, chooses
-one, and sets the variable as a cache entry to hold the value persistently.
-
-When CMake first chooses an instance, if the ``VS150COMNTOOLS`` environment
-variable is set and points to the ``Common7/Tools`` directory within
-one of the instances, that instance will be used.  Otherwise, if more
-than one instance is installed we do not define which one is chosen
-by default.
+VS 2017 supports multiple installations on the same machine.  The
+:variable:`CMAKE_GENERATOR_INSTANCE` variable may be used to select one.
 
 Platform Selection
 ^^^^^^^^^^^^^^^^^^
diff --git a/Help/generator/Visual Studio 16 2019.rst b/Help/generator/Visual Studio 16 2019.rst
index 72399e0..6cefe6d 100644
--- a/Help/generator/Visual Studio 16 2019.rst	
+++ b/Help/generator/Visual Studio 16 2019.rst	
@@ -15,18 +15,8 @@ Powershell, Python, etc.) are not supported.
 Instance Selection
 ^^^^^^^^^^^^^^^^^^
 
-VS 2019 supports multiple installations on the same machine.
-The :variable:`CMAKE_GENERATOR_INSTANCE` variable may be set as a
-cache entry containing the absolute path to a Visual Studio instance.
-If the value is not specified explicitly by the user or a toolchain file,
-CMake queries the Visual Studio Installer to locate VS instances, chooses
-one, and sets the variable as a cache entry to hold the value persistently.
-
-When CMake first chooses an instance, if the ``VS160COMNTOOLS`` environment
-variable is set and points to the ``Common7/Tools`` directory within
-one of the instances, that instance will be used.  Otherwise, if more
-than one instance is installed we do not define which one is chosen
-by default.
+VS 2019 supports multiple installations on the same machine.  The
+:variable:`CMAKE_GENERATOR_INSTANCE` variable may be used to select one.
 
 Platform Selection
 ^^^^^^^^^^^^^^^^^^
diff --git a/Help/generator/Visual Studio 17 2022.rst b/Help/generator/Visual Studio 17 2022.rst
index b3f49f3..edf9d60 100644
--- a/Help/generator/Visual Studio 17 2022.rst	
+++ b/Help/generator/Visual Studio 17 2022.rst	
@@ -15,18 +15,8 @@ Powershell, Python, etc.) are not supported.
 Instance Selection
 ^^^^^^^^^^^^^^^^^^
 
-VS 2022 supports multiple installations on the same machine.
-The :variable:`CMAKE_GENERATOR_INSTANCE` variable may be set as a
-cache entry containing the absolute path to a Visual Studio instance.
-If the value is not specified explicitly by the user or a toolchain file,
-CMake queries the Visual Studio Installer to locate VS instances, chooses
-one, and sets the variable as a cache entry to hold the value persistently.
-
-When CMake first chooses an instance, if the ``VS170COMNTOOLS`` environment
-variable is set and points to the ``Common7/Tools`` directory within
-one of the instances, that instance will be used.  Otherwise, if more
-than one instance is installed we do not define which one is chosen
-by default.
+VS 2022 supports multiple installations on the same machine.  The
+:variable:`CMAKE_GENERATOR_INSTANCE` variable may be used to select one.
 
 Platform Selection
 ^^^^^^^^^^^^^^^^^^
diff --git a/Help/variable/CMAKE_GENERATOR_INSTANCE.rst b/Help/variable/CMAKE_GENERATOR_INSTANCE.rst
index 5858d7a..5a76f09 100644
--- a/Help/variable/CMAKE_GENERATOR_INSTANCE.rst
+++ b/Help/variable/CMAKE_GENERATOR_INSTANCE.rst
@@ -18,10 +18,21 @@ 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:
+Instance specification is supported only on specific generators.
 
-* For the :generator:`Visual Studio 15 2017` generator (and above)
-  this specifies the absolute path to the VS installation directory
-  of the selected VS instance.
+Visual Studio Instance Selection
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-See native build system documentation for allowed instance values.
+:ref:`Visual Studio Generators` support instance specification for
+Visual Studio 2017 and above.  The ``CMAKE_GENERATOR_INSTANCE`` variable
+may be set as a cache entry selecting an instance of Visual Studio
+via the absolute path to the top-level directory of the VS installation.
+
+If the value of ``CMAKE_GENERATOR_INSTANCE`` is not specified explicitly
+by the user or a toolchain file, CMake queries the Visual Studio Installer
+to locate VS instances, chooses one, and sets the variable as a cache entry
+to hold the value persistently.  If an environment variable of the form
+``VS##0COMNTOOLS``, where ``##`` the Visual Studio major version number,
+is set and points to the ``Common7/Tools`` directory within one of the
+VS instances, that instance will be used.  Otherwise, if more than one
+VS instance is installed we do not define which one is chosen by default.
-- 
cgit v0.12


From 3213e2595da3ce75af489a63f53b05c5560aa547 Mon Sep 17 00:00:00 2001
From: Brad King <brad.king@kitware.com>
Date: Wed, 20 Oct 2021 13:47:16 -0400
Subject: cmVSSetupHelper: Drop unused ullVersion field

The field has not been used since commit 3fd65f5ca6 (VS: Compare VS
instance versions as strings, 2021-06-17, v3.21.0-rc1~11^2~1).
---
 Source/cmVSSetupHelper.cxx | 7 -------
 Source/cmVSSetupHelper.h   | 1 -
 2 files changed, 8 deletions(-)

diff --git a/Source/cmVSSetupHelper.cxx b/Source/cmVSSetupHelper.cxx
index 969a2c2..1b67611 100644
--- a/Source/cmVSSetupHelper.cxx
+++ b/Source/cmVSSetupHelper.cxx
@@ -164,17 +164,11 @@ bool cmVSSetupAPIHelper::GetVSInstanceInfo(
     return false;
   }
 
-  ULONGLONG ullVersion = 0;
   SmartBSTR bstrVersion;
   if (FAILED(pInstance->GetInstallationVersion(&bstrVersion))) {
     return false;
   } else {
     vsInstanceInfo.Version = std::wstring(bstrVersion);
-    if (FAILED(setupHelper->ParseVersion(bstrVersion, &ullVersion))) {
-      vsInstanceInfo.ullVersion = 0;
-    } else {
-      vsInstanceInfo.ullVersion = ullVersion;
-    }
   }
 
   // Reboot may have been required before the installation path was created.
@@ -316,7 +310,6 @@ bool cmVSSetupAPIHelper::EnumerateAndChooseVSInstance()
     chosenInstanceInfo.Version =
       std::wstring(envVSVersion.begin(), envVSVersion.end());
     chosenInstanceInfo.VCToolsetVersion = envVSVersion;
-    chosenInstanceInfo.ullVersion = std::stoi(envVSVersion);
     chosenInstanceInfo.IsWin10SDKInstalled = true;
     chosenInstanceInfo.IsWin81SDKInstalled = !envWindowsSdkDir81.empty();
     return true;
diff --git a/Source/cmVSSetupHelper.h b/Source/cmVSSetupHelper.h
index 61a3ac7..b2ba086 100644
--- a/Source/cmVSSetupHelper.h
+++ b/Source/cmVSSetupHelper.h
@@ -88,7 +88,6 @@ struct VSInstanceInfo
   std::wstring VSInstallLocation;
   std::wstring Version;
   std::string VCToolsetVersion;
-  ULONGLONG ullVersion = 0; // A.B.C.D = (A<<48)|(B<<32)|(C<<16)|D
   bool IsWin10SDKInstalled = false;
   bool IsWin81SDKInstalled = false;
 
-- 
cgit v0.12


From f5dfc788b8bb455f7accc21a07d8a74f05651467 Mon Sep 17 00:00:00 2001
From: Brad King <brad.king@kitware.com>
Date: Wed, 20 Oct 2021 13:50:08 -0400
Subject: cmVSSetupHelper: Drop unused InstanceId field

Minimize the amount of information needed about a VS instance.
---
 Source/cmVSSetupHelper.cxx | 7 -------
 Source/cmVSSetupHelper.h   | 1 -
 2 files changed, 8 deletions(-)

diff --git a/Source/cmVSSetupHelper.cxx b/Source/cmVSSetupHelper.cxx
index 1b67611..f1b22b9 100644
--- a/Source/cmVSSetupHelper.cxx
+++ b/Source/cmVSSetupHelper.cxx
@@ -152,13 +152,6 @@ bool cmVSSetupAPIHelper::GetVSInstanceInfo(
   if (pInstance == NULL)
     return false;
 
-  SmartBSTR bstrId;
-  if (SUCCEEDED(pInstance->GetInstanceId(&bstrId))) {
-    vsInstanceInfo.InstanceId = std::wstring(bstrId);
-  } else {
-    return false;
-  }
-
   InstanceState state;
   if (FAILED(pInstance->GetState(&state))) {
     return false;
diff --git a/Source/cmVSSetupHelper.h b/Source/cmVSSetupHelper.h
index b2ba086..5a7daa2 100644
--- a/Source/cmVSSetupHelper.h
+++ b/Source/cmVSSetupHelper.h
@@ -84,7 +84,6 @@ private:
 
 struct VSInstanceInfo
 {
-  std::wstring InstanceId;
   std::wstring VSInstallLocation;
   std::wstring Version;
   std::string VCToolsetVersion;
-- 
cgit v0.12


From 006fe1e919f3d326f9a8d749b59324b5512a0d87 Mon Sep 17 00:00:00 2001
From: Brad King <brad.king@kitware.com>
Date: Thu, 21 Oct 2021 11:48:06 -0400
Subject: cmVSSetupHelper: Convert wide to narrow strings early

---
 Source/cmVSSetupHelper.cxx | 23 +++++++++++------------
 Source/cmVSSetupHelper.h   |  4 ++--
 2 files changed, 13 insertions(+), 14 deletions(-)

diff --git a/Source/cmVSSetupHelper.cxx b/Source/cmVSSetupHelper.cxx
index f1b22b9..d0932ba 100644
--- a/Source/cmVSSetupHelper.cxx
+++ b/Source/cmVSSetupHelper.cxx
@@ -54,9 +54,7 @@ const WCHAR* ComponentType = L"Component";
 
 std::string VSInstanceInfo::GetInstallLocation() const
 {
-  std::string loc = cmsys::Encoding::ToNarrow(this->VSInstallLocation);
-  cmSystemTools::ConvertToUnixSlashes(loc);
-  return loc;
+  return this->VSInstallLocation;
 }
 
 cmVSSetupAPIHelper::cmVSSetupAPIHelper(unsigned int version)
@@ -161,7 +159,8 @@ bool cmVSSetupAPIHelper::GetVSInstanceInfo(
   if (FAILED(pInstance->GetInstallationVersion(&bstrVersion))) {
     return false;
   } else {
-    vsInstanceInfo.Version = std::wstring(bstrVersion);
+    vsInstanceInfo.Version =
+      cmsys::Encoding::ToNarrow(std::wstring(bstrVersion));
   }
 
   // Reboot may have been required before the installation path was created.
@@ -170,7 +169,9 @@ bool cmVSSetupAPIHelper::GetVSInstanceInfo(
     if (FAILED(pInstance->GetInstallationPath(&bstrInstallationPath))) {
       return false;
     } else {
-      vsInstanceInfo.VSInstallLocation = std::wstring(bstrInstallationPath);
+      vsInstanceInfo.VSInstallLocation =
+        cmsys::Encoding::ToNarrow(std::wstring(bstrInstallationPath));
+      cmSystemTools::ConvertToUnixSlashes(vsInstanceInfo.VSInstallLocation);
     }
   }
 
@@ -251,7 +252,7 @@ bool cmVSSetupAPIHelper::GetVSInstanceVersion(std::string& vsInstanceVersion)
   bool isInstalled = this->EnumerateAndChooseVSInstance();
 
   if (isInstalled) {
-    vsInstanceVersion = cmsys::Encoding::ToNarrow(chosenInstanceInfo.Version);
+    vsInstanceVersion = chosenInstanceInfo.Version;
   }
 
   return isInstalled;
@@ -285,7 +286,7 @@ bool cmVSSetupAPIHelper::IsEWDKEnabled()
 bool cmVSSetupAPIHelper::EnumerateAndChooseVSInstance()
 {
   bool isVSInstanceExists = false;
-  if (chosenInstanceInfo.VSInstallLocation.compare(L"") != 0) {
+  if (chosenInstanceInfo.VSInstallLocation.compare("") != 0) {
     return true;
   }
 
@@ -298,10 +299,8 @@ bool cmVSSetupAPIHelper::EnumerateAndChooseVSInstance()
     if (envVSVersion.empty() || envVsInstallDir.empty())
       return false;
 
-    chosenInstanceInfo.VSInstallLocation =
-      std::wstring(envVsInstallDir.begin(), envVsInstallDir.end());
-    chosenInstanceInfo.Version =
-      std::wstring(envVSVersion.begin(), envVSVersion.end());
+    chosenInstanceInfo.VSInstallLocation = envVsInstallDir;
+    chosenInstanceInfo.Version = envVSVersion;
     chosenInstanceInfo.VCToolsetVersion = envVSVersion;
     chosenInstanceInfo.IsWin10SDKInstalled = true;
     chosenInstanceInfo.IsWin81SDKInstalled = !envWindowsSdkDir81.empty();
@@ -329,7 +328,7 @@ bool cmVSSetupAPIHelper::EnumerateAndChooseVSInstance()
     return false;
   }
 
-  std::wstring const wantVersion = std::to_wstring(this->Version) + L'.';
+  std::string const wantVersion = std::to_string(this->Version) + '.';
 
   SmartCOMPtr<ISetupInstance> instance;
   while (SUCCEEDED(enumInstances->Next(1, &instance, NULL)) && instance) {
diff --git a/Source/cmVSSetupHelper.h b/Source/cmVSSetupHelper.h
index 5a7daa2..ad242a5 100644
--- a/Source/cmVSSetupHelper.h
+++ b/Source/cmVSSetupHelper.h
@@ -84,8 +84,8 @@ private:
 
 struct VSInstanceInfo
 {
-  std::wstring VSInstallLocation;
-  std::wstring Version;
+  std::string VSInstallLocation;
+  std::string Version;
   std::string VCToolsetVersion;
   bool IsWin10SDKInstalled = false;
   bool IsWin81SDKInstalled = false;
-- 
cgit v0.12


From 5d1f377737a9d1d14f4925072c46201d6b7b3c30 Mon Sep 17 00:00:00 2001
From: Brad King <brad.king@kitware.com>
Date: Thu, 21 Oct 2021 11:54:19 -0400
Subject: cmVSSetupHelper: Factor out helper to load MSVC toolset version

---
 Source/cmVSSetupHelper.cxx | 42 ++++++++++++++++++++++++++----------------
 1 file changed, 26 insertions(+), 16 deletions(-)

diff --git a/Source/cmVSSetupHelper.cxx b/Source/cmVSSetupHelper.cxx
index d0932ba..b9443d6 100644
--- a/Source/cmVSSetupHelper.cxx
+++ b/Source/cmVSSetupHelper.cxx
@@ -46,12 +46,33 @@ const CLSID CLSID_SetupConfiguration = {
 /* clang-format on */
 #endif
 
+namespace {
 const WCHAR* Win10SDKComponent =
   L"Microsoft.VisualStudio.Component.Windows10SDK";
 const WCHAR* Win81SDKComponent =
   L"Microsoft.VisualStudio.Component.Windows81SDK";
 const WCHAR* ComponentType = L"Component";
 
+bool LoadVSInstanceVCToolsetVersion(VSInstanceInfo& vsInstanceInfo)
+{
+  std::string const vcRoot = vsInstanceInfo.GetInstallLocation();
+  std::string vcToolsVersionFile =
+    vcRoot + "/VC/Auxiliary/Build/Microsoft.VCToolsVersion.default.txt";
+  std::string vcToolsVersion;
+  cmsys::ifstream fin(vcToolsVersionFile.c_str());
+  if (!fin || !cmSystemTools::GetLineFromStream(fin, vcToolsVersion)) {
+    return false;
+  }
+  vcToolsVersion = cmTrimWhitespace(vcToolsVersion);
+  std::string const vcToolsDir = vcRoot + "/VC/Tools/MSVC/" + vcToolsVersion;
+  if (!cmSystemTools::FileIsDirectory(vcToolsDir)) {
+    return false;
+  }
+  vsInstanceInfo.VCToolsetVersion = vcToolsVersion;
+  return true;
+}
+}
+
 std::string VSInstanceInfo::GetInstallLocation() const
 {
   return this->VSInstallLocation;
@@ -176,21 +197,8 @@ bool cmVSSetupAPIHelper::GetVSInstanceInfo(
   }
 
   // Check if a compiler is installed with this instance.
-  {
-    std::string const vcRoot = vsInstanceInfo.GetInstallLocation();
-    std::string vcToolsVersionFile =
-      vcRoot + "/VC/Auxiliary/Build/Microsoft.VCToolsVersion.default.txt";
-    std::string vcToolsVersion;
-    cmsys::ifstream fin(vcToolsVersionFile.c_str());
-    if (!fin || !cmSystemTools::GetLineFromStream(fin, vcToolsVersion)) {
-      return false;
-    }
-    vcToolsVersion = cmTrimWhitespace(vcToolsVersion);
-    std::string const vcToolsDir = vcRoot + "/VC/Tools/MSVC/" + vcToolsVersion;
-    if (!cmSystemTools::FileIsDirectory(vcToolsDir)) {
-      return false;
-    }
-    vsInstanceInfo.VCToolsetVersion = vcToolsVersion;
+  if (!LoadVSInstanceVCToolsetVersion(vsInstanceInfo)) {
+    return false;
   }
 
   // Reboot may have been required before the product package was registered
@@ -301,7 +309,9 @@ bool cmVSSetupAPIHelper::EnumerateAndChooseVSInstance()
 
     chosenInstanceInfo.VSInstallLocation = envVsInstallDir;
     chosenInstanceInfo.Version = envVSVersion;
-    chosenInstanceInfo.VCToolsetVersion = envVSVersion;
+    if (!LoadVSInstanceVCToolsetVersion(chosenInstanceInfo)) {
+      return false;
+    }
     chosenInstanceInfo.IsWin10SDKInstalled = true;
     chosenInstanceInfo.IsWin81SDKInstalled = !envWindowsSdkDir81.empty();
     return true;
-- 
cgit v0.12


From 8e6d930e8c3fb951e3c4388cc6a9a96dedcb412b Mon Sep 17 00:00:00 2001
From: Brad King <brad.king@kitware.com>
Date: Tue, 26 Oct 2021 15:13:53 -0400
Subject: VS: Parse comma-separated fields from CMAKE_GENERATOR_INSTANCE

---
 Help/variable/CMAKE_GENERATOR_INSTANCE.rst         | 13 +++-
 Source/cmGlobalVisualStudioVersionedGenerator.cxx  | 87 +++++++++++++++++++++-
 Source/cmGlobalVisualStudioVersionedGenerator.h    |  7 ++
 .../GeneratorInstance/BadFieldNoComma-result.txt   |  1 +
 .../GeneratorInstance/BadFieldNoComma-stderr.txt   | 11 +++
 .../GeneratorInstance/BadFieldNoComma.cmake        |  1 +
 .../GeneratorInstance/BadFieldUnknown-result.txt   |  1 +
 .../GeneratorInstance/BadFieldUnknown-stderr.txt   | 11 +++
 .../GeneratorInstance/BadFieldUnknown.cmake        |  1 +
 .../RunCMake/GeneratorInstance/RunCMakeTest.cmake  |  5 ++
 10 files changed, 135 insertions(+), 3 deletions(-)
 create mode 100644 Tests/RunCMake/GeneratorInstance/BadFieldNoComma-result.txt
 create mode 100644 Tests/RunCMake/GeneratorInstance/BadFieldNoComma-stderr.txt
 create mode 100644 Tests/RunCMake/GeneratorInstance/BadFieldNoComma.cmake
 create mode 100644 Tests/RunCMake/GeneratorInstance/BadFieldUnknown-result.txt
 create mode 100644 Tests/RunCMake/GeneratorInstance/BadFieldUnknown-stderr.txt
 create mode 100644 Tests/RunCMake/GeneratorInstance/BadFieldUnknown.cmake

diff --git a/Help/variable/CMAKE_GENERATOR_INSTANCE.rst b/Help/variable/CMAKE_GENERATOR_INSTANCE.rst
index 5a76f09..57dba53 100644
--- a/Help/variable/CMAKE_GENERATOR_INSTANCE.rst
+++ b/Help/variable/CMAKE_GENERATOR_INSTANCE.rst
@@ -26,7 +26,18 @@ Visual Studio Instance Selection
 :ref:`Visual Studio Generators` support instance specification for
 Visual Studio 2017 and above.  The ``CMAKE_GENERATOR_INSTANCE`` variable
 may be set as a cache entry selecting an instance of Visual Studio
-via the absolute path to the top-level directory of the VS installation.
+via one of the following forms:
+
+* ``location``
+* ``location[,key=value]*``
+* ``key=value[,key=value]*``
+
+The ``location`` specifies the absolute path to the top-level directory
+of the VS installation.
+
+The ``key=value`` pairs form a comma-separated list of options to
+specify details of the instance selection.
+There are no supported pairs: this syntax is reserved for future use.
 
 If the value of ``CMAKE_GENERATOR_INSTANCE`` is not specified explicitly
 by the user or a toolchain file, CMake queries the Visual Studio Installer
diff --git a/Source/cmGlobalVisualStudioVersionedGenerator.cxx b/Source/cmGlobalVisualStudioVersionedGenerator.cxx
index f27b2c4..78bebac 100644
--- a/Source/cmGlobalVisualStudioVersionedGenerator.cxx
+++ b/Source/cmGlobalVisualStudioVersionedGenerator.cxx
@@ -441,8 +441,12 @@ bool cmGlobalVisualStudioVersionedGenerator::SetGeneratorInstance(
     return true;
   }
 
-  if (!i.empty()) {
-    if (!this->vsSetupAPIHelper.SetVSInstance(i)) {
+  if (!this->ParseGeneratorInstance(i, mf)) {
+    return false;
+  }
+
+  if (!this->GeneratorInstance.empty()) {
+    if (!this->vsSetupAPIHelper.SetVSInstance(this->GeneratorInstance)) {
       std::ostringstream e;
       /* clang-format off */
       e <<
@@ -485,6 +489,85 @@ bool cmGlobalVisualStudioVersionedGenerator::SetGeneratorInstance(
   return true;
 }
 
+bool cmGlobalVisualStudioVersionedGenerator::ParseGeneratorInstance(
+  std::string const& is, cmMakefile* mf)
+{
+  this->GeneratorInstance.clear();
+
+  std::vector<std::string> const fields = cmTokenize(is, ",");
+  std::vector<std::string>::const_iterator fi = fields.begin();
+  if (fi == fields.end()) {
+    return true;
+  }
+
+  // The first field may be the VS instance.
+  if (fi->find('=') == fi->npos) {
+    this->GeneratorInstance = *fi;
+    ++fi;
+  }
+
+  std::set<std::string> handled;
+
+  // The rest of the fields must be key=value pairs.
+  for (; fi != fields.end(); ++fi) {
+    std::string::size_type pos = fi->find('=');
+    if (pos == fi->npos) {
+      std::ostringstream e;
+      /* clang-format off */
+      e <<
+        "Generator\n"
+        "  " << this->GetName() << "\n"
+        "given instance specification\n"
+        "  " << is << "\n"
+        "that contains a field after the first ',' with no '='."
+        ;
+      /* clang-format on */
+      mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
+      return false;
+    }
+    std::string const key = fi->substr(0, pos);
+    std::string const value = fi->substr(pos + 1);
+    if (!handled.insert(key).second) {
+      std::ostringstream e;
+      /* clang-format off */
+      e <<
+        "Generator\n"
+        "  " << this->GetName() << "\n"
+        "given instance specification\n"
+        "  " << is << "\n"
+        "that contains duplicate field key '" << key << "'."
+        ;
+      /* clang-format on */
+      mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
+      return false;
+    }
+    if (!this->ProcessGeneratorInstanceField(key, value)) {
+      std::ostringstream e;
+      /* clang-format off */
+      e <<
+        "Generator\n"
+        "  " << this->GetName() << "\n"
+        "given instance specification\n"
+        "  " << is << "\n"
+        "that contains invalid field '" << *fi << "'."
+        ;
+      /* clang-format on */
+      mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool cmGlobalVisualStudioVersionedGenerator::ProcessGeneratorInstanceField(
+  std::string const& key, std::string const& value)
+{
+  static_cast<void>(key);
+  static_cast<void>(value);
+  return false;
+}
+
 bool cmGlobalVisualStudioVersionedGenerator::GetVSInstance(
   std::string& dir) const
 {
diff --git a/Source/cmGlobalVisualStudioVersionedGenerator.h b/Source/cmGlobalVisualStudioVersionedGenerator.h
index 2aed65b..a19e506 100644
--- a/Source/cmGlobalVisualStudioVersionedGenerator.h
+++ b/Source/cmGlobalVisualStudioVersionedGenerator.h
@@ -65,6 +65,9 @@ protected:
 
   std::string GetWindows10SDKMaxVersionDefault(cmMakefile*) const override;
 
+  virtual bool ProcessGeneratorInstanceField(std::string const& key,
+                                             std::string const& value);
+
   std::string FindMSBuildCommand() override;
   std::string FindDevEnvCommand() override;
 
@@ -76,5 +79,9 @@ private:
   class Factory17;
   friend class Factory17;
   mutable cmVSSetupAPIHelper vsSetupAPIHelper;
+
+  bool ParseGeneratorInstance(std::string const& is, cmMakefile* mf);
+
+  std::string GeneratorInstance;
   cm::optional<std::string> LastGeneratorInstanceString;
 };
diff --git a/Tests/RunCMake/GeneratorInstance/BadFieldNoComma-result.txt b/Tests/RunCMake/GeneratorInstance/BadFieldNoComma-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/BadFieldNoComma-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GeneratorInstance/BadFieldNoComma-stderr.txt b/Tests/RunCMake/GeneratorInstance/BadFieldNoComma-stderr.txt
new file mode 100644
index 0000000..d6c73c8
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/BadFieldNoComma-stderr.txt
@@ -0,0 +1,11 @@
+^CMake Error at CMakeLists.txt:[0-9]+ \(project\):
+  Generator
+
+    Visual Studio [^
+]+
+
+  given instance specification
+
+    Test Instance,nocomma
+
+  that contains a field after the first ',' with no '='\.$
diff --git a/Tests/RunCMake/GeneratorInstance/BadFieldNoComma.cmake b/Tests/RunCMake/GeneratorInstance/BadFieldNoComma.cmake
new file mode 100644
index 0000000..2fc38e5
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/BadFieldNoComma.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached!")
diff --git a/Tests/RunCMake/GeneratorInstance/BadFieldUnknown-result.txt b/Tests/RunCMake/GeneratorInstance/BadFieldUnknown-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/BadFieldUnknown-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GeneratorInstance/BadFieldUnknown-stderr.txt b/Tests/RunCMake/GeneratorInstance/BadFieldUnknown-stderr.txt
new file mode 100644
index 0000000..ecfe229
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/BadFieldUnknown-stderr.txt
@@ -0,0 +1,11 @@
+^CMake Error at CMakeLists.txt:[0-9]+ \(project\):
+  Generator
+
+    Visual Studio [^
+]+
+
+  given instance specification
+
+    Test Instance,unknown=
+
+  that contains invalid field 'unknown='\.$
diff --git a/Tests/RunCMake/GeneratorInstance/BadFieldUnknown.cmake b/Tests/RunCMake/GeneratorInstance/BadFieldUnknown.cmake
new file mode 100644
index 0000000..2fc38e5
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/BadFieldUnknown.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached!")
diff --git a/Tests/RunCMake/GeneratorInstance/RunCMakeTest.cmake b/Tests/RunCMake/GeneratorInstance/RunCMakeTest.cmake
index e7f9ccb..5eeac8e 100644
--- a/Tests/RunCMake/GeneratorInstance/RunCMakeTest.cmake
+++ b/Tests/RunCMake/GeneratorInstance/RunCMakeTest.cmake
@@ -9,6 +9,11 @@ if("${RunCMake_GENERATOR}" MATCHES "^Visual Studio 1[56789]")
   set(RunCMake_TEST_OPTIONS -DCMAKE_TOOLCHAIN_FILE=${RunCMake_SOURCE_DIR}/MissingInstance-toolchain.cmake)
   run_cmake(MissingInstanceToolchain)
   unset(RunCMake_TEST_OPTIONS)
+
+  set(RunCMake_GENERATOR_INSTANCE "Test Instance,nocomma")
+  run_cmake(BadFieldNoComma)
+  set(RunCMake_GENERATOR_INSTANCE "Test Instance,unknown=")
+  run_cmake(BadFieldUnknown)
 else()
   set(RunCMake_GENERATOR_INSTANCE "")
   run_cmake(NoInstance)
-- 
cgit v0.12


From ec8d37b3b1ca10535e219b37cf4889d59b1dfedb Mon Sep 17 00:00:00 2001
From: Brad King <brad.king@kitware.com>
Date: Tue, 26 Oct 2021 15:22:41 -0400
Subject: VS: Support version specification in CMAKE_GENERATOR_INSTANCE

---
 Help/variable/CMAKE_GENERATOR_INSTANCE.rst         |  7 ++-
 Source/cmGlobalVisualStudioVersionedGenerator.cxx  | 70 +++++++++++++++++++---
 Source/cmGlobalVisualStudioVersionedGenerator.h    |  1 +
 Source/cmVSSetupHelper.cxx                         | 13 +++-
 Source/cmVSSetupHelper.h                           |  4 +-
 .../GeneratorInstance/BadFieldDuplicate-result.txt |  1 +
 .../GeneratorInstance/BadFieldDuplicate-stderr.txt | 11 ++++
 .../GeneratorInstance/BadFieldDuplicate.cmake      |  1 +
 .../GeneratorInstance/BadVersionFormat1-result.txt |  1 +
 .../GeneratorInstance/BadVersionFormat1-stderr.txt | 11 ++++
 .../GeneratorInstance/BadVersionFormat1.cmake      |  1 +
 .../GeneratorInstance/BadVersionFormat2-result.txt |  1 +
 .../GeneratorInstance/BadVersionFormat2-stderr.txt | 11 ++++
 .../GeneratorInstance/BadVersionFormat2.cmake      |  1 +
 .../GeneratorInstance/BadVersionNumber-result.txt  |  1 +
 .../GeneratorInstance/BadVersionNumber-stderr.txt  |  9 +++
 .../GeneratorInstance/BadVersionNumber.cmake       |  1 +
 .../GeneratorInstance/DefaultInstance.cmake        |  1 +
 .../RunCMake/GeneratorInstance/RunCMakeTest.cmake  | 20 ++++++-
 .../GeneratorInstance/WrongVersion-result.txt      |  1 +
 .../GeneratorInstance/WrongVersion-stderr.txt      | 10 ++++
 .../RunCMake/GeneratorInstance/WrongVersion.cmake  |  1 +
 22 files changed, 167 insertions(+), 11 deletions(-)
 create mode 100644 Tests/RunCMake/GeneratorInstance/BadFieldDuplicate-result.txt
 create mode 100644 Tests/RunCMake/GeneratorInstance/BadFieldDuplicate-stderr.txt
 create mode 100644 Tests/RunCMake/GeneratorInstance/BadFieldDuplicate.cmake
 create mode 100644 Tests/RunCMake/GeneratorInstance/BadVersionFormat1-result.txt
 create mode 100644 Tests/RunCMake/GeneratorInstance/BadVersionFormat1-stderr.txt
 create mode 100644 Tests/RunCMake/GeneratorInstance/BadVersionFormat1.cmake
 create mode 100644 Tests/RunCMake/GeneratorInstance/BadVersionFormat2-result.txt
 create mode 100644 Tests/RunCMake/GeneratorInstance/BadVersionFormat2-stderr.txt
 create mode 100644 Tests/RunCMake/GeneratorInstance/BadVersionFormat2.cmake
 create mode 100644 Tests/RunCMake/GeneratorInstance/BadVersionNumber-result.txt
 create mode 100644 Tests/RunCMake/GeneratorInstance/BadVersionNumber-stderr.txt
 create mode 100644 Tests/RunCMake/GeneratorInstance/BadVersionNumber.cmake
 create mode 100644 Tests/RunCMake/GeneratorInstance/WrongVersion-result.txt
 create mode 100644 Tests/RunCMake/GeneratorInstance/WrongVersion-stderr.txt
 create mode 100644 Tests/RunCMake/GeneratorInstance/WrongVersion.cmake

diff --git a/Help/variable/CMAKE_GENERATOR_INSTANCE.rst b/Help/variable/CMAKE_GENERATOR_INSTANCE.rst
index 57dba53..3596bf6 100644
--- a/Help/variable/CMAKE_GENERATOR_INSTANCE.rst
+++ b/Help/variable/CMAKE_GENERATOR_INSTANCE.rst
@@ -37,7 +37,12 @@ of the VS installation.
 
 The ``key=value`` pairs form a comma-separated list of options to
 specify details of the instance selection.
-There are no supported pairs: this syntax is reserved for future use.
+Supported pairs are:
+
+``version=<major>.<minor>.<MMMDD>.<BBB>``
+  .. versionadded:: 3.23
+
+  Specify the 4-component VS Build Version.
 
 If the value of ``CMAKE_GENERATOR_INSTANCE`` is not specified explicitly
 by the user or a toolchain file, CMake queries the Visual Studio Installer
diff --git a/Source/cmGlobalVisualStudioVersionedGenerator.cxx b/Source/cmGlobalVisualStudioVersionedGenerator.cxx
index 78bebac..806871d 100644
--- a/Source/cmGlobalVisualStudioVersionedGenerator.cxx
+++ b/Source/cmGlobalVisualStudioVersionedGenerator.cxx
@@ -6,6 +6,7 @@
 
 #include "cmsys/FStream.hxx"
 #include "cmsys/Glob.hxx"
+#include "cmsys/RegularExpression.hxx"
 
 #include "cmAlgorithms.h"
 #include "cmDocumentationEntry.h"
@@ -109,6 +110,30 @@ static const char* VSVersionToToolset(
   return "";
 }
 
+static std::string VSVersionToMajorString(
+  cmGlobalVisualStudioGenerator::VSVersion v)
+{
+  switch (v) {
+    case cmGlobalVisualStudioGenerator::VS9:
+      return "9";
+    case cmGlobalVisualStudioGenerator::VS10:
+      return "10";
+    case cmGlobalVisualStudioGenerator::VS11:
+      return "11";
+    case cmGlobalVisualStudioGenerator::VS12:
+      return "12";
+    case cmGlobalVisualStudioGenerator::VS14:
+      return "14";
+    case cmGlobalVisualStudioGenerator::VS15:
+      return "15";
+    case cmGlobalVisualStudioGenerator::VS16:
+      return "16";
+    case cmGlobalVisualStudioGenerator::VS17:
+      return "17";
+  }
+  return "";
+}
+
 static const char* VSVersionToAndroidToolset(
   cmGlobalVisualStudioGenerator::VSVersion v)
 {
@@ -445,15 +470,21 @@ bool cmGlobalVisualStudioVersionedGenerator::SetGeneratorInstance(
     return false;
   }
 
-  if (!this->GeneratorInstance.empty()) {
-    if (!this->vsSetupAPIHelper.SetVSInstance(this->GeneratorInstance)) {
+  if (!this->GeneratorInstanceVersion.empty()) {
+    std::string const majorStr = VSVersionToMajorString(this->Version);
+    cmsys::RegularExpression versionRegex(
+      cmStrCat("^", majorStr, "\\.[0-9]+\\.[0-9]+\\.[0-9]+$"));
+    if (!versionRegex.find(this->GeneratorInstanceVersion)) {
       std::ostringstream e;
       /* clang-format off */
       e <<
         "Generator\n"
         "  " << this->GetName() << "\n"
-        "could not find specified instance of Visual Studio:\n"
-        "  " << i;
+        "given instance specification\n"
+        "  " << i << "\n"
+        "but the version field is not 4 integer components"
+        " starting in " << majorStr << "."
+        ;
       /* clang-format on */
       mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
       return false;
@@ -461,7 +492,29 @@ bool cmGlobalVisualStudioVersionedGenerator::SetGeneratorInstance(
   }
 
   std::string vsInstance;
-  if (!this->vsSetupAPIHelper.GetVSInstanceInfo(vsInstance)) {
+  if (!i.empty()) {
+    vsInstance = i;
+    if (!this->vsSetupAPIHelper.SetVSInstance(
+          this->GeneratorInstance, this->GeneratorInstanceVersion)) {
+      std::ostringstream e;
+      /* clang-format off */
+      e <<
+        "Generator\n"
+        "  " << this->GetName() << "\n"
+        "could not find specified instance of Visual Studio:\n"
+        "  " << i;
+      /* clang-format on */
+      if (!this->GeneratorInstance.empty() &&
+          this->GeneratorInstanceVersion.empty() &&
+          cmSystemTools::FileIsDirectory(this->GeneratorInstance)) {
+        e << "\n"
+             "The directory exists, but the instance is not known to the "
+             "Visual Studio Installer.";
+      }
+      mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
+      return false;
+    }
+  } else if (!this->vsSetupAPIHelper.GetVSInstanceInfo(vsInstance)) {
     std::ostringstream e;
     /* clang-format off */
     e <<
@@ -493,6 +546,7 @@ bool cmGlobalVisualStudioVersionedGenerator::ParseGeneratorInstance(
   std::string const& is, cmMakefile* mf)
 {
   this->GeneratorInstance.clear();
+  this->GeneratorInstanceVersion.clear();
 
   std::vector<std::string> const fields = cmTokenize(is, ",");
   std::vector<std::string>::const_iterator fi = fields.begin();
@@ -563,8 +617,10 @@ bool cmGlobalVisualStudioVersionedGenerator::ParseGeneratorInstance(
 bool cmGlobalVisualStudioVersionedGenerator::ProcessGeneratorInstanceField(
   std::string const& key, std::string const& value)
 {
-  static_cast<void>(key);
-  static_cast<void>(value);
+  if (key == "version") {
+    this->GeneratorInstanceVersion = value;
+    return true;
+  }
   return false;
 }
 
diff --git a/Source/cmGlobalVisualStudioVersionedGenerator.h b/Source/cmGlobalVisualStudioVersionedGenerator.h
index a19e506..54c38d9 100644
--- a/Source/cmGlobalVisualStudioVersionedGenerator.h
+++ b/Source/cmGlobalVisualStudioVersionedGenerator.h
@@ -83,5 +83,6 @@ private:
   bool ParseGeneratorInstance(std::string const& is, cmMakefile* mf);
 
   std::string GeneratorInstance;
+  std::string GeneratorInstanceVersion;
   cm::optional<std::string> LastGeneratorInstanceString;
 };
diff --git a/Source/cmVSSetupHelper.cxx b/Source/cmVSSetupHelper.cxx
index b9443d6..39ddce3 100644
--- a/Source/cmVSSetupHelper.cxx
+++ b/Source/cmVSSetupHelper.cxx
@@ -102,10 +102,12 @@ cmVSSetupAPIHelper::~cmVSSetupAPIHelper()
     CoUninitialize();
 }
 
-bool cmVSSetupAPIHelper::SetVSInstance(std::string const& vsInstallLocation)
+bool cmVSSetupAPIHelper::SetVSInstance(std::string const& vsInstallLocation,
+                                       std::string const& vsInstallVersion)
 {
   this->SpecifiedVSInstallLocation = vsInstallLocation;
   cmSystemTools::ConvertToUnixSlashes(this->SpecifiedVSInstallLocation);
+  this->SpecifiedVSInstallVersion = vsInstallVersion;
   chosenInstanceInfo = VSInstanceInfo();
   return this->EnumerateAndChooseVSInstance();
 }
@@ -366,6 +368,15 @@ bool cmVSSetupAPIHelper::EnumerateAndChooseVSInstance()
         std::string currentVSLocation = instanceInfo.GetInstallLocation();
         if (cmSystemTools::ComparePath(currentVSLocation,
                                        this->SpecifiedVSInstallLocation)) {
+          if (this->SpecifiedVSInstallVersion.empty() ||
+              instanceInfo.Version == this->SpecifiedVSInstallVersion) {
+            chosenInstanceInfo = instanceInfo;
+            return true;
+          }
+        }
+      } else if (!this->SpecifiedVSInstallVersion.empty()) {
+        // We are looking for a specific version.
+        if (instanceInfo.Version == this->SpecifiedVSInstallVersion) {
           chosenInstanceInfo = instanceInfo;
           return true;
         }
diff --git a/Source/cmVSSetupHelper.h b/Source/cmVSSetupHelper.h
index ad242a5..d7e82d0 100644
--- a/Source/cmVSSetupHelper.h
+++ b/Source/cmVSSetupHelper.h
@@ -99,7 +99,8 @@ public:
   cmVSSetupAPIHelper(unsigned int version);
   ~cmVSSetupAPIHelper();
 
-  bool SetVSInstance(std::string const& vsInstallLocation);
+  bool SetVSInstance(std::string const& vsInstallLocation,
+                     std::string const& vsInstallVersion);
 
   bool IsVSInstalled();
   bool GetVSInstanceInfo(std::string& vsInstallLocation);
@@ -132,4 +133,5 @@ private:
   bool IsEWDKEnabled();
 
   std::string SpecifiedVSInstallLocation;
+  std::string SpecifiedVSInstallVersion;
 };
diff --git a/Tests/RunCMake/GeneratorInstance/BadFieldDuplicate-result.txt b/Tests/RunCMake/GeneratorInstance/BadFieldDuplicate-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/BadFieldDuplicate-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GeneratorInstance/BadFieldDuplicate-stderr.txt b/Tests/RunCMake/GeneratorInstance/BadFieldDuplicate-stderr.txt
new file mode 100644
index 0000000..ef71404
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/BadFieldDuplicate-stderr.txt
@@ -0,0 +1,11 @@
+^CMake Error at CMakeLists.txt:[0-9]+ \(project\):
+  Generator
+
+    Visual Studio [^
+]+
+
+  given instance specification
+
+    Test Instance,version=1\.2\.3\.4,version=1\.2\.3\.4
+
+  that contains duplicate field key 'version'\.$
diff --git a/Tests/RunCMake/GeneratorInstance/BadFieldDuplicate.cmake b/Tests/RunCMake/GeneratorInstance/BadFieldDuplicate.cmake
new file mode 100644
index 0000000..2fc38e5
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/BadFieldDuplicate.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached!")
diff --git a/Tests/RunCMake/GeneratorInstance/BadVersionFormat1-result.txt b/Tests/RunCMake/GeneratorInstance/BadVersionFormat1-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/BadVersionFormat1-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GeneratorInstance/BadVersionFormat1-stderr.txt b/Tests/RunCMake/GeneratorInstance/BadVersionFormat1-stderr.txt
new file mode 100644
index 0000000..a0894b6
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/BadVersionFormat1-stderr.txt
@@ -0,0 +1,11 @@
+^CMake Error at CMakeLists.txt:[0-9]+ \(project\):
+  Generator
+
+    Visual Studio [^
+]+
+
+  given instance specification
+
+    version=1\.2\.3
+
+  but the version field is not 4 integer components starting in [0-9]+\.$
diff --git a/Tests/RunCMake/GeneratorInstance/BadVersionFormat1.cmake b/Tests/RunCMake/GeneratorInstance/BadVersionFormat1.cmake
new file mode 100644
index 0000000..2fc38e5
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/BadVersionFormat1.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached!")
diff --git a/Tests/RunCMake/GeneratorInstance/BadVersionFormat2-result.txt b/Tests/RunCMake/GeneratorInstance/BadVersionFormat2-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/BadVersionFormat2-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GeneratorInstance/BadVersionFormat2-stderr.txt b/Tests/RunCMake/GeneratorInstance/BadVersionFormat2-stderr.txt
new file mode 100644
index 0000000..2b3a23b
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/BadVersionFormat2-stderr.txt
@@ -0,0 +1,11 @@
+^CMake Error at CMakeLists.txt:[0-9]+ \(project\):
+  Generator
+
+    Visual Studio [^
+]+
+
+  given instance specification
+
+    version=1\.2\.3\.x
+
+  but the version field is not 4 integer components starting in [0-9]+\.$
diff --git a/Tests/RunCMake/GeneratorInstance/BadVersionFormat2.cmake b/Tests/RunCMake/GeneratorInstance/BadVersionFormat2.cmake
new file mode 100644
index 0000000..2fc38e5
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/BadVersionFormat2.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached!")
diff --git a/Tests/RunCMake/GeneratorInstance/BadVersionNumber-result.txt b/Tests/RunCMake/GeneratorInstance/BadVersionNumber-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/BadVersionNumber-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GeneratorInstance/BadVersionNumber-stderr.txt b/Tests/RunCMake/GeneratorInstance/BadVersionNumber-stderr.txt
new file mode 100644
index 0000000..3a27341
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/BadVersionNumber-stderr.txt
@@ -0,0 +1,9 @@
+^CMake Error at CMakeLists.txt:[0-9] \(project\):
+  Generator
+
+    Visual Studio [^
+]+
+
+  could not find specified instance of Visual Studio:
+
+    version=[0-9]+\.999\.99999\.999$
diff --git a/Tests/RunCMake/GeneratorInstance/BadVersionNumber.cmake b/Tests/RunCMake/GeneratorInstance/BadVersionNumber.cmake
new file mode 100644
index 0000000..2fc38e5
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/BadVersionNumber.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached!")
diff --git a/Tests/RunCMake/GeneratorInstance/DefaultInstance.cmake b/Tests/RunCMake/GeneratorInstance/DefaultInstance.cmake
index 7750c2e..9761f0c 100644
--- a/Tests/RunCMake/GeneratorInstance/DefaultInstance.cmake
+++ b/Tests/RunCMake/GeneratorInstance/DefaultInstance.cmake
@@ -11,3 +11,4 @@ elseif(NOT IS_DIRECTORY "${CMAKE_GENERATOR_INSTANCE}")
     "  ${CMAKE_GENERATOR_INSTANCE}\n"
     "which is not an existing directory.")
 endif()
+file(WRITE "${CMAKE_BINARY_DIR}/instance.txt" "${CMAKE_GENERATOR_INSTANCE}")
diff --git a/Tests/RunCMake/GeneratorInstance/RunCMakeTest.cmake b/Tests/RunCMake/GeneratorInstance/RunCMakeTest.cmake
index 5eeac8e..cdcaac8 100644
--- a/Tests/RunCMake/GeneratorInstance/RunCMakeTest.cmake
+++ b/Tests/RunCMake/GeneratorInstance/RunCMakeTest.cmake
@@ -1,8 +1,14 @@
 include(RunCMake)
 
-if("${RunCMake_GENERATOR}" MATCHES "^Visual Studio 1[56789]")
+if("${RunCMake_GENERATOR}" MATCHES "^Visual Studio (1[56789])")
+  set(vs_major "${CMAKE_MATCH_1}")
+
   set(RunCMake_GENERATOR_INSTANCE "")
   run_cmake(DefaultInstance)
+  set(instance_txt "${RunCMake_BINARY_DIR}/DefaultInstance-build/instance.txt")
+  if(EXISTS "${instance_txt}")
+    file(READ "${instance_txt}" default_instance)
+  endif()
 
   set(RunCMake_GENERATOR_INSTANCE "${RunCMake_SOURCE_DIR}/instance_does_not_exist")
   run_cmake(MissingInstance)
@@ -14,6 +20,18 @@ if("${RunCMake_GENERATOR}" MATCHES "^Visual Studio 1[56789]")
   run_cmake(BadFieldNoComma)
   set(RunCMake_GENERATOR_INSTANCE "Test Instance,unknown=")
   run_cmake(BadFieldUnknown)
+  set(RunCMake_GENERATOR_INSTANCE "Test Instance,version=1.2.3.4,version=1.2.3.4")
+  run_cmake(BadFieldDuplicate)
+  set(RunCMake_GENERATOR_INSTANCE "version=1.2.3")
+  run_cmake(BadVersionFormat1)
+  set(RunCMake_GENERATOR_INSTANCE "version=1.2.3.x")
+  run_cmake(BadVersionFormat2)
+  set(RunCMake_GENERATOR_INSTANCE "version=${vs_major}.999.99999.999")
+  run_cmake(BadVersionNumber)
+  if(IS_DIRECTORY "${default_instance}")
+    set(RunCMake_GENERATOR_INSTANCE "${default_instance},version=${vs_major}.999.99999.999")
+    run_cmake(WrongVersion)
+  endif()
 else()
   set(RunCMake_GENERATOR_INSTANCE "")
   run_cmake(NoInstance)
diff --git a/Tests/RunCMake/GeneratorInstance/WrongVersion-result.txt b/Tests/RunCMake/GeneratorInstance/WrongVersion-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/WrongVersion-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GeneratorInstance/WrongVersion-stderr.txt b/Tests/RunCMake/GeneratorInstance/WrongVersion-stderr.txt
new file mode 100644
index 0000000..156a9ee
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/WrongVersion-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Error at CMakeLists.txt:[0-9] \(project\):
+  Generator
+
+    Visual Studio [^
+]+
+
+  could not find specified instance of Visual Studio:
+
+    [^,
+]+,version=[0-9]+\.999\.99999\.999$
diff --git a/Tests/RunCMake/GeneratorInstance/WrongVersion.cmake b/Tests/RunCMake/GeneratorInstance/WrongVersion.cmake
new file mode 100644
index 0000000..2fc38e5
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/WrongVersion.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached!")
-- 
cgit v0.12


From 195d47e2139171cbae18a7164daac1f59be54ebe Mon Sep 17 00:00:00 2001
From: Brad King <brad.king@kitware.com>
Date: Thu, 21 Oct 2021 12:17:37 -0400
Subject: VS: Allow CMAKE_GENERATOR_INSTANCE to specify portable instance

Previously the `CMAKE_GENERATOR_INSTANCE` value was used only to filter
the instances reported by the Visual Studio Installer tool.  If the
specified install location is not known to the VS Installer, but the
user provided a `version=` field, check for the installation directly
on disk.

Fixes: #21639, #22197
---
 Help/release/dev/vs-instance.rst                   |  6 ++++
 Help/variable/CMAKE_GENERATOR_INSTANCE.rst         |  6 ++++
 Source/cmGlobalVisualStudioVersionedGenerator.cxx  |  2 +-
 Source/cmVSSetupHelper.cxx                         | 38 ++++++++++++++++++++++
 Source/cmVSSetupHelper.h                           |  1 +
 .../GeneratorInstance/PortableNoVersion-result.txt |  1 +
 .../GeneratorInstance/PortableNoVersion-stderr.txt | 13 ++++++++
 .../GeneratorInstance/PortableNoVersion.cmake      |  1 +
 .../RunCMake/GeneratorInstance/RunCMakeTest.cmake  |  3 ++
 9 files changed, 70 insertions(+), 1 deletion(-)
 create mode 100644 Help/release/dev/vs-instance.rst
 create mode 100644 Tests/RunCMake/GeneratorInstance/PortableNoVersion-result.txt
 create mode 100644 Tests/RunCMake/GeneratorInstance/PortableNoVersion-stderr.txt
 create mode 100644 Tests/RunCMake/GeneratorInstance/PortableNoVersion.cmake

diff --git a/Help/release/dev/vs-instance.rst b/Help/release/dev/vs-instance.rst
new file mode 100644
index 0000000..0b9ff4b
--- /dev/null
+++ b/Help/release/dev/vs-instance.rst
@@ -0,0 +1,6 @@
+vs-instance
+-----------
+
+* The :ref:`Visual Studio Generators` for VS 2017 and above learned to
+  use portable instances of Visual Studio not known to the VS installer.
+  See the :variable:`CMAKE_GENERATOR_INSTANCE` variable.
diff --git a/Help/variable/CMAKE_GENERATOR_INSTANCE.rst b/Help/variable/CMAKE_GENERATOR_INSTANCE.rst
index 3596bf6..6a35f17 100644
--- a/Help/variable/CMAKE_GENERATOR_INSTANCE.rst
+++ b/Help/variable/CMAKE_GENERATOR_INSTANCE.rst
@@ -44,6 +44,12 @@ Supported pairs are:
 
   Specify the 4-component VS Build Version.
 
+.. versionadded:: 3.23
+
+  A portable VS instance may be specified that is not known to the
+  Visual Studio Installer tool.  The ``location`` and ``version=``
+  values must both be provided.
+
 If the value of ``CMAKE_GENERATOR_INSTANCE`` is not specified explicitly
 by the user or a toolchain file, CMake queries the Visual Studio Installer
 to locate VS instances, chooses one, and sets the variable as a cache entry
diff --git a/Source/cmGlobalVisualStudioVersionedGenerator.cxx b/Source/cmGlobalVisualStudioVersionedGenerator.cxx
index 806871d..f2ce83e 100644
--- a/Source/cmGlobalVisualStudioVersionedGenerator.cxx
+++ b/Source/cmGlobalVisualStudioVersionedGenerator.cxx
@@ -509,7 +509,7 @@ bool cmGlobalVisualStudioVersionedGenerator::SetGeneratorInstance(
           cmSystemTools::FileIsDirectory(this->GeneratorInstance)) {
         e << "\n"
              "The directory exists, but the instance is not known to the "
-             "Visual Studio Installer.";
+             "Visual Studio Installer, and no 'version=' field was given.";
       }
       mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
       return false;
diff --git a/Source/cmVSSetupHelper.cxx b/Source/cmVSSetupHelper.cxx
index 39ddce3..cbd241b 100644
--- a/Source/cmVSSetupHelper.cxx
+++ b/Source/cmVSSetupHelper.cxx
@@ -2,6 +2,8 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmVSSetupHelper.h"
 
+#include <utility>
+
 #include "cmsys/Encoding.hxx"
 #include "cmsys/FStream.hxx"
 
@@ -342,6 +344,8 @@ bool cmVSSetupAPIHelper::EnumerateAndChooseVSInstance()
 
   std::string const wantVersion = std::to_string(this->Version) + '.';
 
+  bool specifiedLocationNotSpecifiedVersion = false;
+
   SmartCOMPtr<ISetupInstance> instance;
   while (SUCCEEDED(enumInstances->Next(1, &instance, NULL)) && instance) {
     SmartCOMPtr<ISetupInstance2> instance2 = NULL;
@@ -373,6 +377,7 @@ bool cmVSSetupAPIHelper::EnumerateAndChooseVSInstance()
             chosenInstanceInfo = instanceInfo;
             return true;
           }
+          specifiedLocationNotSpecifiedVersion = true;
         }
       } else if (!this->SpecifiedVSInstallVersion.empty()) {
         // We are looking for a specific version.
@@ -398,6 +403,13 @@ bool cmVSSetupAPIHelper::EnumerateAndChooseVSInstance()
     }
   }
 
+  if (!this->SpecifiedVSInstallLocation.empty() &&
+      !specifiedLocationNotSpecifiedVersion) {
+    // The VS Installer does not know about the specified location.
+    // Check for one directly on disk.
+    return this->LoadSpecifiedVSInstanceFromDisk();
+  }
+
   if (vecVSInstances.size() > 0) {
     isVSInstanceExists = true;
     int index = ChooseVSInstance(vecVSInstances);
@@ -460,6 +472,32 @@ int cmVSSetupAPIHelper::ChooseVSInstance(
   return chosenIndex;
 }
 
+bool cmVSSetupAPIHelper::LoadSpecifiedVSInstanceFromDisk()
+{
+  if (!cmSystemTools::FileIsDirectory(this->SpecifiedVSInstallLocation)) {
+    return false;
+  }
+  VSInstanceInfo vsInstanceInfo;
+  vsInstanceInfo.VSInstallLocation = this->SpecifiedVSInstallLocation;
+  // FIXME: Is there a better way to get SDK information?
+  vsInstanceInfo.IsWin10SDKInstalled = true;
+  vsInstanceInfo.IsWin81SDKInstalled = false;
+
+  if (!this->SpecifiedVSInstallVersion.empty()) {
+    // Assume the version specified by the user is correct.
+    vsInstanceInfo.Version = this->SpecifiedVSInstallVersion;
+  } else {
+    return false;
+  }
+
+  if (!LoadVSInstanceVCToolsetVersion(vsInstanceInfo)) {
+    return false;
+  }
+
+  chosenInstanceInfo = std::move(vsInstanceInfo);
+  return true;
+}
+
 bool cmVSSetupAPIHelper::Initialize()
 {
   if (initializationFailure)
diff --git a/Source/cmVSSetupHelper.h b/Source/cmVSSetupHelper.h
index d7e82d0..44c883b 100644
--- a/Source/cmVSSetupHelper.h
+++ b/Source/cmVSSetupHelper.h
@@ -117,6 +117,7 @@ private:
                                bool& bWin10SDK, bool& bWin81SDK);
   int ChooseVSInstance(const std::vector<VSInstanceInfo>& vecVSInstances);
   bool EnumerateAndChooseVSInstance();
+  bool LoadSpecifiedVSInstanceFromDisk();
 
   unsigned int Version;
 
diff --git a/Tests/RunCMake/GeneratorInstance/PortableNoVersion-result.txt b/Tests/RunCMake/GeneratorInstance/PortableNoVersion-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/PortableNoVersion-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GeneratorInstance/PortableNoVersion-stderr.txt b/Tests/RunCMake/GeneratorInstance/PortableNoVersion-stderr.txt
new file mode 100644
index 0000000..baa17aa
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/PortableNoVersion-stderr.txt
@@ -0,0 +1,13 @@
+^CMake Error at CMakeLists.txt:[0-9]+ \(project\):
+  Generator
+
+    Visual Studio [^
+]+
+
+  could not find specified instance of Visual Studio:
+
+    [^
+]+/Tests/RunCMake/GeneratorInstance
+
+  The directory exists, but the instance is not known to the Visual Studio
+  Installer, and no 'version=' field was given\.$
diff --git a/Tests/RunCMake/GeneratorInstance/PortableNoVersion.cmake b/Tests/RunCMake/GeneratorInstance/PortableNoVersion.cmake
new file mode 100644
index 0000000..2fc38e5
--- /dev/null
+++ b/Tests/RunCMake/GeneratorInstance/PortableNoVersion.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached!")
diff --git a/Tests/RunCMake/GeneratorInstance/RunCMakeTest.cmake b/Tests/RunCMake/GeneratorInstance/RunCMakeTest.cmake
index cdcaac8..dfcdcf8 100644
--- a/Tests/RunCMake/GeneratorInstance/RunCMakeTest.cmake
+++ b/Tests/RunCMake/GeneratorInstance/RunCMakeTest.cmake
@@ -32,6 +32,9 @@ if("${RunCMake_GENERATOR}" MATCHES "^Visual Studio (1[56789])")
     set(RunCMake_GENERATOR_INSTANCE "${default_instance},version=${vs_major}.999.99999.999")
     run_cmake(WrongVersion)
   endif()
+
+  set(RunCMake_GENERATOR_INSTANCE "${RunCMake_SOURCE_DIR}")
+  run_cmake(PortableNoVersion)
 else()
   set(RunCMake_GENERATOR_INSTANCE "")
   run_cmake(NoInstance)
-- 
cgit v0.12