summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrad King <brad.king@kitware.com>2019-08-05 14:40:43 +0000
committerKitware Robot <kwrobot@kitware.com>2019-08-05 10:42:50 -0400
commit2327cc0e0575175e8dec4b7a6fa6cafd5d0f7ca9 (patch)
tree0e0ac231e503b95b16112189407c160158def992
parentc969d4628bff6c5b45c788d3a61fff85c36272f6 (diff)
parent41364824ad84a40c9906b7b5de492e45a74c8945 (diff)
downloadcmake-2327cc0e0575175e8dec4b7a6fa6cafd5d0f7ca9.tar.gz
Merge topic 'control-block3'
41364824ad cmFunctionBlocker: Recycle functions 6491270e0d cmFunctionBlocker: Move check for matching args af24e4ef6e cmFunctionBlocker: Move common logic to base ef38ff22f7 cm*FunctionBlocker: Extract function Replay b51fba6298 cmMakefile: Add OnExecuteCommand callback c76500949d cm*FunctionBlocker: Move to source file Acked-by: Kitware Robot <kwrobot@kitware.com> Merge-request: !3632
-rw-r--r--Source/CMakeLists.txt2
-rw-r--r--Source/CTest/cmCTestScriptHandler.cxx33
-rw-r--r--Source/cmForEachCommand.cxx141
-rw-r--r--Source/cmForEachCommand.h20
-rw-r--r--Source/cmFunctionBlocker.cxx46
-rw-r--r--Source/cmFunctionBlocker.h31
-rw-r--r--Source/cmFunctionCommand.cxx78
-rw-r--r--Source/cmFunctionCommand.h15
-rw-r--r--Source/cmIfCommand.cxx249
-rw-r--r--Source/cmIfCommand.h20
-rw-r--r--Source/cmMacroCommand.cxx80
-rw-r--r--Source/cmMacroCommand.h15
-rw-r--r--Source/cmMakefile.cxx31
-rw-r--r--Source/cmMakefile.h9
-rw-r--r--Source/cmWhileCommand.cxx185
-rw-r--r--Source/cmWhileCommand.h22
-rwxr-xr-xbootstrap1
17 files changed, 438 insertions, 540 deletions
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index fe40af3110..b1f4ca51cc 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -532,6 +532,8 @@ set(SRCS
cmFindProgramCommand.h
cmForEachCommand.cxx
cmForEachCommand.h
+ cmFunctionBlocker.cxx
+ cmFunctionBlocker.h
cmFunctionCommand.cxx
cmFunctionCommand.h
cmGetCMakePropertyCommand.cxx
diff --git a/Source/CTest/cmCTestScriptHandler.cxx b/Source/CTest/cmCTestScriptHandler.cxx
index 7a5b8d1394..f8d9f1b4a1 100644
--- a/Source/CTest/cmCTestScriptHandler.cxx
+++ b/Source/CTest/cmCTestScriptHandler.cxx
@@ -24,7 +24,6 @@
#include "cmCTestUploadCommand.h"
#include "cmCommand.h"
#include "cmDuration.h"
-#include "cmFunctionBlocker.h"
#include "cmGeneratedFileStream.h"
#include "cmGlobalGenerator.h"
#include "cmMakefile.h"
@@ -49,32 +48,8 @@
# include <unistd.h>
#endif
-class cmExecutionStatus;
-struct cmListFileFunction;
-
#define CTEST_INITIAL_CMAKE_OUTPUT_FILE_NAME "CTestInitialCMakeOutput.log"
-// used to keep elapsed time up to date
-class cmCTestScriptFunctionBlocker : public cmFunctionBlocker
-{
-public:
- bool IsFunctionBlocked(const cmListFileFunction& lff, cmMakefile& mf,
- cmExecutionStatus& /*status*/) override;
- // virtual bool ShouldRemove(const cmListFileFunction& lff, cmMakefile &mf);
- // virtual void ScopeEnded(cmMakefile &mf);
-
- cmCTestScriptHandler* CTestScriptHandler;
-};
-
-// simply update the time and don't block anything
-bool cmCTestScriptFunctionBlocker::IsFunctionBlocked(
- const cmListFileFunction& /*lff*/, cmMakefile& /*mf*/,
- cmExecutionStatus& /*status*/)
-{
- this->CTestScriptHandler->UpdateElapsedTime();
- return false;
-}
-
cmCTestScriptHandler::cmCTestScriptHandler()
{
this->Backup = false;
@@ -373,12 +348,8 @@ int cmCTestScriptHandler::ReadInScript(const std::string& total_script_arg)
this->Makefile->AddDefinition("CMAKE_LEGACY_CYGWIN_WIN32", "0");
#endif
- // always add a function blocker to update the elapsed time
- {
- auto fb = cm::make_unique<cmCTestScriptFunctionBlocker>();
- fb->CTestScriptHandler = this;
- this->Makefile->AddFunctionBlocker(std::move(fb));
- }
+ // set a callback function to update the elapsed time
+ this->Makefile->OnExecuteCommand([this] { this->UpdateElapsedTime(); });
/* Execute CTestScriptMode.cmake, which loads CMakeDetermineSystem and
CMakeSystemSpecificInformation, so
diff --git a/Source/cmForEachCommand.cxx b/Source/cmForEachCommand.cxx
index 06dce2c5b7..1d961be06f 100644
--- a/Source/cmForEachCommand.cxx
+++ b/Source/cmForEachCommand.cxx
@@ -8,16 +8,40 @@
#include <utility>
#include "cm_memory.hxx"
+#include "cm_static_string_view.hxx"
+#include "cm_string_view.hxx"
#include "cmExecutionStatus.h"
+#include "cmFunctionBlocker.h"
+#include "cmListFileCache.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
#include "cmRange.h"
#include "cmSystemTools.h"
+class cmForEachFunctionBlocker : public cmFunctionBlocker
+{
+public:
+ cmForEachFunctionBlocker(cmMakefile* mf);
+ ~cmForEachFunctionBlocker() override;
+
+ cm::string_view StartCommandName() const override { return "foreach"_s; }
+ cm::string_view EndCommandName() const override { return "endforeach"_s; }
+
+ bool ArgumentsMatch(cmListFileFunction const& lff,
+ cmMakefile& mf) const override;
+
+ bool Replay(std::vector<cmListFileFunction> functions,
+ cmExecutionStatus& inStatus) override;
+
+ std::vector<std::string> Args;
+
+private:
+ cmMakefile* Makefile;
+};
+
cmForEachFunctionBlocker::cmForEachFunctionBlocker(cmMakefile* mf)
: Makefile(mf)
- , Depth(0)
{
this->Makefile->PushLoopBlock();
}
@@ -27,89 +51,58 @@ cmForEachFunctionBlocker::~cmForEachFunctionBlocker()
this->Makefile->PopLoopBlock();
}
-bool cmForEachFunctionBlocker::IsFunctionBlocked(const cmListFileFunction& lff,
- cmMakefile& mf,
- cmExecutionStatus& inStatus)
+bool cmForEachFunctionBlocker::ArgumentsMatch(cmListFileFunction const& lff,
+ cmMakefile& mf) const
{
- if (lff.Name.Lower == "foreach") {
- // record the number of nested foreach commands
- this->Depth++;
- } else if (lff.Name.Lower == "endforeach") {
- // if this is the endofreach for this statement
- if (!this->Depth) {
- // Remove the function blocker for this scope or bail.
- std::unique_ptr<cmFunctionBlocker> fb(
- mf.RemoveFunctionBlocker(this, lff));
- if (!fb) {
- return false;
- }
+ std::vector<std::string> expandedArguments;
+ mf.ExpandArguments(lff.Arguments, expandedArguments);
+ return expandedArguments.empty() || expandedArguments[0] == this->Args[0];
+}
- // at end of for each execute recorded commands
- // store the old value
- std::string oldDef;
- if (mf.GetDefinition(this->Args[0])) {
- oldDef = mf.GetDefinition(this->Args[0]);
- }
+bool cmForEachFunctionBlocker::Replay(
+ std::vector<cmListFileFunction> functions, cmExecutionStatus& inStatus)
+{
+ cmMakefile& mf = inStatus.GetMakefile();
+ // at end of for each execute recorded commands
+ // store the old value
+ std::string oldDef;
+ if (mf.GetDefinition(this->Args[0])) {
+ oldDef = mf.GetDefinition(this->Args[0]);
+ }
- for (std::string const& arg : cmMakeRange(this->Args).advance(1)) {
- // set the variable to the loop value
- mf.AddDefinition(this->Args[0], arg);
- // Invoke all the functions that were collected in the block.
- cmExecutionStatus status(mf);
- for (cmListFileFunction const& func : this->Functions) {
- status.Clear();
- mf.ExecuteCommand(func, status);
- if (status.GetReturnInvoked()) {
- inStatus.SetReturnInvoked();
- // restore the variable to its prior value
- mf.AddDefinition(this->Args[0], oldDef);
- return true;
- }
- if (status.GetBreakInvoked()) {
- // restore the variable to its prior value
- mf.AddDefinition(this->Args[0], oldDef);
- return true;
- }
- if (status.GetContinueInvoked()) {
- break;
- }
- if (cmSystemTools::GetFatalErrorOccured()) {
- return true;
- }
- }
+ for (std::string const& arg : cmMakeRange(this->Args).advance(1)) {
+ // set the variable to the loop value
+ mf.AddDefinition(this->Args[0], arg);
+ // Invoke all the functions that were collected in the block.
+ cmExecutionStatus status(mf);
+ for (cmListFileFunction const& func : functions) {
+ status.Clear();
+ mf.ExecuteCommand(func, status);
+ if (status.GetReturnInvoked()) {
+ inStatus.SetReturnInvoked();
+ // restore the variable to its prior value
+ mf.AddDefinition(this->Args[0], oldDef);
+ return true;
+ }
+ if (status.GetBreakInvoked()) {
+ // restore the variable to its prior value
+ mf.AddDefinition(this->Args[0], oldDef);
+ return true;
+ }
+ if (status.GetContinueInvoked()) {
+ break;
+ }
+ if (cmSystemTools::GetFatalErrorOccured()) {
+ return true;
}
-
- // restore the variable to its prior value
- mf.AddDefinition(this->Args[0], oldDef);
- return true;
}
- // close out a nested foreach
- this->Depth--;
}
- // record the command
- this->Functions.push_back(lff);
-
- // always return true
+ // restore the variable to its prior value
+ mf.AddDefinition(this->Args[0], oldDef);
return true;
}
-bool cmForEachFunctionBlocker::ShouldRemove(const cmListFileFunction& lff,
- cmMakefile& mf)
-{
- if (lff.Name.Lower == "endforeach") {
- std::vector<std::string> expandedArguments;
- mf.ExpandArguments(lff.Arguments, expandedArguments);
- // if the endforeach has arguments then make sure
- // they match the begin foreach arguments
- if ((expandedArguments.empty() ||
- (expandedArguments[0] == this->Args[0]))) {
- return true;
- }
- }
- return false;
-}
-
bool cmForEachCommand::InitialPass(std::vector<std::string> const& args,
cmExecutionStatus&)
{
diff --git a/Source/cmForEachCommand.h b/Source/cmForEachCommand.h
index cd112b8b81..135abf02a0 100644
--- a/Source/cmForEachCommand.h
+++ b/Source/cmForEachCommand.h
@@ -11,28 +11,8 @@
#include "cm_memory.hxx"
#include "cmCommand.h"
-#include "cmFunctionBlocker.h"
-#include "cmListFileCache.h"
class cmExecutionStatus;
-class cmMakefile;
-
-class cmForEachFunctionBlocker : public cmFunctionBlocker
-{
-public:
- cmForEachFunctionBlocker(cmMakefile* mf);
- ~cmForEachFunctionBlocker() override;
- bool IsFunctionBlocked(const cmListFileFunction& lff, cmMakefile& mf,
- cmExecutionStatus&) override;
- bool ShouldRemove(const cmListFileFunction& lff, cmMakefile& mf) override;
-
- std::vector<std::string> Args;
- std::vector<cmListFileFunction> Functions;
-
-private:
- cmMakefile* Makefile;
- int Depth;
-};
/// Starts foreach() ... endforeach() block
class cmForEachCommand : public cmCommand
diff --git a/Source/cmFunctionBlocker.cxx b/Source/cmFunctionBlocker.cxx
new file mode 100644
index 0000000000..5778a71526
--- /dev/null
+++ b/Source/cmFunctionBlocker.cxx
@@ -0,0 +1,46 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmFunctionBlocker.h"
+
+#include <cassert>
+#include <sstream>
+#include <utility>
+
+#include "cmExecutionStatus.h"
+#include "cmMakefile.h"
+#include "cmMessageType.h"
+
+bool cmFunctionBlocker::IsFunctionBlocked(const cmListFileFunction& lff,
+ cmExecutionStatus& status)
+{
+ if (lff.Name.Lower == this->StartCommandName()) {
+ this->ScopeDepth++;
+ } else if (lff.Name.Lower == this->EndCommandName()) {
+ this->ScopeDepth--;
+ if (this->ScopeDepth == 0U) {
+ cmMakefile& mf = status.GetMakefile();
+ auto self = mf.RemoveFunctionBlocker();
+ assert(self.get() == this);
+
+ if (!this->ArgumentsMatch(lff, mf)) {
+ cmListFileContext const& lfc = this->GetStartingContext();
+ cmListFileContext closingContext =
+ cmListFileContext::FromCommandContext(lff, lfc.FilePath);
+ std::ostringstream e;
+ /* clang-format off */
+ e << "A logical block opening on the line\n"
+ << " " << lfc << "\n"
+ << "closes on the line\n"
+ << " " << closingContext << "\n"
+ << "with mis-matching arguments.";
+ /* clang-format on */
+ mf.IssueMessage(MessageType::AUTHOR_WARNING, e.str());
+ }
+
+ return this->Replay(std::move(this->Functions), status);
+ }
+ }
+
+ this->Functions.push_back(lff);
+ return true;
+}
diff --git a/Source/cmFunctionBlocker.h b/Source/cmFunctionBlocker.h
index cd6b05db47..87bdccdc6d 100644
--- a/Source/cmFunctionBlocker.h
+++ b/Source/cmFunctionBlocker.h
@@ -3,6 +3,12 @@
#ifndef cmFunctionBlocker_h
#define cmFunctionBlocker_h
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <vector>
+
+#include "cm_string_view.hxx"
+
#include "cmListFileCache.h"
class cmExecutionStatus;
@@ -14,17 +20,8 @@ public:
/**
* should a function be blocked
*/
- virtual bool IsFunctionBlocked(const cmListFileFunction& lff, cmMakefile& mf,
- cmExecutionStatus& status) = 0;
-
- /**
- * should this function blocker be removed, useful when one function adds a
- * blocker and another must remove it
- */
- virtual bool ShouldRemove(const cmListFileFunction&, cmMakefile&)
- {
- return false;
- }
+ bool IsFunctionBlocked(cmListFileFunction const& lff,
+ cmExecutionStatus& status);
virtual ~cmFunctionBlocker() = default;
@@ -39,7 +36,19 @@ public:
}
private:
+ virtual cm::string_view StartCommandName() const = 0;
+ virtual cm::string_view EndCommandName() const = 0;
+
+ virtual bool ArgumentsMatch(cmListFileFunction const& lff,
+ cmMakefile& mf) const = 0;
+
+ virtual bool Replay(std::vector<cmListFileFunction> functions,
+ cmExecutionStatus& status) = 0;
+
+private:
cmListFileContext StartingContext;
+ std::vector<cmListFileFunction> Functions;
+ unsigned int ScopeDepth = 1;
};
#endif
diff --git a/Source/cmFunctionCommand.cxx b/Source/cmFunctionCommand.cxx
index 2809cf7ddf..610f51671d 100644
--- a/Source/cmFunctionCommand.cxx
+++ b/Source/cmFunctionCommand.cxx
@@ -5,8 +5,13 @@
#include <sstream>
#include <utility>
+#include "cm_static_string_view.hxx"
+#include "cm_string_view.hxx"
+
#include "cmAlgorithms.h"
#include "cmExecutionStatus.h"
+#include "cmFunctionBlocker.h"
+#include "cmListFileCache.h"
#include "cmMakefile.h"
#include "cmPolicies.h"
#include "cmRange.h"
@@ -102,53 +107,42 @@ bool cmFunctionHelperCommand::operator()(
return true;
}
-bool cmFunctionFunctionBlocker::IsFunctionBlocked(
- const cmListFileFunction& lff, cmMakefile& mf, cmExecutionStatus&)
+class cmFunctionFunctionBlocker : public cmFunctionBlocker
{
- // record commands until we hit the ENDFUNCTION
- // at the ENDFUNCTION call we shift gears and start looking for invocations
- if (lff.Name.Lower == "function") {
- this->Depth++;
- } else if (lff.Name.Lower == "endfunction") {
- // if this is the endfunction for this function then execute
- if (!this->Depth) {
- // create a new command and add it to cmake
- cmFunctionHelperCommand f;
- f.Args = this->Args;
- f.Functions = this->Functions;
- f.FilePath = this->GetStartingContext().FilePath;
- mf.RecordPolicies(f.Policies);
- mf.GetState()->AddScriptedCommand(this->Args[0], std::move(f));
- // remove the function blocker now that the function is defined
- mf.RemoveFunctionBlocker(this, lff);
- return true;
- }
- // decrement for each nested function that ends
- this->Depth--;
- }
+public:
+ cm::string_view StartCommandName() const override { return "function"_s; }
+ cm::string_view EndCommandName() const override { return "endfunction"_s; }
- // if it wasn't an endfunction and we are not executing then we must be
- // recording
- this->Functions.push_back(lff);
- return true;
-}
+ bool ArgumentsMatch(cmListFileFunction const&,
+ cmMakefile& mf) const override;
-bool cmFunctionFunctionBlocker::ShouldRemove(const cmListFileFunction& lff,
- cmMakefile& mf)
+ bool Replay(std::vector<cmListFileFunction> functions,
+ cmExecutionStatus& status) override;
+
+ std::vector<std::string> Args;
+};
+
+bool cmFunctionFunctionBlocker::ArgumentsMatch(cmListFileFunction const& lff,
+ cmMakefile& mf) const
{
- if (lff.Name.Lower == "endfunction") {
- std::vector<std::string> expandedArguments;
- mf.ExpandArguments(lff.Arguments, expandedArguments,
- this->GetStartingContext().FilePath.c_str());
- // if the endfunction has arguments then make sure
- // they match the ones in the opening function command
- if ((expandedArguments.empty() ||
- (expandedArguments[0] == this->Args[0]))) {
- return true;
- }
- }
+ std::vector<std::string> expandedArguments;
+ mf.ExpandArguments(lff.Arguments, expandedArguments,
+ this->GetStartingContext().FilePath.c_str());
+ return expandedArguments.empty() || expandedArguments[0] == this->Args[0];
+}
- return false;
+bool cmFunctionFunctionBlocker::Replay(
+ std::vector<cmListFileFunction> functions, cmExecutionStatus& status)
+{
+ cmMakefile& mf = status.GetMakefile();
+ // create a new command and add it to cmake
+ cmFunctionHelperCommand f;
+ f.Args = this->Args;
+ f.Functions = std::move(functions);
+ f.FilePath = this->GetStartingContext().FilePath;
+ mf.RecordPolicies(f.Policies);
+ mf.GetState()->AddScriptedCommand(this->Args[0], std::move(f));
+ return true;
}
bool cmFunctionCommand::InitialPass(std::vector<std::string> const& args,
diff --git a/Source/cmFunctionCommand.h b/Source/cmFunctionCommand.h
index 449a1801ba..b33452503d 100644
--- a/Source/cmFunctionCommand.h
+++ b/Source/cmFunctionCommand.h
@@ -11,23 +11,8 @@
#include "cm_memory.hxx"
#include "cmCommand.h"
-#include "cmFunctionBlocker.h"
-#include "cmListFileCache.h"
class cmExecutionStatus;
-class cmMakefile;
-
-class cmFunctionFunctionBlocker : public cmFunctionBlocker
-{
-public:
- bool IsFunctionBlocked(const cmListFileFunction&, cmMakefile& mf,
- cmExecutionStatus&) override;
- bool ShouldRemove(const cmListFileFunction&, cmMakefile& mf) override;
-
- std::vector<std::string> Args;
- std::vector<cmListFileFunction> Functions;
- int Depth = 0;
-};
/// Starts function() ... endfunction() block
class cmFunctionCommand : public cmCommand
diff --git a/Source/cmIfCommand.cxx b/Source/cmIfCommand.cxx
index 385022ca03..c5cfd8c02a 100644
--- a/Source/cmIfCommand.cxx
+++ b/Source/cmIfCommand.cxx
@@ -3,10 +3,14 @@
#include "cmIfCommand.h"
#include "cm_memory.hxx"
+#include "cm_static_string_view.hxx"
+#include "cm_string_view.hxx"
#include "cmConditionEvaluator.h"
#include "cmExecutionStatus.h"
#include "cmExpandedCommandArgument.h"
+#include "cmFunctionBlocker.h"
+#include "cmListFileCache.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
#include "cmOutputConverter.h"
@@ -28,152 +32,138 @@ static std::string cmIfCommandError(
return err;
}
-//=========================================================================
-bool cmIfFunctionBlocker::IsFunctionBlocked(const cmListFileFunction& lff,
- cmMakefile& mf,
- cmExecutionStatus& inStatus)
+class cmIfFunctionBlocker : public cmFunctionBlocker
{
- // we start by recording all the functions
- if (lff.Name.Lower == "if") {
- this->ScopeDepth++;
- } else if (lff.Name.Lower == "endif") {
- this->ScopeDepth--;
- // if this is the endif for this if statement, then start executing
- if (!this->ScopeDepth) {
- // Remove the function blocker for this scope or bail.
- std::unique_ptr<cmFunctionBlocker> fb(
- mf.RemoveFunctionBlocker(this, lff));
- if (!fb) {
- return false;
+public:
+ cm::string_view StartCommandName() const override { return "if"_s; }
+ cm::string_view EndCommandName() const override { return "endif"_s; }
+
+ bool ArgumentsMatch(cmListFileFunction const& lff,
+ cmMakefile&) const override;
+
+ bool Replay(std::vector<cmListFileFunction> functions,
+ cmExecutionStatus& inStatus) override;
+
+ std::vector<cmListFileArgument> Args;
+ bool IsBlocking;
+ bool HasRun = false;
+ bool ElseSeen = false;
+};
+
+bool cmIfFunctionBlocker::ArgumentsMatch(cmListFileFunction const& lff,
+ cmMakefile&) const
+{
+ return lff.Arguments.empty() || lff.Arguments == this->Args;
+}
+
+bool cmIfFunctionBlocker::Replay(std::vector<cmListFileFunction> functions,
+ cmExecutionStatus& inStatus)
+{
+ cmMakefile& mf = inStatus.GetMakefile();
+ // execute the functions for the true parts of the if statement
+ cmExecutionStatus status(mf);
+ int scopeDepth = 0;
+ for (cmListFileFunction const& func : functions) {
+ // keep track of scope depth
+ if (func.Name.Lower == "if") {
+ scopeDepth++;
+ }
+ if (func.Name.Lower == "endif") {
+ scopeDepth--;
+ }
+ // watch for our state change
+ if (scopeDepth == 0 && func.Name.Lower == "else") {
+
+ if (this->ElseSeen) {
+ cmListFileBacktrace bt = mf.GetBacktrace(func);
+ mf.GetCMakeInstance()->IssueMessage(
+ MessageType::FATAL_ERROR,
+ "A duplicate ELSE command was found inside an IF block.", bt);
+ cmSystemTools::SetFatalErrorOccured();
+ return true;
}
- // execute the functions for the true parts of the if statement
- cmExecutionStatus status(mf);
- int scopeDepth = 0;
- for (cmListFileFunction const& func : this->Functions) {
- // keep track of scope depth
- if (func.Name.Lower == "if") {
- scopeDepth++;
- }
- if (func.Name.Lower == "endif") {
- scopeDepth--;
+ this->IsBlocking = this->HasRun;
+ this->HasRun = true;
+ this->ElseSeen = true;
+
+ // if trace is enabled, print a (trivially) evaluated "else"
+ // statement
+ if (!this->IsBlocking && mf.GetCMakeInstance()->GetTrace()) {
+ mf.PrintCommandTrace(func);
+ }
+ } else if (scopeDepth == 0 && func.Name.Lower == "elseif") {
+ if (this->ElseSeen) {
+ cmListFileBacktrace bt = mf.GetBacktrace(func);
+ mf.GetCMakeInstance()->IssueMessage(
+ MessageType::FATAL_ERROR,
+ "An ELSEIF command was found after an ELSE command.", bt);
+ cmSystemTools::SetFatalErrorOccured();
+ return true;
+ }
+
+ if (this->HasRun) {
+ this->IsBlocking = true;
+ } else {
+ // if trace is enabled, print the evaluated "elseif" statement
+ if (mf.GetCMakeInstance()->GetTrace()) {
+ mf.PrintCommandTrace(func);
}
- // watch for our state change
- if (scopeDepth == 0 && func.Name.Lower == "else") {
-
- if (this->ElseSeen) {
- cmListFileBacktrace bt = mf.GetBacktrace(func);
- mf.GetCMakeInstance()->IssueMessage(
- MessageType::FATAL_ERROR,
- "A duplicate ELSE command was found inside an IF block.", bt);
- cmSystemTools::SetFatalErrorOccured();
- return true;
- }
- this->IsBlocking = this->HasRun;
- this->HasRun = true;
- this->ElseSeen = true;
+ std::string errorString;
- // if trace is enabled, print a (trivially) evaluated "else"
- // statement
- if (!this->IsBlocking && mf.GetCMakeInstance()->GetTrace()) {
- mf.PrintCommandTrace(func);
- }
- } else if (scopeDepth == 0 && func.Name.Lower == "elseif") {
- if (this->ElseSeen) {
- cmListFileBacktrace bt = mf.GetBacktrace(func);
- mf.GetCMakeInstance()->IssueMessage(
- MessageType::FATAL_ERROR,
- "An ELSEIF command was found after an ELSE command.", bt);
+ std::vector<cmExpandedCommandArgument> expandedArguments;
+ mf.ExpandArguments(func.Arguments, expandedArguments);
+
+ MessageType messType;
+
+ cmListFileContext conditionContext =
+ cmListFileContext::FromCommandContext(
+ func, this->GetStartingContext().FilePath);
+
+ cmConditionEvaluator conditionEvaluator(mf, conditionContext,
+ mf.GetBacktrace(func));
+
+ bool isTrue =
+ conditionEvaluator.IsTrue(expandedArguments, errorString, messType);
+
+ if (!errorString.empty()) {
+ std::string err = cmIfCommandError(expandedArguments);
+ err += errorString;
+ cmListFileBacktrace bt = mf.GetBacktrace(func);
+ mf.GetCMakeInstance()->IssueMessage(messType, err, bt);
+ if (messType == MessageType::FATAL_ERROR) {
cmSystemTools::SetFatalErrorOccured();
return true;
}
-
- if (this->HasRun) {
- this->IsBlocking = true;
- } else {
- // if trace is enabled, print the evaluated "elseif" statement
- if (mf.GetCMakeInstance()->GetTrace()) {
- mf.PrintCommandTrace(func);
- }
-
- std::string errorString;
-
- std::vector<cmExpandedCommandArgument> expandedArguments;
- mf.ExpandArguments(func.Arguments, expandedArguments);
-
- MessageType messType;
-
- cmListFileContext conditionContext =
- cmListFileContext::FromCommandContext(
- func, this->GetStartingContext().FilePath);
-
- cmConditionEvaluator conditionEvaluator(mf, conditionContext,
- mf.GetBacktrace(func));
-
- bool isTrue = conditionEvaluator.IsTrue(expandedArguments,
- errorString, messType);
-
- if (!errorString.empty()) {
- std::string err = cmIfCommandError(expandedArguments);
- err += errorString;
- cmListFileBacktrace bt = mf.GetBacktrace(func);
- mf.GetCMakeInstance()->IssueMessage(messType, err, bt);
- if (messType == MessageType::FATAL_ERROR) {
- cmSystemTools::SetFatalErrorOccured();
- return true;
- }
- }
-
- if (isTrue) {
- this->IsBlocking = false;
- this->HasRun = true;
- }
- }
}
- // should we execute?
- else if (!this->IsBlocking) {
- status.Clear();
- mf.ExecuteCommand(func, status);
- if (status.GetReturnInvoked()) {
- inStatus.SetReturnInvoked();
- return true;
- }
- if (status.GetBreakInvoked()) {
- inStatus.SetBreakInvoked();
- return true;
- }
- if (status.GetContinueInvoked()) {
- inStatus.SetContinueInvoked();
- return true;
- }
+ if (isTrue) {
+ this->IsBlocking = false;
+ this->HasRun = true;
}
}
- return true;
}
- }
- // record the command
- this->Functions.push_back(lff);
-
- // always return true
- return true;
-}
-
-//=========================================================================
-bool cmIfFunctionBlocker::ShouldRemove(const cmListFileFunction& lff,
- cmMakefile&)
-{
- if (lff.Name.Lower == "endif") {
- // if the endif has arguments, then make sure
- // they match the arguments of the matching if
- if (lff.Arguments.empty() || lff.Arguments == this->Args) {
- return true;
+ // should we execute?
+ else if (!this->IsBlocking) {
+ status.Clear();
+ mf.ExecuteCommand(func, status);
+ if (status.GetReturnInvoked()) {
+ inStatus.SetReturnInvoked();
+ return true;
+ }
+ if (status.GetBreakInvoked()) {
+ inStatus.SetBreakInvoked();
+ return true;
+ }
+ if (status.GetContinueInvoked()) {
+ inStatus.SetContinueInvoked();
+ return true;
+ }
}
}
-
- return false;
+ return true;
}
//=========================================================================
@@ -208,7 +198,6 @@ bool cmIfCommand(std::vector<cmListFileArgument> const& args,
{
auto fb = cm::make_unique<cmIfFunctionBlocker>();
// if is isn't true block the commands
- fb->ScopeDepth = 1;
fb->IsBlocking = !isTrue;
if (isTrue) {
fb->HasRun = true;
diff --git a/Source/cmIfCommand.h b/Source/cmIfCommand.h
index 775e6097a0..820ffa4c60 100644
--- a/Source/cmIfCommand.h
+++ b/Source/cmIfCommand.h
@@ -7,26 +7,8 @@
#include <vector>
-#include "cmFunctionBlocker.h"
-#include "cmListFileCache.h"
-
class cmExecutionStatus;
-class cmMakefile;
-
-class cmIfFunctionBlocker : public cmFunctionBlocker
-{
-public:
- bool IsFunctionBlocked(const cmListFileFunction& lff, cmMakefile& mf,
- cmExecutionStatus&) override;
- bool ShouldRemove(const cmListFileFunction& lff, cmMakefile& mf) override;
-
- std::vector<cmListFileArgument> Args;
- std::vector<cmListFileFunction> Functions;
- bool IsBlocking;
- bool HasRun = false;
- bool ElseSeen = false;
- unsigned int ScopeDepth = 0;
-};
+struct cmListFileArgument;
/// Starts an if block
bool cmIfCommand(std::vector<cmListFileArgument> const& args,
diff --git a/Source/cmMacroCommand.cxx b/Source/cmMacroCommand.cxx
index 1f2b5b2e1b..8689c8f330 100644
--- a/Source/cmMacroCommand.cxx
+++ b/Source/cmMacroCommand.cxx
@@ -7,9 +7,13 @@
#include <utility>
#include "cm_memory.hxx"
+#include "cm_static_string_view.hxx"
+#include "cm_string_view.hxx"
#include "cmAlgorithms.h"
#include "cmExecutionStatus.h"
+#include "cmFunctionBlocker.h"
+#include "cmListFileCache.h"
#include "cmMakefile.h"
#include "cmPolicies.h"
#include "cmRange.h"
@@ -136,55 +140,43 @@ bool cmMacroHelperCommand::operator()(
return true;
}
-bool cmMacroFunctionBlocker::IsFunctionBlocked(const cmListFileFunction& lff,
- cmMakefile& mf,
- cmExecutionStatus&)
+class cmMacroFunctionBlocker : public cmFunctionBlocker
{
- // record commands until we hit the ENDMACRO
- // at the ENDMACRO call we shift gears and start looking for invocations
- if (lff.Name.Lower == "macro") {
- this->Depth++;
- } else if (lff.Name.Lower == "endmacro") {
- // if this is the endmacro for this macro then execute
- if (!this->Depth) {
- mf.AppendProperty("MACROS", this->Args[0].c_str());
- // create a new command and add it to cmake
- cmMacroHelperCommand f;
- f.Args = this->Args;
- f.Functions = this->Functions;
- f.FilePath = this->GetStartingContext().FilePath;
- mf.RecordPolicies(f.Policies);
- mf.GetState()->AddScriptedCommand(this->Args[0], std::move(f));
- // remove the function blocker now that the macro is defined
- mf.RemoveFunctionBlocker(this, lff);
- return true;
- }
- // decrement for each nested macro that ends
- this->Depth--;
- }
+public:
+ cm::string_view StartCommandName() const override { return "macro"_s; }
+ cm::string_view EndCommandName() const override { return "endmacro"_s; }
- // if it wasn't an endmacro and we are not executing then we must be
- // recording
- this->Functions.push_back(lff);
- return true;
-}
+ bool ArgumentsMatch(cmListFileFunction const&,
+ cmMakefile& mf) const override;
+
+ bool Replay(std::vector<cmListFileFunction> functions,
+ cmExecutionStatus& status) override;
+
+ std::vector<std::string> Args;
+};
-bool cmMacroFunctionBlocker::ShouldRemove(const cmListFileFunction& lff,
- cmMakefile& mf)
+bool cmMacroFunctionBlocker::ArgumentsMatch(cmListFileFunction const& lff,
+ cmMakefile& mf) const
{
- if (lff.Name.Lower == "endmacro") {
- std::vector<std::string> expandedArguments;
- mf.ExpandArguments(lff.Arguments, expandedArguments,
- this->GetStartingContext().FilePath.c_str());
- // if the endmacro has arguments make sure they
- // match the arguments of the macro
- if ((expandedArguments.empty() ||
- (expandedArguments[0] == this->Args[0]))) {
- return true;
- }
- }
+ std::vector<std::string> expandedArguments;
+ mf.ExpandArguments(lff.Arguments, expandedArguments,
+ this->GetStartingContext().FilePath.c_str());
+ return expandedArguments.empty() || expandedArguments[0] == this->Args[0];
+}
- return false;
+bool cmMacroFunctionBlocker::Replay(std::vector<cmListFileFunction> functions,
+ cmExecutionStatus& status)
+{
+ cmMakefile& mf = status.GetMakefile();
+ mf.AppendProperty("MACROS", this->Args[0].c_str());
+ // create a new command and add it to cmake
+ cmMacroHelperCommand f;
+ f.Args = this->Args;
+ f.Functions = std::move(functions);
+ f.FilePath = this->GetStartingContext().FilePath;
+ mf.RecordPolicies(f.Policies);
+ mf.GetState()->AddScriptedCommand(this->Args[0], std::move(f));
+ return true;
}
bool cmMacroCommand::InitialPass(std::vector<std::string> const& args,
diff --git a/Source/cmMacroCommand.h b/Source/cmMacroCommand.h
index 3ebd959e38..0d7083a8c1 100644
--- a/Source/cmMacroCommand.h
+++ b/Source/cmMacroCommand.h
@@ -11,23 +11,8 @@
#include "cm_memory.hxx"
#include "cmCommand.h"
-#include "cmFunctionBlocker.h"
-#include "cmListFileCache.h"
class cmExecutionStatus;
-class cmMakefile;
-
-class cmMacroFunctionBlocker : public cmFunctionBlocker
-{
-public:
- bool IsFunctionBlocked(const cmListFileFunction&, cmMakefile& mf,
- cmExecutionStatus&) override;
- bool ShouldRemove(const cmListFileFunction&, cmMakefile& mf) override;
-
- std::vector<std::string> Args;
- std::vector<cmListFileFunction> Functions;
- int Depth = 0;
-};
/// Starts macro() ... endmacro() block
class cmMacroCommand : public cmCommand
diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx
index 8188ffab72..015453ad00 100644
--- a/Source/cmMakefile.cxx
+++ b/Source/cmMakefile.cxx
@@ -353,6 +353,11 @@ private:
cmMakefile* Makefile;
};
+void cmMakefile::OnExecuteCommand(std::function<void()> callback)
+{
+ this->ExecuteCommandCallback = std::move(callback);
+}
+
bool cmMakefile::ExecuteCommand(const cmListFileFunction& lff,
cmExecutionStatus& status)
{
@@ -364,6 +369,10 @@ bool cmMakefile::ExecuteCommand(const cmListFileFunction& lff,
return result;
}
+ if (this->ExecuteCommandCallback) {
+ this->ExecuteCommandCallback();
+ }
+
// Place this call on the call stack.
cmMakefileCall stack_manager(this, lff, status);
static_cast<void>(stack_manager);
@@ -3061,7 +3070,7 @@ bool cmMakefile::IsFunctionBlocked(const cmListFileFunction& lff,
return false;
}
- return this->FunctionBlockers.top()->IsFunctionBlocked(lff, *this, status);
+ return this->FunctionBlockers.top()->IsFunctionBlocked(lff, status);
}
void cmMakefile::PushFunctionBlockerBarrier()
@@ -3211,30 +3220,12 @@ void cmMakefile::AddFunctionBlocker(std::unique_ptr<cmFunctionBlocker> fb)
this->FunctionBlockers.push(std::move(fb));
}
-std::unique_ptr<cmFunctionBlocker> cmMakefile::RemoveFunctionBlocker(
- cmFunctionBlocker* fb, const cmListFileFunction& lff)
+std::unique_ptr<cmFunctionBlocker> cmMakefile::RemoveFunctionBlocker()
{
assert(!this->FunctionBlockers.empty());
- assert(this->FunctionBlockers.top().get() == fb);
assert(this->FunctionBlockerBarriers.empty() ||
this->FunctionBlockers.size() > this->FunctionBlockerBarriers.back());
- // Warn if the arguments do not match, but always remove.
- if (!fb->ShouldRemove(lff, *this)) {
- cmListFileContext const& lfc = fb->GetStartingContext();
- cmListFileContext closingContext =
- cmListFileContext::FromCommandContext(lff, lfc.FilePath);
- std::ostringstream e;
- /* clang-format off */
- e << "A logical block opening on the line\n"
- << " " << lfc << "\n"
- << "closes on the line\n"
- << " " << closingContext << "\n"
- << "with mis-matching arguments.";
- /* clang-format on */
- this->IssueMessage(MessageType::AUTHOR_WARNING, e.str());
- }
-
auto b = std::move(this->FunctionBlockers.top());
this->FunctionBlockers.pop();
return b;
diff --git a/Source/cmMakefile.h b/Source/cmMakefile.h
index dc196ac119..4d61c05cff 100644
--- a/Source/cmMakefile.h
+++ b/Source/cmMakefile.h
@@ -107,8 +107,7 @@ public:
* Remove the function blocker whose scope ends with the given command.
* This returns ownership of the function blocker object.
*/
- std::unique_ptr<cmFunctionBlocker> RemoveFunctionBlocker(
- cmFunctionBlocker* fb, const cmListFileFunction& lff);
+ std::unique_ptr<cmFunctionBlocker> RemoveFunctionBlocker();
/**
* Try running cmake and building a file. This is used for dynalically
@@ -628,6 +627,11 @@ public:
void PrintCommandTrace(const cmListFileFunction& lff) const;
/**
+ * Set a callback that is invoked whenever ExecuteCommand is called.
+ */
+ void OnExecuteCommand(std::function<void()> callback);
+
+ /**
* Execute a single CMake command. Returns true if the command
* succeeded or false if it failed.
*/
@@ -964,6 +968,7 @@ private:
bool EnforceUniqueDir(const std::string& srcPath,
const std::string& binPath) const;
+ std::function<void()> ExecuteCommandCallback;
using FunctionBlockerPtr = std::unique_ptr<cmFunctionBlocker>;
using FunctionBlockersType =
std::stack<FunctionBlockerPtr, std::vector<FunctionBlockerPtr>>;
diff --git a/Source/cmWhileCommand.cxx b/Source/cmWhileCommand.cxx
index 37d1c74025..a3968529b0 100644
--- a/Source/cmWhileCommand.cxx
+++ b/Source/cmWhileCommand.cxx
@@ -3,10 +3,14 @@
#include "cmWhileCommand.h"
#include "cm_memory.hxx"
+#include "cm_static_string_view.hxx"
+#include "cm_string_view.hxx"
#include "cmConditionEvaluator.h"
#include "cmExecutionStatus.h"
#include "cmExpandedCommandArgument.h"
+#include "cmFunctionBlocker.h"
+#include "cmListFileCache.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
#include "cmSystemTools.h"
@@ -14,9 +18,29 @@
#include <string>
#include <utility>
+class cmWhileFunctionBlocker : public cmFunctionBlocker
+{
+public:
+ cmWhileFunctionBlocker(cmMakefile* mf);
+ ~cmWhileFunctionBlocker() override;
+
+ cm::string_view StartCommandName() const override { return "while"_s; }
+ cm::string_view EndCommandName() const override { return "endwhile"_s; }
+
+ bool ArgumentsMatch(cmListFileFunction const& lff,
+ cmMakefile& mf) const override;
+
+ bool Replay(std::vector<cmListFileFunction> functions,
+ cmExecutionStatus& inStatus) override;
+
+ std::vector<cmListFileArgument> Args;
+
+private:
+ cmMakefile* Makefile;
+};
+
cmWhileFunctionBlocker::cmWhileFunctionBlocker(cmMakefile* mf)
: Makefile(mf)
- , Depth(0)
{
this->Makefile->PushLoopBlock();
}
@@ -26,108 +50,77 @@ cmWhileFunctionBlocker::~cmWhileFunctionBlocker()
this->Makefile->PopLoopBlock();
}
-bool cmWhileFunctionBlocker::IsFunctionBlocked(const cmListFileFunction& lff,
- cmMakefile& mf,
- cmExecutionStatus& inStatus)
+bool cmWhileFunctionBlocker::ArgumentsMatch(cmListFileFunction const& lff,
+ cmMakefile&) const
{
- // at end of for each execute recorded commands
- if (lff.Name.Lower == "while") {
- // record the number of while commands past this one
- this->Depth++;
- } else if (lff.Name.Lower == "endwhile") {
- // if this is the endwhile for this while loop then execute
- if (!this->Depth) {
- // Remove the function blocker for this scope or bail.
- std::unique_ptr<cmFunctionBlocker> fb(
- mf.RemoveFunctionBlocker(this, lff));
- if (!fb) {
- return false;
- }
+ return lff.Arguments.empty() || lff.Arguments == this->Args;
+}
- std::string errorString;
-
- std::vector<cmExpandedCommandArgument> expandedArguments;
- mf.ExpandArguments(this->Args, expandedArguments);
- MessageType messageType;
-
- cmListFileContext execContext = this->GetStartingContext();
-
- cmCommandContext commandContext;
- commandContext.Line = execContext.Line;
- commandContext.Name = execContext.Name;
-
- cmConditionEvaluator conditionEvaluator(mf, this->GetStartingContext(),
- mf.GetBacktrace(commandContext));
-
- bool isTrue =
- conditionEvaluator.IsTrue(expandedArguments, errorString, messageType);
-
- while (isTrue) {
- if (!errorString.empty()) {
- std::string err = "had incorrect arguments: ";
- for (cmListFileArgument const& arg : this->Args) {
- err += (arg.Delim ? "\"" : "");
- err += arg.Value;
- err += (arg.Delim ? "\"" : "");
- err += " ";
- }
- err += "(";
- err += errorString;
- err += ").";
- mf.IssueMessage(messageType, err);
- if (messageType == MessageType::FATAL_ERROR) {
- cmSystemTools::SetFatalErrorOccured();
- return true;
- }
- }
-
- // Invoke all the functions that were collected in the block.
- for (cmListFileFunction const& fn : this->Functions) {
- cmExecutionStatus status(mf);
- mf.ExecuteCommand(fn, status);
- if (status.GetReturnInvoked()) {
- inStatus.SetReturnInvoked();
- return true;
- }
- if (status.GetBreakInvoked()) {
- return true;
- }
- if (status.GetContinueInvoked()) {
- break;
- }
- if (cmSystemTools::GetFatalErrorOccured()) {
- return true;
- }
- }
- expandedArguments.clear();
- mf.ExpandArguments(this->Args, expandedArguments);
- isTrue = conditionEvaluator.IsTrue(expandedArguments, errorString,
- messageType);
- }
- return true;
- }
- // decrement for each nested while that ends
- this->Depth--;
- }
+bool cmWhileFunctionBlocker::Replay(std::vector<cmListFileFunction> functions,
+ cmExecutionStatus& inStatus)
+{
+ cmMakefile& mf = inStatus.GetMakefile();
+ std::string errorString;
- // record the command
- this->Functions.push_back(lff);
+ std::vector<cmExpandedCommandArgument> expandedArguments;
+ mf.ExpandArguments(this->Args, expandedArguments);
+ MessageType messageType;
- // always return true
- return true;
-}
+ cmListFileContext execContext = this->GetStartingContext();
-bool cmWhileFunctionBlocker::ShouldRemove(const cmListFileFunction& lff,
- cmMakefile&)
-{
- if (lff.Name.Lower == "endwhile") {
- // if the endwhile has arguments, then make sure
- // they match the arguments of the matching while
- if (lff.Arguments.empty() || lff.Arguments == this->Args) {
- return true;
+ cmCommandContext commandContext;
+ commandContext.Line = execContext.Line;
+ commandContext.Name = execContext.Name;
+
+ cmConditionEvaluator conditionEvaluator(mf, this->GetStartingContext(),
+ mf.GetBacktrace(commandContext));
+
+ bool isTrue =
+ conditionEvaluator.IsTrue(expandedArguments, errorString, messageType);
+
+ while (isTrue) {
+ if (!errorString.empty()) {
+ std::string err = "had incorrect arguments: ";
+ for (cmListFileArgument const& arg : this->Args) {
+ err += (arg.Delim ? "\"" : "");
+ err += arg.Value;
+ err += (arg.Delim ? "\"" : "");
+ err += " ";
+ }
+ err += "(";
+ err += errorString;
+ err += ").";
+ mf.IssueMessage(messageType, err);
+ if (messageType == MessageType::FATAL_ERROR) {
+ cmSystemTools::SetFatalErrorOccured();
+ return true;
+ }
}
+
+ // 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()) {
+ return true;
+ }
+ if (status.GetContinueInvoked()) {
+ break;
+ }
+ if (cmSystemTools::GetFatalErrorOccured()) {
+ return true;
+ }
+ }
+ expandedArguments.clear();
+ mf.ExpandArguments(this->Args, expandedArguments);
+ isTrue =
+ conditionEvaluator.IsTrue(expandedArguments, errorString, messageType);
}
- return false;
+ return true;
}
bool cmWhileCommand(std::vector<cmListFileArgument> const& args,
diff --git a/Source/cmWhileCommand.h b/Source/cmWhileCommand.h
index 2257799407..beca652c6a 100644
--- a/Source/cmWhileCommand.h
+++ b/Source/cmWhileCommand.h
@@ -7,28 +7,8 @@
#include <vector>
-#include "cmFunctionBlocker.h"
-#include "cmListFileCache.h"
-
class cmExecutionStatus;
-class cmMakefile;
-
-class cmWhileFunctionBlocker : public cmFunctionBlocker
-{
-public:
- cmWhileFunctionBlocker(cmMakefile* mf);
- ~cmWhileFunctionBlocker() override;
- bool IsFunctionBlocked(const cmListFileFunction& lff, cmMakefile& mf,
- cmExecutionStatus&) override;
- bool ShouldRemove(const cmListFileFunction& lff, cmMakefile& mf) override;
-
- std::vector<cmListFileArgument> Args;
- std::vector<cmListFileFunction> Functions;
-
-private:
- cmMakefile* Makefile;
- int Depth;
-};
+struct cmListFileArgument;
/// \brief Starts a while loop
bool cmWhileCommand(std::vector<cmListFileArgument> const& args,
diff --git a/bootstrap b/bootstrap
index ac5adc8498..06e11b5e31 100755
--- a/bootstrap
+++ b/bootstrap
@@ -326,6 +326,7 @@ CMAKE_CXX_SOURCES="\
cmFindPathCommand \
cmFindProgramCommand \
cmForEachCommand \
+ cmFunctionBlocker \
cmFunctionCommand \
cmFSPermissions \
cmGeneratedFileStream \