Browse Source

cmake_language: Add signature to DEFER calls to later times

Fixes: #19575
Brad King 5 years ago
parent
commit
e8b0359a43
100 changed files with 949 additions and 25 deletions
  1. 119 0
      Help/command/cmake_language.rst
  2. 1 0
      Help/command/return.rst
  3. 6 1
      Help/manual/cmake.1.rst
  4. 5 0
      Help/release/dev/cmake_language-DEFER.rst
  5. 4 0
      Help/variable/CMAKE_CURRENT_LIST_LINE.rst
  6. 218 2
      Source/cmCMakeLanguageCommand.cxx
  7. 11 1
      Source/cmCommandArgumentParserHelper.cxx
  8. 7 0
      Source/cmGlobalGenerator.cxx
  9. 5 0
      Source/cmGlobalGenerator.h
  10. 5 2
      Source/cmListFileCache.cxx
  11. 8 2
      Source/cmListFileCache.h
  12. 179 11
      Source/cmMakefile.cxx
  13. 33 4
      Source/cmMakefile.h
  14. 15 0
      Source/cmState.cxx
  15. 2 0
      Source/cmState.h
  16. 1 0
      Source/cmStateTypes.h
  17. 1 1
      Source/cmake.cxx
  18. 1 1
      Tests/RunCMake/CommandLine/trace-json-v1-check.py
  19. 50 0
      Tests/RunCMake/cmake_language/RunCMakeTest.cmake
  20. 15 0
      Tests/RunCMake/cmake_language/defer_call-stderr.txt
  21. 8 0
      Tests/RunCMake/cmake_language/defer_call-stdout.txt
  22. 12 0
      Tests/RunCMake/cmake_language/defer_call.cmake
  23. 11 0
      Tests/RunCMake/cmake_language/defer_call/CMakeLists.txt
  24. 1 0
      Tests/RunCMake/cmake_language/defer_call/include.cmake
  25. 1 0
      Tests/RunCMake/cmake_language/defer_call_add_subdirectory-result.txt
  26. 9 0
      Tests/RunCMake/cmake_language/defer_call_add_subdirectory-stderr.txt
  27. 2 0
      Tests/RunCMake/cmake_language/defer_call_add_subdirectory.cmake
  28. 0 0
      Tests/RunCMake/cmake_language/defer_call_add_subdirectory/CMakeLists.txt
  29. 1 0
      Tests/RunCMake/cmake_language/defer_call_enable_language-result.txt
  30. 9 0
      Tests/RunCMake/cmake_language/defer_call_enable_language-stderr.txt
  31. 2 0
      Tests/RunCMake/cmake_language/defer_call_enable_language.cmake
  32. 1 0
      Tests/RunCMake/cmake_language/defer_call_error-result.txt
  33. 9 0
      Tests/RunCMake/cmake_language/defer_call_error-stderr.txt
  34. 3 0
      Tests/RunCMake/cmake_language/defer_call_error.cmake
  35. 2 0
      Tests/RunCMake/cmake_language/defer_call_error/CMakeLists.txt
  36. 13 0
      Tests/RunCMake/cmake_language/defer_call_ids-stdout.txt
  37. 14 0
      Tests/RunCMake/cmake_language/defer_call_ids.cmake
  38. 1 0
      Tests/RunCMake/cmake_language/defer_call_invalid_command-result.txt
  39. 4 0
      Tests/RunCMake/cmake_language/defer_call_invalid_command-stderr.txt
  40. 1 0
      Tests/RunCMake/cmake_language/defer_call_invalid_command.cmake
  41. 1 0
      Tests/RunCMake/cmake_language/defer_call_invalid_directory-result.txt
  42. 9 0
      Tests/RunCMake/cmake_language/defer_call_invalid_directory-stderr.txt
  43. 2 0
      Tests/RunCMake/cmake_language/defer_call_invalid_directory.cmake
  44. 0 0
      Tests/RunCMake/cmake_language/defer_call_invalid_directory/CMakeLists.txt
  45. 1 0
      Tests/RunCMake/cmake_language/defer_call_missing_directory-result.txt
  46. 9 0
      Tests/RunCMake/cmake_language/defer_call_missing_directory-stderr.txt
  47. 1 0
      Tests/RunCMake/cmake_language/defer_call_missing_directory.cmake
  48. 1 0
      Tests/RunCMake/cmake_language/defer_call_policy_PUSH-result.txt
  49. 2 0
      Tests/RunCMake/cmake_language/defer_call_policy_PUSH-stderr.txt
  50. 1 0
      Tests/RunCMake/cmake_language/defer_call_policy_PUSH.cmake
  51. 1 0
      Tests/RunCMake/cmake_language/defer_call_syntax_error-result.txt
  52. 13 0
      Tests/RunCMake/cmake_language/defer_call_syntax_error-stderr.txt
  53. 2 0
      Tests/RunCMake/cmake_language/defer_call_syntax_error.cmake
  54. 8 0
      Tests/RunCMake/cmake_language/defer_call_trace-stderr.txt
  55. 3 0
      Tests/RunCMake/cmake_language/defer_call_trace.cmake
  56. 5 0
      Tests/RunCMake/cmake_language/defer_call_trace_json-stderr.txt
  57. 3 0
      Tests/RunCMake/cmake_language/defer_call_trace_json.cmake
  58. 1 0
      Tests/RunCMake/cmake_language/defer_cancel_call_id-result.txt
  59. 4 0
      Tests/RunCMake/cmake_language/defer_cancel_call_id-stderr.txt
  60. 1 0
      Tests/RunCMake/cmake_language/defer_cancel_call_id.cmake
  61. 1 0
      Tests/RunCMake/cmake_language/defer_cancel_call_id_var-result.txt
  62. 4 0
      Tests/RunCMake/cmake_language/defer_cancel_call_id_var-stderr.txt
  63. 1 0
      Tests/RunCMake/cmake_language/defer_cancel_call_id_var.cmake
  64. 1 0
      Tests/RunCMake/cmake_language/defer_cancel_call_invalid_directory-result.txt
  65. 9 0
      Tests/RunCMake/cmake_language/defer_cancel_call_invalid_directory-stderr.txt
  66. 2 0
      Tests/RunCMake/cmake_language/defer_cancel_call_invalid_directory.cmake
  67. 0 0
      Tests/RunCMake/cmake_language/defer_cancel_call_invalid_directory/CMakeLists.txt
  68. 1 0
      Tests/RunCMake/cmake_language/defer_cancel_call_unknown_argument-result.txt
  69. 6 0
      Tests/RunCMake/cmake_language/defer_cancel_call_unknown_argument-stderr.txt
  70. 1 0
      Tests/RunCMake/cmake_language/defer_cancel_call_unknown_argument.cmake
  71. 1 0
      Tests/RunCMake/cmake_language/defer_directory_empty-result.txt
  72. 4 0
      Tests/RunCMake/cmake_language/defer_directory_empty-stderr.txt
  73. 1 0
      Tests/RunCMake/cmake_language/defer_directory_empty.cmake
  74. 1 0
      Tests/RunCMake/cmake_language/defer_directory_missing-result.txt
  75. 4 0
      Tests/RunCMake/cmake_language/defer_directory_missing-stderr.txt
  76. 1 0
      Tests/RunCMake/cmake_language/defer_directory_missing.cmake
  77. 1 0
      Tests/RunCMake/cmake_language/defer_directory_multiple-result.txt
  78. 4 0
      Tests/RunCMake/cmake_language/defer_directory_multiple-stderr.txt
  79. 1 0
      Tests/RunCMake/cmake_language/defer_directory_multiple.cmake
  80. 1 0
      Tests/RunCMake/cmake_language/defer_get_call_id-result.txt
  81. 4 0
      Tests/RunCMake/cmake_language/defer_get_call_id-stderr.txt
  82. 1 0
      Tests/RunCMake/cmake_language/defer_get_call_id.cmake
  83. 1 0
      Tests/RunCMake/cmake_language/defer_get_call_id_empty-result.txt
  84. 4 0
      Tests/RunCMake/cmake_language/defer_get_call_id_empty-stderr.txt
  85. 1 0
      Tests/RunCMake/cmake_language/defer_get_call_id_empty.cmake
  86. 1 0
      Tests/RunCMake/cmake_language/defer_get_call_id_var-result.txt
  87. 4 0
      Tests/RunCMake/cmake_language/defer_get_call_id_var-stderr.txt
  88. 1 0
      Tests/RunCMake/cmake_language/defer_get_call_id_var.cmake
  89. 1 0
      Tests/RunCMake/cmake_language/defer_get_call_ids_id-result.txt
  90. 4 0
      Tests/RunCMake/cmake_language/defer_get_call_ids_id-stderr.txt
  91. 1 0
      Tests/RunCMake/cmake_language/defer_get_call_ids_id.cmake
  92. 1 0
      Tests/RunCMake/cmake_language/defer_get_call_ids_id_var-result.txt
  93. 4 0
      Tests/RunCMake/cmake_language/defer_get_call_ids_id_var-stderr.txt
  94. 1 0
      Tests/RunCMake/cmake_language/defer_get_call_ids_id_var.cmake
  95. 1 0
      Tests/RunCMake/cmake_language/defer_get_call_ids_invalid_directory-result.txt
  96. 9 0
      Tests/RunCMake/cmake_language/defer_get_call_ids_invalid_directory-stderr.txt
  97. 2 0
      Tests/RunCMake/cmake_language/defer_get_call_ids_invalid_directory.cmake
  98. 0 0
      Tests/RunCMake/cmake_language/defer_get_call_ids_invalid_directory/CMakeLists.txt
  99. 1 0
      Tests/RunCMake/cmake_language/defer_get_call_ids_missing_var-result.txt
  100. 4 0
      Tests/RunCMake/cmake_language/defer_get_call_ids_missing_var-stderr.txt

+ 119 - 0
Help/command/cmake_language.rst

@@ -12,6 +12,7 @@ Synopsis
 
   cmake_language(`CALL`_ <command> [<arg>...])
   cmake_language(`EVAL`_ CODE <code>...)
+  cmake_language(`DEFER`_ <options>... CALL <command> [<arg>...])
 
 Introduction
 ^^^^^^^^^^^^
@@ -99,3 +100,121 @@ is equivalent to
   )
 
   include(${CMAKE_CURRENT_BINARY_DIR}/eval.cmake)
+
+Deferring Calls
+^^^^^^^^^^^^^^^
+
+.. versionadded:: 3.19
+
+.. _DEFER:
+
+.. code-block:: cmake
+
+  cmake_language(DEFER <options>... CALL <command> [<arg>...])
+
+Schedules a call to the named ``<command>`` with the given arguments (if any)
+to occur at a later time.  By default, deferred calls are executed as if
+written at the end of the current directory's ``CMakeLists.txt`` file,
+except that they run even after a :command:`return` call.  Variable
+references in arguments are evaluated at the time the deferred call is
+executed.
+
+The options are:
+
+``DIRECTORY <dir>``
+  Schedule the call for the end of the given directory instead of the
+  current directory.  The ``<dir>`` may reference either a source
+  directory or its corresponding binary directory.  Relative paths are
+  treated as relative to the current source directory.
+
+  The given directory must be known to CMake, being either the top-level
+  directory or one added by :command:`add_subdirectory`.  Furthermore,
+  the given directory must not yet be finished processing.  This means
+  it can be the current directory or one of its ancestors.
+
+``ID <id>``
+  Specify an identification for the deferred call.
+  The id may not be empty and may not begin in a capital letter ``A-Z``.
+  The id may begin in a ``_`` only if it was generated by another call
+  that used ``ID_VAR`` to get the id.
+
+``ID_VAR <var>``
+  Sepcify a variable in which to store the identification for the
+  deferred call.  If ``ID <id>`` is not given, a new identification
+  will be generated starting in a ``_``.
+
+The currently scheduled list of deferred calls may be retrieved:
+
+.. code-block:: cmake
+
+  cmake_language(DEFER [DIRECTORY <dir>] GET_CALL_IDS <var>)
+
+This will store in ``<var>`` a :ref:`Semicolon-separated list <CMake Language
+Lists>` of deferred call ids.
+
+Details of a specific call may be retrieved from its id:
+
+.. code-block:: cmake
+
+  cmake_language(DEFER [DIRECTORY <dir>] GET_CALL <id> <var>)
+
+This will store in ``<var>`` a :ref:`Semicolon-separated list <CMake Language
+Lists>` in which the first element is the name of the command to be
+called, and the remaining elements are its unevaluated arguments (any
+contained ``;`` characters are included literally and cannot be distinguished
+from multiple arguments).  If multiple calls are scheduled with the same id,
+this retrieves the first one.  If no call is scheduled with the given id,
+this stores an empty string in the variable.
+
+Deferred calls may be canceled by their id:
+
+.. code-block:: cmake
+
+  cmake_language(DEFER [DIRECTORY <dir>] CANCEL_CALL <id>...)
+
+This cancels all deferred calls matching any of the given ids.
+Unknown ids are silently ignored.
+
+Deferred Call Examples
+""""""""""""""""""""""
+
+For example, the code:
+
+.. code-block:: cmake
+
+  cmake_language(DEFER CALL message "${deferred_message}")
+  cmake_language(DEFER ID_VAR id CALL message "Cancelled Message")
+  cmake_language(DEFER CANCEL_CALL ${id})
+  message("Immediate Message")
+  set(deferred_message "Deferred Message")
+
+prints::
+
+  Immediate Message
+  Deferred Message
+
+The ``Cancelled Message`` is never printed because its command is
+cancelled.  The ``deferred_message`` variable reference is not evaluated
+until the call site, so it can be set after the deferred call is scheduled.
+
+In order to evaluate variable references immediately when scheduling a
+deferred call, wrap it using ``cmake_language(EVAL)``.  However, note that
+arguments will be re-evaluated in the deferred call, though that can be
+avoided by using bracket arguments.  For example:
+
+.. code-block:: cmake
+
+  set(deferred_message "Deferred Message 1")
+  set(re_evaluated [[${deferred_message}]])
+  cmake_language(EVAL CODE "
+    cmake_language(DEFER CALL message [[${deferred_message}]])
+    cmake_language(DEFER CALL message \"${re_evaluated}\")
+  ")
+  message("Immediate Message")
+  set(deferred_message "Deferred Message 2")
+
+also prints::
+
+  Immediate Message
+  Deferred Message 1
+  Deferred Message 2

+ 1 - 0
Help/command/return.rst

@@ -12,6 +12,7 @@ encountered in an included file (via :command:`include` or
 :command:`find_package`), it causes processing of the current file to stop
 and control is returned to the including file.  If it is encountered in a
 file which is not included by another file, e.g.  a ``CMakeLists.txt``,
+deferred calls scheduled by :command:`cmake_language(DEFER)` are invoked and
 control is returned to the parent directory if there is one.  If return is
 called in a function, control is returned to the caller of the function.
 

+ 6 - 1
Help/manual/cmake.1.rst

@@ -295,6 +295,11 @@ Options
      ``line``
        The line in ``file`` of the function call.
 
+     ``defer``
+       Optional member that is present when the function call was deferred
+       by :command:`cmake_language(DEFER)`.  If present, its value is a
+       string containing the deferred call ``<id>``.
+
      ``cmd``
        The name of the function that was called.
 
@@ -317,7 +322,7 @@ Options
        {
          "version": {
            "major": 1,
-           "minor": 0
+           "minor": 1
          }
        }
 

+ 5 - 0
Help/release/dev/cmake_language-DEFER.rst

@@ -0,0 +1,5 @@
+cmake_language-DEFER
+--------------------
+
+* The :command:`cmake_language` command gained a ``DEFER`` mode to
+  schedule command calls to occur at the end of processing a directory.

+ 4 - 0
Help/variable/CMAKE_CURRENT_LIST_LINE.rst

@@ -5,3 +5,7 @@ The line number of the current file being processed.
 
 This is the line number of the file currently being processed by
 cmake.
+
+If CMake is currently processing deferred calls scheduled by
+the :command:`cmake_language(DEFER)` command, this variable
+evaluates to ``DEFERRED`` instead of a specific line number.

+ 218 - 2
Source/cmCMakeLanguageCommand.cxx

@@ -7,11 +7,14 @@
 #include <cstddef>
 #include <memory>
 #include <string>
+#include <utility>
 
+#include <cm/optional>
 #include <cm/string_view>
 #include <cmext/string_view>
 
 #include "cmExecutionStatus.h"
+#include "cmGlobalGenerator.h"
 #include "cmListFileCache.h"
 #include "cmMakefile.h"
 #include "cmRange.h"
@@ -37,9 +40,24 @@ std::array<cm::static_string_view, 12> InvalidCommands{
   } // clang-format on
 };
 
+std::array<cm::static_string_view, 1> InvalidDeferCommands{
+  {
+    // clang-format off
+  "return"_s,
+  } // clang-format on
+};
+
+struct Defer
+{
+  std::string Id;
+  std::string IdVar;
+  cmMakefile* Directory = nullptr;
+};
+
 bool cmCMakeLanguageCommandCALL(std::vector<cmListFileArgument> const& args,
                                 std::string const& callCommand,
-                                size_t startArg, cmExecutionStatus& status)
+                                size_t startArg, cm::optional<Defer> defer,
+                                cmExecutionStatus& status)
 {
   // ensure specified command is valid
   // start/end flow control commands are not allowed
@@ -49,6 +67,12 @@ bool cmCMakeLanguageCommandCALL(std::vector<cmListFileArgument> const& args,
     return FatalError(status,
                       cmStrCat("invalid command specified: "_s, callCommand));
   }
+  if (defer &&
+      std::find(InvalidDeferCommands.cbegin(), InvalidDeferCommands.cend(),
+                cmd) != InvalidDeferCommands.cend()) {
+    return FatalError(status,
+                      cmStrCat("invalid command specified: "_s, callCommand));
+  }
 
   cmMakefile& makefile = status.GetMakefile();
   cmListFileContext context = makefile.GetBacktrace().Top();
@@ -66,9 +90,106 @@ bool cmCMakeLanguageCommandCALL(std::vector<cmListFileArgument> const& args,
     func.Arguments.emplace_back(lfarg);
   }
 
+  if (defer) {
+    if (defer->Id.empty()) {
+      defer->Id = makefile.NewDeferId();
+    }
+    if (!defer->IdVar.empty()) {
+      makefile.AddDefinition(defer->IdVar, defer->Id);
+    }
+    cmMakefile* deferMakefile =
+      defer->Directory ? defer->Directory : &makefile;
+    if (!deferMakefile->DeferCall(defer->Id, context.FilePath, func)) {
+      return FatalError(
+        status,
+        cmStrCat("DEFER CALL may not be scheduled in directory:\n  "_s,
+                 deferMakefile->GetCurrentBinaryDirectory(),
+                 "\nat this time."_s));
+    }
+    return true;
+  }
   return makefile.ExecuteCommand(func, status);
 }
 
+bool cmCMakeLanguageCommandDEFER(Defer const& defer,
+                                 std::vector<std::string> const& args,
+                                 size_t arg, cmExecutionStatus& status)
+{
+  cmMakefile* deferMakefile =
+    defer.Directory ? defer.Directory : &status.GetMakefile();
+  if (args[arg] == "CANCEL_CALL"_s) {
+    ++arg; // Consume CANCEL_CALL.
+    auto ids = cmMakeRange(args).advance(arg);
+    for (std::string const& id : ids) {
+      if (id[0] >= 'A' && id[0] <= 'Z') {
+        return FatalError(
+          status, cmStrCat("DEFER CANCEL_CALL unknown argument:\n  "_s, id));
+      }
+      if (!deferMakefile->DeferCancelCall(id)) {
+        return FatalError(
+          status,
+          cmStrCat("DEFER CANCEL_CALL may not update directory:\n  "_s,
+                   deferMakefile->GetCurrentBinaryDirectory(),
+                   "\nat this time."_s));
+      }
+    }
+    return true;
+  }
+  if (args[arg] == "GET_CALL_IDS"_s) {
+    ++arg; // Consume GET_CALL_IDS.
+    if (arg == args.size()) {
+      return FatalError(status, "DEFER GET_CALL_IDS missing output variable");
+    }
+    std::string const& var = args[arg++];
+    if (arg != args.size()) {
+      return FatalError(status, "DEFER GET_CALL_IDS given too many arguments");
+    }
+    cm::optional<std::string> ids = deferMakefile->DeferGetCallIds();
+    if (!ids) {
+      return FatalError(
+        status,
+        cmStrCat("DEFER GET_CALL_IDS may not access directory:\n  "_s,
+                 deferMakefile->GetCurrentBinaryDirectory(),
+                 "\nat this time."_s));
+    }
+    status.GetMakefile().AddDefinition(var, *ids);
+    return true;
+  }
+  if (args[arg] == "GET_CALL"_s) {
+    ++arg; // Consume GET_CALL.
+    if (arg == args.size()) {
+      return FatalError(status, "DEFER GET_CALL missing id");
+    }
+    std::string const& id = args[arg++];
+    if (arg == args.size()) {
+      return FatalError(status, "DEFER GET_CALL missing output variable");
+    }
+    std::string const& var = args[arg++];
+    if (arg != args.size()) {
+      return FatalError(status, "DEFER GET_CALL given too many arguments");
+    }
+    if (id.empty()) {
+      return FatalError(status, "DEFER GET_CALL id may not be empty");
+    }
+    if (id[0] >= 'A' && id[0] <= 'Z') {
+      return FatalError(status,
+                        cmStrCat("DEFER GET_CALL unknown argument:\n "_s, id));
+    }
+    cm::optional<std::string> call = deferMakefile->DeferGetCall(id);
+    if (!call) {
+      return FatalError(
+        status,
+        cmStrCat("DEFER GET_CALL may not access directory:\n  "_s,
+                 deferMakefile->GetCurrentBinaryDirectory(),
+                 "\nat this time."_s));
+    }
+    status.GetMakefile().AddDefinition(var, *call);
+    return true;
+  }
+  return FatalError(status,
+                    cmStrCat("DEFER operation unknown: "_s, args[arg]));
+}
+
 bool cmCMakeLanguageCommandEVAL(std::vector<cmListFileArgument> const& args,
                                 cmExecutionStatus& status)
 {
@@ -118,11 +239,105 @@ bool cmCMakeLanguageCommand(std::vector<cmListFileArgument> const& args,
     }
     return true;
   };
+  auto finishArgs = [&]() {
+    std::vector<cmListFileArgument> tmpArgs(args.begin() + rawArg, args.end());
+    status.GetMakefile().ExpandArguments(tmpArgs, expArgs);
+    rawArg = args.size();
+  };
 
   if (!moreArgs()) {
     return FatalError(status, "called with incorrect number of arguments");
   }
 
+  cm::optional<Defer> maybeDefer;
+  if (expArgs[expArg] == "DEFER"_s) {
+    ++expArg; // Consume "DEFER".
+
+    if (!moreArgs()) {
+      return FatalError(status, "DEFER requires at least one argument");
+    }
+
+    Defer defer;
+
+    // Process optional arguments.
+    while (moreArgs()) {
+      if (expArgs[expArg] == "CALL"_s) {
+        break;
+      }
+      if (expArgs[expArg] == "CANCEL_CALL"_s ||
+          expArgs[expArg] == "GET_CALL_IDS"_s ||
+          expArgs[expArg] == "GET_CALL"_s) {
+        if (!defer.Id.empty() || !defer.IdVar.empty()) {
+          return FatalError(status,
+                            cmStrCat("DEFER "_s, expArgs[expArg],
+                                     " does not accept ID or ID_VAR."_s));
+        }
+        finishArgs();
+        return cmCMakeLanguageCommandDEFER(defer, expArgs, expArg, status);
+      }
+      if (expArgs[expArg] == "DIRECTORY"_s) {
+        ++expArg; // Consume "DIRECTORY".
+        if (defer.Directory) {
+          return FatalError(status,
+                            "DEFER given multiple DIRECTORY arguments");
+        }
+        if (!moreArgs()) {
+          return FatalError(status, "DEFER DIRECTORY missing value");
+        }
+        std::string dir = expArgs[expArg++];
+        if (dir.empty()) {
+          return FatalError(status, "DEFER DIRECTORY may not be empty");
+        }
+        dir = cmSystemTools::CollapseFullPath(
+          dir, status.GetMakefile().GetCurrentSourceDirectory());
+        defer.Directory =
+          status.GetMakefile().GetGlobalGenerator()->FindMakefile(dir);
+        if (!defer.Directory) {
+          return FatalError(status,
+                            cmStrCat("DEFER DIRECTORY:\n  "_s, dir,
+                                     "\nis not known.  "_s,
+                                     "It may not have been processed yet."_s));
+        }
+      } else if (expArgs[expArg] == "ID"_s) {
+        ++expArg; // Consume "ID".
+        if (!defer.Id.empty()) {
+          return FatalError(status, "DEFER given multiple ID arguments");
+        }
+        if (!moreArgs()) {
+          return FatalError(status, "DEFER ID missing value");
+        }
+        defer.Id = expArgs[expArg++];
+        if (defer.Id.empty()) {
+          return FatalError(status, "DEFER ID may not be empty");
+        }
+        if (defer.Id[0] >= 'A' && defer.Id[0] <= 'Z') {
+          return FatalError(status, "DEFER ID may not start in A-Z.");
+        }
+      } else if (expArgs[expArg] == "ID_VAR"_s) {
+        ++expArg; // Consume "ID_VAR".
+        if (!defer.IdVar.empty()) {
+          return FatalError(status, "DEFER given multiple ID_VAR arguments");
+        }
+        if (!moreArgs()) {
+          return FatalError(status, "DEFER ID_VAR missing variable name");
+        }
+        defer.IdVar = expArgs[expArg++];
+        if (defer.IdVar.empty()) {
+          return FatalError(status, "DEFER ID_VAR may not be empty");
+        }
+      } else {
+        return FatalError(
+          status, cmStrCat("DEFER unknown option:\n  "_s, expArgs[expArg]));
+      }
+    }
+
+    if (!(moreArgs() && expArgs[expArg] == "CALL"_s)) {
+      return FatalError(status, "DEFER must be followed by a CALL argument");
+    }
+
+    maybeDefer = std::move(defer);
+  }
+
   if (expArgs[expArg] == "CALL") {
     ++expArg; // Consume "CALL".
 
@@ -138,7 +353,8 @@ bool cmCMakeLanguageCommand(std::vector<cmListFileArgument> const& args,
     }
 
     // Run the CALL.
-    return cmCMakeLanguageCommandCALL(args, callCommand, rawArg, status);
+    return cmCMakeLanguageCommandCALL(args, callCommand, rawArg,
+                                      std::move(maybeDefer), status);
   }
 
   if (expArgs[expArg] == "EVAL") {

+ 11 - 1
Source/cmCommandArgumentParserHelper.cxx

@@ -8,8 +8,11 @@
 #include <utility>
 
 #include <cm/memory>
+#include <cm/optional>
+#include <cmext/string_view>
 
 #include "cmCommandArgumentLexer.h"
+#include "cmListFileCache.h"
 #include "cmMakefile.h"
 #include "cmProperty.h"
 #include "cmState.h"
@@ -91,7 +94,14 @@ const char* cmCommandArgumentParserHelper::ExpandVariable(const char* var)
     return nullptr;
   }
   if (this->FileLine >= 0 && strcmp(var, "CMAKE_CURRENT_LIST_LINE") == 0) {
-    return this->AddString(std::to_string(this->FileLine));
+    std::string line;
+    cmListFileContext const& top = this->Makefile->GetBacktrace().Top();
+    if (top.DeferId) {
+      line = cmStrCat("DEFERRED:"_s, *top.DeferId);
+    } else {
+      line = std::to_string(this->FileLine);
+    }
+    return this->AddString(line);
   }
   cmProp value = this->Makefile->GetDefinition(var);
   if (!value) {

+ 7 - 0
Source/cmGlobalGenerator.cxx

@@ -15,6 +15,7 @@
 
 #include <cm/memory>
 #include <cmext/algorithm>
+#include <cmext/string_view>
 
 #include "cmsys/Directory.hxx"
 #include "cmsys/FStream.hxx"
@@ -1211,6 +1212,7 @@ void cmGlobalGenerator::Configure()
 {
   this->FirstTimeProgress = 0.0f;
   this->ClearGeneratorMembers();
+  this->NextDeferId = 0;
 
   cmStateSnapshot snapshot = this->CMakeInstance->GetCurrentSnapshot();
 
@@ -3256,6 +3258,11 @@ const std::string& cmGlobalGenerator::GetRealPath(const std::string& dir)
   return i->second;
 }
 
+std::string cmGlobalGenerator::NewDeferId()
+{
+  return cmStrCat("__"_s, std::to_string(this->NextDeferId++));
+}
+
 void cmGlobalGenerator::ProcessEvaluationFiles()
 {
   std::vector<std::string> generatedFiles;

+ 5 - 0
Source/cmGlobalGenerator.h

@@ -508,6 +508,8 @@ public:
 
   std::string const& GetRealPath(std::string const& dir);
 
+  std::string NewDeferId();
+
 protected:
   // for a project collect all its targets by following depend
   // information, and also collect all the targets
@@ -633,6 +635,9 @@ private:
   std::map<std::string, int> LanguageToLinkerPreference;
   std::map<std::string, std::string> LanguageToOriginalSharedLibFlags;
 
+  // Deferral id generation.
+  size_t NextDeferId = 0;
+
   // Record hashes for rules and outputs.
   struct RuleHash
   {

+ 5 - 2
Source/cmListFileCache.cxx

@@ -446,7 +446,8 @@ void cmListFileBacktrace::PrintCallStack(std::ostream& out) const
   cmStateSnapshot bottom = this->GetBottom();
   for (Entry const* cur = this->TopEntry->Parent.get(); !cur->IsBottom();
        cur = cur->Parent.get()) {
-    if (cur->Context.Name.empty()) {
+    if (cur->Context.Name.empty() &&
+        cur->Context.Line != cmListFileContext::DeferPlaceholderLine) {
       // Skip this whole-file scope.  When we get here we already will
       // have printed a more-specific context within the file.
       continue;
@@ -483,11 +484,13 @@ bool cmListFileBacktrace::Empty() const
 std::ostream& operator<<(std::ostream& os, cmListFileContext const& lfc)
 {
   os << lfc.FilePath;
-  if (lfc.Line) {
+  if (lfc.Line > 0) {
     os << ":" << lfc.Line;
     if (!lfc.Name.empty()) {
       os << " (" << lfc.Name << ")";
     }
+  } else if (lfc.Line == cmListFileContext::DeferPlaceholderLine) {
+    os << ":DEFERRED";
   }
   return os;
 }

+ 8 - 2
Source/cmListFileCache.h

@@ -11,6 +11,8 @@
 #include <utility>
 #include <vector>
 
+#include <cm/optional>
+
 #include "cmStateSnapshot.h"
 
 /** \class cmListFileCache
@@ -72,6 +74,8 @@ public:
   std::string Name;
   std::string FilePath;
   long Line = 0;
+  static long const DeferPlaceholderLine = -1;
+  cm::optional<std::string> DeferId;
 
   cmListFileContext() = default;
   cmListFileContext(std::string name, std::string filePath, long line)
@@ -81,13 +85,15 @@ public:
   {
   }
 
-  static cmListFileContext FromCommandContext(cmCommandContext const& lfcc,
-                                              std::string const& fileName)
+  static cmListFileContext FromCommandContext(
+    cmCommandContext const& lfcc, std::string const& fileName,
+    cm::optional<std::string> deferId = {})
   {
     cmListFileContext lfc;
     lfc.FilePath = fileName;
     lfc.Line = lfcc.Line;
     lfc.Name = lfcc.Name.Original;
+    lfc.DeferId = std::move(deferId);
     return lfc;
   }
 };

+ 179 - 11
Source/cmMakefile.cxx

@@ -16,6 +16,7 @@
 #include <cm/iterator>
 #include <cm/memory>
 #include <cm/optional>
+#include <cm/type_traits> // IWYU pragma: keep
 #include <cm/vector>
 #include <cmext/algorithm>
 #include <cmext/string_view>
@@ -274,7 +275,9 @@ cmListFileBacktrace cmMakefile::GetBacktrace() const
   return this->Backtrace;
 }
 
-void cmMakefile::PrintCommandTrace(const cmListFileFunction& lff) const
+void cmMakefile::PrintCommandTrace(
+  cmListFileFunction const& lff,
+  cm::optional<std::string> const& deferId) const
 {
   // Check if current file in the list of requested to trace...
   std::vector<std::string> const& trace_only_this_files =
@@ -322,6 +325,9 @@ void cmMakefile::PrintCommandTrace(const cmListFileFunction& lff) const
       builder["indentation"] = "";
       val["file"] = full_path;
       val["line"] = static_cast<Json::Value::Int64>(lff.Line);
+      if (deferId) {
+        val["defer"] = *deferId;
+      }
       val["cmd"] = lff.Name.Original;
       val["args"] = Json::Value(Json::arrayValue);
       for (std::string const& arg : args) {
@@ -335,8 +341,11 @@ void cmMakefile::PrintCommandTrace(const cmListFileFunction& lff) const
       break;
     }
     case cmake::TraceFormat::TRACE_HUMAN:
-      msg << full_path << "(" << lff.Line << "):  ";
-      msg << lff.Name.Original << "(";
+      msg << full_path << "(" << lff.Line << "):";
+      if (deferId) {
+        msg << "DEFERRED:" << *deferId << ":";
+      }
+      msg << "  " << lff.Name.Original << "(";
 
       for (std::string const& arg : args) {
         msg << arg << " ";
@@ -361,11 +370,12 @@ class cmMakefileCall
 {
 public:
   cmMakefileCall(cmMakefile* mf, cmListFileFunction const& lff,
-                 cmExecutionStatus& status)
+                 cm::optional<std::string> deferId, cmExecutionStatus& status)
     : Makefile(mf)
   {
     cmListFileContext const& lfc = cmListFileContext::FromCommandContext(
-      lff, this->Makefile->StateSnapshot.GetExecutionListFile());
+      lff, this->Makefile->StateSnapshot.GetExecutionListFile(),
+      std::move(deferId));
     this->Makefile->Backtrace = this->Makefile->Backtrace.Push(lfc);
     ++this->Makefile->RecursionDepth;
     this->Makefile->ExecutionStatusStack.push_back(&status);
@@ -402,7 +412,8 @@ void cmMakefile::OnExecuteCommand(std::function<void()> callback)
 }
 
 bool cmMakefile::ExecuteCommand(const cmListFileFunction& lff,
-                                cmExecutionStatus& status)
+                                cmExecutionStatus& status,
+                                cm::optional<std::string> deferId)
 {
   bool result = true;
 
@@ -417,7 +428,7 @@ bool cmMakefile::ExecuteCommand(const cmListFileFunction& lff,
   }
 
   // Place this call on the call stack.
-  cmMakefileCall stack_manager(this, lff, status);
+  cmMakefileCall stack_manager(this, lff, std::move(deferId), status);
   static_cast<void>(stack_manager);
 
   // Check for maximum recursion depth.
@@ -445,7 +456,7 @@ bool cmMakefile::ExecuteCommand(const cmListFileFunction& lff,
     if (!cmSystemTools::GetFatalErrorOccured()) {
       // if trace is enabled, print out invoke information
       if (this->GetCMakeInstance()->GetTrace()) {
-        this->PrintCommandTrace(lff);
+        this->PrintCommandTrace(lff, this->Backtrace.Top().DeferId);
       }
       // Try invoking the command.
       bool invokeSucceeded = command(lff.Arguments, status);
@@ -663,6 +674,53 @@ private:
   bool ReportError;
 };
 
+class cmMakefile::DeferScope
+{
+public:
+  DeferScope(cmMakefile* mf, std::string const& deferredInFile)
+    : Makefile(mf)
+  {
+    cmListFileContext lfc;
+    lfc.Line = cmListFileContext::DeferPlaceholderLine;
+    lfc.FilePath = deferredInFile;
+    this->Makefile->Backtrace = this->Makefile->Backtrace.Push(lfc);
+    this->Makefile->DeferRunning = true;
+  }
+
+  ~DeferScope()
+  {
+    this->Makefile->DeferRunning = false;
+    this->Makefile->Backtrace = this->Makefile->Backtrace.Pop();
+  }
+
+  DeferScope(const DeferScope&) = delete;
+  DeferScope& operator=(const DeferScope&) = delete;
+
+private:
+  cmMakefile* Makefile;
+};
+
+class cmMakefile::DeferCallScope
+{
+public:
+  DeferCallScope(cmMakefile* mf, std::string const& deferredFromFile)
+    : Makefile(mf)
+  {
+    this->Makefile->StateSnapshot =
+      this->Makefile->GetState()->CreateDeferCallSnapshot(
+        this->Makefile->StateSnapshot, deferredFromFile);
+    assert(this->Makefile->StateSnapshot.IsValid());
+  }
+
+  ~DeferCallScope() { this->Makefile->PopSnapshot(); }
+
+  DeferCallScope(const DeferCallScope&) = delete;
+  DeferCallScope& operator=(const DeferCallScope&) = delete;
+
+private:
+  cmMakefile* Makefile;
+};
+
 bool cmMakefile::ReadListFile(const std::string& filename)
 {
   std::string filenametoread = cmSystemTools::CollapseFullPath(
@@ -705,7 +763,8 @@ bool cmMakefile::ReadListFileAsString(const std::string& content,
 }
 
 void cmMakefile::RunListFile(cmListFile const& listFile,
-                             std::string const& filenametoread)
+                             std::string const& filenametoread,
+                             DeferCommands* defer)
 {
   // add this list file to the list of dependencies
   this->ListFiles.push_back(filenametoread);
@@ -736,6 +795,33 @@ void cmMakefile::RunListFile(cmListFile const& listFile,
     }
   }
 
+  // Run any deferred commands.
+  if (defer) {
+    // Add a backtrace level indicating calls are deferred.
+    DeferScope scope(this, filenametoread);
+
+    // Iterate by index in case one deferred call schedules another.
+    // NOLINTNEXTLINE(modernize-loop-convert)
+    for (size_t i = 0; i < defer->Commands.size(); ++i) {
+      DeferCommand& d = defer->Commands[i];
+      if (d.Id.empty()) {
+        // Cancelled.
+        continue;
+      }
+      // Mark as executed.
+      std::string id = std::move(d.Id);
+
+      // The deferred call may have come from another file.
+      DeferCallScope callScope(this, d.FilePath);
+
+      cmExecutionStatus status(*this);
+      this->ExecuteCommand(d.Command, status, std::move(id));
+      if (cmSystemTools::GetFatalErrorOccured()) {
+        break;
+      }
+    }
+  }
+
   this->AddDefinition("CMAKE_PARENT_LIST_FILE", currentParentFile);
   this->AddDefinition("CMAKE_CURRENT_LIST_FILE", currentFile);
   this->AddDefinition("CMAKE_CURRENT_LIST_DIR",
@@ -1678,7 +1764,9 @@ void cmMakefile::Configure()
     }
   }
 
-  this->RunListFile(listFile, currentStart);
+  this->Defer = cm::make_unique<DeferCommands>();
+  this->RunListFile(listFile, currentStart, this->Defer.get());
+  this->Defer.reset();
   if (cmSystemTools::GetFatalErrorOccured()) {
     scope.Quiet();
   }
@@ -1753,6 +1841,13 @@ void cmMakefile::AddSubDirectory(const std::string& srcPath,
                                  const std::string& binPath,
                                  bool excludeFromAll, bool immediate)
 {
+  if (this->DeferRunning) {
+    this->IssueMessage(
+      MessageType::FATAL_ERROR,
+      "Subdirectories may not be created during deferred execution.");
+    return;
+  }
+
   // Make sure the binary directory is unique.
   if (!this->EnforceUniqueDir(srcPath, binPath)) {
     return;
@@ -2960,6 +3055,68 @@ void cmMakefile::SetRecursionDepth(int recursionDepth)
   this->RecursionDepth = recursionDepth;
 }
 
+std::string cmMakefile::NewDeferId()
+{
+  return this->GetGlobalGenerator()->NewDeferId();
+}
+
+bool cmMakefile::DeferCall(std::string id, std::string file,
+                           cmListFileFunction lff)
+{
+  if (!this->Defer) {
+    return false;
+  }
+  this->Defer->Commands.emplace_back(
+    DeferCommand{ std::move(id), std::move(file), std::move(lff) });
+  return true;
+}
+
+bool cmMakefile::DeferCancelCall(std::string const& id)
+{
+  if (!this->Defer) {
+    return false;
+  }
+  for (DeferCommand& dc : this->Defer->Commands) {
+    if (dc.Id == id) {
+      dc.Id.clear();
+    }
+  }
+  return true;
+}
+
+cm::optional<std::string> cmMakefile::DeferGetCallIds() const
+{
+  cm::optional<std::string> ids;
+  if (this->Defer) {
+    ids = cmJoin(
+      cmMakeRange(this->Defer->Commands)
+        .filter([](DeferCommand const& dc) -> bool { return !dc.Id.empty(); })
+        .transform(
+          [](DeferCommand const& dc) -> std::string const& { return dc.Id; }),
+      ";");
+  }
+  return ids;
+}
+
+cm::optional<std::string> cmMakefile::DeferGetCall(std::string const& id) const
+{
+  cm::optional<std::string> call;
+  if (this->Defer) {
+    std::string tmp;
+    for (DeferCommand const& dc : this->Defer->Commands) {
+      if (dc.Id == id) {
+        tmp = dc.Command.Name.Original;
+        for (cmListFileArgument const& arg : dc.Command.Arguments) {
+          tmp = cmStrCat(tmp, ';', arg.Value);
+        }
+        break;
+      }
+    }
+    call = std::move(tmp);
+  }
+  return call;
+}
+
 MessageType cmMakefile::ExpandVariablesInStringNew(
   std::string& errorstr, std::string& source, bool escapeQuotes,
   bool noEscapes, bool atOnly, const char* filename, long line,
@@ -2997,7 +3154,12 @@ MessageType cmMakefile::ExpandVariablesInStringNew(
           switch (var.domain) {
             case NORMAL:
               if (filename && lookup == lineVar) {
-                varresult = std::to_string(line);
+                cmListFileContext const& top = this->Backtrace.Top();
+                if (top.DeferId) {
+                  varresult = cmStrCat("DEFERRED:"_s, *top.DeferId);
+                } else {
+                  varresult = std::to_string(line);
+                }
               } else {
                 value = this->GetDefinition(lookup);
               }
@@ -3561,6 +3723,12 @@ void cmMakefile::AddTargetObject(std::string const& tgtName,
 void cmMakefile::EnableLanguage(std::vector<std::string> const& lang,
                                 bool optional)
 {
+  if (this->DeferRunning) {
+    this->IssueMessage(
+      MessageType::FATAL_ERROR,
+      "Languages may not be enabled during deferred execution.");
+    return;
+  }
   if (const char* def = this->GetGlobalGenerator()->GetCMakeCFGIntDir()) {
     this->AddDefinition("CMAKE_CFG_INTDIR", def);
   }

+ 33 - 4
Source/cmMakefile.h

@@ -15,6 +15,7 @@
 #include <unordered_map>
 #include <vector>
 
+#include <cm/optional>
 #include <cm/string_view>
 
 #include "cmsys/RegularExpression.hxx"
@@ -695,7 +696,8 @@ public:
   /**
    * Print a command's invocation
    */
-  void PrintCommandTrace(const cmListFileFunction& lff) const;
+  void PrintCommandTrace(cmListFileFunction const& lff,
+                         cm::optional<std::string> const& deferId = {}) const;
 
   /**
    * Set a callback that is invoked whenever ExecuteCommand is called.
@@ -706,8 +708,8 @@ public:
    * Execute a single CMake command.  Returns true if the command
    * succeeded or false if it failed.
    */
-  bool ExecuteCommand(const cmListFileFunction& lff,
-                      cmExecutionStatus& status);
+  bool ExecuteCommand(const cmListFileFunction& lff, cmExecutionStatus& status,
+                      cm::optional<std::string> deferId = {});
 
   //! Enable support for named language, if nil then all languages are
   /// enabled.
@@ -965,6 +967,12 @@ public:
   int GetRecursionDepth() const;
   void SetRecursionDepth(int recursionDepth);
 
+  std::string NewDeferId();
+  bool DeferCall(std::string id, std::string fileName, cmListFileFunction lff);
+  bool DeferCancelCall(std::string const& id);
+  cm::optional<std::string> DeferGetCallIds() const;
+  cm::optional<std::string> DeferGetCall(std::string const& id) const;
+
 protected:
   // add link libraries and directories to the target
   void AddGlobalLinkInformation(cmTarget& target);
@@ -1026,10 +1034,25 @@ private:
   cmListFileBacktrace Backtrace;
   int RecursionDepth;
 
+  struct DeferCommand
+  {
+    // Id is empty for an already-executed or cancelled operation.
+    std::string Id;
+    std::string FilePath;
+    cmListFileFunction Command;
+  };
+  struct DeferCommands
+  {
+    std::vector<DeferCommand> Commands;
+  };
+  std::unique_ptr<DeferCommands> Defer;
+  bool DeferRunning = false;
+
   void DoGenerate(cmLocalGenerator& lg);
 
   void RunListFile(cmListFile const& listFile,
-                   const std::string& filenametoread);
+                   const std::string& filenametoread,
+                   DeferCommands* defer = nullptr);
 
   bool ParseDefineFlag(std::string const& definition, bool remove);
 
@@ -1080,6 +1103,12 @@ private:
   class ListFileScope;
   friend class ListFileScope;
 
+  class DeferScope;
+  friend class DeferScope;
+
+  class DeferCallScope;
+  friend class DeferCallScope;
+
   class BuildsystemFileScope;
   friend class BuildsystemFileScope;
 

+ 15 - 0
Source/cmState.cxx

@@ -837,6 +837,21 @@ cmStateSnapshot cmState::CreateBuildsystemDirectorySnapshot(
   return snapshot;
 }
 
+cmStateSnapshot cmState::CreateDeferCallSnapshot(
+  cmStateSnapshot const& originSnapshot, std::string const& fileName)
+{
+  cmStateDetail::PositionType pos =
+    this->SnapshotData.Push(originSnapshot.Position, *originSnapshot.Position);
+  pos->SnapshotType = cmStateEnums::DeferCallType;
+  pos->Keep = false;
+  pos->ExecutionListFile = this->ExecutionListFiles.Push(
+    originSnapshot.Position->ExecutionListFile, fileName);
+  assert(originSnapshot.Position->Vars.IsValid());
+  pos->BuildSystemDirectory->DirectoryEnd = pos;
+  pos->PolicyScope = originSnapshot.Position->Policies;
+  return { this, pos };
+}
+
 cmStateSnapshot cmState::CreateFunctionCallSnapshot(
   cmStateSnapshot const& originSnapshot, std::string const& fileName)
 {

+ 2 - 0
Source/cmState.h

@@ -55,6 +55,8 @@ public:
   cmStateSnapshot CreateBaseSnapshot();
   cmStateSnapshot CreateBuildsystemDirectorySnapshot(
     cmStateSnapshot const& originSnapshot);
+  cmStateSnapshot CreateDeferCallSnapshot(
+    cmStateSnapshot const& originSnapshot, std::string const& fileName);
   cmStateSnapshot CreateFunctionCallSnapshot(
     cmStateSnapshot const& originSnapshot, std::string const& fileName);
   cmStateSnapshot CreateMacroCallSnapshot(

+ 1 - 0
Source/cmStateTypes.h

@@ -18,6 +18,7 @@ enum SnapshotType
 {
   BaseType,
   BuildsystemDirectoryType,
+  DeferCallType,
   FunctionCallType,
   MacroCallType,
   IncludeFileType,

+ 1 - 1
Source/cmake.cxx

@@ -983,7 +983,7 @@ void cmake::PrintTraceFormatVersion()
       Json::StreamWriterBuilder builder;
       builder["indentation"] = "";
       version["major"] = 1;
-      version["minor"] = 0;
+      version["minor"] = 1;
       val["version"] = version;
       msg = Json::writeString(builder, val);
 #endif

+ 1 - 1
Tests/RunCMake/CommandLine/trace-json-v1-check.py

@@ -56,7 +56,7 @@ with open(trace_file, 'r') as fp:
     assert sorted(vers.keys()) == ['version']
     assert sorted(vers['version'].keys()) == ['major', 'minor']
     assert vers['version']['major'] == 1
-    assert vers['version']['minor'] == 0
+    assert vers['version']['minor'] == 1
 
     for i in fp.readlines():
         line = json.loads(i)

+ 50 - 0
Tests/RunCMake/cmake_language/RunCMakeTest.cmake

@@ -32,3 +32,53 @@ run_cmake(eval_message_fatal_error)
 run_cmake(eval_no_code)
 run_cmake(eval_no_parameters)
 run_cmake(eval_variable_outside_message)
+run_cmake(defer_call)
+run_cmake(defer_call_add_subdirectory)
+run_cmake(defer_call_enable_language)
+run_cmake(defer_call_ids)
+foreach(command IN ITEMS
+    "function" "endfunction"
+    "macro" "endmacro"
+    "if" "elseif" "else" "endif"
+    "while" "endwhile"
+    "foreach" "endforeach"
+    "return"
+    )
+  message(STATUS "Running defer_call_invalid_command for ${command}...")
+  run_cmake_with_options(defer_call_invalid_command -Dcommand=${command})
+endforeach()
+run_cmake(defer_call_invalid_directory)
+run_cmake(defer_call_error)
+run_cmake(defer_call_missing_directory)
+run_cmake(defer_call_policy_PUSH)
+run_cmake(defer_call_syntax_error)
+run_cmake_with_options(defer_call_trace --trace-expand)
+run_cmake_with_options(defer_call_trace_json --trace --trace-format=json-v1)
+run_cmake(defer_cancel_call_unknown_argument)
+run_cmake(defer_cancel_call_invalid_directory)
+run_cmake(defer_cancel_call_id)
+run_cmake(defer_cancel_call_id_var)
+run_cmake(defer_directory_empty)
+run_cmake(defer_directory_missing)
+run_cmake(defer_directory_multiple)
+run_cmake(defer_id_empty)
+run_cmake(defer_id_missing)
+run_cmake(defer_id_multiple)
+run_cmake(defer_id_var_empty)
+run_cmake(defer_id_var_missing)
+run_cmake(defer_id_var_multiple)
+run_cmake(defer_get_call_ids_missing_var)
+run_cmake(defer_get_call_ids_too_many_args)
+run_cmake(defer_get_call_ids_invalid_directory)
+run_cmake(defer_get_call_ids_id)
+run_cmake(defer_get_call_ids_id_var)
+run_cmake(defer_get_call_missing_id)
+run_cmake(defer_get_call_missing_var)
+run_cmake(defer_get_call_too_many_args)
+run_cmake(defer_get_call_id_empty)
+run_cmake(defer_get_call_unknown_argument)
+run_cmake(defer_get_call_id)
+run_cmake(defer_get_call_id_var)
+run_cmake(defer_missing_arg)
+run_cmake(defer_missing_call)
+run_cmake(defer_unknown_option)

+ 15 - 0
Tests/RunCMake/cmake_language/defer_call-stderr.txt

@@ -0,0 +1,15 @@
+^CMake Deprecation Warning at defer_call/CMakeLists.txt:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0053 will be removed from a future version
+  of CMake.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD.
++
+CMake Warning at defer_call/CMakeLists.txt:3 \(message\):
+  Double-Deferred Warning In Subdirectory:
+
+   '[^']*/Tests/RunCMake/cmake_language/defer_call/CMakeLists.txt:DEFERRED:id3'
+Call Stack \(most recent call first\):
+  defer_call/CMakeLists.txt:DEFERRED$

+ 8 - 0
Tests/RunCMake/cmake_language/defer_call-stdout.txt

@@ -0,0 +1,8 @@
+-- Immediate Message In Subdirectory: ids='__0;__1'
+-- Deferred Message In Subdirectory: '[^']*/Tests/RunCMake/cmake_language/defer_call/CMakeLists.txt:DEFERRED:id1'
+-- Deferred Message In Included File: '[^']*/Tests/RunCMake/cmake_language/defer_call/include.cmake:1'
+-- Immediate Message: ids='__0;__1;__2;__3;__4'
+-- First Deferred Message
+-- Deferred Message From Subdirectory
+-- Deferred Message: ids='__4;__5'
+-- Final Deferred Message

+ 12 - 0
Tests/RunCMake/cmake_language/defer_call.cmake

@@ -0,0 +1,12 @@
+set(message_command "message")
+set(final_message "This should not be printed because variable evaluation is deferred too.")
+cmake_language(DEFER CALL ${message_command} STATUS "First Deferred Message")
+add_subdirectory(defer_call)
+cmake_language(DEFER CALL cmake_language DEFER CALL "${final_message_command}" STATUS "${final_message}")
+cmake_language(DEFER CALL cmake_language DEFER GET_CALL_IDS ids)
+cmake_language(DEFER CALL cmake_language EVAL CODE [[message(STATUS "Deferred Message: ids='${ids}'")]])
+cmake_language(DEFER GET_CALL_IDS ids)
+message(STATUS "Immediate Message: ids='${ids}'")
+set(final_message_command "message")
+set(final_message "Final Deferred Message")
+set(subdir_message "Deferred Message From Subdirectory")

+ 11 - 0
Tests/RunCMake/cmake_language/defer_call/CMakeLists.txt

@@ -0,0 +1,11 @@
+cmake_policy(SET CMP0053 OLD)
+cmake_language(DEFER ID id1 CALL message STATUS "Deferred Message In Subdirectory: '${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE}'")
+cmake_language(DEFER ID id2 CALL
+  cmake_language DEFER ID id3 CALL
+  message WARNING "Double-Deferred Warning In Subdirectory:\n '${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE}'")
+cmake_language(DEFER ID id4 CALL include "${CMAKE_CURRENT_LIST_DIR}/include.cmake")
+
+set(subdir_message "This should not be printed because variable evaluation is in deferred scope.")
+cmake_language(DEFER DIRECTORY .. CALL message STATUS "${subdir_message}")
+cmake_language(DEFER DIRECTORY .. GET_CALL_IDS ids)
+message(STATUS "Immediate Message In Subdirectory: ids='${ids}'")

+ 1 - 0
Tests/RunCMake/cmake_language/defer_call/include.cmake

@@ -0,0 +1 @@
+message(STATUS "Deferred Message In Included File: '${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE}'")

+ 1 - 0
Tests/RunCMake/cmake_language/defer_call_add_subdirectory-result.txt

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

+ 9 - 0
Tests/RunCMake/cmake_language/defer_call_add_subdirectory-stderr.txt

@@ -0,0 +1,9 @@
+^CMake Error at defer_call_add_subdirectory.cmake:1 \(add_subdirectory\):
+  Subdirectories may not be created during deferred execution.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:DEFERRED
++
+CMake Error at defer_call_add_subdirectory.cmake:2 \(subdirs\):
+  Subdirectories may not be created during deferred execution.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:DEFERRED$

+ 2 - 0
Tests/RunCMake/cmake_language/defer_call_add_subdirectory.cmake

@@ -0,0 +1,2 @@
+cmake_language(DEFER CALL add_subdirectory defer_call_add_subdirectory)
+cmake_language(DEFER CALL subdirs defer_call_add_subdirectory)

+ 0 - 0
Tests/RunCMake/cmake_language/defer_call_add_subdirectory/CMakeLists.txt


+ 1 - 0
Tests/RunCMake/cmake_language/defer_call_enable_language-result.txt

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

+ 9 - 0
Tests/RunCMake/cmake_language/defer_call_enable_language-stderr.txt

@@ -0,0 +1,9 @@
+^CMake Error at defer_call_enable_language.cmake:1 \(enable_language\):
+  Languages may not be enabled during deferred execution.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:DEFERRED
++
+CMake Error at defer_call_enable_language.cmake:2 \(project\):
+  Languages may not be enabled during deferred execution.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:DEFERRED$

+ 2 - 0
Tests/RunCMake/cmake_language/defer_call_enable_language.cmake

@@ -0,0 +1,2 @@
+cmake_language(DEFER CALL enable_language C)
+cmake_language(DEFER CALL project foo C)

+ 1 - 0
Tests/RunCMake/cmake_language/defer_call_error-result.txt

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

+ 9 - 0
Tests/RunCMake/cmake_language/defer_call_error-stderr.txt

@@ -0,0 +1,9 @@
+^CMake Error at defer_call_error.cmake:2 \(message\):
+  Deferred Error
+Call Stack \(most recent call first\):
+  CMakeLists.txt:DEFERRED
++
+CMake Error at defer_call_error/CMakeLists.txt:2 \(message\):
+  Deferred Error from Subdirectory
+Call Stack \(most recent call first\):
+  CMakeLists.txt:DEFERRED$

+ 3 - 0
Tests/RunCMake/cmake_language/defer_call_error.cmake

@@ -0,0 +1,3 @@
+# Error message backtrace points here but call stack shows DEFERRED execution.
+cmake_language(DEFER CALL message SEND_ERROR "Deferred Error")
+add_subdirectory(defer_call_error)

+ 2 - 0
Tests/RunCMake/cmake_language/defer_call_error/CMakeLists.txt

@@ -0,0 +1,2 @@
+# Error message backtrace points here but call stack shows DEFERRED execution in parent.
+cmake_language(DEFER DIRECTORY .. CALL message SEND_ERROR "Deferred Error from Subdirectory")

+ 13 - 0
Tests/RunCMake/cmake_language/defer_call_ids-stdout.txt

@@ -0,0 +1,13 @@
+-- Immediate Message: ids='message0;getCallIds1;messageIds1;cancelCall;getCallIds2;messageIds2;toBeCancelled;message3'
+-- Immediate Message: message0='message;STATUS;First Deferred Message'
+-- Immediate Message: getCallIds1='cmake_language;DEFER;GET_CALL_IDS;ids'
+-- Immediate Message: messageIds1='cmake_language;EVAL;CODE;message\(STATUS "Deferred Message: ids='\${ids}'"\)'
+-- Immediate Message: cancelCall='cmake_language;DEFER;CANCEL_CALL;toBeCancelled'
+-- Immediate Message: getCallIds2='cmake_language;DEFER;GET_CALL_IDS;ids'
+-- Immediate Message: messageIds2='cmake_language;EVAL;CODE;message\(STATUS "Deferred Message: ids='\${ids}'"\)'
+-- Immediate Message: toBeCancelled='message;STATUS;Cancelled Message'
+-- Immediate Message: message3='message;STATUS;Final Deferred Message'
+-- First Deferred Message
+-- Deferred Message: ids='messageIds1;cancelCall;getCallIds2;messageIds2;toBeCancelled;message3'
+-- Deferred Message: ids='messageIds2;message3'
+-- Final Deferred Message

+ 14 - 0
Tests/RunCMake/cmake_language/defer_call_ids.cmake

@@ -0,0 +1,14 @@
+cmake_language(DEFER ID message0 CALL message STATUS "First Deferred Message")
+cmake_language(DEFER ID getCallIds1 CALL cmake_language DEFER GET_CALL_IDS ids)
+cmake_language(DEFER ID messageIds1 CALL cmake_language EVAL CODE [[message(STATUS "Deferred Message: ids='${ids}'")]])
+cmake_language(DEFER ID cancelCall CALL cmake_language DEFER CANCEL_CALL toBeCancelled)
+cmake_language(DEFER ID getCallIds2 CALL cmake_language DEFER GET_CALL_IDS ids)
+cmake_language(DEFER ID messageIds2 CALL cmake_language EVAL CODE [[message(STATUS "Deferred Message: ids='${ids}'")]])
+cmake_language(DEFER ID toBeCancelled CALL message STATUS "Cancelled Message")
+cmake_language(DEFER ID message3 CALL message STATUS "Final Deferred Message")
+cmake_language(DEFER GET_CALL_IDS ids)
+message(STATUS "Immediate Message: ids='${ids}'")
+foreach(id ${ids})
+  cmake_language(DEFER GET_CALL ${id} call)
+  message(STATUS "Immediate Message: ${id}='${call}'")
+endforeach()

+ 1 - 0
Tests/RunCMake/cmake_language/defer_call_invalid_command-result.txt

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

+ 4 - 0
Tests/RunCMake/cmake_language/defer_call_invalid_command-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at defer_call_invalid_command.cmake:1 \(cmake_language\):
+  cmake_language invalid command specified: [A-Za-z_]+
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)$

+ 1 - 0
Tests/RunCMake/cmake_language/defer_call_invalid_command.cmake

@@ -0,0 +1 @@
+cmake_language(DEFER CALL ${command})

+ 1 - 0
Tests/RunCMake/cmake_language/defer_call_invalid_directory-result.txt

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

+ 9 - 0
Tests/RunCMake/cmake_language/defer_call_invalid_directory-stderr.txt

@@ -0,0 +1,9 @@
+^CMake Error at defer_call_invalid_directory.cmake:2 \(cmake_language\):
+  cmake_language DEFER CALL may not be scheduled in directory:
+
+    [^
+]*/Tests/RunCMake/cmake_language/defer_call_invalid_directory-build/defer_call_invalid_directory
+
+  at this time.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)$

+ 2 - 0
Tests/RunCMake/cmake_language/defer_call_invalid_directory.cmake

@@ -0,0 +1,2 @@
+add_subdirectory(defer_call_invalid_directory)
+cmake_language(DEFER DIRECTORY defer_call_invalid_directory CALL message "Should not be allowed.")

+ 0 - 0
Tests/RunCMake/cmake_language/defer_call_invalid_directory/CMakeLists.txt


+ 1 - 0
Tests/RunCMake/cmake_language/defer_call_missing_directory-result.txt

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

+ 9 - 0
Tests/RunCMake/cmake_language/defer_call_missing_directory-stderr.txt

@@ -0,0 +1,9 @@
+^CMake Error at defer_call_missing_directory.cmake:1 \(cmake_language\):
+  cmake_language DEFER DIRECTORY:
+
+    [^
+]*/Tests/RunCMake/cmake_language/does_not_exist
+
+  is not known.  It may not have been processed yet.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)$

+ 1 - 0
Tests/RunCMake/cmake_language/defer_call_missing_directory.cmake

@@ -0,0 +1 @@
+cmake_language(DEFER DIRECTORY does_not_exist CALL message "Should not be allowed.")

+ 1 - 0
Tests/RunCMake/cmake_language/defer_call_policy_PUSH-result.txt

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

+ 2 - 0
Tests/RunCMake/cmake_language/defer_call_policy_PUSH-stderr.txt

@@ -0,0 +1,2 @@
+^CMake Error at CMakeLists.txt:DEFERRED:
+  cmake_policy PUSH without matching POP$

+ 1 - 0
Tests/RunCMake/cmake_language/defer_call_policy_PUSH.cmake

@@ -0,0 +1 @@
+cmake_language(DEFER CALL cmake_policy PUSH)

+ 1 - 0
Tests/RunCMake/cmake_language/defer_call_syntax_error-result.txt

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

+ 13 - 0
Tests/RunCMake/cmake_language/defer_call_syntax_error-stderr.txt

@@ -0,0 +1,13 @@
+^CMake Error at defer_call_syntax_error.cmake:2 \(message\):
+  Syntax error in cmake code at
+
+    [^
+]*/Tests/RunCMake/cmake_language/defer_call_syntax_error.cmake:2
+
+  when parsing string
+
+    Deferred \\X Error
+
+  Invalid character escape '\\X'.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:DEFERRED$

+ 2 - 0
Tests/RunCMake/cmake_language/defer_call_syntax_error.cmake

@@ -0,0 +1,2 @@
+# Argument syntax error evaluated at deferred call site.
+cmake_language(DEFER CALL message "Deferred \X Error")

+ 8 - 0
Tests/RunCMake/cmake_language/defer_call_trace-stderr.txt

@@ -0,0 +1,8 @@
+[^
+]*/Tests/RunCMake/cmake_language/defer_call_trace.cmake\(2\):  cmake_language\(DEFER CALL message Deferred Message \)
+[^
+]*/Tests/RunCMake/cmake_language/defer_call_trace.cmake\(3\):  message\(Immediate Message \)
+Immediate Message
+[^
+]*/Tests/RunCMake/cmake_language/defer_call_trace.cmake\(2\):DEFERRED:__0:  message\(Deferred Message \)
+Deferred Message$

+ 3 - 0
Tests/RunCMake/cmake_language/defer_call_trace.cmake

@@ -0,0 +1,3 @@
+# The --trace and --trace-expand output point here for deferred call.
+cmake_language(DEFER CALL message "Deferred Message")
+message("Immediate Message")

+ 5 - 0
Tests/RunCMake/cmake_language/defer_call_trace_json-stderr.txt

@@ -0,0 +1,5 @@
+{"args":\["DEFER","CALL","message","Deferred Message"\],"cmd":"cmake_language","file":"[^"]*/Tests/RunCMake/cmake_language/defer_call_trace_json.cmake","frame":2,"line":2,"time":[0-9.]+}
+{"args":\["Immediate Message"\],"cmd":"message","file":"[^"]*/Tests/RunCMake/cmake_language/defer_call_trace_json.cmake","frame":2,"line":3,"time":[0-9.]+}
+Immediate Message
+{"args":\["Deferred Message"],"cmd":"message","defer":"__0","file":"[^"]*/Tests/RunCMake/cmake_language/defer_call_trace_json.cmake","frame":1,"line":2,"time":[0-9.]+}
+Deferred Message$

+ 3 - 0
Tests/RunCMake/cmake_language/defer_call_trace_json.cmake

@@ -0,0 +1,3 @@
+# The --trace and --trace-expand output point here for deferred call.
+cmake_language(DEFER CALL message "Deferred Message")
+message("Immediate Message")

+ 1 - 0
Tests/RunCMake/cmake_language/defer_cancel_call_id-result.txt

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

+ 4 - 0
Tests/RunCMake/cmake_language/defer_cancel_call_id-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at defer_cancel_call_id.cmake:1 \(cmake_language\):
+  cmake_language DEFER CANCEL_CALL does not accept ID or ID_VAR.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)$

+ 1 - 0
Tests/RunCMake/cmake_language/defer_cancel_call_id.cmake

@@ -0,0 +1 @@
+cmake_language(DEFER ID id CANCEL_CALL)

+ 1 - 0
Tests/RunCMake/cmake_language/defer_cancel_call_id_var-result.txt

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

+ 4 - 0
Tests/RunCMake/cmake_language/defer_cancel_call_id_var-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at defer_cancel_call_id_var.cmake:1 \(cmake_language\):
+  cmake_language DEFER CANCEL_CALL does not accept ID or ID_VAR.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)$

+ 1 - 0
Tests/RunCMake/cmake_language/defer_cancel_call_id_var.cmake

@@ -0,0 +1 @@
+cmake_language(DEFER ID_VAR id_var CANCEL_CALL)

+ 1 - 0
Tests/RunCMake/cmake_language/defer_cancel_call_invalid_directory-result.txt

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

+ 9 - 0
Tests/RunCMake/cmake_language/defer_cancel_call_invalid_directory-stderr.txt

@@ -0,0 +1,9 @@
+^CMake Error at defer_cancel_call_invalid_directory.cmake:2 \(cmake_language\):
+  cmake_language DEFER CANCEL_CALL may not update directory:
+
+    [^
+]*/Tests/RunCMake/cmake_language/defer_cancel_call_invalid_directory-build/defer_cancel_call_invalid_directory
+
+  at this time.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)

+ 2 - 0
Tests/RunCMake/cmake_language/defer_cancel_call_invalid_directory.cmake

@@ -0,0 +1,2 @@
+add_subdirectory(defer_cancel_call_invalid_directory)
+cmake_language(DEFER DIRECTORY defer_cancel_call_invalid_directory CANCEL_CALL _)

+ 0 - 0
Tests/RunCMake/cmake_language/defer_cancel_call_invalid_directory/CMakeLists.txt


+ 1 - 0
Tests/RunCMake/cmake_language/defer_cancel_call_unknown_argument-result.txt

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

+ 6 - 0
Tests/RunCMake/cmake_language/defer_cancel_call_unknown_argument-stderr.txt

@@ -0,0 +1,6 @@
+^CMake Error at defer_cancel_call_unknown_argument.cmake:1 \(cmake_language\):
+  cmake_language DEFER CANCEL_CALL unknown argument:
+
+    UNKNOWN
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)$

+ 1 - 0
Tests/RunCMake/cmake_language/defer_cancel_call_unknown_argument.cmake

@@ -0,0 +1 @@
+cmake_language(DEFER CANCEL_CALL UNKNOWN)

+ 1 - 0
Tests/RunCMake/cmake_language/defer_directory_empty-result.txt

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

+ 4 - 0
Tests/RunCMake/cmake_language/defer_directory_empty-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at defer_directory_empty.cmake:1 \(cmake_language\):
+  cmake_language DEFER DIRECTORY may not be empty
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)$

+ 1 - 0
Tests/RunCMake/cmake_language/defer_directory_empty.cmake

@@ -0,0 +1 @@
+cmake_language(DEFER DIRECTORY "")

+ 1 - 0
Tests/RunCMake/cmake_language/defer_directory_missing-result.txt

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

+ 4 - 0
Tests/RunCMake/cmake_language/defer_directory_missing-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at defer_directory_missing.cmake:1 \(cmake_language\):
+  cmake_language DEFER DIRECTORY missing value
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)$

+ 1 - 0
Tests/RunCMake/cmake_language/defer_directory_missing.cmake

@@ -0,0 +1 @@
+cmake_language(DEFER DIRECTORY)

+ 1 - 0
Tests/RunCMake/cmake_language/defer_directory_multiple-result.txt

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

+ 4 - 0
Tests/RunCMake/cmake_language/defer_directory_multiple-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at defer_directory_multiple.cmake:1 \(cmake_language\):
+  cmake_language DEFER given multiple DIRECTORY arguments
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)$

+ 1 - 0
Tests/RunCMake/cmake_language/defer_directory_multiple.cmake

@@ -0,0 +1 @@
+cmake_language(DEFER DIRECTORY . DIRECTORY x CALL message "Should not be allowed.")

+ 1 - 0
Tests/RunCMake/cmake_language/defer_get_call_id-result.txt

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

+ 4 - 0
Tests/RunCMake/cmake_language/defer_get_call_id-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at defer_get_call_id.cmake:1 \(cmake_language\):
+  cmake_language DEFER GET_CALL does not accept ID or ID_VAR.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)$

+ 1 - 0
Tests/RunCMake/cmake_language/defer_get_call_id.cmake

@@ -0,0 +1 @@
+cmake_language(DEFER ID id GET_CALL)

+ 1 - 0
Tests/RunCMake/cmake_language/defer_get_call_id_empty-result.txt

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

+ 4 - 0
Tests/RunCMake/cmake_language/defer_get_call_id_empty-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at defer_get_call_id_empty.cmake:1 \(cmake_language\):
+  cmake_language DEFER GET_CALL id may not be empty
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)$

+ 1 - 0
Tests/RunCMake/cmake_language/defer_get_call_id_empty.cmake

@@ -0,0 +1 @@
+cmake_language(DEFER GET_CALL "" var)

+ 1 - 0
Tests/RunCMake/cmake_language/defer_get_call_id_var-result.txt

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

+ 4 - 0
Tests/RunCMake/cmake_language/defer_get_call_id_var-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at defer_get_call_id_var.cmake:1 \(cmake_language\):
+  cmake_language DEFER GET_CALL does not accept ID or ID_VAR.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)$

+ 1 - 0
Tests/RunCMake/cmake_language/defer_get_call_id_var.cmake

@@ -0,0 +1 @@
+cmake_language(DEFER ID_VAR id_var GET_CALL)

+ 1 - 0
Tests/RunCMake/cmake_language/defer_get_call_ids_id-result.txt

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

+ 4 - 0
Tests/RunCMake/cmake_language/defer_get_call_ids_id-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at defer_get_call_ids_id.cmake:1 \(cmake_language\):
+  cmake_language DEFER GET_CALL_IDS does not accept ID or ID_VAR.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)$

+ 1 - 0
Tests/RunCMake/cmake_language/defer_get_call_ids_id.cmake

@@ -0,0 +1 @@
+cmake_language(DEFER ID id GET_CALL_IDS)

+ 1 - 0
Tests/RunCMake/cmake_language/defer_get_call_ids_id_var-result.txt

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

+ 4 - 0
Tests/RunCMake/cmake_language/defer_get_call_ids_id_var-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at defer_get_call_ids_id_var.cmake:1 \(cmake_language\):
+  cmake_language DEFER GET_CALL_IDS does not accept ID or ID_VAR.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)$

+ 1 - 0
Tests/RunCMake/cmake_language/defer_get_call_ids_id_var.cmake

@@ -0,0 +1 @@
+cmake_language(DEFER ID_VAR id_var GET_CALL_IDS)

+ 1 - 0
Tests/RunCMake/cmake_language/defer_get_call_ids_invalid_directory-result.txt

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

+ 9 - 0
Tests/RunCMake/cmake_language/defer_get_call_ids_invalid_directory-stderr.txt

@@ -0,0 +1,9 @@
+^CMake Error at defer_get_call_ids_invalid_directory.cmake:2 \(cmake_language\):
+  cmake_language DEFER GET_CALL_IDS may not access directory:
+
+    [^
+]*/Tests/RunCMake/cmake_language/defer_get_call_ids_invalid_directory-build/defer_get_call_ids_invalid_directory
+
+  at this time.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)$

+ 2 - 0
Tests/RunCMake/cmake_language/defer_get_call_ids_invalid_directory.cmake

@@ -0,0 +1,2 @@
+add_subdirectory(defer_get_call_ids_invalid_directory)
+cmake_language(DEFER DIRECTORY defer_get_call_ids_invalid_directory GET_CALL_IDS var)

+ 0 - 0
Tests/RunCMake/cmake_language/defer_get_call_ids_invalid_directory/CMakeLists.txt


+ 1 - 0
Tests/RunCMake/cmake_language/defer_get_call_ids_missing_var-result.txt

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

+ 4 - 0
Tests/RunCMake/cmake_language/defer_get_call_ids_missing_var-stderr.txt

@@ -0,0 +1,4 @@
+^CMake Error at defer_get_call_ids_missing_var.cmake:1 \(cmake_language\):
+  cmake_language DEFER GET_CALL_IDS missing output variable
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)$

Some files were not shown because too many files changed in this diff