diff options
author | Marc Chevrier <marc.chevrier@gmail.com> | 2022-08-05 08:55:32 (GMT) |
---|---|---|
committer | Marc Chevrier <marc.chevrier@gmail.com> | 2022-08-22 14:25:53 (GMT) |
commit | 44a2f3f3324a608062eb7b88072c7640f80f4a5c (patch) | |
tree | bcb7ee5b371d05c71d76fb12214c0b82039fa2de /Source | |
parent | 604993248fdee0bec8ab8c74c1173c67496a7dfd (diff) | |
download | CMake-44a2f3f3324a608062eb7b88072c7640f80f4a5c.zip CMake-44a2f3f3324a608062eb7b88072c7640f80f4a5c.tar.gz CMake-44a2f3f3324a608062eb7b88072c7640f80f4a5c.tar.bz2 |
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
Diffstat (limited to 'Source')
-rw-r--r-- | Source/CMakeLists.txt | 2 | ||||
-rw-r--r-- | Source/cmBlockCommand.cxx | 200 | ||||
-rw-r--r-- | Source/cmBlockCommand.h | 14 | ||||
-rw-r--r-- | Source/cmCMakeLanguageCommand.cxx | 5 | ||||
-rw-r--r-- | Source/cmCommands.cxx | 6 | ||||
-rw-r--r-- | Source/cmListFileCache.cxx | 11 |
6 files changed, 236 insertions, 2 deletions
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index fe92716..b6f7c85 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 0000000..c358aa2 --- /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 0000000..5fd8f42 --- /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 76561c3..68e658c 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 5e616b3..3bc4f0e 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 91157cb..6270c82 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(); } } |