Commit 4df3f530 authored by Brad King's avatar Brad King Committed by Kitware Robot
Browse files

Merge topic 'foreach-loop-variable'

46896d98

 foreach(): loop variables are only available in the loop scope
Acked-by: Kitware Robot's avatarKitware Robot <kwrobot@kitware.com>
Acked-by: Ben Boeckel's avatarBen Boeckel <ben.boeckel@kitware.com>
Acked-by: Michael Hirsch's avatarMichael Hirsch <michael@scivision.dev>
Merge-request: !6044
parents ec6ba9a3 46896d98
......@@ -14,9 +14,12 @@ semicolon or whitespace.
All commands between ``foreach`` and the matching ``endforeach`` are recorded
without being invoked. Once the ``endforeach`` is evaluated, the recorded
list of commands is invoked once for each item in ``<items>``.
At the beginning of each iteration the variable ``loop_var`` will be set
At the beginning of each iteration the variable ``<loop_var>`` will be set
to the value of the current item.
The scope of ``<loop_var>`` is restricted to the loop scope. See policy
:policy:`CMP0124` for details.
The commands :command:`break` and :command:`continue` provide means to
escape from the normal control flow.
......
......@@ -57,6 +57,7 @@ Policies Introduced by CMake 3.21
.. toctree::
:maxdepth: 1
CMP0124: foreach() loop variables are only available in the loop scope. </policy/CMP0124>
CMP0123: ARMClang cpu/arch compile and link flags must be set explicitly. </policy/CMP0123>
CMP0122: UseSWIG use standard library name conventions for csharp language. </policy/CMP0122>
CMP0121: The list command detects invalid indicies. </policy/CMP0121>
......
CMP0124
-------
.. versionadded:: 3.21
The loop variables created by :command:`foreach` command have now their scope
restricted to the loop scope.
Starting with CMake 3.21, the :command:`foreach` command ensures that the loop
variables have their scope restricted to the loop scope.
The ``OLD`` behavior for this policy let the loop variables to exist, with an
empty value, in the outer scope of loop scope.
This policy was introduced in CMake version 3.21. Use the
:command:`cmake_policy` command to set it to ``OLD`` or ``NEW`` explicitly.
Unlike many policies, CMake version |release| does *not* warn when the policy
is not set and simply uses ``OLD`` behavior.
.. include:: DEPRECATED.txt
foreach-variable-scope
----------------------
* The :command:`foreach` command restrict loop variables to the loop scope.
See policy :policy:`CMP0124` for details.
......@@ -17,6 +17,7 @@
#include <utility>
#include <cm/memory>
#include <cm/optional>
#include <cm/string_view>
#include <cmext/string_view>
......@@ -25,7 +26,7 @@
#include "cmListFileCache.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
#include "cmProperty.h"
#include "cmPolicies.h"
#include "cmRange.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
......@@ -113,9 +114,11 @@ bool cmForEachFunctionBlocker::ReplayItems(
// At end of for each execute recorded commands
// store the old value
std::string oldDef;
if (cmProp d = mf.GetDefinition(this->Args.front())) {
oldDef = *d;
cm::optional<std::string> oldDef;
if (mf.GetPolicyStatus(cmPolicies::CMP0124) != cmPolicies::NEW) {
oldDef = mf.GetSafeDefinition(this->Args.front());
} else if (mf.IsNormalDefinitionSet(this->Args.front())) {
oldDef = *mf.GetDefinition(this->Args.front());
}
auto restore = false;
......@@ -131,9 +134,14 @@ bool cmForEachFunctionBlocker::ReplayItems(
}
if (restore) {
// restore the variable to its prior value
mf.AddDefinition(this->Args.front(), oldDef);
if (oldDef) {
// restore the variable to its prior value
mf.AddDefinition(this->Args.front(), *oldDef);
} else {
mf.RemoveDefinition(this->Args.front());
}
}
return true;
}
......@@ -185,10 +193,15 @@ bool cmForEachFunctionBlocker::ReplayZipLists(
assert("Sanity check" && iterationVars.size() == values.size());
// Store old values for iteration variables
std::map<std::string, std::string> oldDefs;
std::map<std::string, cm::optional<std::string>> oldDefs;
for (auto i = 0u; i < values.size(); ++i) {
if (cmProp d = mf.GetDefinition(iterationVars[i])) {
oldDefs.emplace(iterationVars[i], *d);
const auto& varName = iterationVars[i];
if (mf.GetPolicyStatus(cmPolicies::CMP0124) != cmPolicies::NEW) {
oldDefs.emplace(varName, mf.GetSafeDefinition(varName));
} else if (mf.IsNormalDefinitionSet(varName)) {
oldDefs.emplace(varName, *mf.GetDefinition(varName));
} else {
oldDefs.emplace(varName, cm::nullopt);
}
}
......@@ -226,7 +239,11 @@ bool cmForEachFunctionBlocker::ReplayZipLists(
// Restore the variables to its prior value
if (restore) {
for (auto const& p : oldDefs) {
mf.AddDefinition(p.first, p.second);
if (p.second) {
mf.AddDefinition(p.first, *p.second);
} else {
mf.RemoveDefinition(p.first);
}
}
}
return true;
......
......@@ -2507,6 +2507,20 @@ bool cmMakefile::IsDefinitionSet(const std::string& name) const
return def != nullptr;
}
bool cmMakefile::IsNormalDefinitionSet(const std::string& name) const
{
cmProp def = this->StateSnapshot.GetDefinition(name);
#ifndef CMAKE_BOOTSTRAP
if (cmVariableWatch* vv = this->GetVariableWatch()) {
if (!def) {
vv->VariableAccessed(
name, cmVariableWatch::UNKNOWN_VARIABLE_DEFINED_ACCESS, nullptr, this);
}
}
#endif
return def != nullptr;
}
cmProp cmMakefile::GetDefinition(const std::string& name) const
{
cmProp def = this->StateSnapshot.GetDefinition(name);
......
......@@ -486,6 +486,7 @@ public:
const std::string& GetSafeDefinition(const std::string&) const;
const std::string& GetRequiredDefinition(const std::string& name) const;
bool IsDefinitionSet(const std::string&) const;
bool IsNormalDefinitionSet(const std::string&) const;
bool GetDefExpandList(const std::string& name, std::vector<std::string>& out,
bool emptyArgs = false) const;
/**
......
......@@ -369,7 +369,10 @@ class cmMakefile;
21, 0, cmPolicies::WARN) \
SELECT(POLICY, CMP0123, \
"ARMClang cpu/arch compile and link flags must be set explicitly.", \
3, 21, 0, cmPolicies::WARN)
3, 21, 0, cmPolicies::WARN) \
SELECT(POLICY, CMP0124, \
"foreach() loop variables are only available in the loop scope.", 3, \
21, 0, cmPolicies::WARN)
#define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
#define CM_FOR_EACH_POLICY_ID(POLICY) \
......
......@@ -20,3 +20,5 @@ run_cmake(foreach-RANGE-non-int-test-3-2)
run_cmake(foreach-RANGE-non-int-test-3-3)
run_cmake(foreach-RANGE-invalid-test)
run_cmake(foreach-RANGE-out-of-range-test)
run_cmake(foreach-var-scope-CMP0124-OLD)
run_cmake(foreach-var-scope-CMP0124-NEW)
cmake_policy(SET CMP0124 NEW)
foreach(VAR a b c)
endforeach()
if (DEFINED VAR)
message(SEND_ERROR "Variable 'VAR' unexpectedly defined.")
endif()
set(LIST1 a b c)
set(LIST2 x y z)
foreach(VAR1_1 VAR1_2 IN ZIP_LISTS LIST1 LIST2)
endforeach()
if (DEFINED VAR1_1 OR DEFINED VAR1_2)
message(SEND_ERROR "Variables 'VAR1_1' or 'VAR1_2' unexpectedly defined.")
endif()
set (VAR2 OLD)
foreach(VAR2 a b c)
endforeach()
if (NOT DEFINED VAR2 OR NOT VAR2 STREQUAL "OLD")
message(SEND_ERROR "Variable 'VAR2' not defined or wrong value.")
endif()
set (VAR2_2 OLD)
foreach(VAR2_1 VAR2_2 IN ZIP_LISTS LIST1 LIST2)
endforeach()
if (DEFINED VAR2_1 OR NOT DEFINED VAR2_2)
message(SEND_ERROR "Variable 'VAR2_1' unexpectedly defined or variable 'VAR2_2' not defined.")
endif()
set (VAR3 OLD CACHE STRING "")
foreach(VAR3 a b c)
endforeach()
# check that only cache variable is defined
set(OLD_VALUE "${VAR3}")
unset(VAR3 CACHE)
if (DEFINED VAR3 OR NOT OLD_VALUE STREQUAL "OLD")
message(SEND_ERROR "Variable 'VAR3' wrongly defined or wrong value.")
endif()
set (VAR3_2 OLD CACHE STRING "")
foreach(VAR3_1 VAR3_2 IN ZIP_LISTS LIST1 LIST2)
endforeach()
set(OLD_VALUE "${VAR3_2}")
unset(VAR3_2 CACHE)
if (DEFINED VAR3_1 OR DEFINED VAR3_2 OR NOT OLD_VALUE STREQUAL "OLD")
message(SEND_ERROR "Variable 'VAR3_1' unexpectedly defined or variable 'VAR2_2' wrongly defined or wrong value.")
endif()
cmake_policy(SET CMP0124 OLD)
foreach(VAR a b c)
endforeach()
if (NOT DEFINED VAR OR NOT VAR STREQUAL "")
message(SEND_ERROR "Variable 'VAR' not defined or wrong value.")
endif()
set(LIST1 a b c)
set(LIST2 x y z)
foreach(VAR1_1 VAR1_2 IN ZIP_LISTS LIST1 LIST2)
endforeach()
if (NOT DEFINED VAR1_1 OR NOT VAR1_1 STREQUAL ""
OR NOT DEFINED VAR1_2 OR NOT VAR1_2 STREQUAL "")
message(SEND_ERROR "Variables 'VAR1_1' or 'VAR1_2' not defined or wrong value.")
endif()
set (VAR2 OLD)
foreach(VAR2 a b c)
endforeach()
if (NOT DEFINED VAR2 OR NOT VAR2 STREQUAL "OLD")
message(SEND_ERROR "Variable 'VAR2' not defined or wrong value.")
endif()
set (VAR2_2 OLD)
foreach(VAR2_1 VAR2_2 IN ZIP_LISTS LIST1 LIST2)
endforeach()
if (NOT DEFINED VAR2_1 OR NOT VAR2_1 STREQUAL ""
OR NOT DEFINED VAR2_2 OR NOT VAR2_2 STREQUAL "OLD")
message(SEND_ERROR "Variables 'VAR2_1' or 'VAR2_2' not defined or wrong value.")
endif()
set (VAR3 OLD CACHE STRING "")
foreach(VAR3 a b c)
endforeach()
# a normal variable is defined, holding cache variable value
unset(VAR3 CACHE)
if (NOT DEFINED VAR3 OR NOT VAR3 STREQUAL "OLD")
message(SEND_ERROR "Variable 'VAR3' not defined or wrong value.")
endif()
set (VAR3_2 OLD CACHE STRING "")
foreach(VAR3_1 VAR3_2 IN ZIP_LISTS LIST1 LIST2)
endforeach()
# a normal variable is defined, holding cache variable value
unset(VAR3_2 CACHE)
if (NOT DEFINED VAR3_1 OR NOT VAR3_1 STREQUAL ""
OR NOT DEFINED VAR3_2 OR NOT VAR3_2 STREQUAL "OLD")
message(SEND_ERROR "Variables 'VAR3_1' or 'VAR3_2' not defined or wrong value.")
endif()
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment