Browse Source

FileAPI: Add "configureLog" object kind

Provide clients with a way to get a known set of configure log event
versions.

Issue: #23200
Brad King 2 years ago
parent
commit
d811d86fd7

+ 10 - 0
Help/manual/cmake-configure-log.7.rst

@@ -73,6 +73,16 @@ they do not understand:
 * If an existing build tree is re-configured with a different version of
   CMake, the log may contain different versions of the same event kind.
 
+* If :manual:`cmake-file-api(7)` queries request one or more
+  :ref:`configureLog <file-api configureLog>` object versions,
+  the log may contain multiple entries for the same event, each
+  with a different version of its event kind.
+
+IDEs should write a :manual:`cmake-file-api(7)` query requesting a
+specific :ref:`configureLog <file-api configureLog>` object version,
+before running CMake, and then read the configure log only as described
+by the file-api reply.
+
 Text Block Encoding
 -------------------
 

+ 39 - 0
Help/manual/cmake-file-api.7.rst

@@ -1298,6 +1298,45 @@ elsewhere in the containing object.  The backtrace graph object members are:
   directory then the path is specified relative to that directory.
   Otherwise the path is absolute.
 
+.. _`file-api configureLog`:
+
+Object Kind "configureLog"
+--------------------------
+
+The ``configureLog`` object kind describes the location and contents of
+a :manual:`cmake-configure-log(7)` file.
+
+There is only one ``configureLog`` object major version, version 1.
+
+"configureLog" version 1
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+``configureLog`` object version 1 is a JSON object:
+
+.. code-block:: json
+
+  {
+    "kind": "configureLog",
+    "version": { "major": 1, "minor": 0 },
+    "path": "/path/to/top-level-build-dir/CMakeFiles/CMakeConfigureLog.yaml",
+    "eventKindNames": [ "try_compile-v1", "try_run-v1" ]
+  }
+
+The members specific to ``configureLog`` objects are:
+
+``path``
+  A string specifying the path to the configure log file.
+  Clients must read the log file from this path, which may be
+  different than the path documented by :manual:`cmake-configure-log(7)`.
+  The log file may not exist if no events are logged.
+
+``eventKindNames``
+  A JSON array whose entries are each a JSON string naming one
+  of the :manual:`cmake-configure-log(7)` versioned event kinds.
+  At most one version of each configure log event kind will be listed.
+  Although the configure log may contain other (versioned) event kinds,
+  clients must ignore those that are not listed in this field.
+
 Object Kind "cache"
 -------------------
 

+ 3 - 0
Help/release/dev/configure-log.rst

@@ -3,3 +3,6 @@ Configure Log
 
 * CMake now writes a YAML log of configure-time checks.
   See the :manual:`cmake-configure-log(7)` manual.
+
+* The :manual:`cmake-file-api(7)` gained a new "configureLog" object kind
+  that enables stable access to the :manual:`cmake-configure-log(7)`.

+ 2 - 0
Source/CMakeLists.txt

@@ -237,6 +237,8 @@ add_library(
   cmFileAPICache.h
   cmFileAPICodemodel.cxx
   cmFileAPICodemodel.h
+  cmFileAPIConfigureLog.cxx
+  cmFileAPIConfigureLog.h
   cmFileAPICMakeFiles.cxx
   cmFileAPICMakeFiles.h
   cmFileAPIToolchains.cxx

+ 89 - 5
Source/cmFileAPI.cxx

@@ -18,6 +18,7 @@
 #include "cmFileAPICMakeFiles.h"
 #include "cmFileAPICache.h"
 #include "cmFileAPICodemodel.h"
+#include "cmFileAPIConfigureLog.h"
 #include "cmFileAPIToolchains.h"
 #include "cmGlobalGenerator.h"
 #include "cmStringAlgorithms.h"
@@ -66,6 +67,26 @@ void cmFileAPI::ReadQueries()
   }
 }
 
+std::vector<unsigned long> cmFileAPI::GetConfigureLogVersions()
+{
+  std::vector<unsigned long> versions;
+  auto getConfigureLogVersions = [&versions](Query const& q) {
+    for (Object const& o : q.Known) {
+      if (o.Kind == ObjectKind::ConfigureLog) {
+        versions.emplace_back(o.Version);
+      }
+    }
+  };
+  getConfigureLogVersions(this->TopQuery);
+  for (auto const& client : this->ClientQueries) {
+    getConfigureLogVersions(client.second.DirQuery);
+  }
+  std::sort(versions.begin(), versions.end());
+  versions.erase(std::unique(versions.begin(), versions.end()),
+                 versions.end());
+  return versions;
+}
+
 void cmFileAPI::WriteReplies()
 {
   if (this->QueryExists) {
@@ -241,6 +262,17 @@ bool cmFileAPI::ReadQuery(std::string const& query,
     objects.push_back(o);
     return true;
   }
+  if (kindName == ObjectKindName(ObjectKind::ConfigureLog)) {
+    Object o;
+    o.Kind = ObjectKind::ConfigureLog;
+    if (verStr == "v1") {
+      o.Version = 1;
+    } else {
+      return false;
+    }
+    objects.push_back(o);
+    return true;
+  }
   if (kindName == ObjectKindName(ObjectKind::Cache)) {
     Object o;
     o.Kind = ObjectKind::Cache;
@@ -411,11 +443,12 @@ const char* cmFileAPI::ObjectKindName(ObjectKind kind)
 {
   // Keep in sync with ObjectKind enum.
   static const char* objectKindNames[] = {
-    "codemodel",  //
-    "cache",      //
-    "cmakeFiles", //
-    "toolchains", //
-    "__test"      //
+    "codemodel",    //
+    "configureLog", //
+    "cache",        //
+    "cmakeFiles",   //
+    "toolchains",   //
+    "__test"        //
   };
   return objectKindNames[static_cast<size_t>(kind)];
 }
@@ -442,6 +475,9 @@ Json::Value cmFileAPI::BuildObject(Object const& object)
     case ObjectKind::CodeModel:
       value = this->BuildCodeModel(object);
       break;
+    case ObjectKind::ConfigureLog:
+      value = this->BuildConfigureLog(object);
+      break;
     case ObjectKind::Cache:
       value = this->BuildCache(object);
       break;
@@ -503,6 +539,8 @@ cmFileAPI::ClientRequest cmFileAPI::BuildClientRequest(
 
   if (kindName == this->ObjectKindName(ObjectKind::CodeModel)) {
     r.Kind = ObjectKind::CodeModel;
+  } else if (kindName == this->ObjectKindName(ObjectKind::ConfigureLog)) {
+    r.Kind = ObjectKind::ConfigureLog;
   } else if (kindName == this->ObjectKindName(ObjectKind::Cache)) {
     r.Kind = ObjectKind::Cache;
   } else if (kindName == this->ObjectKindName(ObjectKind::CMakeFiles)) {
@@ -530,6 +568,9 @@ cmFileAPI::ClientRequest cmFileAPI::BuildClientRequest(
     case ObjectKind::CodeModel:
       this->BuildClientRequestCodeModel(r, versions);
       break;
+    case ObjectKind::ConfigureLog:
+      this->BuildClientRequestConfigureLog(r, versions);
+      break;
     case ObjectKind::Cache:
       this->BuildClientRequestCache(r, versions);
       break;
@@ -719,6 +760,41 @@ Json::Value cmFileAPI::BuildCodeModel(Object const& object)
   return codemodel;
 }
 
+// The "configureLog" object kind.
+
+// Update Help/manual/cmake-file-api.7.rst when updating this constant.
+static unsigned int const ConfigureLogV1Minor = 0;
+
+void cmFileAPI::BuildClientRequestConfigureLog(
+  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 <= ConfigureLogV1Minor)) {
+      r.Version = v.Major;
+      break;
+    }
+  }
+  if (!r.Version) {
+    r.Error = NoSupportedVersion(versions);
+  }
+}
+
+Json::Value cmFileAPI::BuildConfigureLog(Object const& object)
+{
+  Json::Value configureLog = cmFileAPIConfigureLogDump(*this, object.Version);
+  configureLog["kind"] = this->ObjectKindName(object.Kind);
+
+  Json::Value& version = configureLog["version"];
+  if (object.Version == 1) {
+    version = BuildVersion(1, ConfigureLogV1Minor);
+  } else {
+    return configureLog; // should be unreachable
+  }
+
+  return configureLog;
+}
+
 // The "cache" object kind.
 
 static unsigned int const CacheV2Minor = 0;
@@ -868,6 +944,14 @@ Json::Value cmFileAPI::ReportCapabilities()
     requests.append(std::move(request)); // NOLINT(*)
   }
 
+  {
+    Json::Value request = Json::objectValue;
+    request["kind"] = ObjectKindName(ObjectKind::ConfigureLog);
+    Json::Value& versions = request["version"] = Json::arrayValue;
+    versions.append(BuildVersion(1, ConfigureLogV1Minor));
+    requests.append(std::move(request)); // NOLINT(*)
+  }
+
   {
     Json::Value request = Json::objectValue;
     request["kind"] = ObjectKindName(ObjectKind::Cache);

+ 8 - 0
Source/cmFileAPI.h

@@ -24,6 +24,9 @@ public:
   /** Read fileapi queries from disk.  */
   void ReadQueries();
 
+  /** Get the list of configureLog object kind versions requested.  */
+  std::vector<unsigned long> GetConfigureLogVersions();
+
   /** Write fileapi replies to disk.  */
   void WriteReplies();
 
@@ -54,6 +57,7 @@ private:
   enum class ObjectKind
   {
     CodeModel,
+    ConfigureLog,
     Cache,
     CMakeFiles,
     Toolchains,
@@ -193,6 +197,10 @@ private:
     ClientRequest& r, std::vector<RequestVersion> const& versions);
   Json::Value BuildCodeModel(Object const& object);
 
+  void BuildClientRequestConfigureLog(
+    ClientRequest& r, std::vector<RequestVersion> const& versions);
+  Json::Value BuildConfigureLog(Object const& object);
+
   void BuildClientRequestCache(ClientRequest& r,
                                std::vector<RequestVersion> const& versions);
   Json::Value BuildCache(Object const& object);

+ 67 - 0
Source/cmFileAPIConfigureLog.cxx

@@ -0,0 +1,67 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmFileAPIConfigureLog.h"
+
+#include <cm3p/json/value.h>
+
+#include "cmFileAPI.h"
+#include "cmStringAlgorithms.h"
+#include "cmake.h"
+
+namespace {
+
+class ConfigureLog
+{
+  cmFileAPI& FileAPI;
+  unsigned long Version;
+
+  Json::Value DumpPath();
+  Json::Value DumpEventKindNames();
+
+public:
+  ConfigureLog(cmFileAPI& fileAPI, unsigned long version);
+  Json::Value Dump();
+};
+
+ConfigureLog::ConfigureLog(cmFileAPI& fileAPI, unsigned long version)
+  : FileAPI(fileAPI)
+  , Version(version)
+{
+  static_cast<void>(this->Version);
+}
+
+Json::Value ConfigureLog::Dump()
+{
+  Json::Value configureLog = Json::objectValue;
+  configureLog["path"] = this->DumpPath();
+  configureLog["eventKindNames"] = this->DumpEventKindNames();
+  return configureLog;
+}
+
+Json::Value ConfigureLog::DumpPath()
+{
+  return cmStrCat(this->FileAPI.GetCMakeInstance()->GetHomeOutputDirectory(),
+                  "/CMakeFiles/CMakeConfigureLog.yaml");
+}
+
+Json::Value ConfigureLog::DumpEventKindNames()
+{
+  // Report at most one version of each event kind.
+  // If a new event kind is added, increment ConfigureLogV1Minor.
+  // If a new version of an existing event kind is added, a new
+  // major version of the configureLog object kind is needed.
+  Json::Value eventKindNames = Json::arrayValue;
+  if (this->Version == 1) {
+    eventKindNames.append("try_compile-v1"); // WriteTryCompileEvent
+    eventKindNames.append("try_run-v1");     // WriteTryRunEvent
+  }
+  return eventKindNames;
+}
+}
+
+Json::Value cmFileAPIConfigureLogDump(cmFileAPI& fileAPI,
+                                      unsigned long version)
+{
+  ConfigureLog configureLog(fileAPI, version);
+  return configureLog.Dump();
+}

+ 12 - 0
Source/cmFileAPIConfigureLog.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 cmFileAPIConfigureLogDump(cmFileAPI& fileAPI,
+                                             unsigned long version);

+ 1 - 0
Source/cmTryCompileCommand.cxx

@@ -21,6 +21,7 @@ namespace {
 void WriteTryCompileEvent(cmConfigureLog& log, cmMakefile const& mf,
                           cmTryCompileResult const& compileResult)
 {
+  // Keep in sync with cmFileAPIConfigureLog's DumpEventKindNames.
   static const std::vector<unsigned long> LogVersionsWithTryCompileV1{ 1 };
 
   if (log.IsAnyLogVersionEnabled(LogVersionsWithTryCompileV1)) {

+ 1 - 0
Source/cmTryRunCommand.cxx

@@ -40,6 +40,7 @@ void WriteTryRunEvent(cmConfigureLog& log, cmMakefile const& mf,
                       cmTryCompileResult const& compileResult,
                       cmTryRunResult const& runResult)
 {
+  // Keep in sync with cmFileAPIConfigureLog's DumpEventKindNames.
   static const std::vector<unsigned long> LogVersionsWithTryRunV1{ 1 };
 
   if (log.IsAnyLogVersionEnabled(LogVersionsWithTryRunV1)) {

+ 1 - 1
Source/cmake.cxx

@@ -2429,7 +2429,7 @@ int cmake::ActualConfigure()
     this->TruncateOutputLog("CMakeConfigureLog.yaml");
     this->ConfigureLog = cm::make_unique<cmConfigureLog>(
       cmStrCat(this->GetHomeOutputDirectory(), "/CMakeFiles"_s),
-      std::vector<unsigned long>());
+      this->FileAPI->GetConfigureLogVersions());
   }
 #endif
 

+ 1 - 1
Tests/RunCMake/CommandLine/E_capabilities-stdout.txt

@@ -1 +1 @@
-^{"fileApi":{"requests":\[{"kind":"codemodel","version":\[{"major":2,"minor":5}]},{"kind":"cache","version":\[{"major":2,"minor":0}]},{"kind":"cmakeFiles","version":\[{"major":1,"minor":0}]},{"kind":"toolchains","version":\[{"major":1,"minor":0}]}]},"generators":\[.*\],"serverMode":false,"tls":(true|false),"version":{.*}}$
+^{"fileApi":{"requests":\[{"kind":"codemodel","version":\[{"major":2,"minor":5}]},{"kind":"configureLog","version":\[{"major":1,"minor":0}]},{"kind":"cache","version":\[{"major":2,"minor":0}]},{"kind":"cmakeFiles","version":\[{"major":1,"minor":0}]},{"kind":"toolchains","version":\[{"major":1,"minor":0}]}]},"generators":\[.*\],"serverMode":false,"tls":(true|false),"version":{.*}}$

+ 1 - 0
Tests/RunCMake/FileAPI/RunCMakeTest.cmake

@@ -65,6 +65,7 @@ function(run_object object)
 endfunction()
 
 run_object(codemodel-v2)
+run_object(configureLog-v1)
 run_object(cache-v2)
 run_object(cmakeFiles-v1)
 run_object(toolchains-v1)

+ 11 - 0
Tests/RunCMake/FileAPI/configureLog-v1-ClientStateful-check.cmake

@@ -0,0 +1,11 @@
+set(expect
+  query
+  query/client-foo
+  query/client-foo/query.json
+  reply
+  reply/configureLog-v1-[0-9a-f]+.json
+  reply/index-[0-9.T-]+.json
+  )
+check_api("^${expect}$")
+
+check_python(configureLog-v1)

+ 4 - 0
Tests/RunCMake/FileAPI/configureLog-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": "configureLog", "version" : 1 } ] }
+]])

+ 11 - 0
Tests/RunCMake/FileAPI/configureLog-v1-ClientStateless-check.cmake

@@ -0,0 +1,11 @@
+set(expect
+  query
+  query/client-foo
+  query/client-foo/configureLog-v1
+  reply
+  reply/configureLog-v1-[0-9a-f]+.json
+  reply/index-[0-9.T-]+.json
+  )
+check_api("^${expect}$")
+
+check_python(configureLog-v1)

+ 2 - 0
Tests/RunCMake/FileAPI/configureLog-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/configureLog-v1" "")

+ 10 - 0
Tests/RunCMake/FileAPI/configureLog-v1-SharedStateless-check.cmake

@@ -0,0 +1,10 @@
+set(expect
+  query
+  query/configureLog-v1
+  reply
+  reply/configureLog-v1-[0-9a-f]+.json
+  reply/index-[0-9.T-]+.json
+  )
+check_api("^${expect}$")
+
+check_python(configureLog-v1)

+ 2 - 0
Tests/RunCMake/FileAPI/configureLog-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/configureLog-v1" "")

+ 21 - 0
Tests/RunCMake/FileAPI/configureLog-v1-check.py

@@ -0,0 +1,21 @@
+from check_index import *
+import os
+
+def check_objects(o):
+    assert is_list(o)
+    assert len(o) == 1
+    check_index_object(o[0], "configureLog", 1, 0, check_object_configureLog)
+
+def check_object_configureLog(o):
+    assert sorted(o.keys()) == ["eventKindNames", "kind", "path", "version"]
+    # The "kind" and "version" members are handled by check_index_object.
+    path = o["path"]
+    assert matches(path, "^.*/CMakeFiles/CMakeConfigureLog\\.yaml$")
+    assert os.path.exists(path)
+    eventKindNames = o["eventKindNames"]
+    assert is_list(eventKindNames)
+    assert sorted(eventKindNames) == ["try_compile-v1", "try_run-v1"]
+
+assert is_dict(index)
+assert sorted(index.keys()) == ["cmake", "objects", "reply"]
+check_objects(index["objects"])

+ 1 - 0
Tests/RunCMake/FileAPI/configureLog-v1.cmake

@@ -0,0 +1 @@
+enable_language(C)