Merge topic 'fileapi-toolchains'

6418dabb87 Tests: Add test for toolchains-v1 File API object
1c5bd1bed5 Tests: Add toolchains kind to capabilities test
f72bb2ee0d Help: Add documentation for "toolchains" object kind
bb069c0857 cmFileAPI: Add "toolchains" object kind.

Acked-by: Kitware Robot <kwrobot@kitware.com>
Merge-request: !5678
diff --git a/Help/manual/cmake-file-api.7.rst b/Help/manual/cmake-file-api.7.rst
index 6876e1c..89739b7 100644
--- a/Help/manual/cmake-file-api.7.rst
+++ b/Help/manual/cmake-file-api.7.rst
@@ -1154,3 +1154,160 @@
   ``isCMake``
     Optional member that is present with boolean value ``true``
     if the path specifies a file in the CMake installation.
+
+Object Kind "toolchains"
+------------------------
+
+The ``toolchains`` object kind lists properties of the toolchains used during
+the build.  These include the language, compiler path, ID, and version.
+
+There is only one ``toolchains`` object major version, version 1.
+
+"toolchains" version 1
+^^^^^^^^^^^^^^^^^^^^^^
+
+``toolchains`` object version 1 is a JSON object:
+
+.. code-block:: json
+
+  {
+    "kind": "toolchains",
+    "version": { "major": 1, "minor": 0 },
+    "toolchains": [
+      {
+        "language": "C",
+        "compiler": {
+          "path": "/usr/bin/cc",
+          "id": "GNU",
+          "version": "9.3.0",
+          "implicit": {
+            "includeDirectories": [
+              "/usr/lib/gcc/x86_64-linux-gnu/9/include",
+              "/usr/local/include",
+              "/usr/include/x86_64-linux-gnu",
+              "/usr/include"
+            ],
+            "linkDirectories": [
+              "/usr/lib/gcc/x86_64-linux-gnu/9",
+              "/usr/lib/x86_64-linux-gnu",
+              "/usr/lib",
+              "/lib/x86_64-linux-gnu",
+              "/lib"
+            ],
+            "linkFrameworkDirectories": [],
+            "linkLibraries": [ "gcc", "gcc_s", "c", "gcc", "gcc_s" ]
+          }
+        },
+        "sourceFileExtensions": [ "c", "m" ]
+      },
+      {
+        "language": "CXX",
+        "compiler": {
+          "path": "/usr/bin/c++",
+          "id": "GNU",
+          "version": "9.3.0",
+          "implicit": {
+            "includeDirectories": [
+              "/usr/include/c++/9",
+              "/usr/include/x86_64-linux-gnu/c++/9",
+              "/usr/include/c++/9/backward",
+              "/usr/lib/gcc/x86_64-linux-gnu/9/include",
+              "/usr/local/include",
+              "/usr/include/x86_64-linux-gnu",
+              "/usr/include"
+            ],
+            "linkDirectories": [
+              "/usr/lib/gcc/x86_64-linux-gnu/9",
+              "/usr/lib/x86_64-linux-gnu",
+              "/usr/lib",
+              "/lib/x86_64-linux-gnu",
+              "/lib"
+            ],
+            "linkFrameworkDirectories": [],
+            "linkLibraries": [
+              "stdc++", "m", "gcc_s", "gcc", "c", "gcc_s", "gcc"
+            ]
+          }
+        },
+        "sourceFileExtensions": [
+          "C", "M", "c++", "cc", "cpp", "cxx", "mm", "CPP"
+        ]
+      }
+    ]
+  }
+
+The members specific to ``toolchains`` objects are:
+
+``toolchains``
+  A JSON array whose entries are each a JSON object specifying a toolchain
+  associated with a particular language. The members of each entry are:
+
+  ``language``
+    A JSON string specifying the toolchain language, like C or CXX. Language
+    names are the same as langauge names that can be passed to the
+    :command:`project` command. Because CMake only supports a single toolchain
+    per language, this field can be used as a key.
+
+  ``compiler``
+    A JSON object containing members:
+
+    ``path``
+      Optional member that is present when the
+      :variable:`CMAKE_<LANG>_COMPILER` variable is defined for the current
+      language. Its value is a JSON string holding the path to the compiler.
+
+    ``id``
+      Optional member that is present when the
+      :variable:`CMAKE_<LANG>_COMPILER_ID` variable is defined for the current
+      language. Its value is a JSON string holding the ID (GNU, MSVC, etc.) of
+      the compiler.
+
+    ``version``
+      Optional member that is present when the
+      :variable:`CMAKE_<LANG>_COMPILER_VERSION` variable is defined for the
+      current language. Its value is a JSON string holding the version of the
+      compiler.
+
+    ``target``
+      Optional member that is present when the
+      :variable:`CMAKE_<LANG>_COMPILER_TARGET` variable is defined for the
+      current language. Its value is a JSON string holding the cross-compiling
+      target of the compiler.
+
+    ``implicit``
+      A JSON object containing members:
+
+      ``includeDirectories``
+        Optional member that is present when the
+        :variable:`CMAKE_<LANG>_IMPLICIT_INCLUDE_DIRECTORIES` variable is
+        defined for the current language. Its value is a JSON array of JSON
+        strings where each string holds a path to an implicit include
+        directory for the compiler.
+
+      ``linkDirectories``
+        Optional member that is present when the
+        :variable:`CMAKE_<LANG>_IMPLICIT_LINK_DIRECTORIES` variable is
+        defined for the current language. Its value is a JSON array of JSON
+        strings where each string holds a path to an implicit link directory
+        for the compiler.
+
+      ``linkFrameworkDirectories``
+        Optional member that is present when the
+        :variable:`CMAKE_<LANG>_IMPLICIT_LINK_FRAMEWORK_DIRECTORIES` variable
+        is defined for the current language. Its value is a JSON array of JSON
+        strings where each string holds a path to an implicit link framework
+        directory for the compiler.
+
+      ``linkLibraries``
+        Optional member that is present when the
+        :variable:`CMAKE_<LANG>_IMPLICIT_LINK_LIBRARIES` variable is defined
+        for the current language. Its value is a JSON array of JSON strings
+        where each string holds a path to an implicit link library for the
+        compiler.
+
+  ``sourceFileExtensions``
+    Optional member that is present when the
+    :variable:`CMAKE_<LANG>_SOURCE_FILE_EXTENSIONS` variable is defined for
+    the current language. Its value is a JSON array of JSON strings where each
+    each string holds a file extension (without the leading dot) for the
+    language.
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index c5b67c0..dca94ee 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -268,6 +268,8 @@
   cmFileAPICodemodel.h
   cmFileAPICMakeFiles.cxx
   cmFileAPICMakeFiles.h
+  cmFileAPIToolchains.cxx
+  cmFileAPIToolchains.h
   cmFileCopier.cxx
   cmFileCopier.h
   cmFileInstaller.cxx
diff --git a/Source/cmFileAPI.cxx b/Source/cmFileAPI.cxx
index c2ab2f1..d2a9bec 100644
--- a/Source/cmFileAPI.cxx
+++ b/Source/cmFileAPI.cxx
@@ -18,6 +18,7 @@
 #include "cmFileAPICMakeFiles.h"
 #include "cmFileAPICache.h"
 #include "cmFileAPICodemodel.h"
+#include "cmFileAPIToolchains.h"
 #include "cmGlobalGenerator.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
@@ -262,6 +263,17 @@
     objects.push_back(o);
     return true;
   }
+  if (kindName == ObjectKindName(ObjectKind::Toolchains)) {
+    Object o;
+    o.Kind = ObjectKind::Toolchains;
+    if (verStr == "v1") {
+      o.Version = 1;
+    } else {
+      return false;
+    }
+    objects.push_back(o);
+    return true;
+  }
   if (kindName == ObjectKindName(ObjectKind::InternalTest)) {
     Object o;
     o.Kind = ObjectKind::InternalTest;
@@ -402,6 +414,7 @@
     "codemodel",  //
     "cache",      //
     "cmakeFiles", //
+    "toolchains", //
     "__test"      //
   };
   return objectKindNames[size_t(kind)];
@@ -435,6 +448,9 @@
     case ObjectKind::CMakeFiles:
       value = this->BuildCMakeFiles(object);
       break;
+    case ObjectKind::Toolchains:
+      value = this->BuildToolchains(object);
+      break;
     case ObjectKind::InternalTest:
       value = this->BuildInternalTest(object);
       break;
@@ -491,6 +507,8 @@
     r.Kind = ObjectKind::Cache;
   } else if (kindName == this->ObjectKindName(ObjectKind::CMakeFiles)) {
     r.Kind = ObjectKind::CMakeFiles;
+  } else if (kindName == this->ObjectKindName(ObjectKind::Toolchains)) {
+    r.Kind = ObjectKind::Toolchains;
   } else if (kindName == this->ObjectKindName(ObjectKind::InternalTest)) {
     r.Kind = ObjectKind::InternalTest;
   } else {
@@ -518,6 +536,9 @@
     case ObjectKind::CMakeFiles:
       this->BuildClientRequestCMakeFiles(r, versions);
       break;
+    case ObjectKind::Toolchains:
+      this->BuildClientRequestToolchains(r, versions);
+      break;
     case ObjectKind::InternalTest:
       this->BuildClientRequestInternalTest(r, versions);
       break;
@@ -765,6 +786,40 @@
   return cmakeFiles;
 }
 
+// The "toolchains" object kind.
+
+static unsigned int const ToolchainsV1Minor = 0;
+
+void cmFileAPI::BuildClientRequestToolchains(
+  ClientRequest& r, std::vector<RequestVersion> const& versions)
+{
+  // Select a known version from those requested.
+  for (RequestVersion const& v : versions) {
+    if ((v.Major == 1 && v.Minor <= ToolchainsV1Minor)) {
+      r.Version = v.Major;
+      break;
+    }
+  }
+  if (!r.Version) {
+    r.Error = NoSupportedVersion(versions);
+  }
+}
+
+Json::Value cmFileAPI::BuildToolchains(Object const& object)
+{
+  Json::Value toolchains = cmFileAPIToolchainsDump(*this, object.Version);
+  toolchains["kind"] = this->ObjectKindName(object.Kind);
+
+  Json::Value& version = toolchains["version"];
+  if (object.Version == 1) {
+    version = BuildVersion(1, ToolchainsV1Minor);
+  } else {
+    return toolchains; // should be unreachable
+  }
+
+  return toolchains;
+}
+
 // The "__test" object kind is for internal testing of CMake.
 
 static unsigned int const InternalTestV1Minor = 3;
@@ -828,5 +883,13 @@
     requests.append(std::move(request)); // NOLINT(*)
   }
 
+  {
+    Json::Value request = Json::objectValue;
+    request["kind"] = ObjectKindName(ObjectKind::Toolchains);
+    Json::Value& versions = request["version"] = Json::arrayValue;
+    versions.append(BuildVersion(1, ToolchainsV1Minor));
+    requests.append(std::move(request)); // NOLINT(*)
+  }
+
   return capabilities;
 }
diff --git a/Source/cmFileAPI.h b/Source/cmFileAPI.h
index 086a92a..22302b4 100644
--- a/Source/cmFileAPI.h
+++ b/Source/cmFileAPI.h
@@ -56,6 +56,7 @@
     CodeModel,
     Cache,
     CMakeFiles,
+    Toolchains,
     InternalTest
   };
 
@@ -200,6 +201,10 @@
     ClientRequest& r, std::vector<RequestVersion> const& versions);
   Json::Value BuildCMakeFiles(Object const& object);
 
+  void BuildClientRequestToolchains(
+    ClientRequest& r, std::vector<RequestVersion> const& versions);
+  Json::Value BuildToolchains(Object const& object);
+
   void BuildClientRequestInternalTest(
     ClientRequest& r, std::vector<RequestVersion> const& versions);
   Json::Value BuildInternalTest(Object const& object);
diff --git a/Source/cmFileAPIToolchains.cxx b/Source/cmFileAPIToolchains.cxx
new file mode 100644
index 0000000..722c114
--- /dev/null
+++ b/Source/cmFileAPIToolchains.cxx
@@ -0,0 +1,151 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmFileAPIToolchains.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <cm3p/json/value.h>
+
+#include "cmFileAPI.h"
+#include "cmGlobalGenerator.h"
+#include "cmMakefile.h"
+#include "cmProperty.h"
+#include "cmState.h"
+#include "cmStringAlgorithms.h"
+#include "cmake.h"
+
+namespace {
+
+struct ToolchainVariable
+{
+  std::string ObjectKey;
+  std::string VariableSuffix;
+  bool IsList;
+};
+
+class Toolchains
+{
+  cmFileAPI& FileAPI;
+  unsigned long Version;
+
+  static const std::vector<ToolchainVariable> CompilerVariables;
+  static const std::vector<ToolchainVariable> CompilerImplicitVariables;
+  static const ToolchainVariable SourceFileExtensionsVariable;
+
+  Json::Value DumpToolchains();
+  Json::Value DumpToolchain(std::string const& lang);
+  Json::Value DumpToolchainVariables(
+    cmMakefile const* mf, std::string const& lang,
+    std::vector<ToolchainVariable> const& variables);
+  void DumpToolchainVariable(cmMakefile const* mf, Json::Value& object,
+                             std::string const& lang,
+                             ToolchainVariable const& variable);
+
+public:
+  Toolchains(cmFileAPI& fileAPI, unsigned long version);
+  Json::Value Dump();
+};
+
+const std::vector<ToolchainVariable> Toolchains::CompilerVariables{
+  { "path", "COMPILER", false },
+  { "id", "COMPILER_ID", false },
+  { "version", "COMPILER_VERSION", false },
+  { "target", "COMPILER_TARGET", false },
+};
+
+const std::vector<ToolchainVariable> Toolchains::CompilerImplicitVariables{
+  { "includeDirectories", "IMPLICIT_INCLUDE_DIRECTORIES", true },
+  { "linkDirectories", "IMPLICIT_LINK_DIRECTORIES", true },
+  { "linkFrameworkDirectories", "IMPLICIT_LINK_FRAMEWORK_DIRECTORIES", true },
+  { "linkLibraries", "IMPLICIT_LINK_LIBRARIES", true },
+};
+
+const ToolchainVariable Toolchains::SourceFileExtensionsVariable{
+  "sourceFileExtensions", "SOURCE_FILE_EXTENSIONS", true
+};
+
+Toolchains::Toolchains(cmFileAPI& fileAPI, unsigned long version)
+  : FileAPI(fileAPI)
+  , Version(version)
+{
+  static_cast<void>(this->Version);
+}
+
+Json::Value Toolchains::Dump()
+{
+  Json::Value toolchains = Json::objectValue;
+  toolchains["toolchains"] = this->DumpToolchains();
+  return toolchains;
+}
+
+Json::Value Toolchains::DumpToolchains()
+{
+  Json::Value toolchains = Json::arrayValue;
+
+  for (std::string const& lang :
+       this->FileAPI.GetCMakeInstance()->GetState()->GetEnabledLanguages()) {
+    toolchains.append(this->DumpToolchain(lang));
+  }
+
+  return toolchains;
+}
+
+Json::Value Toolchains::DumpToolchain(std::string const& lang)
+{
+  const auto& mf =
+    this->FileAPI.GetCMakeInstance()->GetGlobalGenerator()->GetMakefiles()[0];
+  Json::Value toolchain = Json::objectValue;
+  toolchain["language"] = lang;
+  toolchain["compiler"] =
+    this->DumpToolchainVariables(mf.get(), lang, CompilerVariables);
+  toolchain["compiler"]["implicit"] =
+    this->DumpToolchainVariables(mf.get(), lang, CompilerImplicitVariables);
+  this->DumpToolchainVariable(mf.get(), toolchain, lang,
+                              SourceFileExtensionsVariable);
+  return toolchain;
+}
+
+Json::Value Toolchains::DumpToolchainVariables(
+  cmMakefile const* mf, std::string const& lang,
+  std::vector<ToolchainVariable> const& variables)
+{
+  Json::Value object = Json::objectValue;
+  for (const auto& variable : variables) {
+    this->DumpToolchainVariable(mf, object, lang, variable);
+  }
+  return object;
+}
+
+void Toolchains::DumpToolchainVariable(cmMakefile const* mf,
+                                       Json::Value& object,
+                                       std::string const& lang,
+                                       ToolchainVariable const& variable)
+{
+  std::string const variableName =
+    cmStrCat("CMAKE_", lang, "_", variable.VariableSuffix);
+
+  if (variable.IsList) {
+    std::vector<std::string> values;
+    if (mf->GetDefExpandList(variableName, values)) {
+      Json::Value jsonArray = Json::arrayValue;
+      for (std::string const& value : values) {
+        jsonArray.append(value);
+      }
+      object[variable.ObjectKey] = jsonArray;
+    }
+  } else {
+    cmProp def = mf->GetDefinition(variableName);
+    if (def) {
+      object[variable.ObjectKey] = *def;
+    }
+  }
+}
+}
+
+Json::Value cmFileAPIToolchainsDump(cmFileAPI& fileAPI, unsigned long version)
+{
+  Toolchains toolchains(fileAPI, version);
+  return toolchains.Dump();
+}
diff --git a/Source/cmFileAPIToolchains.h b/Source/cmFileAPIToolchains.h
new file mode 100644
index 0000000..c188807
--- /dev/null
+++ b/Source/cmFileAPIToolchains.h
@@ -0,0 +1,12 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <cm3p/json/value.h>
+
+class cmFileAPI;
+
+extern Json::Value cmFileAPIToolchainsDump(cmFileAPI& fileAPI,
+                                           unsigned long version);
diff --git a/Tests/RunCMake/CommandLine/E_capabilities-stdout.txt b/Tests/RunCMake/CommandLine/E_capabilities-stdout.txt
index a8b6584..c76c92d 100644
--- a/Tests/RunCMake/CommandLine/E_capabilities-stdout.txt
+++ b/Tests/RunCMake/CommandLine/E_capabilities-stdout.txt
@@ -1 +1 @@
-^{"fileApi":{"requests":\[{"kind":"codemodel","version":\[{"major":2,"minor":2}]},{"kind":"cache","version":\[{"major":2,"minor":0}]},{"kind":"cmakeFiles","version":\[{"major":1,"minor":0}]}]},"generators":\[.*\],"serverMode":false,"version":{.*}}$
+^{"fileApi":{"requests":\[{"kind":"codemodel","version":\[{"major":2,"minor":2}]},{"kind":"cache","version":\[{"major":2,"minor":0}]},{"kind":"cmakeFiles","version":\[{"major":1,"minor":0}]},{"kind":"toolchains","version":\[{"major":1,"minor":0}]}]},"generators":\[.*\],"serverMode":false,"version":{.*}}$
diff --git a/Tests/RunCMake/FileAPI/RunCMakeTest.cmake b/Tests/RunCMake/FileAPI/RunCMakeTest.cmake
index 2bb2765..ae3d179 100644
--- a/Tests/RunCMake/FileAPI/RunCMakeTest.cmake
+++ b/Tests/RunCMake/FileAPI/RunCMakeTest.cmake
@@ -24,6 +24,7 @@
   file(GLOB index ${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/reply/index-*.json)
   execute_process(
     COMMAND ${PYTHON_EXECUTABLE} "${RunCMake_SOURCE_DIR}/${case}-check.py" "${index}" "${CMAKE_CXX_COMPILER_ID}"
+      "${RunCMake_TEST_BINARY_DIR}"
     RESULT_VARIABLE result
     OUTPUT_VARIABLE output
     ERROR_VARIABLE output
@@ -62,3 +63,4 @@
 run_object(codemodel-v2)
 run_object(cache-v2)
 run_object(cmakeFiles-v1)
+run_object(toolchains-v1)
diff --git a/Tests/RunCMake/FileAPI/toolchains-v1-ClientStateful-check.cmake b/Tests/RunCMake/FileAPI/toolchains-v1-ClientStateful-check.cmake
new file mode 100644
index 0000000..ce38461
--- /dev/null
+++ b/Tests/RunCMake/FileAPI/toolchains-v1-ClientStateful-check.cmake
@@ -0,0 +1,11 @@
+set(expect
+  query
+  query/client-foo
+  query/client-foo/query.json
+  reply
+  reply/index-[0-9.T-]+.json
+  reply/toolchains-v1-[0-9a-f]+.json
+  )
+check_api("^${expect}$")
+
+check_python(toolchains-v1)
diff --git a/Tests/RunCMake/FileAPI/toolchains-v1-ClientStateful-prep.cmake b/Tests/RunCMake/FileAPI/toolchains-v1-ClientStateful-prep.cmake
new file mode 100644
index 0000000..ca62edf
--- /dev/null
+++ b/Tests/RunCMake/FileAPI/toolchains-v1-ClientStateful-prep.cmake
@@ -0,0 +1,4 @@
+file(REMOVE_RECURSE ${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query)
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-foo/query.json" [[
+{ "requests": [ { "kind": "toolchains", "version" : 1 } ] }
+]])
diff --git a/Tests/RunCMake/FileAPI/toolchains-v1-ClientStateless-check.cmake b/Tests/RunCMake/FileAPI/toolchains-v1-ClientStateless-check.cmake
new file mode 100644
index 0000000..4676dd8
--- /dev/null
+++ b/Tests/RunCMake/FileAPI/toolchains-v1-ClientStateless-check.cmake
@@ -0,0 +1,11 @@
+set(expect
+  query
+  query/client-foo
+  query/client-foo/toolchains-v1
+  reply
+  reply/index-[0-9.T-]+.json
+  reply/toolchains-v1-[0-9a-f]+.json
+  )
+check_api("^${expect}$")
+
+check_python(toolchains-v1)
diff --git a/Tests/RunCMake/FileAPI/toolchains-v1-ClientStateless-prep.cmake b/Tests/RunCMake/FileAPI/toolchains-v1-ClientStateless-prep.cmake
new file mode 100644
index 0000000..7edff93
--- /dev/null
+++ b/Tests/RunCMake/FileAPI/toolchains-v1-ClientStateless-prep.cmake
@@ -0,0 +1,2 @@
+file(REMOVE_RECURSE ${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query)
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-foo/toolchains-v1" "")
diff --git a/Tests/RunCMake/FileAPI/toolchains-v1-SharedStateless-check.cmake b/Tests/RunCMake/FileAPI/toolchains-v1-SharedStateless-check.cmake
new file mode 100644
index 0000000..8e83758
--- /dev/null
+++ b/Tests/RunCMake/FileAPI/toolchains-v1-SharedStateless-check.cmake
@@ -0,0 +1,10 @@
+set(expect
+  query
+  query/toolchains-v1
+  reply
+  reply/index-[0-9.T-]+.json
+  reply/toolchains-v1-[0-9a-f]+.json
+  )
+check_api("^${expect}$")
+
+check_python(toolchains-v1)
diff --git a/Tests/RunCMake/FileAPI/toolchains-v1-SharedStateless-prep.cmake b/Tests/RunCMake/FileAPI/toolchains-v1-SharedStateless-prep.cmake
new file mode 100644
index 0000000..2db73a1
--- /dev/null
+++ b/Tests/RunCMake/FileAPI/toolchains-v1-SharedStateless-prep.cmake
@@ -0,0 +1,2 @@
+file(REMOVE_RECURSE ${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query)
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/toolchains-v1" "")
diff --git a/Tests/RunCMake/FileAPI/toolchains-v1-check.py b/Tests/RunCMake/FileAPI/toolchains-v1-check.py
new file mode 100644
index 0000000..a0e50c2
--- /dev/null
+++ b/Tests/RunCMake/FileAPI/toolchains-v1-check.py
@@ -0,0 +1,86 @@
+from check_index import *
+import os
+
+class ExpectedVar(object):
+    def __init__(self, name):
+        self.name = name
+
+class ExpectedList(object):
+    def __init__(self, name):
+        self.name = name
+
+EXPECTED_TOOLCHAIN = {
+    "language": "CXX",
+    "compiler": {
+        "path": ExpectedVar("CMAKE_CXX_COMPILER"),
+        "id": ExpectedVar("CMAKE_CXX_COMPILER_ID"),
+        "version": ExpectedVar("CMAKE_CXX_COMPILER_VERSION"),
+        "target": ExpectedVar("CMAKE_CXX_COMPILER_TARGET"),
+        "implicit": {
+            "includeDirectories": \
+                ExpectedList("CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES"),
+            "linkDirectories": \
+                ExpectedList("CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES"),
+            "linkFrameworkDirectories": \
+                ExpectedList(
+                    "CMAKE_CXX_IMPLICIT_LINK_FRAMEWORK_DIRECTORIES"),
+            "linkLibraries": \
+                ExpectedList("CMAKE_CXX_IMPLICIT_LINK_LIBRARIES"),
+        }
+    },
+    "sourceFileExtensions": \
+        ExpectedList("CMAKE_CXX_SOURCE_FILE_EXTENSIONS"),
+}
+
+def check_objects(o):
+    assert is_list(o)
+    assert len(o) == 1
+    check_index_object(o[0], "toolchains", 1, 0, check_object_toolchains)
+
+def check_object_toolchains(o):
+    assert sorted(o.keys()) == ["kind", "toolchains", "version"]
+    # The "kind" and "version" members are handled by check_index_object.
+    toolchains = o["toolchains"]
+    assert is_list(toolchains)
+
+    # Other platform-specific toolchains may exist (like RC on Windows).
+    has_cxx_toolchain = False
+    for toolchain in toolchains:
+        assert is_dict(toolchain)
+        assert "language" in toolchain
+        if toolchain["language"] == "CXX":
+            check_object_toolchain(toolchain, EXPECTED_TOOLCHAIN)
+            has_cxx_toolchain = True
+
+    assert has_cxx_toolchain
+
+def check_object_toolchain(o, expected):
+    expected_keys = [
+        key for (key, value) in expected.items()
+        if is_string(value) or is_dict(value)
+            or (type(value) in (ExpectedVar, ExpectedList)
+                and variables[value.name]["defined"])]
+    assert sorted(o.keys()) == sorted(expected_keys)
+
+    for key in expected_keys:
+        value = expected[key]
+        if is_string(value):
+            assert o[key] == value
+        elif is_dict(value):
+            check_object_toolchain(o[key], value)
+        elif type(value) == ExpectedVar:
+            assert o[key] == variables[value.name]["value"]
+        elif type(value) == ExpectedList:
+            expected_items = filter(
+                None, variables[value.name]["value"].split(";"))
+            check_list_match(lambda a, b: a == b, o[key], expected_items)
+        else:
+            assert False
+
+with open(os.path.join(sys.argv[3], "toolchain_variables.json")) as f:
+    variables = json.load(f)
+
+assert is_dict(variables)
+assert is_dict(index)
+assert sorted(index.keys()) == ["cmake", "objects", "reply"]
+check_objects(index["objects"])
diff --git a/Tests/RunCMake/FileAPI/toolchains-v1.cmake b/Tests/RunCMake/FileAPI/toolchains-v1.cmake
new file mode 100644
index 0000000..367aade
--- /dev/null
+++ b/Tests/RunCMake/FileAPI/toolchains-v1.cmake
@@ -0,0 +1,22 @@
+enable_language(CXX)
+
+set(variable_suffixes
+  COMPILER COMPILER_ID COMPILER_VERSION COMPILER_TARGET
+  IMPLICIT_INCLUDE_DIRECTORIES IMPLICIT_LINK_DIRECTORIES
+  IMPLICIT_LINK_FRAMEWORK_DIRECTORIES IMPLICIT_LINK_LIBRARIES
+  SOURCE_FILE_EXTENSIONS)
+set(language CXX)
+set(json "{}")
+
+foreach(variable_suffix ${variable_suffixes})
+  set(variable "CMAKE_${language}_${variable_suffix}")
+  string(JSON json SET "${json}" "${variable}" "{}")
+  if(DEFINED "${variable}")
+    string(JSON json SET "${json}" "${variable}" "defined" "true")
+    string(JSON json SET "${json}" "${variable}" "value" "\"${${variable}}\"")
+  else()
+    string(JSON json SET "${json}" "${variable}" "defined" "false")
+  endif()
+endforeach()
+
+file(WRITE ${CMAKE_BINARY_DIR}/toolchain_variables.json "${json}")