summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc Chevrier <marc.chevrier@gmail.com>2022-08-05 10:55:32 +0200
committerMarc Chevrier <marc.chevrier@gmail.com>2022-08-22 16:25:53 +0200
commit44a2f3f3324a608062eb7b88072c7640f80f4a5c (patch)
treebcb7ee5b371d05c71d76fb12214c0b82039fa2de
parent604993248fdee0bec8ab8c74c1173c67496a7dfd (diff)
downloadcmake-44a2f3f3324a608062eb7b88072c7640f80f4a5c.tar.gz
Add new flow-control commands for variables and policies scopes management
Add block() and endblock() commands offering the capability to create new scopes for variables and/or policies. Fixes: #20171
-rw-r--r--Auxiliary/cmake-mode.el4
-rw-r--r--Auxiliary/vim/indent/cmake.vim4
-rw-r--r--Auxiliary/vim/syntax/cmake.vim2
-rw-r--r--Help/command/block.rst74
-rw-r--r--Help/command/cmake_language.rst1
-rw-r--r--Help/command/cmake_policy.rst41
-rw-r--r--Help/command/endblock.rst11
-rw-r--r--Help/command/set.rst17
-rw-r--r--Help/manual/cmake-commands.7.rst2
-rw-r--r--Help/release/dev/block-command.rst5
-rw-r--r--Source/CMakeLists.txt2
-rw-r--r--Source/cmBlockCommand.cxx200
-rw-r--r--Source/cmBlockCommand.h14
-rw-r--r--Source/cmCMakeLanguageCommand.cxx5
-rw-r--r--Source/cmCommands.cxx6
-rw-r--r--Source/cmListFileCache.cxx11
-rw-r--r--Tests/RunCMake/CMakeLists.txt1
-rw-r--r--Tests/RunCMake/block/CMakeLists.txt3
-rw-r--r--Tests/RunCMake/block/EndAlone-result.txt1
-rw-r--r--Tests/RunCMake/block/EndAlone-stderr.txt4
-rw-r--r--Tests/RunCMake/block/EndAlone.cmake1
-rw-r--r--Tests/RunCMake/block/EndAloneWithArgument-result.txt1
-rw-r--r--Tests/RunCMake/block/EndAloneWithArgument-stderr.txt4
-rw-r--r--Tests/RunCMake/block/EndAloneWithArgument.cmake1
-rw-r--r--Tests/RunCMake/block/EndMissing-result.txt1
-rw-r--r--Tests/RunCMake/block/EndMissing-stderr.txt4
-rw-r--r--Tests/RunCMake/block/EndMissing.cmake1
-rw-r--r--Tests/RunCMake/block/EndWithArgument-stderr.txt9
-rw-r--r--Tests/RunCMake/block/EndWithArgument.cmake2
-rw-r--r--Tests/RunCMake/block/InvalidArgument-result.txt1
-rw-r--r--Tests/RunCMake/block/InvalidArgument-stderr.txt4
-rw-r--r--Tests/RunCMake/block/InvalidArgument.cmake2
-rw-r--r--Tests/RunCMake/block/InvalidNesting1-result.txt1
-rw-r--r--Tests/RunCMake/block/InvalidNesting1-stderr.txt4
-rw-r--r--Tests/RunCMake/block/InvalidNesting1.cmake6
-rw-r--r--Tests/RunCMake/block/InvalidNesting2-result.txt1
-rw-r--r--Tests/RunCMake/block/InvalidNesting2-stderr.txt4
-rw-r--r--Tests/RunCMake/block/InvalidNesting2.cmake6
-rw-r--r--Tests/RunCMake/block/InvalidNesting3-result.txt1
-rw-r--r--Tests/RunCMake/block/InvalidNesting3-stderr.txt4
-rw-r--r--Tests/RunCMake/block/InvalidNesting3.cmake5
-rw-r--r--Tests/RunCMake/block/InvalidNesting4-result.txt1
-rw-r--r--Tests/RunCMake/block/InvalidNesting4-stderr.txt4
-rw-r--r--Tests/RunCMake/block/InvalidNesting4.cmake5
-rw-r--r--Tests/RunCMake/block/InvalidNesting5-result.txt1
-rw-r--r--Tests/RunCMake/block/InvalidNesting5-stderr.txt4
-rw-r--r--Tests/RunCMake/block/InvalidNesting5.cmake5
-rw-r--r--Tests/RunCMake/block/InvalidNesting6-result.txt1
-rw-r--r--Tests/RunCMake/block/InvalidNesting6-stderr.txt4
-rw-r--r--Tests/RunCMake/block/InvalidNesting6.cmake5
-rw-r--r--Tests/RunCMake/block/MissingArgument-result.txt1
-rw-r--r--Tests/RunCMake/block/MissingArgument-stderr.txt7
-rw-r--r--Tests/RunCMake/block/MissingArgument.cmake2
-rw-r--r--Tests/RunCMake/block/RunCMakeTest.cmake22
-rw-r--r--Tests/RunCMake/block/Scope-POLICIES.cmake30
-rw-r--r--Tests/RunCMake/block/Scope-VARIABLES.cmake52
-rw-r--r--Tests/RunCMake/block/Scope.cmake52
-rw-r--r--Tests/RunCMake/block/Workflows.cmake78
-rw-r--r--Tests/RunCMake/block/WrongArgument-result.txt1
-rw-r--r--Tests/RunCMake/block/WrongArgument-stderr.txt4
-rw-r--r--Tests/RunCMake/block/WrongArgument.cmake2
-rw-r--r--Tests/RunCMake/block/WrongScope-result.txt1
-rw-r--r--Tests/RunCMake/block/WrongScope-stderr.txt4
-rw-r--r--Tests/RunCMake/block/WrongScope.cmake2
-rw-r--r--Tests/RunCMake/cmake_language/RunCMakeTest.cmake2
-rwxr-xr-xbootstrap1
66 files changed, 750 insertions, 12 deletions
diff --git a/Auxiliary/cmake-mode.el b/Auxiliary/cmake-mode.el
index 2de9b98eac..a11becbe20 100644
--- a/Auxiliary/cmake-mode.el
+++ b/Auxiliary/cmake-mode.el
@@ -41,8 +41,8 @@ set the path with these commands:
:group 'cmake)
;; Keywords
-(defconst cmake-keywords-block-open '("IF" "MACRO" "FOREACH" "ELSE" "ELSEIF" "WHILE" "FUNCTION"))
-(defconst cmake-keywords-block-close '("ENDIF" "ENDFOREACH" "ENDMACRO" "ELSE" "ELSEIF" "ENDWHILE" "ENDFUNCTION"))
+(defconst cmake-keywords-block-open '("BLOCK" "IF" "MACRO" "FOREACH" "ELSE" "ELSEIF" "WHILE" "FUNCTION"))
+(defconst cmake-keywords-block-close '("ENDBLOCK" "ENDIF" "ENDFOREACH" "ENDMACRO" "ELSE" "ELSEIF" "ENDWHILE" "ENDFUNCTION"))
(defconst cmake-keywords
(let ((kwds (append cmake-keywords-block-open cmake-keywords-block-close nil)))
(delete-dups kwds)))
diff --git a/Auxiliary/vim/indent/cmake.vim b/Auxiliary/vim/indent/cmake.vim
index 672bdccc46..0c662faa01 100644
--- a/Auxiliary/vim/indent/cmake.vim
+++ b/Auxiliary/vim/indent/cmake.vim
@@ -59,8 +59,8 @@ fun! CMakeGetIndent(lnum)
let cmake_closing_parens_line = '^\s*\()\+\)\s*$'
- let cmake_indent_begin_regex = '^\s*\(IF\|MACRO\|FOREACH\|ELSE\|ELSEIF\|WHILE\|FUNCTION\)\s*('
- let cmake_indent_end_regex = '^\s*\(ENDIF\|ENDFOREACH\|ENDMACRO\|ELSE\|ELSEIF\|ENDWHILE\|ENDFUNCTION\)\s*('
+ let cmake_indent_begin_regex = '^\s*\(BLOCK\|IF\|MACRO\|FOREACH\|ELSE\|ELSEIF\|WHILE\|FUNCTION\)\s*('
+ let cmake_indent_end_regex = '^\s*\(ENDBLOCK\|ENDIF\|ENDFOREACH\|ENDMACRO\|ELSE\|ELSEIF\|ENDWHILE\|ENDFUNCTION\)\s*('
if this_line =~? cmake_closing_parens_line
if previous_line !~? cmake_indent_open_regex
diff --git a/Auxiliary/vim/syntax/cmake.vim b/Auxiliary/vim/syntax/cmake.vim
index 1273c0031d..ca257baaad 100644
--- a/Auxiliary/vim/syntax/cmake.vim
+++ b/Auxiliary/vim/syntax/cmake.vim
@@ -3832,6 +3832,7 @@ syn keyword cmakeCommand
\ add_subdirectory
\ add_test
\ aux_source_directory
+ \ block
\ break
\ build_command
\ cmake_host_system_information
@@ -3859,6 +3860,7 @@ syn keyword cmakeCommand
\ define_property
\ enable_language
\ enable_testing
+ \ endblock
\ endfunction
\ endmacro
\ execute_process
diff --git a/Help/command/block.rst b/Help/command/block.rst
new file mode 100644
index 0000000000..f9d85c8a39
--- /dev/null
+++ b/Help/command/block.rst
@@ -0,0 +1,74 @@
+block
+-----
+
+.. versionadded:: 3.25
+
+Evaluate a group of commands with a dedicated variable and/or policy scope.
+
+.. code-block:: cmake
+
+ block([SCOPE_FOR (POLICIES|VARIABLES)] [PROPAGATE <var-name>...])
+ <commands>
+ endblock()
+
+All commands between ``block()`` and the matching :command:`endblock` are
+recorded without being invoked. Once the :command:`endblock` is evaluated, the
+recorded list of commands is invoked inside the requested scopes, and, finally,
+the scopes created by ``block()`` command are removed.
+
+``SCOPE_FOR``
+ Specify which scopes must be created.
+
+ ``POLICIES``
+ Create a new policy scope. This is equivalent to
+ :command:`cmake_policy(PUSH)`.
+
+ ``VARIABLES``
+ Create a new variable scope.
+
+ If ``SCOPE_FOR`` is not specified, this is equivalent to:
+
+ .. code-block:: cmake
+
+ block(SCOPE_FOR VARIABLES POLICIES)
+
+``PROPAGATE``
+ When a variable scope is created by :command:`block` command, this option
+ set or unset the specified variables in the parent scope. This is equivalent
+ to :command:`set(PARENT_SCOPE)` or :command:`unset(PARENT_SCOPE)` commands.
+
+ .. code-block:: cmake
+
+ set(VAR1 "INIT1")
+ set(VAR2 "INIT2")
+
+ block(PROPAGATE VAR1 VAR2)
+ set(VAR1 "VALUE1")
+ unset(VAR2)
+ endblock()
+
+ # here, VAR1 holds value VALUE1 and VAR2 is unset
+
+ This option is only allowed when a variable scope is created. An error will
+ be raised in the other cases.
+
+When the ``block`` is local to a :command:`foreach` or :command:`while`
+command, the commands :command:`break` and :command:`continue` can be used
+inside this block.
+
+.. code-block:: cmake
+
+ while(TRUE)
+ block()
+ ...
+ # the break() command will terminate the while() command
+ break()
+ endblock()
+ endwhile()
+
+
+See Also
+^^^^^^^^
+
+ * :command:`endblock`
+ * :command:`cmake_policy`
diff --git a/Help/command/cmake_language.rst b/Help/command/cmake_language.rst
index d0c7c1960b..8801a9fdfe 100644
--- a/Help/command/cmake_language.rst
+++ b/Help/command/cmake_language.rst
@@ -51,6 +51,7 @@ is equivalent to
To ensure consistency of the code, the following commands are not allowed:
* ``if`` / ``elseif`` / ``else`` / ``endif``
+ * ``block`` / ``endblock``
* ``while`` / ``endwhile``
* ``foreach`` / ``endforeach``
* ``function`` / ``endfunction``
diff --git a/Help/command/cmake_policy.rst b/Help/command/cmake_policy.rst
index 94060d9b82..54fc548158 100644
--- a/Help/command/cmake_policy.rst
+++ b/Help/command/cmake_policy.rst
@@ -103,6 +103,47 @@ Calls to the :command:`cmake_minimum_required(VERSION)`,
``cmake_policy(VERSION)``, or ``cmake_policy(SET)`` commands
influence only the current top of the policy stack.
+.. versionadded:: 3.25
+ The :command:`block` and :command:`endblock` commands offer a more flexible
+ and more secure way to manage the policy stack. The pop action is done
+ automatically when the :command:`endblock` command is executed, so it avoid
+ to call the :command:`cmake_policy(POP)` command before each
+ :command:`return` command.
+
+ .. code-block:: cmake
+
+ # stack management with cmake_policy()
+ function(my_func)
+ cmake_policy(PUSH)
+ cmake_policy(SET ...)
+ if (<cond1>)
+ ...
+ cmake_policy(POP)
+ return()
+ elseif(<cond2>)
+ ...
+ cmake_policy(POP)
+ return()
+ endif()
+ ...
+ cmake_policy(POP)
+ endfunction()
+
+ # stack management with block()/endblock()
+ function(my_func)
+ block(SCOPE_FOR POLICIES)
+ cmake_policy(SET ...)
+ if (<cond1>)
+ ...
+ return()
+ elseif(<cond2>)
+ ...
+ return()
+ endif()
+ ...
+ endblock()
+ endfunction()
+
Commands created by the :command:`function` and :command:`macro`
commands record policy settings when they are created and
use the pre-record policies when they are invoked. If the function or
diff --git a/Help/command/endblock.rst b/Help/command/endblock.rst
new file mode 100644
index 0000000000..3b21c1205f
--- /dev/null
+++ b/Help/command/endblock.rst
@@ -0,0 +1,11 @@
+endblock
+--------
+
+.. versionadded:: 3.25
+
+Ends a list of commands in a :command:`block` and removes the scopes
+created by the :command:`block` command.
+
+.. code-block:: cmake
+
+ endblock()
diff --git a/Help/command/set.rst b/Help/command/set.rst
index af862e4da3..aa2ea55d4d 100644
--- a/Help/command/set.rst
+++ b/Help/command/set.rst
@@ -22,12 +22,17 @@ Set Normal Variable
Sets the given ``<variable>`` in the current function or directory scope.
If the ``PARENT_SCOPE`` option is given the variable will be set in
-the scope above the current scope. Each new directory or function
-creates a new scope. This command will set the value of a variable
-into the parent directory or calling function (whichever is applicable
-to the case at hand). The previous state of the variable's value stays the
-same in the current scope (e.g., if it was undefined before, it is still
-undefined and if it had a value, it is still that value).
+the scope above the current scope. Each new directory or :command:`function`
+command creates a new scope. A scope can also be created with the
+:command:`block` command. This command will set the value of a variable into
+the parent directory, calling function or encompassing scope (whichever is
+applicable to the case at hand). The previous state of the variable's value
+stays the same in the current scope (e.g., if it was undefined before, it is
+still undefined and if it had a value, it is still that value).
+
+The :command:`block(PROPAGATE)` command can be used as an alternate method to
+:command:`set(PARENT_SCOPE)` and :command:`unset(PARENT_SCOPE)` commands to
+update the parent scope.
Set Cache Entry
^^^^^^^^^^^^^^^
diff --git a/Help/manual/cmake-commands.7.rst b/Help/manual/cmake-commands.7.rst
index 036fa8fe54..0f35632d58 100644
--- a/Help/manual/cmake-commands.7.rst
+++ b/Help/manual/cmake-commands.7.rst
@@ -15,6 +15,7 @@ These commands are always available.
.. toctree::
:maxdepth: 1
+ /command/block
/command/break
/command/cmake_host_system_information
/command/cmake_language
@@ -26,6 +27,7 @@ These commands are always available.
/command/continue
/command/else
/command/elseif
+ /command/endblock
/command/endforeach
/command/endfunction
/command/endif
diff --git a/Help/release/dev/block-command.rst b/Help/release/dev/block-command.rst
new file mode 100644
index 0000000000..a740c0b6c8
--- /dev/null
+++ b/Help/release/dev/block-command.rst
@@ -0,0 +1,5 @@
+block-command
+-------------
+
+* CMake language gains the commands :command:`block` and :command:`endblock` to
+ manage specific scopes (policy or variable) for group of commands.
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index fe92716828..b6f7c858ab 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -571,6 +571,8 @@ set(SRCS
cmFindProgramCommand.h
cmForEachCommand.cxx
cmForEachCommand.h
+ cmBlockCommand.cxx
+ cmBlockCommand.h
cmFunctionBlocker.cxx
cmFunctionBlocker.h
cmFunctionCommand.cxx
diff --git a/Source/cmBlockCommand.cxx b/Source/cmBlockCommand.cxx
new file mode 100644
index 0000000000..c358aa2cc3
--- /dev/null
+++ b/Source/cmBlockCommand.cxx
@@ -0,0 +1,200 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+#include "cmBlockCommand.h"
+
+#include <cstdint> // IWYU pragma: keep
+#include <utility>
+
+#include <cm/memory>
+#include <cm/optional>
+#include <cm/string_view>
+#include <cmext/enum_set>
+#include <cmext/string_view>
+
+#include "cmArgumentParser.h"
+#include "cmArgumentParserTypes.h"
+#include "cmExecutionStatus.h"
+#include "cmFunctionBlocker.h"
+#include "cmListFileCache.h"
+#include "cmMakefile.h"
+#include "cmStringAlgorithms.h"
+#include "cmSystemTools.h"
+
+namespace {
+enum class ScopeType : std::uint8_t
+{
+ VARIABLES,
+ POLICIES
+};
+using ScopeSet = cm::enum_set<ScopeType>;
+
+class BlockScopePushPop
+{
+public:
+ BlockScopePushPop(cmMakefile* m, const ScopeSet& scopes);
+ ~BlockScopePushPop() = default;
+
+ BlockScopePushPop(const BlockScopePushPop&) = delete;
+ BlockScopePushPop& operator=(const BlockScopePushPop&) = delete;
+
+private:
+ std::unique_ptr<cmMakefile::PolicyPushPop> PolicyScope;
+ std::unique_ptr<cmMakefile::VariablePushPop> VariableScope;
+};
+
+BlockScopePushPop::BlockScopePushPop(cmMakefile* mf, const ScopeSet& scopes)
+{
+ if (scopes.contains(ScopeType::POLICIES)) {
+ this->PolicyScope = cm::make_unique<cmMakefile::PolicyPushPop>(mf);
+ }
+ if (scopes.contains(ScopeType::VARIABLES)) {
+ this->VariableScope = cm::make_unique<cmMakefile::VariablePushPop>(mf);
+ }
+}
+
+class cmBlockFunctionBlocker : public cmFunctionBlocker
+{
+public:
+ cmBlockFunctionBlocker(cmMakefile* mf, const ScopeSet& scopes,
+ std::vector<std::string> variableNames);
+ ~cmBlockFunctionBlocker() override;
+
+ cm::string_view StartCommandName() const override { return "block"_s; }
+ cm::string_view EndCommandName() const override { return "endblock"_s; }
+
+ bool EndCommandSupportsArguments() const override { return false; }
+
+ bool ArgumentsMatch(cmListFileFunction const& lff,
+ cmMakefile& mf) const override;
+
+ bool Replay(std::vector<cmListFileFunction> functions,
+ cmExecutionStatus& inStatus) override;
+
+private:
+ cmMakefile* Makefile;
+ BlockScopePushPop BlockScope;
+ std::vector<std::string> VariableNames;
+};
+
+cmBlockFunctionBlocker::cmBlockFunctionBlocker(
+ cmMakefile* const mf, const ScopeSet& scopes,
+ std::vector<std::string> variableNames)
+ : Makefile{ mf }
+ , BlockScope{ mf, scopes }
+ , VariableNames{ std::move(variableNames) }
+{
+}
+
+cmBlockFunctionBlocker::~cmBlockFunctionBlocker()
+{
+ for (auto const& varName : this->VariableNames) {
+ if (this->Makefile->IsNormalDefinitionSet(varName)) {
+ this->Makefile->RaiseScope(varName,
+ this->Makefile->GetDefinition(varName));
+ } else {
+ // unset variable in parent scope
+ this->Makefile->RaiseScope(varName, nullptr);
+ }
+ }
+}
+
+bool cmBlockFunctionBlocker::ArgumentsMatch(cmListFileFunction const& lff,
+ cmMakefile&) const
+{
+ // no arguments expected for endblock()
+ // but this method should not be called because EndCommandHasArguments()
+ // returns false.
+ return lff.Arguments().empty();
+}
+
+bool cmBlockFunctionBlocker::Replay(std::vector<cmListFileFunction> functions,
+ cmExecutionStatus& inStatus)
+{
+ auto& mf = inStatus.GetMakefile();
+
+ // Invoke all the functions that were collected in the block.
+ for (cmListFileFunction const& fn : functions) {
+ cmExecutionStatus status(mf);
+ mf.ExecuteCommand(fn, status);
+ if (status.GetReturnInvoked()) {
+ inStatus.SetReturnInvoked();
+ return true;
+ }
+ if (status.GetBreakInvoked()) {
+ inStatus.SetBreakInvoked();
+ return true;
+ }
+ if (status.GetContinueInvoked()) {
+ inStatus.SetContinueInvoked();
+ return true;
+ }
+ if (cmSystemTools::GetFatalErrorOccurred()) {
+ return true;
+ }
+ }
+ return true;
+}
+
+} // anonymous namespace
+
+bool cmBlockCommand(std::vector<std::string> const& args,
+ cmExecutionStatus& status)
+{
+ struct Arguments : public ArgumentParser::ParseResult
+ {
+ cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>> ScopeFor;
+ ArgumentParser::MaybeEmpty<std::vector<std::string>> Propagate;
+ };
+ static auto const parser = cmArgumentParser<Arguments>{}
+ .Bind("SCOPE_FOR"_s, &Arguments::ScopeFor)
+ .Bind("PROPAGATE"_s, &Arguments::Propagate);
+ std::vector<std::string> unrecognizedArguments;
+ auto parsedArgs = parser.Parse(args, &unrecognizedArguments);
+
+ if (!unrecognizedArguments.empty()) {
+ status.SetError(cmStrCat("called with unsupported argument \"",
+ unrecognizedArguments[0], '"'));
+ cmSystemTools::SetFatalErrorOccurred();
+ return false;
+ }
+
+ if (parsedArgs.MaybeReportError(status.GetMakefile())) {
+ cmSystemTools::SetFatalErrorOccurred();
+ return true;
+ }
+
+ ScopeSet scopes;
+
+ if (parsedArgs.ScopeFor) {
+ for (auto const& scope : *parsedArgs.ScopeFor) {
+ if (scope == "VARIABLES"_s) {
+ scopes.insert(ScopeType::VARIABLES);
+ continue;
+ }
+ if (scope == "POLICIES"_s) {
+ scopes.insert(ScopeType::POLICIES);
+ continue;
+ }
+ status.SetError(cmStrCat("SCOPE_FOR unsupported scope \"", scope, '"'));
+ cmSystemTools::SetFatalErrorOccurred();
+ return false;
+ }
+ } else {
+ scopes = { ScopeType::VARIABLES, ScopeType::POLICIES };
+ }
+ if (!scopes.contains(ScopeType::VARIABLES) &&
+ !parsedArgs.Propagate.empty()) {
+ status.SetError(
+ "PROPAGATE cannot be specified without a new scope for VARIABLES");
+ cmSystemTools::SetFatalErrorOccurred();
+ return false;
+ }
+
+ // create a function blocker
+ auto fb = cm::make_unique<cmBlockFunctionBlocker>(
+ &status.GetMakefile(), scopes, parsedArgs.Propagate);
+ status.GetMakefile().AddFunctionBlocker(std::move(fb));
+
+ return true;
+}
diff --git a/Source/cmBlockCommand.h b/Source/cmBlockCommand.h
new file mode 100644
index 0000000000..5fd8f42050
--- /dev/null
+++ b/Source/cmBlockCommand.h
@@ -0,0 +1,14 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <string>
+#include <vector>
+
+class cmExecutionStatus;
+
+/// Starts block() ... endblock() block
+bool cmBlockCommand(std::vector<std::string> const& args,
+ cmExecutionStatus& status);
diff --git a/Source/cmCMakeLanguageCommand.cxx b/Source/cmCMakeLanguageCommand.cxx
index 76561c3c32..68e658c7d1 100644
--- a/Source/cmCMakeLanguageCommand.cxx
+++ b/Source/cmCMakeLanguageCommand.cxx
@@ -36,13 +36,14 @@ bool FatalError(cmExecutionStatus& status, std::string const& error)
return false;
}
-std::array<cm::static_string_view, 12> InvalidCommands{
+std::array<cm::static_string_view, 14> InvalidCommands{
{ // clang-format off
"function"_s, "endfunction"_s,
"macro"_s, "endmacro"_s,
"if"_s, "elseif"_s, "else"_s, "endif"_s,
"while"_s, "endwhile"_s,
- "foreach"_s, "endforeach"_s
+ "foreach"_s, "endforeach"_s,
+ "block"_s, "endblock"_s
} // clang-format on
};
diff --git a/Source/cmCommands.cxx b/Source/cmCommands.cxx
index 5e616b30d0..3bc4f0e791 100644
--- a/Source/cmCommands.cxx
+++ b/Source/cmCommands.cxx
@@ -14,6 +14,7 @@
#include "cmAddLibraryCommand.h"
#include "cmAddSubDirectoryCommand.h"
#include "cmAddTestCommand.h"
+#include "cmBlockCommand.h"
#include "cmBreakCommand.h"
#include "cmBuildCommand.h"
#include "cmCMakeLanguageCommand.h"
@@ -126,6 +127,7 @@ void GetScriptingCommands(cmState* state)
state->AddFlowControlCommand("macro", cmMacroCommand);
state->AddFlowControlCommand("return", cmReturnCommand);
state->AddFlowControlCommand("while", cmWhileCommand);
+ state->AddFlowControlCommand("block", cmBlockCommand);
state->AddBuiltinCommand("cmake_language", cmCMakeLanguageCommand);
state->AddBuiltinCommand("cmake_minimum_required", cmCMakeMinimumRequired);
@@ -198,6 +200,10 @@ void GetScriptingCommands(cmState* state)
"An ENDWHILE command was found outside of a proper "
"WHILE ENDWHILE structure. Or its arguments did not "
"match the opening WHILE command.");
+ state->AddUnexpectedFlowControlCommand(
+ "endblock",
+ "An ENDBLOCK command was found outside of a proper "
+ "BLOCK ENDBLOCK structure.");
#if !defined(CMAKE_BOOTSTRAP)
state->AddBuiltinCommand("cmake_host_system_information",
diff --git a/Source/cmListFileCache.cxx b/Source/cmListFileCache.cxx
index 91157cbf03..6270c825f2 100644
--- a/Source/cmListFileCache.cxx
+++ b/Source/cmListFileCache.cxx
@@ -347,6 +347,7 @@ enum class NestingStateEnum
Foreach,
Function,
Macro,
+ Block
};
struct NestingState
@@ -434,6 +435,16 @@ cm::optional<cmListFileContext> cmListFileParser::CheckNesting() const
return cmListFileContext::FromListFileFunction(func, this->FileName);
}
stack.pop_back();
+ } else if (name == "block") {
+ stack.push_back({
+ NestingStateEnum::Block,
+ cmListFileContext::FromListFileFunction(func, this->FileName),
+ });
+ } else if (name == "endblock") {
+ if (!TopIs(stack, NestingStateEnum::Block)) {
+ return cmListFileContext::FromListFileFunction(func, this->FileName);
+ }
+ stack.pop_back();
}
}
diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt
index 2485a7e136..542742c411 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/Tests/RunCMake/CMakeLists.txt
@@ -456,6 +456,7 @@ add_RunCMake_test(find_path)
add_RunCMake_test(find_program -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME})
add_RunCMake_test(foreach)
add_RunCMake_test(function)
+add_RunCMake_test(block)
add_RunCMake_test(get_filename_component)
add_RunCMake_test(get_property)
add_RunCMake_test(if)
diff --git a/Tests/RunCMake/block/CMakeLists.txt b/Tests/RunCMake/block/CMakeLists.txt
new file mode 100644
index 0000000000..45cd10ecc6
--- /dev/null
+++ b/Tests/RunCMake/block/CMakeLists.txt
@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.3...3.25)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/block/EndAlone-result.txt b/Tests/RunCMake/block/EndAlone-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/block/EndAlone-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/block/EndAlone-stderr.txt b/Tests/RunCMake/block/EndAlone-stderr.txt
new file mode 100644
index 0000000000..a588dd7bee
--- /dev/null
+++ b/Tests/RunCMake/block/EndAlone-stderr.txt
@@ -0,0 +1,4 @@
+CMake Error at EndAlone.cmake:[0-9]+ \(endblock\):
+ Flow control statements are not properly nested.
+Call Stack \(most recent call first\):
+ CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/block/EndAlone.cmake b/Tests/RunCMake/block/EndAlone.cmake
new file mode 100644
index 0000000000..0c428a90b8
--- /dev/null
+++ b/Tests/RunCMake/block/EndAlone.cmake
@@ -0,0 +1 @@
+endblock()
diff --git a/Tests/RunCMake/block/EndAloneWithArgument-result.txt b/Tests/RunCMake/block/EndAloneWithArgument-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/block/EndAloneWithArgument-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/block/EndAloneWithArgument-stderr.txt b/Tests/RunCMake/block/EndAloneWithArgument-stderr.txt
new file mode 100644
index 0000000000..c3d25a3154
--- /dev/null
+++ b/Tests/RunCMake/block/EndAloneWithArgument-stderr.txt
@@ -0,0 +1,4 @@
+CMake Error at EndAloneWithArgument.cmake:[0-9]+ \(endblock\):
+ Flow control statements are not properly nested.
+Call Stack \(most recent call first\):
+ CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/block/EndAloneWithArgument.cmake b/Tests/RunCMake/block/EndAloneWithArgument.cmake
new file mode 100644
index 0000000000..05df5b0d68
--- /dev/null
+++ b/Tests/RunCMake/block/EndAloneWithArgument.cmake
@@ -0,0 +1 @@
+endblock(WRONG_ARG)
diff --git a/Tests/RunCMake/block/EndMissing-result.txt b/Tests/RunCMake/block/EndMissing-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/block/EndMissing-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/block/EndMissing-stderr.txt b/Tests/RunCMake/block/EndMissing-stderr.txt
new file mode 100644
index 0000000000..b9739a5be0
--- /dev/null
+++ b/Tests/RunCMake/block/EndMissing-stderr.txt
@@ -0,0 +1,4 @@
+CMake Error at EndMissing.cmake:[0-9]+ \(block\):
+ Flow control statements are not properly nested.
+Call Stack \(most recent call first\):
+ CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/block/EndMissing.cmake b/Tests/RunCMake/block/EndMissing.cmake
new file mode 100644
index 0000000000..335b64eee0
--- /dev/null
+++ b/Tests/RunCMake/block/EndMissing.cmake
@@ -0,0 +1 @@
+block()
diff --git a/Tests/RunCMake/block/EndWithArgument-stderr.txt b/Tests/RunCMake/block/EndWithArgument-stderr.txt
new file mode 100644
index 0000000000..7586453851
--- /dev/null
+++ b/Tests/RunCMake/block/EndWithArgument-stderr.txt
@@ -0,0 +1,9 @@
+CMake Warning \(dev\) in EndWithArgument.cmake:
+ A logical block closing on the line
+
+ .+/Tests/RunCMake/block/EndWithArgument.cmake:[0-9]+ \(endblock\)
+
+ has unexpected arguments.
+Call Stack \(most recent call first\):
+ CMakeLists.txt:[0-9]+ \(include\)
+This warning is for project developers. Use -Wno-dev to suppress it.
diff --git a/Tests/RunCMake/block/EndWithArgument.cmake b/Tests/RunCMake/block/EndWithArgument.cmake
new file mode 100644
index 0000000000..0641c9a103
--- /dev/null
+++ b/Tests/RunCMake/block/EndWithArgument.cmake
@@ -0,0 +1,2 @@
+block()
+endblock(END_ARG)
diff --git a/Tests/RunCMake/block/InvalidArgument-result.txt b/Tests/RunCMake/block/InvalidArgument-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/block/InvalidArgument-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/block/InvalidArgument-stderr.txt b/Tests/RunCMake/block/InvalidArgument-stderr.txt
new file mode 100644
index 0000000000..bee604b892
--- /dev/null
+++ b/Tests/RunCMake/block/InvalidArgument-stderr.txt
@@ -0,0 +1,4 @@
+CMake Error at InvalidArgument.cmake:[0-9]+ \(block\):
+ block PROPAGATE cannot be specified without a new scope for VARIABLES
+Call Stack \(most recent call first\):
+ CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/block/InvalidArgument.cmake b/Tests/RunCMake/block/InvalidArgument.cmake
new file mode 100644
index 0000000000..5269cd077d
--- /dev/null
+++ b/Tests/RunCMake/block/InvalidArgument.cmake
@@ -0,0 +1,2 @@
+block(SCOPE_FOR POLICIES PROPAGATE VAR1)
+endblock()
diff --git a/Tests/RunCMake/block/InvalidNesting1-result.txt b/Tests/RunCMake/block/InvalidNesting1-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/block/InvalidNesting1-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/block/InvalidNesting1-stderr.txt b/Tests/RunCMake/block/InvalidNesting1-stderr.txt
new file mode 100644
index 0000000000..6dfe0e10d8
--- /dev/null
+++ b/Tests/RunCMake/block/InvalidNesting1-stderr.txt
@@ -0,0 +1,4 @@
+CMake Error at InvalidNesting1.cmake:[0-9]+ \(else\):
+ Flow control statements are not properly nested.
+Call Stack \(most recent call first\):
+ CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/block/InvalidNesting1.cmake b/Tests/RunCMake/block/InvalidNesting1.cmake
new file mode 100644
index 0000000000..27b7944638
--- /dev/null
+++ b/Tests/RunCMake/block/InvalidNesting1.cmake
@@ -0,0 +1,6 @@
+
+if (TRUE)
+ block()
+else()
+ endblock()
+endif()
diff --git a/Tests/RunCMake/block/InvalidNesting2-result.txt b/Tests/RunCMake/block/InvalidNesting2-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/block/InvalidNesting2-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/block/InvalidNesting2-stderr.txt b/Tests/RunCMake/block/InvalidNesting2-stderr.txt
new file mode 100644
index 0000000000..71325b6a99
--- /dev/null
+++ b/Tests/RunCMake/block/InvalidNesting2-stderr.txt
@@ -0,0 +1,4 @@
+CMake Error at InvalidNesting2.cmake:[0-9]+ \(endblock\):
+ Flow control statements are not properly nested.
+Call Stack \(most recent call first\):
+ CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/block/InvalidNesting2.cmake b/Tests/RunCMake/block/InvalidNesting2.cmake
new file mode 100644
index 0000000000..ae94cdcb31
--- /dev/null
+++ b/Tests/RunCMake/block/InvalidNesting2.cmake
@@ -0,0 +1,6 @@
+
+block()
+if (TRUE)
+elseif(FALSE)
+endblock()
+endif()
diff --git a/Tests/RunCMake/block/InvalidNesting3-result.txt b/Tests/RunCMake/block/InvalidNesting3-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/block/InvalidNesting3-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/block/InvalidNesting3-stderr.txt b/Tests/RunCMake/block/InvalidNesting3-stderr.txt
new file mode 100644
index 0000000000..344a931e68
--- /dev/null
+++ b/Tests/RunCMake/block/InvalidNesting3-stderr.txt
@@ -0,0 +1,4 @@
+CMake Error at InvalidNesting3.cmake:[0-9]+ \(endwhile\):
+ Flow control statements are not properly nested.
+Call Stack \(most recent call first\):
+ CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/block/InvalidNesting3.cmake b/Tests/RunCMake/block/InvalidNesting3.cmake
new file mode 100644
index 0000000000..f692d244dd
--- /dev/null
+++ b/Tests/RunCMake/block/InvalidNesting3.cmake
@@ -0,0 +1,5 @@
+
+while(TRUE)
+block()
+endwhile()
+endblock()
diff --git a/Tests/RunCMake/block/InvalidNesting4-result.txt b/Tests/RunCMake/block/InvalidNesting4-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/block/InvalidNesting4-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/block/InvalidNesting4-stderr.txt b/Tests/RunCMake/block/InvalidNesting4-stderr.txt
new file mode 100644
index 0000000000..44d6364624
--- /dev/null
+++ b/Tests/RunCMake/block/InvalidNesting4-stderr.txt
@@ -0,0 +1,4 @@
+CMake Error at InvalidNesting4.cmake:[0-9]+ \(endblock\):
+ Flow control statements are not properly nested.
+Call Stack \(most recent call first\):
+ CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/block/InvalidNesting4.cmake b/Tests/RunCMake/block/InvalidNesting4.cmake
new file mode 100644
index 0000000000..6e8e0ae6b3
--- /dev/null
+++ b/Tests/RunCMake/block/InvalidNesting4.cmake
@@ -0,0 +1,5 @@
+
+block()
+foreach(item IN ITEMS A B)
+endblock()
+endforeach()
diff --git a/Tests/RunCMake/block/InvalidNesting5-result.txt b/Tests/RunCMake/block/InvalidNesting5-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/block/InvalidNesting5-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/block/InvalidNesting5-stderr.txt b/Tests/RunCMake/block/InvalidNesting5-stderr.txt
new file mode 100644
index 0000000000..976d2e1ead
--- /dev/null
+++ b/Tests/RunCMake/block/InvalidNesting5-stderr.txt
@@ -0,0 +1,4 @@
+CMake Error at InvalidNesting5.cmake:[0-9]+ \(endfunction\):
+ Flow control statements are not properly nested.
+Call Stack \(most recent call first\):
+ CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/block/InvalidNesting5.cmake b/Tests/RunCMake/block/InvalidNesting5.cmake
new file mode 100644
index 0000000000..0479e8da92
--- /dev/null
+++ b/Tests/RunCMake/block/InvalidNesting5.cmake
@@ -0,0 +1,5 @@
+
+function(FUNC)
+ block()
+endfunction()
+endblock()
diff --git a/Tests/RunCMake/block/InvalidNesting6-result.txt b/Tests/RunCMake/block/InvalidNesting6-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/block/InvalidNesting6-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/block/InvalidNesting6-stderr.txt b/Tests/RunCMake/block/InvalidNesting6-stderr.txt
new file mode 100644
index 0000000000..2d67b16b84
--- /dev/null
+++ b/Tests/RunCMake/block/InvalidNesting6-stderr.txt
@@ -0,0 +1,4 @@
+CMake Error at InvalidNesting6.cmake:[0-9]+ \(endblock\):
+ Flow control statements are not properly nested.
+Call Stack \(most recent call first\):
+ CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/block/InvalidNesting6.cmake b/Tests/RunCMake/block/InvalidNesting6.cmake
new file mode 100644
index 0000000000..a1cb359bb4
--- /dev/null
+++ b/Tests/RunCMake/block/InvalidNesting6.cmake
@@ -0,0 +1,5 @@
+
+ block()
+macro(FUNC)
+endblock()
+endmacro()
diff --git a/Tests/RunCMake/block/MissingArgument-result.txt b/Tests/RunCMake/block/MissingArgument-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/block/MissingArgument-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/block/MissingArgument-stderr.txt b/Tests/RunCMake/block/MissingArgument-stderr.txt
new file mode 100644
index 0000000000..d3e63cadfa
--- /dev/null
+++ b/Tests/RunCMake/block/MissingArgument-stderr.txt
@@ -0,0 +1,7 @@
+CMake Error at MissingArgument.cmake:[0-9]+ \(block\):
+ Error after keyword "SCOPE_FOR":
+
+ missing required value
+
+Call Stack \(most recent call first\):
+ CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/block/MissingArgument.cmake b/Tests/RunCMake/block/MissingArgument.cmake
new file mode 100644
index 0000000000..60188871ae
--- /dev/null
+++ b/Tests/RunCMake/block/MissingArgument.cmake
@@ -0,0 +1,2 @@
+block(SCOPE_FOR)
+endblock()
diff --git a/Tests/RunCMake/block/RunCMakeTest.cmake b/Tests/RunCMake/block/RunCMakeTest.cmake
new file mode 100644
index 0000000000..4260e76ad5
--- /dev/null
+++ b/Tests/RunCMake/block/RunCMakeTest.cmake
@@ -0,0 +1,22 @@
+include(RunCMake)
+
+run_cmake(WrongArgument)
+run_cmake(InvalidArgument)
+run_cmake(MissingArgument)
+run_cmake(WrongScope)
+run_cmake(EndMissing)
+run_cmake(EndWithArgument)
+run_cmake(EndAlone)
+run_cmake(EndAloneWithArgument)
+
+run_cmake(InvalidNesting1)
+run_cmake(InvalidNesting2)
+run_cmake(InvalidNesting3)
+run_cmake(InvalidNesting4)
+run_cmake(InvalidNesting5)
+run_cmake(InvalidNesting6)
+
+run_cmake(Scope)
+run_cmake(Scope-VARIABLES)
+run_cmake(Scope-POLICIES)
+run_cmake(Workflows)
diff --git a/Tests/RunCMake/block/Scope-POLICIES.cmake b/Tests/RunCMake/block/Scope-POLICIES.cmake
new file mode 100644
index 0000000000..789b3d9d97
--- /dev/null
+++ b/Tests/RunCMake/block/Scope-POLICIES.cmake
@@ -0,0 +1,30 @@
+
+set(VAR1 "OUTER1")
+set(VAR2 "OUTER2")
+
+cmake_policy(SET CMP0139 NEW)
+
+# create a block with a new scope for policies
+block(SCOPE_FOR POLICIES)
+ set(VAR1 "INNER1")
+ unset(VAR2)
+ set(VAR3 "INNER3")
+
+ cmake_policy(SET CMP0139 OLD)
+endblock()
+
+# check final values for variables
+if(NOT DEFINED VAR1 OR NOT VAR1 STREQUAL "INNER1")
+ message(SEND_ERROR "block/endblock: VAR1 has unexpected value: ${VAR1}")
+endif()
+if(DEFINED VAR2)
+ message(SEND_ERROR "block/endblock: VAR2 is unexpectedly defined: ${VAR2}")
+endif()
+if(NOT DEFINED VAR3 OR NOT VAR3 STREQUAL "INNER3")
+ message(SEND_ERROR "block/endblock: VAR3 has unexpected value: ${VAR3}")
+endif()
+
+cmake_policy(GET CMP0139 CMP0139_STATUS)
+if(NOT CMP0139_STATUS STREQUAL "NEW")
+ message(SEND_ERROR "block/endblock: CMP0139 has unexpected value: ${CMP0139_STATUS}")
+endif()
diff --git a/Tests/RunCMake/block/Scope-VARIABLES.cmake b/Tests/RunCMake/block/Scope-VARIABLES.cmake
new file mode 100644
index 0000000000..140e63880f
--- /dev/null
+++ b/Tests/RunCMake/block/Scope-VARIABLES.cmake
@@ -0,0 +1,52 @@
+
+set(VAR1 "OUTER1")
+set(VAR2 "OUTER2")
+set(VAR3 "OUTER3")
+set(VAR4 "OUTER4")
+set(VAR5 "OUTER5")
+
+set(VAR6 "CACHE6" CACHE STRING "")
+set(VAR6 "OUTER6")
+
+cmake_policy(SET CMP0139 NEW)
+
+# create a block with a new scope for variables
+block(SCOPE_FOR VARIABLES PROPAGATE VAR3 VAR4 VAR5 VAR6 VAR7)
+ set(VAR1 "INNER1")
+ set(VAR2 "INNER2" PARENT_SCOPE)
+ set(VAR3 "INNER3")
+ unset(VAR4)
+ unset(VAR6)
+ set(VAR7 "INNER7")
+
+ cmake_policy(SET CMP0139 OLD)
+endblock()
+
+# check final values for variables
+if(NOT DEFINED VAR1 OR NOT VAR1 STREQUAL "OUTER1")
+ message(SEND_ERROR "block/endblock: VAR1 has unexpected value: ${VAR1}")
+endif()
+if(NOT DEFINED VAR2 OR NOT VAR2 STREQUAL "INNER2")
+ message(SEND_ERROR "block/endblock: VAR2 has unexpected value: ${VAR2}")
+endif()
+if(NOT DEFINED VAR3 OR NOT VAR3 STREQUAL "INNER3")
+ message(SEND_ERROR "block/endblock: VAR3 has unexpected value: ${VAR3}")
+endif()
+if(DEFINED VAR4)
+ message(SEND_ERROR "block/endblock: VAR4 is unexpectedly defined: ${VAR4}")
+endif()
+if(NOT DEFINED VAR5 OR NOT VAR5 STREQUAL "OUTER5")
+ message(SEND_ERROR "block/endblock: VAR5 has unexpected value: ${VAR5}")
+endif()
+unset(VAR6 CACHE)
+if (DEFINED VAR6)
+ message(SEND_ERROR "block/endblock: VAR6 is unexpectedly defined: ${VAR6}")
+endif()
+if(NOT DEFINED VAR7 OR NOT VAR7 STREQUAL "INNER7")
+ message(SEND_ERROR "block/endblock: VAR7 has unexpected value: ${VAR7}")
+endif()
+
+cmake_policy(GET CMP0139 CMP0139_STATUS)
+if(NOT CMP0139_STATUS STREQUAL "OLD")
+ message(SEND_ERROR "block/endblock: CMP0139 has unexpected value: ${CMP0139_STATUS}")
+endif()
diff --git a/Tests/RunCMake/block/Scope.cmake b/Tests/RunCMake/block/Scope.cmake
new file mode 100644
index 0000000000..e1af50a256
--- /dev/null
+++ b/Tests/RunCMake/block/Scope.cmake
@@ -0,0 +1,52 @@
+
+set(VAR1 "OUTER1")
+set(VAR2 "OUTER2")
+set(VAR3 "OUTER3")
+set(VAR4 "OUTER4")
+set(VAR5 "OUTER5")
+
+set(VAR6 "CACHE6" CACHE STRING "")
+set(VAR6 "OUTER6")
+
+cmake_policy(SET CMP0139 NEW)
+
+# create a block with a new scope for variables and policies
+block(PROPAGATE VAR3 VAR4 VAR5 VAR6 VAR7)
+ set(VAR1 "INNER1")
+ set(VAR2 "INNER2" PARENT_SCOPE)
+ set(VAR3 "INNER3")
+ unset(VAR4)
+ unset(VAR6)
+ set(VAR7 "INNER7")
+
+ cmake_policy(SET CMP0139 OLD)
+endblock()
+
+# check final values for variables
+if(NOT DEFINED VAR1 OR NOT VAR1 STREQUAL "OUTER1")
+ message(SEND_ERROR "block/endblock: VAR1 has unexpected value: ${VAR1}")
+endif()
+if(NOT DEFINED VAR2 OR NOT VAR2 STREQUAL "INNER2")
+ message(SEND_ERROR "block/endblock: VAR2 has unexpected value: ${VAR2}")
+endif()
+if(NOT DEFINED VAR3 OR NOT VAR3 STREQUAL "INNER3")
+ message(SEND_ERROR "block/endblock: VAR3 has unexpected value: ${VAR3}")
+endif()
+if(DEFINED VAR4)
+ message(SEND_ERROR "block/endblock: VAR4 is unexpectedly defined: ${VAR4}")
+endif()
+if(NOT DEFINED VAR5 OR NOT VAR5 STREQUAL "OUTER5")
+ message(SEND_ERROR "block/endblock: VAR5 has unexpected value: ${VAR5}")
+endif()
+unset(VAR6 CACHE)
+if (DEFINED VAR6)
+ message(SEND_ERROR "block/endblock: VAR6 is unexpectedly defined: ${VAR6}")
+endif()
+if(NOT DEFINED VAR7 OR NOT VAR7 STREQUAL "INNER7")
+ message(SEND_ERROR "block/endblock: VAR6 has unexpected value: ${VAR7}")
+endif()
+
+cmake_policy(GET CMP0139 CMP0139_STATUS)
+if(NOT CMP0139_STATUS STREQUAL "NEW")
+ message(SEND_ERROR "block/endblock: CMP0139 has unexpected value: ${CMP0139_STATUS}")
+endif()
diff --git a/Tests/RunCMake/block/Workflows.cmake b/Tests/RunCMake/block/Workflows.cmake
new file mode 100644
index 0000000000..cbf032eb45
--- /dev/null
+++ b/Tests/RunCMake/block/Workflows.cmake
@@ -0,0 +1,78 @@
+
+set(VAR1 "OUTER1")
+set(VAR2 "OUTER2")
+set(VAR3 "OUTER3")
+
+while (TRUE)
+ # create a block with a new scope for variables
+ block(SCOPE_FOR VARIABLES PROPAGATE VAR3)
+ set(VAR2 "INNER2" PARENT_SCOPE)
+ set(VAR3 "INNER3")
+ break()
+ endblock()
+endwhile()
+
+# check final values for variables
+if(NOT DEFINED VAR1 OR NOT VAR1 STREQUAL "OUTER1")
+ message(SEND_ERROR "block/endblock: VAR1 has unexpected value: ${VAR1}")
+endif()
+if(NOT DEFINED VAR2 OR NOT VAR2 STREQUAL "INNER2")
+ message(SEND_ERROR "block/endblock: VAR2 has unexpected value: ${VAR2}")
+endif()
+if(NOT DEFINED VAR3 OR NOT VAR3 STREQUAL "INNER3")
+ message(SEND_ERROR "block/endblock: VAR3 has unexpected value: ${VAR3}")
+endif()
+
+
+
+set(VAR1 "OUTER1")
+set(VAR2 "OUTER2")
+set(VAR3 "OUTER3")
+
+function (OUTER)
+ # create a block with a new scope for variables
+ block(SCOPE_FOR VARIABLES PROPAGATE VAR3)
+ set(VAR2 "INNER2" PARENT_SCOPE)
+ set(VAR3 "INNER3")
+ return()
+ endblock()
+ set(VAR1 "INNER1" PARENT_SCOPE)
+endfunction()
+outer()
+
+# check final values for variables
+if(NOT DEFINED VAR1 OR NOT VAR1 STREQUAL "OUTER1")
+ message(SEND_ERROR "block/endblock: VAR1 has unexpected value: ${VAR1}")
+endif()
+if(NOT DEFINED VAR2 OR NOT VAR2 STREQUAL "OUTER2")
+ message(SEND_ERROR "block/endblock: VAR2 has unexpected value: ${VAR2}")
+endif()
+if(NOT DEFINED VAR3 OR NOT VAR3 STREQUAL "OUTER3")
+ message(SEND_ERROR "block/endblock: VAR3 has unexpected value: ${VAR3}")
+endif()
+
+
+
+set(VAR1 "OUTER1")
+set(VAR2 "OUTER2")
+set(VAR3 "OUTER3")
+
+foreach (id IN ITEMS 1 2 3)
+ # create a block with a new scope for variables
+ block(SCOPE_FOR VARIABLES PROPAGATE VAR${id})
+ set(VAR${id} "INNER${id}")
+ continue()
+ set(VAR${id} "BAD${id}")
+ endblock()
+endforeach()
+
+# check final values for variables
+if(NOT DEFINED VAR1 OR NOT VAR1 STREQUAL "INNER1")
+ message(SEND_ERROR "block/endblock: VAR1 has unexpected value: ${VAR1}")
+endif()
+if(NOT DEFINED VAR2 OR NOT VAR2 STREQUAL "INNER2")
+ message(SEND_ERROR "block/endblock: VAR2 has unexpected value: ${VAR2}")
+endif()
+if(NOT DEFINED VAR3 OR NOT VAR3 STREQUAL "INNER3")
+ message(SEND_ERROR "block/endblock: VAR3 has unexpected value: ${VAR3}")
+endif()
diff --git a/Tests/RunCMake/block/WrongArgument-result.txt b/Tests/RunCMake/block/WrongArgument-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/block/WrongArgument-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/block/WrongArgument-stderr.txt b/Tests/RunCMake/block/WrongArgument-stderr.txt
new file mode 100644
index 0000000000..56faea70c7
--- /dev/null
+++ b/Tests/RunCMake/block/WrongArgument-stderr.txt
@@ -0,0 +1,4 @@
+CMake Error at WrongArgument.cmake:[0-9]+ \(block\):
+ block called with unsupported argument "WRONG_ARG"
+Call Stack \(most recent call first\):
+ CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/block/WrongArgument.cmake b/Tests/RunCMake/block/WrongArgument.cmake
new file mode 100644
index 0000000000..e46086656b
--- /dev/null
+++ b/Tests/RunCMake/block/WrongArgument.cmake
@@ -0,0 +1,2 @@
+block(WRONG_ARG)
+endblock()
diff --git a/Tests/RunCMake/block/WrongScope-result.txt b/Tests/RunCMake/block/WrongScope-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/block/WrongScope-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/block/WrongScope-stderr.txt b/Tests/RunCMake/block/WrongScope-stderr.txt
new file mode 100644
index 0000000000..dd2a1efbe7
--- /dev/null
+++ b/Tests/RunCMake/block/WrongScope-stderr.txt
@@ -0,0 +1,4 @@
+CMake Error at WrongScope.cmake:[0-9]+ \(block\):
+ block SCOPE_FOR unsupported scope "WRONG_SCOPE"
+Call Stack \(most recent call first\):
+ CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/block/WrongScope.cmake b/Tests/RunCMake/block/WrongScope.cmake
new file mode 100644
index 0000000000..97a6783a6c
--- /dev/null
+++ b/Tests/RunCMake/block/WrongScope.cmake
@@ -0,0 +1,2 @@
+block(SCOPE_FOR WRONG_SCOPE)
+endblock()
diff --git a/Tests/RunCMake/cmake_language/RunCMakeTest.cmake b/Tests/RunCMake/cmake_language/RunCMakeTest.cmake
index 03a15fc3f7..38ce10bbd1 100644
--- a/Tests/RunCMake/cmake_language/RunCMakeTest.cmake
+++ b/Tests/RunCMake/cmake_language/RunCMakeTest.cmake
@@ -8,6 +8,7 @@ foreach(command IN ITEMS
"if" "elseif" "else" "endif"
"while" "endwhile"
"foreach" "endforeach"
+ "block" "endblock"
)
message(STATUS "Running call_invalid_command for ${command}...")
run_cmake_with_options(call_invalid_command -Dcommand=${command})
@@ -42,6 +43,7 @@ foreach(command IN ITEMS
"if" "elseif" "else" "endif"
"while" "endwhile"
"foreach" "endforeach"
+ "block" "endblock"
"return"
)
message(STATUS "Running defer_call_invalid_command for ${command}...")
diff --git a/bootstrap b/bootstrap
index 01ff84f048..d5b071eaa4 100755
--- a/bootstrap
+++ b/bootstrap
@@ -301,6 +301,7 @@ CMAKE_CXX_SOURCES="\
cmBinUtilsWindowsPEDumpbinGetRuntimeDependenciesTool \
cmBinUtilsWindowsPELinker \
cmBinUtilsWindowsPEObjdumpGetRuntimeDependenciesTool \
+ cmBlockCommand \
cmBreakCommand \
cmBuildCommand \
cmCMakeLanguageCommand \