| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file LICENSE.rst or https://cmake.org/licensing for details. */ | |
| #include "cmBlockCommand.h" | |
| #include <cstdint> | |
| #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, | |
| DIAGNOSTICS, | |
| }; | |
| using ScopeSet = cm::enum_set<ScopeType>; | |
| class BlockScopePushPop | |
| { | |
| public: | |
| BlockScopePushPop(cmMakefile* m, ScopeSet const& scopes); | |
| ~BlockScopePushPop() = default; | |
| BlockScopePushPop(BlockScopePushPop const&) = delete; | |
| BlockScopePushPop& operator=(BlockScopePushPop const&) = delete; | |
| private: | |
| std::unique_ptr<cmMakefile::VariablePushPop> VariableScope; | |
| std::unique_ptr<cmMakefile::PolicyPushPop> PolicyScope; | |
| std::unique_ptr<cmMakefile::DiagnosticPushPop> DiagnosticScope; | |
| }; | |
| BlockScopePushPop::BlockScopePushPop(cmMakefile* mf, ScopeSet const& scopes) | |
| { | |
| if (scopes.contains(ScopeType::VARIABLES)) { | |
| this->VariableScope = cm::make_unique<cmMakefile::VariablePushPop>(mf); | |
| } | |
| if (scopes.contains(ScopeType::POLICIES)) { | |
| this->PolicyScope = cm::make_unique<cmMakefile::PolicyPushPop>(mf); | |
| } | |
| if (scopes.contains(ScopeType::DIAGNOSTICS)) { | |
| this->DiagnosticScope = cm::make_unique<cmMakefile::DiagnosticPushPop>(mf); | |
| } | |
| } | |
| class cmBlockFunctionBlocker : public cmFunctionBlocker | |
| { | |
| public: | |
| cmBlockFunctionBlocker(cmMakefile* mf, ScopeSet const& 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, ScopeSet const& 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; | |
| } | |
| if (scope == "DIAGNOSTICS"_s) { | |
| scopes.insert(ScopeType::DIAGNOSTICS); | |
| continue; | |
| } | |
| status.SetError(cmStrCat("SCOPE_FOR unsupported scope \"", scope, '"')); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| } else { | |
| scopes = { ScopeType::VARIABLES, ScopeType::POLICIES, | |
| ScopeType::DIAGNOSTICS }; | |
| } | |
| 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; | |
| } |