diff --git a/Help/command/block.rst b/Help/command/block.rst index f9d85c8a39..9d37deb4dc 100644 --- a/Help/command/block.rst +++ b/Help/command/block.rst @@ -71,4 +71,5 @@ See Also ^^^^^^^^ * :command:`endblock` + * :command:`return` * :command:`cmake_policy` diff --git a/Help/command/return.rst b/Help/command/return.rst index ec009d8c8c..029fd050e3 100644 --- a/Help/command/return.rst +++ b/Help/command/return.rst @@ -5,7 +5,7 @@ Return from a file, directory or function. .. code-block:: cmake - return() + return([PROPAGATE ...]) Returns from a file, directory or function. When this command is encountered in an included file (via :command:`include` or @@ -16,5 +16,39 @@ deferred calls scheduled by :command:`cmake_language(DEFER)` are invoked and control is returned to the parent directory if there is one. If return is called in a function, control is returned to the caller of the function. +``PROPAGATE`` + .. versionadded:: 3.25 + + This option set or unset the specified variables in the parent directory or + function caller scope. This is equivalent to :command:`set(PARENT_SCOPE)` or + :command:`unset(PARENT_SCOPE)` commands. + + The option ``PROPAGATE`` can be very useful in conjunction with the + :command:`block` command because the :command:`return` will cross over + various scopes created by the :command:`block` commands. + + .. code-block:: cmake + + function(MULTI_SCOPES RESULT_VARIABLE) + block(SCOPE_FOR VARIABLES) + # here set(PARENT_SCOPE) is not usable because it will not set the + # variable in the caller scope but in the parent scope of the block() + set(${RESULT_VARIABLE} "new-value") + return(PROPAGATE ${RESULT_VARIABLE}) + endblock() + endfunction() + + set(MY_VAR "initial-value") + multi_scopes(MY_VAR) + # here MY_VAR will holds "new-value" + +Policy :policy:`CMP0140` controls the behavior regarding the arguments of the +command. + Note that a :command:`macro `, unlike a :command:`function `, is expanded in place and therefore cannot handle ``return()``. + +See Also +^^^^^^^^ + + * :command:`block` diff --git a/Help/command/set.rst b/Help/command/set.rst index aa2ea55d4d..5e05d9b5d7 100644 --- a/Help/command/set.rst +++ b/Help/command/set.rst @@ -30,9 +30,9 @@ applicable to the case at hand). The previous state of the variable's value stays the same in the current scope (e.g., if it was undefined before, it is still undefined and if it had a value, it is still that value). -The :command:`block(PROPAGATE)` command can be used as an alternate method to -:command:`set(PARENT_SCOPE)` and :command:`unset(PARENT_SCOPE)` commands to -update the parent scope. +The :command:`block(PROPAGATE)` and :command:`return(PROPAGATE)` commands can +be used as an alternate method to the :command:`set(PARENT_SCOPE)` and +:command:`unset(PARENT_SCOPE)` commands to update the parent scope. Set Cache Entry ^^^^^^^^^^^^^^^ diff --git a/Help/manual/cmake-policies.7.rst b/Help/manual/cmake-policies.7.rst index f6ab0c7fb1..a1133b9937 100644 --- a/Help/manual/cmake-policies.7.rst +++ b/Help/manual/cmake-policies.7.rst @@ -52,6 +52,14 @@ to determine whether to report an error on use of deprecated macros or functions. +Policies Introduced by CMake 3.25 +================================= + +.. toctree:: + :maxdepth: 1 + + CMP0140: The return() command checks its arguments. + Policies Introduced by CMake 3.24 ================================= diff --git a/Help/policy/CMP0140.rst b/Help/policy/CMP0140.rst new file mode 100644 index 0000000000..2e2d0c3511 --- /dev/null +++ b/Help/policy/CMP0140.rst @@ -0,0 +1,17 @@ +CMP0140 +------- + +.. versionadded:: 3.25 + +The :command:`return` command checks its parameters. + +The ``OLD`` behavior for this policy is to ignore any parameters given to the +command. +The ``NEW`` behavior is to check validity of the parameters. + +This policy was introduced in CMake version 3.25. +CMake version |release| warns when the policy is not set and uses +``OLD`` behavior. Use the :command:`cmake_policy` command to set +it to ``OLD`` or ``NEW`` explicitly. + +.. include:: DEPRECATED.txt diff --git a/Help/release/dev/return-PROPAGATE.rst b/Help/release/dev/return-PROPAGATE.rst new file mode 100644 index 0000000000..7308d20357 --- /dev/null +++ b/Help/release/dev/return-PROPAGATE.rst @@ -0,0 +1,5 @@ +return-PROPAGATE +---------------- + +* The :command:`return` command gains the capability to propagate variables to + the include directory of function caller scope. See policy :policy:`CMP0140`. diff --git a/Source/cmBlockCommand.cxx b/Source/cmBlockCommand.cxx index c358aa2cc3..ec79149060 100644 --- a/Source/cmBlockCommand.cxx +++ b/Source/cmBlockCommand.cxx @@ -73,6 +73,7 @@ public: private: cmMakefile* Makefile; + ScopeSet Scopes; BlockScopePushPop BlockScope; std::vector VariableNames; }; @@ -81,6 +82,7 @@ cmBlockFunctionBlocker::cmBlockFunctionBlocker( cmMakefile* const mf, const ScopeSet& scopes, std::vector variableNames) : Makefile{ mf } + , Scopes{ scopes } , BlockScope{ mf, scopes } , VariableNames{ std::move(variableNames) } { @@ -88,14 +90,8 @@ cmBlockFunctionBlocker::cmBlockFunctionBlocker( 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); - } + if (this->Scopes.contains(ScopeType::VARIABLES)) { + this->Makefile->RaiseScope(this->VariableNames); } } @@ -118,7 +114,8 @@ bool cmBlockFunctionBlocker::Replay(std::vector functions, cmExecutionStatus status(mf); mf.ExecuteCommand(fn, status); if (status.GetReturnInvoked()) { - inStatus.SetReturnInvoked(); + mf.RaiseScope(status.GetReturnVariables()); + inStatus.SetReturnInvoked(status.GetReturnVariables()); return true; } if (status.GetBreakInvoked()) { diff --git a/Source/cmExecutionStatus.h b/Source/cmExecutionStatus.h index 0feaedf33f..ced3548110 100644 --- a/Source/cmExecutionStatus.h +++ b/Source/cmExecutionStatus.h @@ -5,6 +5,7 @@ #include // IWYU pragma: keep #include +#include class cmMakefile; @@ -27,8 +28,21 @@ public: void SetError(std::string const& e) { this->Error = e; } std::string const& GetError() const { return this->Error; } - void SetReturnInvoked() { this->ReturnInvoked = true; } + void SetReturnInvoked() + { + this->Variables.clear(); + this->ReturnInvoked = true; + } + void SetReturnInvoked(std::vector variables) + { + this->Variables = std::move(variables); + this->ReturnInvoked = true; + } bool GetReturnInvoked() const { return this->ReturnInvoked; } + const std::vector& GetReturnVariables() const + { + return this->Variables; + } void SetBreakInvoked() { this->BreakInvoked = true; } bool GetBreakInvoked() const { return this->BreakInvoked; } @@ -46,4 +60,5 @@ private: bool BreakInvoked = false; bool ContinueInvoked = false; bool NestedError = false; + std::vector Variables; }; diff --git a/Source/cmForEachCommand.cxx b/Source/cmForEachCommand.cxx index b9400c95fd..3465c230f8 100644 --- a/Source/cmForEachCommand.cxx +++ b/Source/cmForEachCommand.cxx @@ -260,7 +260,7 @@ auto cmForEachFunctionBlocker::invoke( cmExecutionStatus status(mf); mf.ExecuteCommand(func, status); if (status.GetReturnInvoked()) { - inStatus.SetReturnInvoked(); + inStatus.SetReturnInvoked(status.GetReturnVariables()); result.Break = true; break; } diff --git a/Source/cmFunctionCommand.cxx b/Source/cmFunctionCommand.cxx index 1359009e6a..f4768b66ac 100644 --- a/Source/cmFunctionCommand.cxx +++ b/Source/cmFunctionCommand.cxx @@ -120,6 +120,7 @@ bool cmFunctionHelperCommand::operator()( return false; } if (status.GetReturnInvoked()) { + makefile.RaiseScope(status.GetReturnVariables()); break; } } diff --git a/Source/cmIfCommand.cxx b/Source/cmIfCommand.cxx index 0da72b15d3..c2a09c1085 100644 --- a/Source/cmIfCommand.cxx +++ b/Source/cmIfCommand.cxx @@ -150,7 +150,7 @@ bool cmIfFunctionBlocker::Replay(std::vector functions, cmExecutionStatus status(mf); mf.ExecuteCommand(func, status); if (status.GetReturnInvoked()) { - inStatus.SetReturnInvoked(); + inStatus.SetReturnInvoked(status.GetReturnVariables()); return true; } if (status.GetBreakInvoked()) { diff --git a/Source/cmMacroCommand.cxx b/Source/cmMacroCommand.cxx index ef1248792e..47ad7491b0 100644 --- a/Source/cmMacroCommand.cxx +++ b/Source/cmMacroCommand.cxx @@ -126,7 +126,7 @@ bool cmMacroHelperCommand::operator()( return false; } if (status.GetReturnInvoked()) { - inStatus.SetReturnInvoked(); + inStatus.SetReturnInvoked(status.GetReturnVariables()); return true; } if (status.GetBreakInvoked()) { diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx index 51a4b3b7ee..760ed5f35c 100644 --- a/Source/cmMakefile.cxx +++ b/Source/cmMakefile.cxx @@ -789,6 +789,7 @@ void cmMakefile::RunListFile(cmListFile const& listFile, break; } if (status.GetReturnInvoked()) { + this->RaiseScope(status.GetReturnVariables()); // Exit early due to return command. break; } diff --git a/Source/cmPolicies.h b/Source/cmPolicies.h index cb7402c026..fdda31edf5 100644 --- a/Source/cmPolicies.h +++ b/Source/cmPolicies.h @@ -421,7 +421,9 @@ class cmMakefile; SELECT( \ POLICY, CMP0139, \ "The if() command supports path comparisons using PATH_EQUAL operator.", \ - 3, 24, 0, cmPolicies::WARN) + 3, 24, 0, cmPolicies::WARN) \ + SELECT(POLICY, CMP0140, "The return() command checks its arguments.", 3, \ + 25, 0, cmPolicies::WARN) #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1) #define CM_FOR_EACH_POLICY_ID(POLICY) \ diff --git a/Source/cmReturnCommand.cxx b/Source/cmReturnCommand.cxx index 59056690c1..765b7721d9 100644 --- a/Source/cmReturnCommand.cxx +++ b/Source/cmReturnCommand.cxx @@ -2,12 +2,52 @@ file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmReturnCommand.h" +#include +#include + #include "cmExecutionStatus.h" +#include "cmMakefile.h" +#include "cmMessageType.h" +#include "cmPolicies.h" +#include "cmStringAlgorithms.h" +#include "cmSystemTools.h" // cmReturnCommand -bool cmReturnCommand(std::vector const&, +bool cmReturnCommand(std::vector const& args, cmExecutionStatus& status) { - status.SetReturnInvoked(); + if (!args.empty()) { + switch (status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0140)) { + case cmPolicies::WARN: + status.GetMakefile().IssueMessage( + MessageType::AUTHOR_WARNING, + cmStrCat( + cmPolicies::GetPolicyWarning(cmPolicies::CMP0140), '\n', + "return() checks its arguments when the policy is set to NEW. " + "Since the policy is not set the OLD behavior will be used so " + "the arguments will be ignored.")); + CM_FALLTHROUGH; + case cmPolicies::OLD: + return true; + case cmPolicies::REQUIRED_IF_USED: + case cmPolicies::REQUIRED_ALWAYS: + status.GetMakefile().IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat('\n', cmPolicies::GetPolicyWarning(cmPolicies::CMP0140))); + cmSystemTools::SetFatalErrorOccurred(); + return false; + default: + break; + } + if (args[0] != "PROPAGATE"_s) { + status.SetError( + cmStrCat("called with unsupported argument \"", args[0], '"')); + cmSystemTools::SetFatalErrorOccurred(); + return false; + } + status.SetReturnInvoked({ args.begin() + 1, args.end() }); + } else { + status.SetReturnInvoked(); + } return true; } diff --git a/Source/cmWhileCommand.cxx b/Source/cmWhileCommand.cxx index fb94273980..e80d1fcf02 100644 --- a/Source/cmWhileCommand.cxx +++ b/Source/cmWhileCommand.cxx @@ -96,7 +96,7 @@ bool cmWhileFunctionBlocker::Replay(std::vector functions, cmExecutionStatus status(mf); mf.ExecuteCommand(fn, status); if (status.GetReturnInvoked()) { - inStatus.SetReturnInvoked(); + inStatus.SetReturnInvoked(status.GetReturnVariables()); return true; } if (status.GetBreakInvoked()) { diff --git a/Tests/RunCMake/return/CMP0140-NEW.cmake b/Tests/RunCMake/return/CMP0140-NEW.cmake new file mode 100644 index 0000000000..eb6b85c57d --- /dev/null +++ b/Tests/RunCMake/return/CMP0140-NEW.cmake @@ -0,0 +1,13 @@ + +cmake_policy(SET CMP0140 NEW) + +function(FUNC) + set(VAR "set") + return(PROPAGATE VAR) +endfunction() + +set(VAR "initial") +func() +if (NOT DEFINED VAR OR NOT VAR STREQUAL "set") + message(FATAL_ERROR "return(PROPAGATE) not handled correctly.") +endif() diff --git a/Tests/RunCMake/return/CMP0140-OLD.cmake b/Tests/RunCMake/return/CMP0140-OLD.cmake new file mode 100644 index 0000000000..8113a43f27 --- /dev/null +++ b/Tests/RunCMake/return/CMP0140-OLD.cmake @@ -0,0 +1,8 @@ + +cmake_policy(SET CMP0140 OLD) + +function(FUNC) + return(PROPAGATE VAR) +endfunction() + +func() diff --git a/Tests/RunCMake/return/CMP0140-WARN-result.txt b/Tests/RunCMake/return/CMP0140-WARN-result.txt new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/Tests/RunCMake/return/CMP0140-WARN-result.txt @@ -0,0 +1 @@ +0 diff --git a/Tests/RunCMake/return/CMP0140-WARN-stderr.txt b/Tests/RunCMake/return/CMP0140-WARN-stderr.txt new file mode 100644 index 0000000000..ed4beb6f5e --- /dev/null +++ b/Tests/RunCMake/return/CMP0140-WARN-stderr.txt @@ -0,0 +1,12 @@ +CMake Warning \(dev\) at CMP0140-WARN.cmake:[0-9]+ \(return\): + Policy CMP0140 is not set: The return\(\) command checks its arguments. Run + "cmake --help-policy CMP0140" for policy details. Use the cmake_policy + command to set the policy and suppress this warning. + + return\(\) checks its arguments when the policy is set to NEW. Since the + policy is not set the OLD behavior will be used so the arguments will be + ignored. +Call Stack \(most recent call first\): + CMP0140-WARN.cmake:[0-9]+ \(func\) + CMakeLists.txt:[0-9]+ \(include\) +This warning is for project developers. Use -Wno-dev to suppress it. diff --git a/Tests/RunCMake/return/CMP0140-WARN.cmake b/Tests/RunCMake/return/CMP0140-WARN.cmake new file mode 100644 index 0000000000..df2fc887bc --- /dev/null +++ b/Tests/RunCMake/return/CMP0140-WARN.cmake @@ -0,0 +1,8 @@ + +cmake_policy(VERSION 3.1) + +function(FUNC) + return(PROPAGATE VAR) +endfunction() + +func() diff --git a/Tests/RunCMake/return/CMakeLists.txt b/Tests/RunCMake/return/CMakeLists.txt index ef2163c298..6cc903fc7d 100644 --- a/Tests/RunCMake/return/CMakeLists.txt +++ b/Tests/RunCMake/return/CMakeLists.txt @@ -1,3 +1,3 @@ -cmake_minimum_required(VERSION 3.1) +cmake_minimum_required(VERSION 3.1...3.25) project(${RunCMake_TEST} NONE) include(${RunCMake_TEST}.cmake) diff --git a/Tests/RunCMake/return/PropagateFromDirectory.cmake b/Tests/RunCMake/return/PropagateFromDirectory.cmake new file mode 100644 index 0000000000..9c820bb7d6 --- /dev/null +++ b/Tests/RunCMake/return/PropagateFromDirectory.cmake @@ -0,0 +1,10 @@ + +set(VAR1 "initial") +set(VAR2 "initial") + +add_subdirectory(subdir) + +if((NOT DEFINED VAR1 OR NOT VAR1 STREQUAL "set") + OR DEFINED VAR2) + message(SEND_ERROR "erroneous propagation for FUNC1") +endif() diff --git a/Tests/RunCMake/return/PropagateFromFunction.cmake b/Tests/RunCMake/return/PropagateFromFunction.cmake new file mode 100644 index 0000000000..1c12bc1984 --- /dev/null +++ b/Tests/RunCMake/return/PropagateFromFunction.cmake @@ -0,0 +1,142 @@ + +function(FUNC1) + set(VAR1 "set") + unset(VAR2) + return(PROPAGATE VAR1 VAR2) +endfunction() + +set(VAR1 "initial") +set(VAR2 "initial") +func1() +if((NOT DEFINED VAR1 OR NOT VAR1 STREQUAL "set") + OR DEFINED VAR2) + message(SEND_ERROR "erroneous propagation for FUNC1") +endif() + + +function(FUNC2) + block() + set(VAR1 "set") + unset(VAR2) + return(PROPAGATE VAR1 VAR2) + endblock() +endfunction() + +set(VAR1 "initial") +set(VAR2 "initial") +func2() +if((NOT DEFINED VAR1 OR NOT VAR1 STREQUAL "set") + OR DEFINED VAR2) + message(SEND_ERROR "erroneous propagation for FUNC2") +endif() + + +function(FUNC3) + block(SCOPE_FOR POLICIES) + set(VAR1 "set") + unset(VAR2) + return(PROPAGATE VAR1 VAR2) + endblock() +endfunction() + +set(VAR1 "initial") +set(VAR2 "initial") +func3() +if((NOT DEFINED VAR1 OR NOT VAR1 STREQUAL "set") + OR DEFINED VAR2) + message(SEND_ERROR "erroneous propagation for FUNC3") +endif() + + +function(FUNC4) + while(TRUE) + set(VAR1 "set") + unset(VAR2) + return(PROPAGATE VAR1 VAR2) + endwhile() +endfunction() + +set(VAR1 "initial") +set(VAR2 "initial") +func4() +if((NOT DEFINED VAR1 OR NOT VAR1 STREQUAL "set") + OR DEFINED VAR2) + message(SEND_ERROR "erroneous propagation for FUNC4") +endif() + + +function(FUNC5) + foreach(item IN ITEMS A B) + set(VAR1 "set") + unset(VAR2) + return(PROPAGATE VAR1 VAR2) + endforeach() +endfunction() + +set(VAR1 "initial") +set(VAR2 "initial") +func5() +if((NOT DEFINED VAR1 OR NOT VAR1 STREQUAL "set") + OR DEFINED VAR2) + message(SEND_ERROR "erroneous propagation for FUNC5") +endif() + + +function(FUNC6) + if(TRUE) + set(VAR1 "set") + unset(VAR2) + return(PROPAGATE VAR1 VAR2) + endif() +endfunction() + +set(VAR1 "initial") +set(VAR2 "initial") +func6() +if((NOT DEFINED VAR1 OR NOT VAR1 STREQUAL "set") + OR DEFINED VAR2) + message(SEND_ERROR "erroneous propagation for FUNC6") +endif() + + +function(FUNC7) + if(FALSE) + else() + set(VAR1 "set") + unset(VAR2) + return(PROPAGATE VAR1 VAR2) + endif() +endfunction() + +set(VAR1 "initial") +set(VAR2 "initial") +func7() +if((NOT DEFINED VAR1 OR NOT VAR1 STREQUAL "set") + OR DEFINED VAR2) + message(SEND_ERROR "erroneous propagation for FUNC7") +endif() + + +set(VAR1 "initial") +set(VAR2 "initial") +cmake_language(CALL func7) +if((NOT DEFINED VAR1 OR NOT VAR1 STREQUAL "set") + OR DEFINED VAR2) + message(SEND_ERROR "erroneous propagation for cmake_language(CALL FUNC7)") +endif() + + +set(VAR1 "initial") +set(VAR2 "initial") +cmake_language(EVAL CODE " + function(FUNC8) + set(VAR1 \"set\") + unset(VAR2) + return(PROPAGATE VAR1 VAR2) + endfunction() + + func8()") +if((NOT DEFINED VAR1 OR NOT VAR1 STREQUAL "set") + OR DEFINED VAR2) + message(SEND_ERROR "erroneous propagation for cmake_language(EVAL CODE)") +endif() diff --git a/Tests/RunCMake/return/PropagateNothing.cmake b/Tests/RunCMake/return/PropagateNothing.cmake new file mode 100644 index 0000000000..0ace58e452 --- /dev/null +++ b/Tests/RunCMake/return/PropagateNothing.cmake @@ -0,0 +1,2 @@ + +return(PROPAGATE) diff --git a/Tests/RunCMake/return/RunCMakeTest.cmake b/Tests/RunCMake/return/RunCMakeTest.cmake index 2cc6c9d328..f9e06a53c3 100644 --- a/Tests/RunCMake/return/RunCMakeTest.cmake +++ b/Tests/RunCMake/return/RunCMakeTest.cmake @@ -1,3 +1,12 @@ include(RunCMake) run_cmake(ReturnFromForeach) + +run_cmake(WrongArgument) +run_cmake(PropagateNothing) +run_cmake(PropagateFromFunction) +run_cmake(PropagateFromDirectory) + +run_cmake(CMP0140-NEW) +run_cmake(CMP0140-OLD) +run_cmake(CMP0140-WARN) diff --git a/Tests/RunCMake/return/WrongArgument-result.txt b/Tests/RunCMake/return/WrongArgument-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/return/WrongArgument-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/return/WrongArgument-stderr.txt b/Tests/RunCMake/return/WrongArgument-stderr.txt new file mode 100644 index 0000000000..4d1c5ad871 --- /dev/null +++ b/Tests/RunCMake/return/WrongArgument-stderr.txt @@ -0,0 +1,4 @@ +CMake Error at WrongArgument.cmake:[0-9]+ \(return\): + return called with unsupported argument "WRONG_ARG" +Call Stack \(most recent call first\): + CMakeLists.txt:[0-9]+ \(include\) diff --git a/Tests/RunCMake/return/WrongArgument.cmake b/Tests/RunCMake/return/WrongArgument.cmake new file mode 100644 index 0000000000..d03b19fa49 --- /dev/null +++ b/Tests/RunCMake/return/WrongArgument.cmake @@ -0,0 +1,2 @@ + +return(WRONG_ARG) diff --git a/Tests/RunCMake/return/subdir/CMakeLists.txt b/Tests/RunCMake/return/subdir/CMakeLists.txt new file mode 100644 index 0000000000..e575d47048 --- /dev/null +++ b/Tests/RunCMake/return/subdir/CMakeLists.txt @@ -0,0 +1,5 @@ + +set(VAR1 "set") +unset(VAR2) + +return(PROPAGATE VAR1 VAR2)