blob: 5bf7bed08bc10c5cb78a664493cdb41fb90890a4 [file] [log] [blame]
/* 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>
#include <initializer_list>
#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;
ScopeSet Scopes;
BlockScopePushPop BlockScope;
std::vector<std::string> VariableNames;
};
cmBlockFunctionBlocker::cmBlockFunctionBlocker(
cmMakefile* const mf, const ScopeSet& scopes,
std::vector<std::string> variableNames)
: Makefile{ mf }
, Scopes{ scopes }
, BlockScope{ mf, scopes }
, VariableNames{ std::move(variableNames) }
{
}
cmBlockFunctionBlocker::~cmBlockFunctionBlocker()
{
if (this->Scopes.contains(ScopeType::VARIABLES)) {
this->Makefile->RaiseScope(this->VariableNames);
}
}
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()) {
mf.RaiseScope(status.GetReturnVariables());
inStatus.SetReturnInvoked(status.GetReturnVariables());
return true;
}
if (status.GetBreakInvoked()) {
inStatus.SetBreakInvoked();
return true;
}
if (status.GetContinueInvoked()) {
inStatus.SetContinueInvoked();
return true;
}
if (status.HasExitCode()) {
inStatus.SetExitCode(status.GetExitCode());
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;
}