Переглянути джерело

fileapi: Generate partial reply when buildsystem generation fails

In particular, the `configureLog` reply is useful for IDEs to read
`CMakeFiles/CMakeConfigureLog.yaml` when configuration fails.

Fixes: #26621
Brad King 6 місяців тому
батько
коміт
0cc962665b
42 змінених файлів з 616 додано та 16 видалено
  1. 56 11
      Help/manual/cmake-file-api.7.rst
  2. 6 0
      Help/release/dev/fileapi-reply-on-failure.rst
  3. 21 3
      Source/cmFileAPI.cxx
  4. 13 1
      Source/cmFileAPI.h
  5. 8 1
      Source/cmake.cxx
  6. 22 0
      Tests/RunCMake/FileAPI/FailConfigure-check.cmake
  7. 74 0
      Tests/RunCMake/FileAPI/FailConfigure-check.py
  8. 26 0
      Tests/RunCMake/FileAPI/FailConfigure-prep.cmake
  9. 1 0
      Tests/RunCMake/FileAPI/FailConfigure-result.txt
  10. 4 0
      Tests/RunCMake/FileAPI/FailConfigure-stderr.txt
  11. 1 0
      Tests/RunCMake/FileAPI/FailConfigure.cmake
  12. 2 0
      Tests/RunCMake/FileAPI/RunCMakeTest.cmake
  13. 16 0
      Tests/RunCMake/FileAPI/cache-v2-FailConfigure-check.cmake
  14. 43 0
      Tests/RunCMake/FileAPI/cache-v2-FailConfigure-check.py
  15. 6 0
      Tests/RunCMake/FileAPI/cache-v2-FailConfigure-prep.cmake
  16. 1 0
      Tests/RunCMake/FileAPI/cache-v2-FailConfigure-result.txt
  17. 4 0
      Tests/RunCMake/FileAPI/cache-v2-FailConfigure-stderr.txt
  18. 4 0
      Tests/RunCMake/FileAPI/cache-v2.cmake
  19. 16 0
      Tests/RunCMake/FileAPI/cmakeFiles-v1-FailConfigure-check.cmake
  20. 43 0
      Tests/RunCMake/FileAPI/cmakeFiles-v1-FailConfigure-check.py
  21. 6 0
      Tests/RunCMake/FileAPI/cmakeFiles-v1-FailConfigure-prep.cmake
  22. 1 0
      Tests/RunCMake/FileAPI/cmakeFiles-v1-FailConfigure-result.txt
  23. 4 0
      Tests/RunCMake/FileAPI/cmakeFiles-v1-FailConfigure-stderr.txt
  24. 4 0
      Tests/RunCMake/FileAPI/cmakeFiles-v1.cmake
  25. 18 0
      Tests/RunCMake/FileAPI/codemodel-v2-FailConfigure-check.cmake
  26. 43 0
      Tests/RunCMake/FileAPI/codemodel-v2-FailConfigure-check.py
  27. 6 0
      Tests/RunCMake/FileAPI/codemodel-v2-FailConfigure-prep.cmake
  28. 1 0
      Tests/RunCMake/FileAPI/codemodel-v2-FailConfigure-result.txt
  29. 4 0
      Tests/RunCMake/FileAPI/codemodel-v2-FailConfigure-stderr.txt
  30. 4 0
      Tests/RunCMake/FileAPI/codemodel-v2.cmake
  31. 16 0
      Tests/RunCMake/FileAPI/configureLog-v1-FailConfigure-check.cmake
  32. 54 0
      Tests/RunCMake/FileAPI/configureLog-v1-FailConfigure-check.py
  33. 6 0
      Tests/RunCMake/FileAPI/configureLog-v1-FailConfigure-prep.cmake
  34. 1 0
      Tests/RunCMake/FileAPI/configureLog-v1-FailConfigure-result.txt
  35. 4 0
      Tests/RunCMake/FileAPI/configureLog-v1-FailConfigure-stderr.txt
  36. 3 0
      Tests/RunCMake/FileAPI/configureLog-v1.cmake
  37. 16 0
      Tests/RunCMake/FileAPI/toolchains-v1-FailConfigure-check.cmake
  38. 43 0
      Tests/RunCMake/FileAPI/toolchains-v1-FailConfigure-check.py
  39. 6 0
      Tests/RunCMake/FileAPI/toolchains-v1-FailConfigure-prep.cmake
  40. 1 0
      Tests/RunCMake/FileAPI/toolchains-v1-FailConfigure-result.txt
  41. 4 0
      Tests/RunCMake/FileAPI/toolchains-v1-FailConfigure-stderr.txt
  42. 4 0
      Tests/RunCMake/FileAPI/toolchains-v1.cmake

+ 56 - 11
Help/manual/cmake-file-api.7.rst

@@ -41,14 +41,22 @@ It has the following subdirectories:
   `v1 Client Stateless Query Files`_, or `v1 Client Stateful Query Files`_.
 
 ``reply/``
-  Holds reply files written by CMake whenever it runs to generate a build
-  system.  These are indexed by a `v1 Reply Index File`_ that may
-  reference additional `v1 Reply Files`_.  CMake owns all reply files.
-  Clients must never remove them.
+  Holds reply files written by CMake when it runs to generate a build system.
+  Clients may read reply files only when referenced by a reply index:
 
-  Clients may look for and read a reply index file at any time.
+  ``index-*.json``
+    A `v1 Reply Index File`_ written when CMake generates a build system.
+
+  ``error-*.json``
+    .. versionadded:: 4.1
+
+    A `v1 Reply Error Index`_ written when CMake fails to generate a build
+    system due to an error.
+
+  Clients may look for and read a reply index at any time.
   Clients may optionally create the ``reply/`` directory at any time
-  and monitor it for the appearance of a new reply index file.
+  and monitor it for the appearance of a new reply index.
+  CMake owns all reply files.  Clients must never remove them.
 
 .. versionadded:: 3.31
   Users can add query files to ``api/v1/query`` inside the
@@ -179,7 +187,7 @@ v1 Reply Index File
 -------------------
 
 CMake writes an ``index-*.json`` file to the ``v1/reply/`` directory
-whenever it runs to generate a build system.  Clients must read the
+when it successfully generates a build system.  Clients must read the
 reply index file first and may read other `v1 Reply Files`_ only by
 following references.  The form of the reply index file name is::
 
@@ -300,8 +308,12 @@ The members are:
     A member of this form appears for each of the
     `v1 Shared Stateless Query Files`_ that CMake recognized as a
     request for object kind ``<kind>`` with major version ``<major>``.
-    The value is a `v1 Reply File Reference`_ to the corresponding
-    reply file for that object kind and version.
+    The value is
+
+    * a `v1 Reply File Reference`_ to the corresponding reply file for
+      that object kind and version, or
+    * in a `v1 Reply Error Index`_, a JSON object with a single ``error``
+      member containing a string with an error message.
 
   ``<unknown>``
     A member of this form appears for each of the
@@ -320,8 +332,12 @@ The members are:
       A member of this form appears for each of the
       `v1 Client Stateless Query Files`_ that CMake recognized as a
       request for object kind ``<kind>`` with major version ``<major>``.
-      The value is a `v1 Reply File Reference`_ to the corresponding
-      reply file for that object kind and version.
+      The value is
+
+      * a `v1 Reply File Reference`_ to the corresponding reply file for
+        that object kind and version, or
+      * in a `v1 Reply Error Index`_, a JSON object with a single ``error``
+        member containing a string with an error message.
 
     ``<unknown>``
       A member of this form appears for each of the
@@ -375,6 +391,35 @@ using a JSON object with members:
   A JSON string specifying a path relative to the reply index file
   to another JSON file containing the object.
 
+.. _`file-api reply error index`:
+
+v1 Reply Error Index
+^^^^^^^^^^^^^^^^^^^^
+
+.. versionadded:: 4.1
+
+CMake writes an ``error-*.json`` file to the ``v1/reply/`` directory
+when it fails to generate a build system.  This reply error index
+follows the same naming pattern, syntax, and semantics of a
+`v1 Reply Index File`_, with the following exceptions:
+
+* The ``index-`` prefix is replaced by an ``error-`` prefix.
+
+* When a new error index is generated, old index files are *not*
+  deleted.  If a `v1 Reply Index File`_ exists, it indexes replies
+  from the most recent successful run.  If multiple ``index-*.json``
+  and/or ``error-*.json`` files are present, the one with the largest
+  name in lexicographic order, excluding the ``index-`` or ``error-``
+  prefix, is the current index.
+
+* Only a subset of `Object Kinds`_ are provided:
+
+  `configureLog <file-api configureLog_>`_
+    .. versionadded:: 4.1
+
+  Index entries for other object kinds contain an ``error`` message
+  instead of a `v1 Reply File Reference`_.
+
 v1 Reply Files
 --------------
 

+ 6 - 0
Help/release/dev/fileapi-reply-on-failure.rst

@@ -0,0 +1,6 @@
+fileapi-reply-on-failure
+------------------------
+
+* The :manual:`cmake-file-api(7)` :ref:`v1 <file-api v1>` now writes
+  partial replies when buildsystem generation fails with an error.
+  See the :ref:`v1 Reply Error Index <file-api reply error index>`.

+ 21 - 3
Source/cmFileAPI.cxx

@@ -105,14 +105,20 @@ std::vector<unsigned long> cmFileAPI::GetConfigureLogVersions()
   return versions;
 }
 
-void cmFileAPI::WriteReplies()
+void cmFileAPI::WriteReplies(IndexFor indexFor)
 {
+  bool const success = indexFor == IndexFor::Success;
+  this->ReplyIndexFor = indexFor;
+
   if (this->QueryExists) {
     cmSystemTools::MakeDirectory(this->APIv1 + "/reply");
-    this->WriteJsonFile(this->BuildReplyIndex(), "index", ComputeSuffixTime);
+    this->WriteJsonFile(this->BuildReplyIndex(), success ? "index" : "error",
+                        ComputeSuffixTime);
   }
 
-  this->RemoveOldReplyFiles();
+  if (success) {
+    this->RemoveOldReplyFiles();
+  }
 }
 
 std::vector<std::string> cmFileAPI::LoadDir(std::string const& dir)
@@ -453,6 +459,18 @@ Json::Value cmFileAPI::BuildReply(Query const& q)
 
 Json::Value cmFileAPI::BuildReplyEntry(Object const& object)
 {
+  if (this->ReplyIndexFor != IndexFor::Success) {
+    switch (object.Kind) {
+      case ObjectKind::ConfigureLog:
+        break;
+      case ObjectKind::CodeModel:
+      case ObjectKind::Cache:
+      case ObjectKind::CMakeFiles:
+      case ObjectKind::Toolchains:
+      case ObjectKind::InternalTest:
+        return this->BuildReplyError("no buildsystem generated");
+    }
+  }
   return this->AddReplyIndexObject(object);
 }
 

+ 13 - 1
Source/cmFileAPI.h

@@ -27,8 +27,17 @@ public:
   /** Get the list of configureLog object kind versions requested.  */
   std::vector<unsigned long> GetConfigureLogVersions();
 
+  /** Identify the situation in which WriteReplies is called.  */
+  enum class IndexFor
+  {
+    Success,
+    FailedConfigure,
+    FailedCompute,
+    FailedGenerate,
+  };
+
   /** Write fileapi replies to disk.  */
-  void WriteReplies();
+  void WriteReplies(IndexFor indexFor);
 
   /** Get the "cmake" instance with which this was constructed.  */
   cmake* GetCMakeInstance() const { return this->CMakeInstance; }
@@ -157,6 +166,9 @@ private:
       This populates the "objects" field of the reply index.  */
   std::map<Object, Json::Value> ReplyIndexObjects;
 
+  /** Identify the situation in which WriteReplies was called.  */
+  IndexFor ReplyIndexFor = IndexFor::Success;
+
   std::unique_ptr<Json::CharReader> JsonReader;
   std::unique_ptr<Json::StreamWriter> JsonWriter;
 

+ 8 - 1
Source/cmake.cxx

@@ -2725,6 +2725,9 @@ int cmake::ActualConfigure()
                                       this->Messenger.get());
   this->SaveCache(this->GetHomeOutputDirectory());
   if (cmSystemTools::GetErrorOccurredFlag()) {
+#if !defined(CMAKE_BOOTSTRAP)
+    this->FileAPI->WriteReplies(cmFileAPI::IndexFor::FailedConfigure);
+#endif
     return -1;
   }
   return 0;
@@ -3041,6 +3044,7 @@ int cmake::Generate()
   auto profilingRAII = this->CreateProfilingEntry("project", "generate");
   auto doGenerate = [this]() -> int {
     if (!this->GlobalGenerator->Compute()) {
+      this->FileAPI->WriteReplies(cmFileAPI::IndexFor::FailedCompute);
       return -1;
     }
     this->GlobalGenerator->Generate();
@@ -3080,6 +3084,9 @@ int cmake::Generate()
     this->RunCheckForUnusedVariables();
   }
   if (cmSystemTools::GetErrorOccurredFlag()) {
+#if !defined(CMAKE_BOOTSTRAP)
+    this->FileAPI->WriteReplies(cmFileAPI::IndexFor::FailedGenerate);
+#endif
     return -1;
   }
   // Save the cache again after a successful Generate so that any internal
@@ -3089,7 +3096,7 @@ int cmake::Generate()
 
 #if !defined(CMAKE_BOOTSTRAP)
   this->GlobalGenerator->WriteInstallJson();
-  this->FileAPI->WriteReplies();
+  this->FileAPI->WriteReplies(cmFileAPI::IndexFor::Success);
 #endif
 
   return 0;

+ 22 - 0
Tests/RunCMake/FileAPI/FailConfigure-check.cmake

@@ -0,0 +1,22 @@
+set(expect
+  query
+  query/cache-v2
+  query/client-bar
+  query/client-bar/query.json
+  query/client-foo
+  query/client-foo/cache-v2
+  query/client-foo/cmakeFiles-v1
+  query/client-foo/codemodel-v2
+  query/client-foo/configureLog-v1
+  query/client-foo/toolchains-v1
+  query/cmakeFiles-v1
+  query/codemodel-v2
+  query/configureLog-v1
+  query/toolchains-v1
+  reply
+  reply/configureLog-v1-[0-9a-f]+\\.json
+  reply/error-[0-9.T-]+\\.json
+  )
+check_api("^${expect}$")
+
+check_python(FailConfigure error)

+ 74 - 0
Tests/RunCMake/FileAPI/FailConfigure-check.py

@@ -0,0 +1,74 @@
+from check_index import *
+import os
+
+def check_reply(r):
+    assert is_dict(r)
+    assert sorted(r.keys()) == [
+        "cache-v2",
+        "client-bar",
+        "client-foo",
+        "cmakeFiles-v1",
+        "codemodel-v2",
+        "configureLog-v1",
+        "toolchains-v1",
+    ]
+    check_error(r["cache-v2"], "no buildsystem generated")
+    check_error(r["cmakeFiles-v1"], "no buildsystem generated")
+    check_reply_client_bar(r["client-bar"])
+    check_reply_client_foo(r["client-foo"])
+    check_error(r["codemodel-v2"], "no buildsystem generated")
+    check_index_object(r["configureLog-v1"], "configureLog", 1, 0, None)
+    check_error(r["toolchains-v1"], "no buildsystem generated")
+
+def check_reply_client_bar(r):
+    assert is_dict(r)
+    assert sorted(r.keys()) == ["query.json"]
+    query = r["query.json"]
+    assert sorted(query.keys()) == ["requests", "responses"]
+    requests = query["requests"]
+    assert is_list(requests)
+    assert len(requests) == 5
+    responses = query["responses"]
+    assert is_list(responses)
+    assert len(responses) == 5
+    check_error(responses[0], "no buildsystem generated")
+    check_index_object(responses[1], "configureLog", 1, 0, None)
+    check_error(responses[2], "no buildsystem generated")
+    check_error(responses[3], "no buildsystem generated")
+    check_error(responses[4], "no buildsystem generated")
+
+def check_reply_client_foo(r):
+    assert is_dict(r)
+    assert sorted(r.keys()) == [
+        "cache-v2",
+        "cmakeFiles-v1",
+        "codemodel-v2",
+        "configureLog-v1",
+        "toolchains-v1",
+    ]
+    check_error(r["cache-v2"], "no buildsystem generated")
+    check_error(r["cmakeFiles-v1"], "no buildsystem generated")
+    check_error(r["codemodel-v2"], "no buildsystem generated")
+    check_index_object(r["configureLog-v1"], "configureLog", 1, 0, None)
+    check_error(r["toolchains-v1"], "no buildsystem generated")
+
+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) == ["message-v1", "try_compile-v1", "try_run-v1"]
+
+assert is_dict(index)
+assert sorted(index.keys()) == ["cmake", "objects", "reply"]
+check_cmake(index["cmake"])
+check_reply(index["reply"])
+check_objects(index["objects"])

+ 26 - 0
Tests/RunCMake/FileAPI/FailConfigure-prep.cmake

@@ -0,0 +1,26 @@
+file(REMOVE_RECURSE ${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query)
+
+# Shared Stateless
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/codemodel-v2" "")
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/configureLog-v1" "")
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/cache-v2" "")
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/cmakeFiles-v1" "")
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/toolchains-v1" "")
+
+# Client Stateless
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-foo/codemodel-v2" "")
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-foo/configureLog-v1" "")
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-foo/cache-v2" "")
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-foo/cmakeFiles-v1" "")
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-foo/toolchains-v1" "")
+
+# Client Stateful
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-bar/query.json" [[
+{ "requests": [
+    { "kind": "codemodel", "version" : 2 },
+    { "kind": "configureLog", "version" : 1 },
+    { "kind": "cache", "version" : 2 },
+    { "kind": "cmakeFiles", "version" : 1 },
+    { "kind": "toolchains", "version" : 1 }
+  ] }
+]])

+ 1 - 0
Tests/RunCMake/FileAPI/FailConfigure-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
Tests/RunCMake/FileAPI/FailConfigure-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at FailConfigure.cmake:1 \(message\):
+  Intentionally fail to configure
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$

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

@@ -0,0 +1 @@
+message(FATAL_ERROR "Intentionally fail to configure")

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

@@ -54,6 +54,7 @@ run_cmake(DuplicateStateless)
 run_cmake(ClientStateful)
 run_cmake(ProjectQueryGood)
 run_cmake(ProjectQueryBad)
+run_cmake(FailConfigure)
 
 function(run_object object)
   set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${object}-build)
@@ -64,6 +65,7 @@ function(run_object object)
   run_cmake_command(${object}-SharedStateless ${CMAKE_COMMAND} .)
   run_cmake_command(${object}-ClientStateless ${CMAKE_COMMAND} .)
   run_cmake_command(${object}-ClientStateful ${CMAKE_COMMAND} .)
+  run_cmake_command(${object}-FailConfigure ${CMAKE_COMMAND} . -DFAIL=1)
 endfunction()
 
 run_object(codemodel-v2)

+ 16 - 0
Tests/RunCMake/FileAPI/cache-v2-FailConfigure-check.cmake

@@ -0,0 +1,16 @@
+set(expect
+  query
+  query/cache-v2
+  query/client-bar
+  query/client-bar/query.json
+  query/client-foo
+  query/client-foo/cache-v2
+  reply
+  reply/cache-v2-[0-9a-f]+\\.json
+  reply/error-[0-9.T-]+.json
+  reply/index-[0-9.T-]+.json
+  )
+check_api("^${expect}$")
+
+check_python(cache-v2-FailConfigure error)
+check_python(cache-v2 index) # Last-good index is intact.

+ 43 - 0
Tests/RunCMake/FileAPI/cache-v2-FailConfigure-check.py

@@ -0,0 +1,43 @@
+from check_index import *
+import os
+
+def check_reply(r):
+    assert is_dict(r)
+    assert sorted(r.keys()) == [
+        "cache-v2",
+        "client-bar",
+        "client-foo",
+    ]
+    check_error(r["cache-v2"], "no buildsystem generated")
+    check_reply_client_bar(r["client-bar"])
+    check_reply_client_foo(r["client-foo"])
+
+def check_reply_client_bar(r):
+    assert is_dict(r)
+    assert sorted(r.keys()) == ["query.json"]
+    query = r["query.json"]
+    assert sorted(query.keys()) == ["requests", "responses"]
+    requests = query["requests"]
+    assert is_list(requests)
+    assert len(requests) == 1
+    responses = query["responses"]
+    assert is_list(responses)
+    assert len(responses) == 1
+    check_error(responses[0], "no buildsystem generated")
+
+def check_reply_client_foo(r):
+    assert is_dict(r)
+    assert sorted(r.keys()) == [
+        "cache-v2",
+    ]
+    check_error(r["cache-v2"], "no buildsystem generated")
+
+def check_objects(o):
+    assert is_list(o)
+    assert len(o) == 0
+
+assert is_dict(index)
+assert sorted(index.keys()) == ["cmake", "objects", "reply"]
+check_cmake(index["cmake"])
+check_reply(index["reply"])
+check_objects(index["objects"])

+ 6 - 0
Tests/RunCMake/FileAPI/cache-v2-FailConfigure-prep.cmake

@@ -0,0 +1,6 @@
+file(REMOVE_RECURSE ${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query)
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/cache-v2" "")
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-foo/cache-v2" "")
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-bar/query.json" [[
+{ "requests": [ { "kind": "cache", "version" : 2 } ] }
+]])

+ 1 - 0
Tests/RunCMake/FileAPI/cache-v2-FailConfigure-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
Tests/RunCMake/FileAPI/cache-v2-FailConfigure-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at cache-v2.cmake:[0-9]+ \(message\):
+  Intentionally fail to configure
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$

+ 4 - 0
Tests/RunCMake/FileAPI/cache-v2.cmake

@@ -12,3 +12,7 @@ set_property(CACHE CM_SET_INTERNAL PROPERTY VALUE "int2")
 set(CM_SET_TYPE "1" CACHE INTERNAL "Testing set(CACHE INTERNAL) with set_property(TYPE)")
 set_property(CACHE CM_SET_TYPE PROPERTY TYPE "STRING")
 set_property(CACHE CM_SET_TYPE PROPERTY ADVANCED "0")
+
+if(FAIL)
+  message(FATAL_ERROR "Intentionally fail to configure")
+endif()

+ 16 - 0
Tests/RunCMake/FileAPI/cmakeFiles-v1-FailConfigure-check.cmake

@@ -0,0 +1,16 @@
+set(expect
+  query
+  query/client-bar
+  query/client-bar/query.json
+  query/client-foo
+  query/client-foo/cmakeFiles-v1
+  query/cmakeFiles-v1
+  reply
+  reply/cmakeFiles-v1-[0-9a-f]+\\.json
+  reply/error-[0-9.T-]+.json
+  reply/index-[0-9.T-]+.json
+  )
+check_api("^${expect}$")
+
+check_python(cmakeFiles-v1-FailConfigure error)
+check_python(cmakeFiles-v1 index) # Last-good index is intact.

+ 43 - 0
Tests/RunCMake/FileAPI/cmakeFiles-v1-FailConfigure-check.py

@@ -0,0 +1,43 @@
+from check_index import *
+import os
+
+def check_reply(r):
+    assert is_dict(r)
+    assert sorted(r.keys()) == [
+        "client-bar",
+        "client-foo",
+        "cmakeFiles-v1",
+    ]
+    check_reply_client_bar(r["client-bar"])
+    check_reply_client_foo(r["client-foo"])
+    check_error(r["cmakeFiles-v1"], "no buildsystem generated")
+
+def check_reply_client_bar(r):
+    assert is_dict(r)
+    assert sorted(r.keys()) == ["query.json"]
+    query = r["query.json"]
+    assert sorted(query.keys()) == ["requests", "responses"]
+    requests = query["requests"]
+    assert is_list(requests)
+    assert len(requests) == 1
+    responses = query["responses"]
+    assert is_list(responses)
+    assert len(responses) == 1
+    check_error(responses[0], "no buildsystem generated")
+
+def check_reply_client_foo(r):
+    assert is_dict(r)
+    assert sorted(r.keys()) == [
+        "cmakeFiles-v1",
+    ]
+    check_error(r["cmakeFiles-v1"], "no buildsystem generated")
+
+def check_objects(o):
+    assert is_list(o)
+    assert len(o) == 0
+
+assert is_dict(index)
+assert sorted(index.keys()) == ["cmake", "objects", "reply"]
+check_cmake(index["cmake"])
+check_reply(index["reply"])
+check_objects(index["objects"])

+ 6 - 0
Tests/RunCMake/FileAPI/cmakeFiles-v1-FailConfigure-prep.cmake

@@ -0,0 +1,6 @@
+file(REMOVE_RECURSE ${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query)
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/cmakeFiles-v1" "")
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-foo/cmakeFiles-v1" "")
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-bar/query.json" [[
+{ "requests": [ { "kind": "cmakeFiles", "version" : 1 } ] }
+]])

+ 1 - 0
Tests/RunCMake/FileAPI/cmakeFiles-v1-FailConfigure-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
Tests/RunCMake/FileAPI/cmakeFiles-v1-FailConfigure-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at cmakeFiles-v1.cmake:[0-9]+ \(message\):
+  Intentionally fail to configure
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$

+ 4 - 0
Tests/RunCMake/FileAPI/cmakeFiles-v1.cmake

@@ -16,3 +16,7 @@ file(GLOB_RECURSE var
   "${CMAKE_CURRENT_SOURCE_DIR}/dir/*.cmake")
 
 add_subdirectory(dir)
+
+if(FAIL)
+  message(FATAL_ERROR "Intentionally fail to configure")
+endif()

+ 18 - 0
Tests/RunCMake/FileAPI/codemodel-v2-FailConfigure-check.cmake

@@ -0,0 +1,18 @@
+set(expect
+  query
+  query/client-bar
+  query/client-bar/query.json
+  query/client-foo
+  query/client-foo/codemodel-v2
+  query/codemodel-v2
+  reply
+  reply/codemodel-v2-[0-9a-f]+\\.json
+  .*
+  reply/error-[0-9.T-]+.json
+  reply/index-[0-9.T-]+.json
+  .*
+  )
+check_api("^${expect}$")
+
+check_python(codemodel-v2-FailConfigure error)
+check_python(codemodel-v2 index) # Last-good index is intact.

+ 43 - 0
Tests/RunCMake/FileAPI/codemodel-v2-FailConfigure-check.py

@@ -0,0 +1,43 @@
+from check_index import *
+import os
+
+def check_reply(r):
+    assert is_dict(r)
+    assert sorted(r.keys()) == [
+        "client-bar",
+        "client-foo",
+        "codemodel-v2",
+    ]
+    check_reply_client_bar(r["client-bar"])
+    check_reply_client_foo(r["client-foo"])
+    check_error(r["codemodel-v2"], "no buildsystem generated")
+
+def check_reply_client_bar(r):
+    assert is_dict(r)
+    assert sorted(r.keys()) == ["query.json"]
+    query = r["query.json"]
+    assert sorted(query.keys()) == ["requests", "responses"]
+    requests = query["requests"]
+    assert is_list(requests)
+    assert len(requests) == 1
+    responses = query["responses"]
+    assert is_list(responses)
+    assert len(responses) == 1
+    check_error(responses[0], "no buildsystem generated")
+
+def check_reply_client_foo(r):
+    assert is_dict(r)
+    assert sorted(r.keys()) == [
+        "codemodel-v2",
+    ]
+    check_error(r["codemodel-v2"], "no buildsystem generated")
+
+def check_objects(o):
+    assert is_list(o)
+    assert len(o) == 0
+
+assert is_dict(index)
+assert sorted(index.keys()) == ["cmake", "objects", "reply"]
+check_cmake(index["cmake"])
+check_reply(index["reply"])
+check_objects(index["objects"])

+ 6 - 0
Tests/RunCMake/FileAPI/codemodel-v2-FailConfigure-prep.cmake

@@ -0,0 +1,6 @@
+file(REMOVE_RECURSE ${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query)
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/codemodel-v2" "")
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-foo/codemodel-v2" "")
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-bar/query.json" [[
+{ "requests": [ { "kind": "codemodel", "version" : 2 } ] }
+]])

+ 1 - 0
Tests/RunCMake/FileAPI/codemodel-v2-FailConfigure-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
Tests/RunCMake/FileAPI/codemodel-v2-FailConfigure-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at codemodel-v2.cmake:[0-9]+ \(message\):
+  Intentionally fail to configure
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$

+ 4 - 0
Tests/RunCMake/FileAPI/codemodel-v2.cmake

@@ -60,3 +60,7 @@ install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/dir
 install(EXPORT FooTargets DESTINATION lib/cmake/foo)
 install(SCRIPT InstallScript.cmake)
 install(CODE "message(foo)" ALL_COMPONENTS)
+
+if(FAIL)
+  message(FATAL_ERROR "Intentionally fail to configure")
+endif()

+ 16 - 0
Tests/RunCMake/FileAPI/configureLog-v1-FailConfigure-check.cmake

@@ -0,0 +1,16 @@
+set(expect
+  query
+  query/client-bar
+  query/client-bar/query.json
+  query/client-foo
+  query/client-foo/configureLog-v1
+  query/configureLog-v1
+  reply
+  reply/configureLog-v1-[0-9a-f]+.json
+  reply/error-[0-9.T-]+.json
+  reply/index-[0-9.T-]+.json
+  )
+check_api("^${expect}$")
+
+check_python(configureLog-v1-FailConfigure error)
+check_python(configureLog-v1 index) # Last-good index is intact.

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

@@ -0,0 +1,54 @@
+from check_index import *
+import os
+
+def check_reply(r):
+    assert is_dict(r)
+    assert sorted(r.keys()) == [
+        "client-bar",
+        "client-foo",
+        "configureLog-v1",
+    ]
+    check_reply_client_bar(r["client-bar"])
+    check_reply_client_foo(r["client-foo"])
+    check_index_object(r["configureLog-v1"], "configureLog", 1, 0, None)
+
+def check_reply_client_bar(r):
+    assert is_dict(r)
+    assert sorted(r.keys()) == ["query.json"]
+    query = r["query.json"]
+    assert sorted(query.keys()) == ["requests", "responses"]
+    requests = query["requests"]
+    assert is_list(requests)
+    assert len(requests) == 1
+    responses = query["responses"]
+    assert is_list(responses)
+    assert len(responses) == 1
+    check_index_object(responses[0], "configureLog", 1, 0, None)
+
+def check_reply_client_foo(r):
+    assert is_dict(r)
+    assert sorted(r.keys()) == [
+        "configureLog-v1",
+    ]
+    check_index_object(r["configureLog-v1"], "configureLog", 1, 0, None)
+
+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) == ["message-v1", "try_compile-v1", "try_run-v1"]
+
+assert is_dict(index)
+assert sorted(index.keys()) == ["cmake", "objects", "reply"]
+check_cmake(index["cmake"])
+check_reply(index["reply"])
+check_objects(index["objects"])

+ 6 - 0
Tests/RunCMake/FileAPI/configureLog-v1-FailConfigure-prep.cmake

@@ -0,0 +1,6 @@
+file(REMOVE_RECURSE ${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query)
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/configureLog-v1" "")
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-foo/configureLog-v1" "")
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-bar/query.json" [[
+{ "requests": [ { "kind": "configureLog", "version" : 1 } ] }
+]])

+ 1 - 0
Tests/RunCMake/FileAPI/configureLog-v1-FailConfigure-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
Tests/RunCMake/FileAPI/configureLog-v1-FailConfigure-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at configureLog-v1.cmake:[0-9]+ \(message\):
+  Intentionally fail to configure
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$

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

@@ -1 +1,4 @@
 enable_language(C)
+if(FAIL)
+  message(FATAL_ERROR "Intentionally fail to configure")
+endif()

+ 16 - 0
Tests/RunCMake/FileAPI/toolchains-v1-FailConfigure-check.cmake

@@ -0,0 +1,16 @@
+set(expect
+  query
+  query/client-bar
+  query/client-bar/query.json
+  query/client-foo
+  query/client-foo/toolchains-v1
+  query/toolchains-v1
+  reply
+  reply/error-[0-9.T-]+.json
+  reply/index-[0-9.T-]+.json
+  reply/toolchains-v1-[0-9a-f]+\\.json
+  )
+check_api("^${expect}$")
+
+check_python(toolchains-v1-FailConfigure error)
+check_python(toolchains-v1 index) # Last-good index is intact.

+ 43 - 0
Tests/RunCMake/FileAPI/toolchains-v1-FailConfigure-check.py

@@ -0,0 +1,43 @@
+from check_index import *
+import os
+
+def check_reply(r):
+    assert is_dict(r)
+    assert sorted(r.keys()) == [
+        "client-bar",
+        "client-foo",
+        "toolchains-v1",
+    ]
+    check_reply_client_bar(r["client-bar"])
+    check_reply_client_foo(r["client-foo"])
+    check_error(r["toolchains-v1"], "no buildsystem generated")
+
+def check_reply_client_bar(r):
+    assert is_dict(r)
+    assert sorted(r.keys()) == ["query.json"]
+    query = r["query.json"]
+    assert sorted(query.keys()) == ["requests", "responses"]
+    requests = query["requests"]
+    assert is_list(requests)
+    assert len(requests) == 1
+    responses = query["responses"]
+    assert is_list(responses)
+    assert len(responses) == 1
+    check_error(responses[0], "no buildsystem generated")
+
+def check_reply_client_foo(r):
+    assert is_dict(r)
+    assert sorted(r.keys()) == [
+        "toolchains-v1",
+    ]
+    check_error(r["toolchains-v1"], "no buildsystem generated")
+
+def check_objects(o):
+    assert is_list(o)
+    assert len(o) == 0
+
+assert is_dict(index)
+assert sorted(index.keys()) == ["cmake", "objects", "reply"]
+check_cmake(index["cmake"])
+check_reply(index["reply"])
+check_objects(index["objects"])

+ 6 - 0
Tests/RunCMake/FileAPI/toolchains-v1-FailConfigure-prep.cmake

@@ -0,0 +1,6 @@
+file(REMOVE_RECURSE ${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query)
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/toolchains-v1" "")
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-foo/toolchains-v1" "")
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/.cmake/api/v1/query/client-bar/query.json" [[
+{ "requests": [ { "kind": "toolchains", "version" : 1 } ] }
+]])

+ 1 - 0
Tests/RunCMake/FileAPI/toolchains-v1-FailConfigure-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
Tests/RunCMake/FileAPI/toolchains-v1-FailConfigure-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at toolchains-v1.cmake:[0-9]+ \(message\):
+  Intentionally fail to configure
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$

+ 4 - 0
Tests/RunCMake/FileAPI/toolchains-v1.cmake

@@ -20,3 +20,7 @@ foreach(variable_suffix ${variable_suffixes})
 endforeach()
 
 file(WRITE ${CMAKE_BINARY_DIR}/toolchain_variables.json "${json}")
+
+if(FAIL)
+  message(FATAL_ERROR "Intentionally fail to configure")
+endif()