return(): Propagate variables to result scope

Fixes: #23871
pipelines/297094
Marc Chevrier 2022-08-22 16:10:56 +02:00
parent 8f0e1f2111
commit 838a5fae23
30 changed files with 360 additions and 22 deletions

View File

@ -71,4 +71,5 @@ See Also
^^^^^^^^
* :command:`endblock`
* :command:`return`
* :command:`cmake_policy`

View File

@ -5,7 +5,7 @@ Return from a file, directory or function.
.. code-block:: cmake
return()
return([PROPAGATE <var-name>...])
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 <macro>`, unlike a :command:`function <function>`,
is expanded in place and therefore cannot handle ``return()``.
See Also
^^^^^^^^
* :command:`block`

View File

@ -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
^^^^^^^^^^^^^^^

View File

@ -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. </policy/CMP0140>
Policies Introduced by CMake 3.24
=================================

17
Help/policy/CMP0140.rst Normal file
View File

@ -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

View File

@ -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`.

View File

@ -73,6 +73,7 @@ public:
private:
cmMakefile* Makefile;
ScopeSet Scopes;
BlockScopePushPop BlockScope;
std::vector<std::string> VariableNames;
};
@ -81,6 +82,7 @@ cmBlockFunctionBlocker::cmBlockFunctionBlocker(
cmMakefile* const mf, const ScopeSet& scopes,
std::vector<std::string> 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<cmListFileFunction> 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()) {

View File

@ -5,6 +5,7 @@
#include <cmConfigure.h> // IWYU pragma: keep
#include <string>
#include <vector>
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<std::string> variables)
{
this->Variables = std::move(variables);
this->ReturnInvoked = true;
}
bool GetReturnInvoked() const { return this->ReturnInvoked; }
const std::vector<std::string>& 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<std::string> Variables;
};

View File

@ -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;
}

View File

@ -120,6 +120,7 @@ bool cmFunctionHelperCommand::operator()(
return false;
}
if (status.GetReturnInvoked()) {
makefile.RaiseScope(status.GetReturnVariables());
break;
}
}

View File

@ -150,7 +150,7 @@ bool cmIfFunctionBlocker::Replay(std::vector<cmListFileFunction> functions,
cmExecutionStatus status(mf);
mf.ExecuteCommand(func, status);
if (status.GetReturnInvoked()) {
inStatus.SetReturnInvoked();
inStatus.SetReturnInvoked(status.GetReturnVariables());
return true;
}
if (status.GetBreakInvoked()) {

View File

@ -126,7 +126,7 @@ bool cmMacroHelperCommand::operator()(
return false;
}
if (status.GetReturnInvoked()) {
inStatus.SetReturnInvoked();
inStatus.SetReturnInvoked(status.GetReturnVariables());
return true;
}
if (status.GetBreakInvoked()) {

View File

@ -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;
}

View File

@ -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) \

View File

@ -2,12 +2,52 @@
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmReturnCommand.h"
#include <cm/string_view>
#include <cmext/string_view>
#include "cmExecutionStatus.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
#include "cmPolicies.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
// cmReturnCommand
bool cmReturnCommand(std::vector<std::string> const&,
bool cmReturnCommand(std::vector<std::string> 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;
}

View File

@ -96,7 +96,7 @@ bool cmWhileFunctionBlocker::Replay(std::vector<cmListFileFunction> functions,
cmExecutionStatus status(mf);
mf.ExecuteCommand(fn, status);
if (status.GetReturnInvoked()) {
inStatus.SetReturnInvoked();
inStatus.SetReturnInvoked(status.GetReturnVariables());
return true;
}
if (status.GetBreakInvoked()) {

View File

@ -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()

View File

@ -0,0 +1,8 @@
cmake_policy(SET CMP0140 OLD)
function(FUNC)
return(PROPAGATE VAR)
endfunction()
func()

View File

@ -0,0 +1 @@
0

View File

@ -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.

View File

@ -0,0 +1,8 @@
cmake_policy(VERSION 3.1)
function(FUNC)
return(PROPAGATE VAR)
endfunction()
func()

View File

@ -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)

View File

@ -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()

View File

@ -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()

View File

@ -0,0 +1,2 @@
return(PROPAGATE)

View File

@ -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)

View File

@ -0,0 +1 @@
1

View File

@ -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\)

View File

@ -0,0 +1,2 @@
return(WRONG_ARG)

View File

@ -0,0 +1,5 @@
set(VAR1 "set")
unset(VAR2)
return(PROPAGATE VAR1 VAR2)