summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Help/command/cmake_language.rst119
-rw-r--r--Help/command/return.rst1
-rw-r--r--Help/manual/cmake.1.rst7
-rw-r--r--Help/release/dev/cmake_language-DEFER.rst5
-rw-r--r--Help/variable/CMAKE_CURRENT_LIST_LINE.rst4
-rw-r--r--Source/cmCMakeLanguageCommand.cxx220
-rw-r--r--Source/cmCommandArgumentParserHelper.cxx12
-rw-r--r--Source/cmGlobalGenerator.cxx7
-rw-r--r--Source/cmGlobalGenerator.h5
-rw-r--r--Source/cmListFileCache.cxx7
-rw-r--r--Source/cmListFileCache.h10
-rw-r--r--Source/cmMakefile.cxx190
-rw-r--r--Source/cmMakefile.h37
-rw-r--r--Source/cmState.cxx15
-rw-r--r--Source/cmState.h2
-rw-r--r--Source/cmStateTypes.h1
-rw-r--r--Source/cmake.cxx2
-rwxr-xr-xTests/RunCMake/CommandLine/trace-json-v1-check.py2
-rw-r--r--Tests/RunCMake/cmake_language/RunCMakeTest.cmake50
-rw-r--r--Tests/RunCMake/cmake_language/defer_call-stderr.txt15
-rw-r--r--Tests/RunCMake/cmake_language/defer_call-stdout.txt8
-rw-r--r--Tests/RunCMake/cmake_language/defer_call.cmake12
-rw-r--r--Tests/RunCMake/cmake_language/defer_call/CMakeLists.txt11
-rw-r--r--Tests/RunCMake/cmake_language/defer_call/include.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_add_subdirectory-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_add_subdirectory-stderr.txt9
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_add_subdirectory.cmake2
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_add_subdirectory/CMakeLists.txt0
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_enable_language-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_enable_language-stderr.txt9
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_enable_language.cmake2
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_error-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_error-stderr.txt9
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_error.cmake3
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_error/CMakeLists.txt2
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_ids-stdout.txt13
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_ids.cmake14
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_invalid_command-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_invalid_command-stderr.txt4
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_invalid_command.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_invalid_directory-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_invalid_directory-stderr.txt9
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_invalid_directory.cmake2
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_invalid_directory/CMakeLists.txt0
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_missing_directory-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_missing_directory-stderr.txt9
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_missing_directory.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_policy_PUSH-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_policy_PUSH-stderr.txt2
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_policy_PUSH.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_syntax_error-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_syntax_error-stderr.txt13
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_syntax_error.cmake2
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_trace-stderr.txt8
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_trace.cmake3
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_trace_json-stderr.txt5
-rw-r--r--Tests/RunCMake/cmake_language/defer_call_trace_json.cmake3
-rw-r--r--Tests/RunCMake/cmake_language/defer_cancel_call_id-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_cancel_call_id-stderr.txt4
-rw-r--r--Tests/RunCMake/cmake_language/defer_cancel_call_id.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_cancel_call_id_var-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_cancel_call_id_var-stderr.txt4
-rw-r--r--Tests/RunCMake/cmake_language/defer_cancel_call_id_var.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_cancel_call_invalid_directory-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_cancel_call_invalid_directory-stderr.txt9
-rw-r--r--Tests/RunCMake/cmake_language/defer_cancel_call_invalid_directory.cmake2
-rw-r--r--Tests/RunCMake/cmake_language/defer_cancel_call_invalid_directory/CMakeLists.txt0
-rw-r--r--Tests/RunCMake/cmake_language/defer_cancel_call_unknown_argument-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_cancel_call_unknown_argument-stderr.txt6
-rw-r--r--Tests/RunCMake/cmake_language/defer_cancel_call_unknown_argument.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_directory_empty-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_directory_empty-stderr.txt4
-rw-r--r--Tests/RunCMake/cmake_language/defer_directory_empty.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_directory_missing-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_directory_missing-stderr.txt4
-rw-r--r--Tests/RunCMake/cmake_language/defer_directory_missing.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_directory_multiple-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_directory_multiple-stderr.txt4
-rw-r--r--Tests/RunCMake/cmake_language/defer_directory_multiple.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_id-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_id-stderr.txt4
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_id.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_id_empty-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_id_empty-stderr.txt4
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_id_empty.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_id_var-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_id_var-stderr.txt4
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_id_var.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_ids_id-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_ids_id-stderr.txt4
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_ids_id.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_ids_id_var-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_ids_id_var-stderr.txt4
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_ids_id_var.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_ids_invalid_directory-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_ids_invalid_directory-stderr.txt9
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_ids_invalid_directory.cmake2
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_ids_invalid_directory/CMakeLists.txt0
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_ids_missing_var-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_ids_missing_var-stderr.txt4
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_ids_missing_var.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_ids_too_many_args-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_ids_too_many_args-stderr.txt4
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_ids_too_many_args.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_missing_id-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_missing_id-stderr.txt4
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_missing_id.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_missing_var-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_missing_var-stderr.txt4
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_missing_var.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_too_many_args-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_too_many_args-stderr.txt4
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_too_many_args.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_unknown_argument-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_unknown_argument-stderr.txt6
-rw-r--r--Tests/RunCMake/cmake_language/defer_get_call_unknown_argument.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_id_empty-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_id_empty-stderr.txt4
-rw-r--r--Tests/RunCMake/cmake_language/defer_id_empty.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_id_missing-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_id_missing-stderr.txt4
-rw-r--r--Tests/RunCMake/cmake_language/defer_id_missing.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_id_multiple-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_id_multiple-stderr.txt4
-rw-r--r--Tests/RunCMake/cmake_language/defer_id_multiple.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_id_var_empty-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_id_var_empty-stderr.txt4
-rw-r--r--Tests/RunCMake/cmake_language/defer_id_var_empty.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_id_var_missing-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_id_var_missing-stderr.txt4
-rw-r--r--Tests/RunCMake/cmake_language/defer_id_var_missing.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_id_var_multiple-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_id_var_multiple-stderr.txt4
-rw-r--r--Tests/RunCMake/cmake_language/defer_id_var_multiple.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_missing_arg-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_missing_arg-stderr.txt4
-rw-r--r--Tests/RunCMake/cmake_language/defer_missing_arg.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_missing_call-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_missing_call-stderr.txt4
-rw-r--r--Tests/RunCMake/cmake_language/defer_missing_call.cmake1
-rw-r--r--Tests/RunCMake/cmake_language/defer_unknown_option-result.txt1
-rw-r--r--Tests/RunCMake/cmake_language/defer_unknown_option-stderr.txt6
-rw-r--r--Tests/RunCMake/cmake_language/defer_unknown_option.cmake1
143 files changed, 1038 insertions, 25 deletions
diff --git a/Help/command/cmake_language.rst b/Help/command/cmake_language.rst
index 9ce5a6f..00e1a69 100644
--- a/Help/command/cmake_language.rst
+++ b/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
diff --git a/Help/command/return.rst b/Help/command/return.rst
index 830992c..ec009d8 100644
--- a/Help/command/return.rst
+++ b/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.
diff --git a/Help/manual/cmake.1.rst b/Help/manual/cmake.1.rst
index c5e0aae..9850116 100644
--- a/Help/manual/cmake.1.rst
+++ b/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
}
}
diff --git a/Help/release/dev/cmake_language-DEFER.rst b/Help/release/dev/cmake_language-DEFER.rst
new file mode 100644
index 0000000..8bd0ee9
--- /dev/null
+++ b/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.
diff --git a/Help/variable/CMAKE_CURRENT_LIST_LINE.rst b/Help/variable/CMAKE_CURRENT_LIST_LINE.rst
index 60e8e26..7f839c2 100644
--- a/Help/variable/CMAKE_CURRENT_LIST_LINE.rst
+++ b/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.
diff --git a/Source/cmCMakeLanguageCommand.cxx b/Source/cmCMakeLanguageCommand.cxx
index d92ca28..9277c20 100644
--- a/Source/cmCMakeLanguageCommand.cxx
+++ b/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") {
diff --git a/Source/cmCommandArgumentParserHelper.cxx b/Source/cmCommandArgumentParserHelper.cxx
index e3d014e..d4f5022 100644
--- a/Source/cmCommandArgumentParserHelper.cxx
+++ b/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) {
diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx
index 46030e0..1197db6 100644
--- a/Source/cmGlobalGenerator.cxx
+++ b/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;
diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h
index 478028e..b532a43 100644
--- a/Source/cmGlobalGenerator.h
+++ b/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
{
diff --git a/Source/cmListFileCache.cxx b/Source/cmListFileCache.cxx
index 7ebb02f..d678b56 100644
--- a/Source/cmListFileCache.cxx
+++ b/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;
}
diff --git a/Source/cmListFileCache.h b/Source/cmListFileCache.h
index 5773e6a..5617536 100644
--- a/Source/cmListFileCache.h
+++ b/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;
}
};
diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx
index 896839b..ac3a193 100644
--- a/Source/cmMakefile.cxx
+++ b/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);
}
diff --git a/Source/cmMakefile.h b/Source/cmMakefile.h
index d851dd6..c7940fb 100644
--- a/Source/cmMakefile.h
+++ b/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;
diff --git a/Source/cmState.cxx b/Source/cmState.cxx
index e96c82f..d5ac9ae 100644
--- a/Source/cmState.cxx
+++ b/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)
{
diff --git a/Source/cmState.h b/Source/cmState.h
index dc3ba65..2aa57e0 100644
--- a/Source/cmState.h
+++ b/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(
diff --git a/Source/cmStateTypes.h b/Source/cmStateTypes.h
index b8c1cca..010d7e3 100644
--- a/Source/cmStateTypes.h
+++ b/Source/cmStateTypes.h
@@ -18,6 +18,7 @@ enum SnapshotType
{
BaseType,
BuildsystemDirectoryType,
+ DeferCallType,
FunctionCallType,
MacroCallType,
IncludeFileType,
diff --git a/Source/cmake.cxx b/Source/cmake.cxx
index 014a707..dc06fae 100644
--- a/Source/cmake.cxx
+++ b/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
diff --git a/Tests/RunCMake/CommandLine/trace-json-v1-check.py b/Tests/RunCMake/CommandLine/trace-json-v1-check.py
index e617b76..1ee005e 100755
--- a/Tests/RunCMake/CommandLine/trace-json-v1-check.py
+++ b/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)
diff --git a/Tests/RunCMake/cmake_language/RunCMakeTest.cmake b/Tests/RunCMake/cmake_language/RunCMakeTest.cmake
index 29a4d23..6480b2e 100644
--- a/Tests/RunCMake/cmake_language/RunCMakeTest.cmake
+++ b/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)
diff --git a/Tests/RunCMake/cmake_language/defer_call-stderr.txt b/Tests/RunCMake/cmake_language/defer_call-stderr.txt
new file mode 100644
index 0000000..7e8d8ca
--- /dev/null
+++ b/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$
diff --git a/Tests/RunCMake/cmake_language/defer_call-stdout.txt b/Tests/RunCMake/cmake_language/defer_call-stdout.txt
new file mode 100644
index 0000000..fcf9f29
--- /dev/null
+++ b/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
diff --git a/Tests/RunCMake/cmake_language/defer_call.cmake b/Tests/RunCMake/cmake_language/defer_call.cmake
new file mode 100644
index 0000000..2e9595f
--- /dev/null
+++ b/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")
diff --git a/Tests/RunCMake/cmake_language/defer_call/CMakeLists.txt b/Tests/RunCMake/cmake_language/defer_call/CMakeLists.txt
new file mode 100644
index 0000000..544b9f4
--- /dev/null
+++ b/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}'")
diff --git a/Tests/RunCMake/cmake_language/defer_call/include.cmake b/Tests/RunCMake/cmake_language/defer_call/include.cmake
new file mode 100644
index 0000000..272c61b
--- /dev/null
+++ b/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}'")
diff --git a/Tests/RunCMake/cmake_language/defer_call_add_subdirectory-result.txt b/Tests/RunCMake/cmake_language/defer_call_add_subdirectory-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_call_add_subdirectory-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_call_add_subdirectory-stderr.txt b/Tests/RunCMake/cmake_language/defer_call_add_subdirectory-stderr.txt
new file mode 100644
index 0000000..ec20b8f
--- /dev/null
+++ b/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$
diff --git a/Tests/RunCMake/cmake_language/defer_call_add_subdirectory.cmake b/Tests/RunCMake/cmake_language/defer_call_add_subdirectory.cmake
new file mode 100644
index 0000000..6b7ee63
--- /dev/null
+++ b/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)
diff --git a/Tests/RunCMake/cmake_language/defer_call_add_subdirectory/CMakeLists.txt b/Tests/RunCMake/cmake_language/defer_call_add_subdirectory/CMakeLists.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_call_add_subdirectory/CMakeLists.txt
diff --git a/Tests/RunCMake/cmake_language/defer_call_enable_language-result.txt b/Tests/RunCMake/cmake_language/defer_call_enable_language-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_call_enable_language-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_call_enable_language-stderr.txt b/Tests/RunCMake/cmake_language/defer_call_enable_language-stderr.txt
new file mode 100644
index 0000000..65a0b99
--- /dev/null
+++ b/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$
diff --git a/Tests/RunCMake/cmake_language/defer_call_enable_language.cmake b/Tests/RunCMake/cmake_language/defer_call_enable_language.cmake
new file mode 100644
index 0000000..eb43f80
--- /dev/null
+++ b/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)
diff --git a/Tests/RunCMake/cmake_language/defer_call_error-result.txt b/Tests/RunCMake/cmake_language/defer_call_error-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_call_error-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_call_error-stderr.txt b/Tests/RunCMake/cmake_language/defer_call_error-stderr.txt
new file mode 100644
index 0000000..63ce145
--- /dev/null
+++ b/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$
diff --git a/Tests/RunCMake/cmake_language/defer_call_error.cmake b/Tests/RunCMake/cmake_language/defer_call_error.cmake
new file mode 100644
index 0000000..083e82a
--- /dev/null
+++ b/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)
diff --git a/Tests/RunCMake/cmake_language/defer_call_error/CMakeLists.txt b/Tests/RunCMake/cmake_language/defer_call_error/CMakeLists.txt
new file mode 100644
index 0000000..0acac69
--- /dev/null
+++ b/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")
diff --git a/Tests/RunCMake/cmake_language/defer_call_ids-stdout.txt b/Tests/RunCMake/cmake_language/defer_call_ids-stdout.txt
new file mode 100644
index 0000000..2fd194d
--- /dev/null
+++ b/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
diff --git a/Tests/RunCMake/cmake_language/defer_call_ids.cmake b/Tests/RunCMake/cmake_language/defer_call_ids.cmake
new file mode 100644
index 0000000..2874894
--- /dev/null
+++ b/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()
diff --git a/Tests/RunCMake/cmake_language/defer_call_invalid_command-result.txt b/Tests/RunCMake/cmake_language/defer_call_invalid_command-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_call_invalid_command-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_call_invalid_command-stderr.txt b/Tests/RunCMake/cmake_language/defer_call_invalid_command-stderr.txt
new file mode 100644
index 0000000..4cdbf0c
--- /dev/null
+++ b/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\)$
diff --git a/Tests/RunCMake/cmake_language/defer_call_invalid_command.cmake b/Tests/RunCMake/cmake_language/defer_call_invalid_command.cmake
new file mode 100644
index 0000000..d6cc936
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_call_invalid_command.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER CALL ${command})
diff --git a/Tests/RunCMake/cmake_language/defer_call_invalid_directory-result.txt b/Tests/RunCMake/cmake_language/defer_call_invalid_directory-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_call_invalid_directory-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_call_invalid_directory-stderr.txt b/Tests/RunCMake/cmake_language/defer_call_invalid_directory-stderr.txt
new file mode 100644
index 0000000..afe9a0e
--- /dev/null
+++ b/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\)$
diff --git a/Tests/RunCMake/cmake_language/defer_call_invalid_directory.cmake b/Tests/RunCMake/cmake_language/defer_call_invalid_directory.cmake
new file mode 100644
index 0000000..cc1eb8d
--- /dev/null
+++ b/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.")
diff --git a/Tests/RunCMake/cmake_language/defer_call_invalid_directory/CMakeLists.txt b/Tests/RunCMake/cmake_language/defer_call_invalid_directory/CMakeLists.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_call_invalid_directory/CMakeLists.txt
diff --git a/Tests/RunCMake/cmake_language/defer_call_missing_directory-result.txt b/Tests/RunCMake/cmake_language/defer_call_missing_directory-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_call_missing_directory-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_call_missing_directory-stderr.txt b/Tests/RunCMake/cmake_language/defer_call_missing_directory-stderr.txt
new file mode 100644
index 0000000..db4f90e
--- /dev/null
+++ b/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\)$
diff --git a/Tests/RunCMake/cmake_language/defer_call_missing_directory.cmake b/Tests/RunCMake/cmake_language/defer_call_missing_directory.cmake
new file mode 100644
index 0000000..01f4c40
--- /dev/null
+++ b/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.")
diff --git a/Tests/RunCMake/cmake_language/defer_call_policy_PUSH-result.txt b/Tests/RunCMake/cmake_language/defer_call_policy_PUSH-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_call_policy_PUSH-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_call_policy_PUSH-stderr.txt b/Tests/RunCMake/cmake_language/defer_call_policy_PUSH-stderr.txt
new file mode 100644
index 0000000..923be13
--- /dev/null
+++ b/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$
diff --git a/Tests/RunCMake/cmake_language/defer_call_policy_PUSH.cmake b/Tests/RunCMake/cmake_language/defer_call_policy_PUSH.cmake
new file mode 100644
index 0000000..66cb760
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_call_policy_PUSH.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER CALL cmake_policy PUSH)
diff --git a/Tests/RunCMake/cmake_language/defer_call_syntax_error-result.txt b/Tests/RunCMake/cmake_language/defer_call_syntax_error-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_call_syntax_error-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_call_syntax_error-stderr.txt b/Tests/RunCMake/cmake_language/defer_call_syntax_error-stderr.txt
new file mode 100644
index 0000000..80db4aa
--- /dev/null
+++ b/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$
diff --git a/Tests/RunCMake/cmake_language/defer_call_syntax_error.cmake b/Tests/RunCMake/cmake_language/defer_call_syntax_error.cmake
new file mode 100644
index 0000000..c3c044b
--- /dev/null
+++ b/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")
diff --git a/Tests/RunCMake/cmake_language/defer_call_trace-stderr.txt b/Tests/RunCMake/cmake_language/defer_call_trace-stderr.txt
new file mode 100644
index 0000000..b61b236
--- /dev/null
+++ b/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$
diff --git a/Tests/RunCMake/cmake_language/defer_call_trace.cmake b/Tests/RunCMake/cmake_language/defer_call_trace.cmake
new file mode 100644
index 0000000..5ed383f
--- /dev/null
+++ b/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")
diff --git a/Tests/RunCMake/cmake_language/defer_call_trace_json-stderr.txt b/Tests/RunCMake/cmake_language/defer_call_trace_json-stderr.txt
new file mode 100644
index 0000000..647beb0
--- /dev/null
+++ b/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$
diff --git a/Tests/RunCMake/cmake_language/defer_call_trace_json.cmake b/Tests/RunCMake/cmake_language/defer_call_trace_json.cmake
new file mode 100644
index 0000000..5ed383f
--- /dev/null
+++ b/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")
diff --git a/Tests/RunCMake/cmake_language/defer_cancel_call_id-result.txt b/Tests/RunCMake/cmake_language/defer_cancel_call_id-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_cancel_call_id-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_cancel_call_id-stderr.txt b/Tests/RunCMake/cmake_language/defer_cancel_call_id-stderr.txt
new file mode 100644
index 0000000..8a13c0d
--- /dev/null
+++ b/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\)$
diff --git a/Tests/RunCMake/cmake_language/defer_cancel_call_id.cmake b/Tests/RunCMake/cmake_language/defer_cancel_call_id.cmake
new file mode 100644
index 0000000..6e5b5c8
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_cancel_call_id.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER ID id CANCEL_CALL)
diff --git a/Tests/RunCMake/cmake_language/defer_cancel_call_id_var-result.txt b/Tests/RunCMake/cmake_language/defer_cancel_call_id_var-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_cancel_call_id_var-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_cancel_call_id_var-stderr.txt b/Tests/RunCMake/cmake_language/defer_cancel_call_id_var-stderr.txt
new file mode 100644
index 0000000..5783c50
--- /dev/null
+++ b/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\)$
diff --git a/Tests/RunCMake/cmake_language/defer_cancel_call_id_var.cmake b/Tests/RunCMake/cmake_language/defer_cancel_call_id_var.cmake
new file mode 100644
index 0000000..9f75221
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_cancel_call_id_var.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER ID_VAR id_var CANCEL_CALL)
diff --git a/Tests/RunCMake/cmake_language/defer_cancel_call_invalid_directory-result.txt b/Tests/RunCMake/cmake_language/defer_cancel_call_invalid_directory-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_cancel_call_invalid_directory-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_cancel_call_invalid_directory-stderr.txt b/Tests/RunCMake/cmake_language/defer_cancel_call_invalid_directory-stderr.txt
new file mode 100644
index 0000000..cacbf9a
--- /dev/null
+++ b/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\)
diff --git a/Tests/RunCMake/cmake_language/defer_cancel_call_invalid_directory.cmake b/Tests/RunCMake/cmake_language/defer_cancel_call_invalid_directory.cmake
new file mode 100644
index 0000000..29a8fc2
--- /dev/null
+++ b/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 _)
diff --git a/Tests/RunCMake/cmake_language/defer_cancel_call_invalid_directory/CMakeLists.txt b/Tests/RunCMake/cmake_language/defer_cancel_call_invalid_directory/CMakeLists.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_cancel_call_invalid_directory/CMakeLists.txt
diff --git a/Tests/RunCMake/cmake_language/defer_cancel_call_unknown_argument-result.txt b/Tests/RunCMake/cmake_language/defer_cancel_call_unknown_argument-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_cancel_call_unknown_argument-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_cancel_call_unknown_argument-stderr.txt b/Tests/RunCMake/cmake_language/defer_cancel_call_unknown_argument-stderr.txt
new file mode 100644
index 0000000..eb8f2b9
--- /dev/null
+++ b/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\)$
diff --git a/Tests/RunCMake/cmake_language/defer_cancel_call_unknown_argument.cmake b/Tests/RunCMake/cmake_language/defer_cancel_call_unknown_argument.cmake
new file mode 100644
index 0000000..fbc6309
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_cancel_call_unknown_argument.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER CANCEL_CALL UNKNOWN)
diff --git a/Tests/RunCMake/cmake_language/defer_directory_empty-result.txt b/Tests/RunCMake/cmake_language/defer_directory_empty-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_directory_empty-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_directory_empty-stderr.txt b/Tests/RunCMake/cmake_language/defer_directory_empty-stderr.txt
new file mode 100644
index 0000000..587dfa9
--- /dev/null
+++ b/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\)$
diff --git a/Tests/RunCMake/cmake_language/defer_directory_empty.cmake b/Tests/RunCMake/cmake_language/defer_directory_empty.cmake
new file mode 100644
index 0000000..f4e4553
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_directory_empty.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER DIRECTORY "")
diff --git a/Tests/RunCMake/cmake_language/defer_directory_missing-result.txt b/Tests/RunCMake/cmake_language/defer_directory_missing-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_directory_missing-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_directory_missing-stderr.txt b/Tests/RunCMake/cmake_language/defer_directory_missing-stderr.txt
new file mode 100644
index 0000000..1db8e99
--- /dev/null
+++ b/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\)$
diff --git a/Tests/RunCMake/cmake_language/defer_directory_missing.cmake b/Tests/RunCMake/cmake_language/defer_directory_missing.cmake
new file mode 100644
index 0000000..fbdb177
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_directory_missing.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER DIRECTORY)
diff --git a/Tests/RunCMake/cmake_language/defer_directory_multiple-result.txt b/Tests/RunCMake/cmake_language/defer_directory_multiple-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_directory_multiple-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_directory_multiple-stderr.txt b/Tests/RunCMake/cmake_language/defer_directory_multiple-stderr.txt
new file mode 100644
index 0000000..f4d09b9
--- /dev/null
+++ b/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\)$
diff --git a/Tests/RunCMake/cmake_language/defer_directory_multiple.cmake b/Tests/RunCMake/cmake_language/defer_directory_multiple.cmake
new file mode 100644
index 0000000..baf037b
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_directory_multiple.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER DIRECTORY . DIRECTORY x CALL message "Should not be allowed.")
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_id-result.txt b/Tests/RunCMake/cmake_language/defer_get_call_id-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_id-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_id-stderr.txt b/Tests/RunCMake/cmake_language/defer_get_call_id-stderr.txt
new file mode 100644
index 0000000..e161a5f
--- /dev/null
+++ b/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\)$
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_id.cmake b/Tests/RunCMake/cmake_language/defer_get_call_id.cmake
new file mode 100644
index 0000000..7a395ea
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_id.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER ID id GET_CALL)
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_id_empty-result.txt b/Tests/RunCMake/cmake_language/defer_get_call_id_empty-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_id_empty-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_id_empty-stderr.txt b/Tests/RunCMake/cmake_language/defer_get_call_id_empty-stderr.txt
new file mode 100644
index 0000000..c7c534b
--- /dev/null
+++ b/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\)$
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_id_empty.cmake b/Tests/RunCMake/cmake_language/defer_get_call_id_empty.cmake
new file mode 100644
index 0000000..4f39f2d
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_id_empty.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER GET_CALL "" var)
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_id_var-result.txt b/Tests/RunCMake/cmake_language/defer_get_call_id_var-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_id_var-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_id_var-stderr.txt b/Tests/RunCMake/cmake_language/defer_get_call_id_var-stderr.txt
new file mode 100644
index 0000000..2cfd942
--- /dev/null
+++ b/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\)$
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_id_var.cmake b/Tests/RunCMake/cmake_language/defer_get_call_id_var.cmake
new file mode 100644
index 0000000..ade0815
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_id_var.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER ID_VAR id_var GET_CALL)
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_ids_id-result.txt b/Tests/RunCMake/cmake_language/defer_get_call_ids_id-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_ids_id-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_ids_id-stderr.txt b/Tests/RunCMake/cmake_language/defer_get_call_ids_id-stderr.txt
new file mode 100644
index 0000000..072ee45
--- /dev/null
+++ b/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\)$
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_ids_id.cmake b/Tests/RunCMake/cmake_language/defer_get_call_ids_id.cmake
new file mode 100644
index 0000000..4eb2555
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_ids_id.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER ID id GET_CALL_IDS)
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_ids_id_var-result.txt b/Tests/RunCMake/cmake_language/defer_get_call_ids_id_var-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_ids_id_var-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_ids_id_var-stderr.txt b/Tests/RunCMake/cmake_language/defer_get_call_ids_id_var-stderr.txt
new file mode 100644
index 0000000..e4a288c
--- /dev/null
+++ b/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\)$
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_ids_id_var.cmake b/Tests/RunCMake/cmake_language/defer_get_call_ids_id_var.cmake
new file mode 100644
index 0000000..c27de79
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_ids_id_var.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER ID_VAR id_var GET_CALL_IDS)
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_ids_invalid_directory-result.txt b/Tests/RunCMake/cmake_language/defer_get_call_ids_invalid_directory-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_ids_invalid_directory-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_ids_invalid_directory-stderr.txt b/Tests/RunCMake/cmake_language/defer_get_call_ids_invalid_directory-stderr.txt
new file mode 100644
index 0000000..edebe32
--- /dev/null
+++ b/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\)$
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_ids_invalid_directory.cmake b/Tests/RunCMake/cmake_language/defer_get_call_ids_invalid_directory.cmake
new file mode 100644
index 0000000..81f098e
--- /dev/null
+++ b/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)
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_ids_invalid_directory/CMakeLists.txt b/Tests/RunCMake/cmake_language/defer_get_call_ids_invalid_directory/CMakeLists.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_ids_invalid_directory/CMakeLists.txt
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_ids_missing_var-result.txt b/Tests/RunCMake/cmake_language/defer_get_call_ids_missing_var-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_ids_missing_var-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_ids_missing_var-stderr.txt b/Tests/RunCMake/cmake_language/defer_get_call_ids_missing_var-stderr.txt
new file mode 100644
index 0000000..a2951cf
--- /dev/null
+++ b/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\)$
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_ids_missing_var.cmake b/Tests/RunCMake/cmake_language/defer_get_call_ids_missing_var.cmake
new file mode 100644
index 0000000..b171f04
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_ids_missing_var.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER GET_CALL_IDS)
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_ids_too_many_args-result.txt b/Tests/RunCMake/cmake_language/defer_get_call_ids_too_many_args-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_ids_too_many_args-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_ids_too_many_args-stderr.txt b/Tests/RunCMake/cmake_language/defer_get_call_ids_too_many_args-stderr.txt
new file mode 100644
index 0000000..5691519
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_ids_too_many_args-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Error at defer_get_call_ids_too_many_args.cmake:1 \(cmake_language\):
+ cmake_language DEFER GET_CALL_IDS given too many arguments
+Call Stack \(most recent call first\):
+ CMakeLists.txt:3 \(include\)$
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_ids_too_many_args.cmake b/Tests/RunCMake/cmake_language/defer_get_call_ids_too_many_args.cmake
new file mode 100644
index 0000000..0158684
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_ids_too_many_args.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER GET_CALL_IDS var extra)
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_missing_id-result.txt b/Tests/RunCMake/cmake_language/defer_get_call_missing_id-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_missing_id-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_missing_id-stderr.txt b/Tests/RunCMake/cmake_language/defer_get_call_missing_id-stderr.txt
new file mode 100644
index 0000000..081aa95
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_missing_id-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Error at defer_get_call_missing_id.cmake:1 \(cmake_language\):
+ cmake_language DEFER GET_CALL missing id
+Call Stack \(most recent call first\):
+ CMakeLists.txt:3 \(include\)$
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_missing_id.cmake b/Tests/RunCMake/cmake_language/defer_get_call_missing_id.cmake
new file mode 100644
index 0000000..0542abc
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_missing_id.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER GET_CALL)
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_missing_var-result.txt b/Tests/RunCMake/cmake_language/defer_get_call_missing_var-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_missing_var-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_missing_var-stderr.txt b/Tests/RunCMake/cmake_language/defer_get_call_missing_var-stderr.txt
new file mode 100644
index 0000000..1b2641f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_missing_var-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Error at defer_get_call_missing_var.cmake:1 \(cmake_language\):
+ cmake_language DEFER GET_CALL missing output variable
+Call Stack \(most recent call first\):
+ CMakeLists.txt:3 \(include\)$
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_missing_var.cmake b/Tests/RunCMake/cmake_language/defer_get_call_missing_var.cmake
new file mode 100644
index 0000000..7916d29
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_missing_var.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER GET_CALL id)
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_too_many_args-result.txt b/Tests/RunCMake/cmake_language/defer_get_call_too_many_args-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_too_many_args-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_too_many_args-stderr.txt b/Tests/RunCMake/cmake_language/defer_get_call_too_many_args-stderr.txt
new file mode 100644
index 0000000..b6ee2d6
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_too_many_args-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Error at defer_get_call_too_many_args.cmake:1 \(cmake_language\):
+ cmake_language DEFER GET_CALL given too many arguments
+Call Stack \(most recent call first\):
+ CMakeLists.txt:3 \(include\)$
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_too_many_args.cmake b/Tests/RunCMake/cmake_language/defer_get_call_too_many_args.cmake
new file mode 100644
index 0000000..ed65779
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_too_many_args.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER GET_CALL id var extra)
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_unknown_argument-result.txt b/Tests/RunCMake/cmake_language/defer_get_call_unknown_argument-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_unknown_argument-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_unknown_argument-stderr.txt b/Tests/RunCMake/cmake_language/defer_get_call_unknown_argument-stderr.txt
new file mode 100644
index 0000000..ac16596
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_unknown_argument-stderr.txt
@@ -0,0 +1,6 @@
+^CMake Error at defer_get_call_unknown_argument.cmake:1 \(cmake_language\):
+ cmake_language DEFER GET_CALL unknown argument:
+
+ UNKNOWN
+Call Stack \(most recent call first\):
+ CMakeLists.txt:3 \(include\)$
diff --git a/Tests/RunCMake/cmake_language/defer_get_call_unknown_argument.cmake b/Tests/RunCMake/cmake_language/defer_get_call_unknown_argument.cmake
new file mode 100644
index 0000000..d0caa39
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_get_call_unknown_argument.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER GET_CALL UNKNOWN var)
diff --git a/Tests/RunCMake/cmake_language/defer_id_empty-result.txt b/Tests/RunCMake/cmake_language/defer_id_empty-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_id_empty-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_id_empty-stderr.txt b/Tests/RunCMake/cmake_language/defer_id_empty-stderr.txt
new file mode 100644
index 0000000..1e7f772
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_id_empty-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Error at defer_id_empty.cmake:1 \(cmake_language\):
+ cmake_language DEFER ID may not be empty
+Call Stack \(most recent call first\):
+ CMakeLists.txt:3 \(include\)$
diff --git a/Tests/RunCMake/cmake_language/defer_id_empty.cmake b/Tests/RunCMake/cmake_language/defer_id_empty.cmake
new file mode 100644
index 0000000..326762c
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_id_empty.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER ID "")
diff --git a/Tests/RunCMake/cmake_language/defer_id_missing-result.txt b/Tests/RunCMake/cmake_language/defer_id_missing-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_id_missing-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_id_missing-stderr.txt b/Tests/RunCMake/cmake_language/defer_id_missing-stderr.txt
new file mode 100644
index 0000000..cef5f0e
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_id_missing-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Error at defer_id_missing.cmake:1 \(cmake_language\):
+ cmake_language DEFER ID missing value
+Call Stack \(most recent call first\):
+ CMakeLists.txt:3 \(include\)$
diff --git a/Tests/RunCMake/cmake_language/defer_id_missing.cmake b/Tests/RunCMake/cmake_language/defer_id_missing.cmake
new file mode 100644
index 0000000..4de687d
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_id_missing.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER ID)
diff --git a/Tests/RunCMake/cmake_language/defer_id_multiple-result.txt b/Tests/RunCMake/cmake_language/defer_id_multiple-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_id_multiple-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_id_multiple-stderr.txt b/Tests/RunCMake/cmake_language/defer_id_multiple-stderr.txt
new file mode 100644
index 0000000..1725521
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_id_multiple-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Error at defer_id_multiple.cmake:1 \(cmake_language\):
+ cmake_language DEFER given multiple ID arguments
+Call Stack \(most recent call first\):
+ CMakeLists.txt:3 \(include\)$
diff --git a/Tests/RunCMake/cmake_language/defer_id_multiple.cmake b/Tests/RunCMake/cmake_language/defer_id_multiple.cmake
new file mode 100644
index 0000000..69187af
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_id_multiple.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER ID a ID b CALL message "Should not be allowed.")
diff --git a/Tests/RunCMake/cmake_language/defer_id_var_empty-result.txt b/Tests/RunCMake/cmake_language/defer_id_var_empty-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_id_var_empty-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_id_var_empty-stderr.txt b/Tests/RunCMake/cmake_language/defer_id_var_empty-stderr.txt
new file mode 100644
index 0000000..bb5cd43
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_id_var_empty-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Error at defer_id_var_empty.cmake:1 \(cmake_language\):
+ cmake_language DEFER ID_VAR may not be empty
+Call Stack \(most recent call first\):
+ CMakeLists.txt:3 \(include\)$
diff --git a/Tests/RunCMake/cmake_language/defer_id_var_empty.cmake b/Tests/RunCMake/cmake_language/defer_id_var_empty.cmake
new file mode 100644
index 0000000..c7198f5
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_id_var_empty.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER ID_VAR "")
diff --git a/Tests/RunCMake/cmake_language/defer_id_var_missing-result.txt b/Tests/RunCMake/cmake_language/defer_id_var_missing-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_id_var_missing-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_id_var_missing-stderr.txt b/Tests/RunCMake/cmake_language/defer_id_var_missing-stderr.txt
new file mode 100644
index 0000000..f4e0d6e
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_id_var_missing-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Error at defer_id_var_missing.cmake:1 \(cmake_language\):
+ cmake_language DEFER ID_VAR missing variable name
+Call Stack \(most recent call first\):
+ CMakeLists.txt:3 \(include\)$
diff --git a/Tests/RunCMake/cmake_language/defer_id_var_missing.cmake b/Tests/RunCMake/cmake_language/defer_id_var_missing.cmake
new file mode 100644
index 0000000..359d149
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_id_var_missing.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER ID_VAR)
diff --git a/Tests/RunCMake/cmake_language/defer_id_var_multiple-result.txt b/Tests/RunCMake/cmake_language/defer_id_var_multiple-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_id_var_multiple-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_id_var_multiple-stderr.txt b/Tests/RunCMake/cmake_language/defer_id_var_multiple-stderr.txt
new file mode 100644
index 0000000..4368b06
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_id_var_multiple-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Error at defer_id_var_multiple.cmake:1 \(cmake_language\):
+ cmake_language DEFER given multiple ID_VAR arguments
+Call Stack \(most recent call first\):
+ CMakeLists.txt:3 \(include\)$
diff --git a/Tests/RunCMake/cmake_language/defer_id_var_multiple.cmake b/Tests/RunCMake/cmake_language/defer_id_var_multiple.cmake
new file mode 100644
index 0000000..665ea94
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_id_var_multiple.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER ID_VAR a ID_VAR b CALL message "Should not be allowed.")
diff --git a/Tests/RunCMake/cmake_language/defer_missing_arg-result.txt b/Tests/RunCMake/cmake_language/defer_missing_arg-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_missing_arg-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_missing_arg-stderr.txt b/Tests/RunCMake/cmake_language/defer_missing_arg-stderr.txt
new file mode 100644
index 0000000..3e656cd
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_missing_arg-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Error at defer_missing_arg.cmake:1 \(cmake_language\):
+ cmake_language DEFER requires at least one argument
+Call Stack \(most recent call first\):
+ CMakeLists.txt:3 \(include\)$
diff --git a/Tests/RunCMake/cmake_language/defer_missing_arg.cmake b/Tests/RunCMake/cmake_language/defer_missing_arg.cmake
new file mode 100644
index 0000000..737a8c8
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_missing_arg.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER)
diff --git a/Tests/RunCMake/cmake_language/defer_missing_call-result.txt b/Tests/RunCMake/cmake_language/defer_missing_call-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_missing_call-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_missing_call-stderr.txt b/Tests/RunCMake/cmake_language/defer_missing_call-stderr.txt
new file mode 100644
index 0000000..7eeef76
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_missing_call-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Error at defer_missing_call.cmake:1 \(cmake_language\):
+ cmake_language DEFER must be followed by a CALL argument
+Call Stack \(most recent call first\):
+ CMakeLists.txt:3 \(include\)$
diff --git a/Tests/RunCMake/cmake_language/defer_missing_call.cmake b/Tests/RunCMake/cmake_language/defer_missing_call.cmake
new file mode 100644
index 0000000..0b330ef
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_missing_call.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER DIRECTORY .)
diff --git a/Tests/RunCMake/cmake_language/defer_unknown_option-result.txt b/Tests/RunCMake/cmake_language/defer_unknown_option-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_unknown_option-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_language/defer_unknown_option-stderr.txt b/Tests/RunCMake/cmake_language/defer_unknown_option-stderr.txt
new file mode 100644
index 0000000..95d87c6
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_unknown_option-stderr.txt
@@ -0,0 +1,6 @@
+^CMake Error at defer_unknown_option.cmake:1 \(cmake_language\):
+ cmake_language DEFER unknown option:
+
+ UNKNOWN
+Call Stack \(most recent call first\):
+ CMakeLists.txt:3 \(include\)$
diff --git a/Tests/RunCMake/cmake_language/defer_unknown_option.cmake b/Tests/RunCMake/cmake_language/defer_unknown_option.cmake
new file mode 100644
index 0000000..876b3f1
--- /dev/null
+++ b/Tests/RunCMake/cmake_language/defer_unknown_option.cmake
@@ -0,0 +1 @@
+cmake_language(DEFER UNKNOWN)