Commit 2327cc0e authored by Brad King's avatar Brad King Committed by Kitware Robot
Browse files

Merge topic 'control-block3'

41364824 cmFunctionBlocker: Recycle functions
6491270e cmFunctionBlocker: Move check for matching args
af24e4ef cmFunctionBlocker: Move common logic to base
ef38ff22 cm*FunctionBlocker: Extract function Replay
b51fba62 cmMakefile: Add OnExecuteCommand callback
c7650094

 cm*FunctionBlocker: Move to source file
Acked-by: Kitware Robot's avatarKitware Robot <kwrobot@kitware.com>
Merge-request: !3632
parents c969d462 41364824
......@@ -532,6 +532,8 @@ set(SRCS
cmFindProgramCommand.h
cmForEachCommand.cxx
cmForEachCommand.h
cmFunctionBlocker.cxx
cmFunctionBlocker.h
cmFunctionCommand.cxx
cmFunctionCommand.h
cmGetCMakePropertyCommand.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
......
......@@ -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&)
{
......
......@@ -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
......
/* 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;
}
......@@ -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;
......@@ -38,8 +35,20 @@ public:
return this->StartingContext;
}
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
......@@ -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,
......
......@@ -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
......
......@@ -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;
}