From f972e4fd3acfb33888102ddc84b4e96db20998df Mon Sep 17 00:00:00 2001
From: Nicholas Sinlock <nsinlock@microsoft.com>
Date: Thu, 1 Sep 2022 14:43:19 -0700
Subject: cmVSGenerator: Add support for two-part toolset versions for Visual
 Studio

Enables the Global Visual Studio Versioned Generator to use two-part toolset versions,
if only one toolset has that version number. For example, (14.32 is specified when
14.32.32142 and 14.32.23242 are installed). This change also add a unique return code
and message if a two-part version is used when multiple matching versions are present.

Fixes: #23933
---
 Source/cmGlobalVisualStudio10Generator.cxx        | 21 +++++++++++++
 Source/cmGlobalVisualStudio10Generator.h          |  3 +-
 Source/cmGlobalVisualStudioVersionedGenerator.cxx | 37 +++++++++++++++++++++--
 3 files changed, 57 insertions(+), 4 deletions(-)

diff --git a/Source/cmGlobalVisualStudio10Generator.cxx b/Source/cmGlobalVisualStudio10Generator.cxx
index 29eeb5a..7886fc7 100644
--- a/Source/cmGlobalVisualStudio10Generator.cxx
+++ b/Source/cmGlobalVisualStudio10Generator.cxx
@@ -389,6 +389,27 @@ bool cmGlobalVisualStudio10Generator::SetGeneratorToolset(
         this->GeneratorToolsetVersion.clear();
         this->GeneratorToolsetVersionProps = {};
       } break;
+      case AuxToolset::PropsIndeterminate: {
+        std::ostringstream e;
+        /* clang-format off */
+        e <<
+          "Generator\n"
+          "  " << this->GetName() << "\n"
+          "given toolset and version specification\n"
+          "  " << this->GetPlatformToolsetString() << ",version=" <<
+          this->GeneratorToolsetVersion << "\n"
+          "has multiple matches installed at\n" <<
+          "  " << auxProps << "\n" <<
+          "The toolset and version specification must resolve \n" <<
+           "to a single installed toolset";
+        ;
+        /* clang-format on */
+        mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
+
+        // Clear the configured tool-set
+        this->GeneratorToolsetVersion.clear();
+        this->GeneratorToolsetVersionProps = {};
+      } break;
     }
   }
 
diff --git a/Source/cmGlobalVisualStudio10Generator.h b/Source/cmGlobalVisualStudio10Generator.h
index 4977a84..a33c94e 100644
--- a/Source/cmGlobalVisualStudio10Generator.h
+++ b/Source/cmGlobalVisualStudio10Generator.h
@@ -200,7 +200,8 @@ protected:
     None,
     Default,
     PropsExist,
-    PropsMissing
+    PropsMissing,
+    PropsIndeterminate
   };
   virtual AuxToolset FindAuxToolset(std::string& version,
                                     std::string& props) const;
diff --git a/Source/cmGlobalVisualStudioVersionedGenerator.cxx b/Source/cmGlobalVisualStudioVersionedGenerator.cxx
index 7e36881..c3ba2c2 100644
--- a/Source/cmGlobalVisualStudioVersionedGenerator.cxx
+++ b/Source/cmGlobalVisualStudioVersionedGenerator.cxx
@@ -770,12 +770,15 @@ cmGlobalVisualStudioVersionedGenerator::FindAuxToolset(
   cmSystemTools::ConvertToUnixSlashes(instancePath);
 
   // Translate three-component format accepted by "vcvarsall -vcvars_ver=".
-  cmsys::RegularExpression threeComponent(
+  cmsys::RegularExpression threeComponentRegex(
     "^([0-9]+\\.[0-9]+)\\.[0-9][0-9][0-9][0-9][0-9]$");
-  if (threeComponent.find(version)) {
+  // The two-component format represents the two major components of the
+  // three-component format
+  cmsys::RegularExpression twoComponentRegex("^([0-9]+\\.[0-9]+)$");
+  if (threeComponentRegex.find(version)) {
     // Load "VC/Auxiliary/Build/*/Microsoft.VCToolsVersion.*.txt" files
     // with two matching components to check their three-component version.
-    std::string const& twoComponent = threeComponent.match(1);
+    std::string const& twoComponent = threeComponentRegex.match(1);
     std::string pattern =
       cmStrCat(instancePath, "/VC/Auxiliary/Build/"_s, twoComponent,
                "*/Microsoft.VCToolsVersion."_s, twoComponent, "*.txt"_s);
@@ -801,6 +804,34 @@ cmGlobalVisualStudioVersionedGenerator::FindAuxToolset(
         }
       }
     }
+  } else if (twoComponentRegex.find(version)) {
+    std::string const& twoComponent = twoComponentRegex.match(1);
+    std::string pattern =
+      cmStrCat(instancePath, "/VC/Auxiliary/Build/"_s, twoComponent,
+               "*/Microsoft.VCToolsVersion."_s, twoComponent, "*.txt"_s);
+    cmsys::Glob glob;
+    glob.SetRecurseThroughSymlinks(false);
+    // Since we are only using the first two components of the toolset version,
+    // we require a definite match
+    if (glob.FindFiles(pattern) && glob.GetFiles().size() == 1) {
+      std::string const& txt = glob.GetFiles()[0];
+      std::string ver;
+      cmsys::ifstream fin(txt.c_str());
+      if (fin && std::getline(fin, ver)) {
+        // Strip trailing whitespace.
+        ver = ver.substr(0, ver.find_first_not_of("0123456789."));
+        // We assume the version is correct, since it is the only one that
+        // matched.
+        cmsys::RegularExpression extractVersion(
+          "VCToolsVersion\\.([0-9.]+)\\.txt$");
+        if (extractVersion.find(txt)) {
+          version = extractVersion.match(1);
+        }
+      }
+    } else {
+      props = cmStrCat(instancePath, "/VC/Auxiliary/Build/"_s);
+      return AuxToolset::PropsIndeterminate;
+    }
   }
 
   if (cmSystemTools::VersionCompareGreaterEq(version, "14.20")) {
-- 
cgit v0.12