mirror of
https://github.com/Kitware/CMake.git
synced 2026-08-04 14:50:23 +00:00
list(TRANSFORM): Add PREDICATE selector
Add a new `PREDICATE` selector to `list(TRANSFORM)` that uses a user-defined function or macro to decide which elements to transform. The predicate callable receives each element value and an output variable name. It must set the output variable to a truthy or falsy value in PARENT_SCOPE; only elements for which the predicate returns true are passed to the transform action. Adds a shared PredicateEvaluator helper class used by both TRANSFORM PREDICATE and (in a subsequent commit) FILTER PREDICATE. Includes parser wiring, error and success tests covering all existing actions (TOUPPER, TOLOWER, REPLACE, STRIP, GENEX_STRIP, APPEND, PREPEND, APPLY) combined with the PREDICATE selector. Issue: #27761
This commit is contained in:
@@ -316,6 +316,39 @@ Modification
|
||||
|
||||
list(TRANSFORM <list> <ACTION> REGEX <regular_expression> ...)
|
||||
|
||||
``PREDICATE``
|
||||
Specify a user-defined callable as a predicate.
|
||||
Only elements for which the callable returns a true value will be
|
||||
transformed.
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
list(TRANSFORM <list> <ACTION> PREDICATE <function> ...)
|
||||
|
||||
.. versionadded:: 4.4
|
||||
|
||||
``<function>`` is a user-defined :command:`function` with exactly
|
||||
two formal parameters: the input value and the name of an output
|
||||
variable. The callable must set the output variable to a boolean
|
||||
value. Standard CMake boolean evaluation is used.
|
||||
If the callable does not set the output variable, it is an error.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
function(is_relative path result)
|
||||
if(NOT IS_ABSOLUTE "${path}")
|
||||
set(${result} TRUE PARENT_SCOPE)
|
||||
else()
|
||||
set(${result} FALSE PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
set(search_paths /usr/include src lib /opt/lib)
|
||||
list(TRANSFORM search_paths PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/"
|
||||
PREDICATE is_relative)
|
||||
|
||||
|
||||
Ordering
|
||||
^^^^^^^^
|
||||
|
||||
@@ -81,6 +81,61 @@ private:
|
||||
cmsys::RegularExpression& Regex;
|
||||
bool const IncludeMatches;
|
||||
};
|
||||
|
||||
class PredicateEvaluator
|
||||
{
|
||||
public:
|
||||
PredicateEvaluator(std::string const& functionName, cmMakefile& makefile,
|
||||
std::string errorPrefix = "sub-command TRANSFORM, "
|
||||
"selector PREDICATE")
|
||||
: FunctionName(functionName)
|
||||
, Makefile(&makefile)
|
||||
, ErrorPrefix(std::move(errorPrefix))
|
||||
{
|
||||
if (!makefile.GetState()->GetCommand(this->FunctionName)) {
|
||||
throw cmList::transform_error(cmStrCat(this->ErrorPrefix,
|
||||
": unknown function \"",
|
||||
this->FunctionName, "\"."));
|
||||
}
|
||||
}
|
||||
|
||||
bool operator()(std::string const& value)
|
||||
{
|
||||
std::string const outputVar = "_list_predicate_out_";
|
||||
this->Makefile->RemoveDefinition(outputVar);
|
||||
|
||||
cmListFileContext context = this->Makefile->GetBacktrace().Top();
|
||||
std::vector<cmListFileArgument> funcArgs;
|
||||
funcArgs.emplace_back(value, cmListFileArgument::Quoted, context.Line);
|
||||
funcArgs.emplace_back(outputVar, cmListFileArgument::Quoted, context.Line);
|
||||
cmListFileFunction func{ this->FunctionName, context.Line, context.Line,
|
||||
std::move(funcArgs) };
|
||||
|
||||
cmExecutionStatus status(*this->Makefile);
|
||||
if (!this->Makefile->ExecuteCommand(func, status) ||
|
||||
status.GetNestedError()) {
|
||||
throw cmList::transform_error(
|
||||
cmStrCat(this->ErrorPrefix, ": function \"", this->FunctionName,
|
||||
"\" failed during execution."));
|
||||
}
|
||||
|
||||
cmValue result = this->Makefile->GetDefinition(outputVar);
|
||||
if (!result) {
|
||||
throw cmList::transform_error(
|
||||
cmStrCat(this->ErrorPrefix, ": function \"", this->FunctionName,
|
||||
"\" did not set the output variable."));
|
||||
}
|
||||
|
||||
bool boolResult = cmIsOn(*result);
|
||||
this->Makefile->RemoveDefinition(outputVar);
|
||||
return boolResult;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string FunctionName;
|
||||
cmMakefile* Makefile = nullptr;
|
||||
std::string ErrorPrefix;
|
||||
};
|
||||
}
|
||||
|
||||
cmList& cmList::filter(cm::string_view pattern, FilterMode mode)
|
||||
@@ -270,6 +325,26 @@ public:
|
||||
|
||||
cmsys::RegularExpression Regex;
|
||||
};
|
||||
class TransformSelectorPredicate : public TransformSelector
|
||||
{
|
||||
public:
|
||||
TransformSelectorPredicate(std::string const& functionName,
|
||||
cmMakefile& makefile)
|
||||
: TransformSelector("PREDICATE")
|
||||
, Evaluator(functionName, makefile)
|
||||
{
|
||||
}
|
||||
|
||||
bool Validate(std::size_t) override { return true; }
|
||||
|
||||
bool InSelection(std::string const& value) override
|
||||
{
|
||||
return this->Evaluator(value);
|
||||
}
|
||||
|
||||
private:
|
||||
PredicateEvaluator Evaluator;
|
||||
};
|
||||
class TransformSelectorIndexes : public TransformSelector
|
||||
{
|
||||
public:
|
||||
@@ -827,6 +902,14 @@ std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewREGEX(
|
||||
return std::unique_ptr<cmList::TransformSelector>(selector.release());
|
||||
}
|
||||
|
||||
std::unique_ptr<cmList::TransformSelector>
|
||||
cmList::TransformSelector::NewPREDICATE(std::string const& functionName,
|
||||
cmMakefile& makefile)
|
||||
{
|
||||
return std::unique_ptr<cmList::TransformSelector>(
|
||||
new TransformSelectorPredicate(functionName, makefile));
|
||||
}
|
||||
|
||||
cmList& cmList::transform(TransformAction action,
|
||||
std::unique_ptr<TransformSelector> selector)
|
||||
{
|
||||
|
||||
@@ -886,6 +886,7 @@ public:
|
||||
struct AT;
|
||||
struct FOR;
|
||||
struct REGEX;
|
||||
struct PREDICATE;
|
||||
|
||||
virtual ~TransformSelector() = default;
|
||||
|
||||
@@ -911,6 +912,9 @@ public:
|
||||
template <typename Type>
|
||||
static std::unique_ptr<TransformSelector> New(std::string&&);
|
||||
|
||||
static std::unique_ptr<TransformSelector> NewPREDICATE(
|
||||
std::string const& functionName, cmMakefile& makefile);
|
||||
|
||||
cmMakefile* Makefile = nullptr;
|
||||
|
||||
private:
|
||||
|
||||
@@ -520,6 +520,7 @@ bool HandleTransformCommand(std::vector<std::string> const& args,
|
||||
std::string const REGEX{ "REGEX" };
|
||||
std::string const AT{ "AT" };
|
||||
std::string const FOR{ "FOR" };
|
||||
std::string const PREDICATE{ "PREDICATE" };
|
||||
std::string const OUTPUT_VARIABLE{ "OUTPUT_VARIABLE" };
|
||||
std::unique_ptr<cmList::TransformSelector> selector;
|
||||
std::string outputName = listName;
|
||||
@@ -527,7 +528,8 @@ bool HandleTransformCommand(std::vector<std::string> const& args,
|
||||
try {
|
||||
// handle optional arguments
|
||||
while (args.size() > index) {
|
||||
if ((args[index] == REGEX || args[index] == AT || args[index] == FOR) &&
|
||||
if ((args[index] == REGEX || args[index] == AT || args[index] == FOR ||
|
||||
args[index] == PREDICATE) &&
|
||||
selector) {
|
||||
status.SetError(
|
||||
cmStrCat("sub-command TRANSFORM, selector already specified (",
|
||||
@@ -653,6 +655,21 @@ bool HandleTransformCommand(std::vector<std::string> const& args,
|
||||
continue;
|
||||
}
|
||||
|
||||
// PREDICATE selector
|
||||
if (args[index] == PREDICATE) {
|
||||
if (args.size() == ++index) {
|
||||
status.SetError("sub-command TRANSFORM, selector PREDICATE expects "
|
||||
"'function name' argument.");
|
||||
return false;
|
||||
}
|
||||
|
||||
selector = cmList::TransformSelector::NewPREDICATE(
|
||||
args[index], status.GetMakefile());
|
||||
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// output variable
|
||||
if (args[index] == OUTPUT_VARIABLE) {
|
||||
if (args.size() == ++index) {
|
||||
|
||||
@@ -65,6 +65,7 @@ run_cmake(TRANSFORM-REPLACE-InvalidRegex)
|
||||
run_cmake(TRANSFORM-REPLACE-InvalidReplace1)
|
||||
run_cmake(TRANSFORM-REPLACE-InvalidReplace2)
|
||||
run_cmake(TRANSFORM-APPLY-NoFunction)
|
||||
run_cmake(TRANSFORM-APPLY-UnknownFunction)
|
||||
run_cmake(TRANSFORM-APPLY-NoOutput)
|
||||
# 'selector' oriented tests
|
||||
run_cmake(TRANSFORM-Selector-REGEX-NoArguments)
|
||||
@@ -81,6 +82,9 @@ run_cmake(TRANSFORM-Selector-FOR-InvalidIndex)
|
||||
run_cmake(TRANSFORM-Selector-FOR-ZeroStepArgument)
|
||||
run_cmake(TRANSFORM-Selector-FOR-NegativeStepArgument)
|
||||
run_cmake(TRANSFORM-Selector-FOR-BackwardsRange)
|
||||
run_cmake(TRANSFORM-Selector-PREDICATE-NoArguments)
|
||||
run_cmake(TRANSFORM-Selector-PREDICATE-UnknownFunction)
|
||||
run_cmake(TRANSFORM-Selector-PREDICATE-NoOutput)
|
||||
# 'output' oriented tests
|
||||
run_cmake(TRANSFORM-Output-OUTPUT_VARIABLE-NoArguments)
|
||||
run_cmake(TRANSFORM-Output-OUTPUT_VARIABLE-TooManyArguments)
|
||||
@@ -93,6 +97,7 @@ run_cmake(TRANSFORM-APPEND)
|
||||
run_cmake(TRANSFORM-PREPEND)
|
||||
run_cmake(TRANSFORM-REPLACE)
|
||||
run_cmake(TRANSFORM-APPLY)
|
||||
run_cmake(TRANSFORM-PREDICATE)
|
||||
run_cmake(CMP0186)
|
||||
|
||||
# argument tests
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
^CMake Error at TRANSFORM-APPLY-UnknownFunction\.cmake:2 \(list\):
|
||||
list sub-command TRANSFORM, action APPLY: unknown function
|
||||
"no_such_function"\.
|
||||
Call Stack \(most recent call first\):
|
||||
CMakeLists\.txt:3 \(include\)$
|
||||
@@ -0,0 +1,2 @@
|
||||
set(mylist alpha bravo charlie)
|
||||
list(TRANSFORM mylist APPLY no_such_function)
|
||||
@@ -104,3 +104,28 @@ list(TRANSFORM mylist APPLY make_empty)
|
||||
if(NOT mylist STREQUAL ";;")
|
||||
message(FATAL_ERROR "TRANSFORM(APPLY make_empty) is \"${mylist}\", expected is \";;\"")
|
||||
endif()
|
||||
|
||||
# Recursive APPLY: an APPLY function that itself calls list(TRANSFORM APPLY).
|
||||
# The inner function returns the output variable name it was given.
|
||||
function(return_out_var_name in out)
|
||||
set(${out} "${out}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# The outer function triggers a nested APPLY and verifies that the inner
|
||||
# output variable name differs from its own.
|
||||
function(inner_name_is_different in out)
|
||||
set(_inner x)
|
||||
list(TRANSFORM _inner APPLY return_out_var_name OUTPUT_VARIABLE _inner_out)
|
||||
# _inner_out now holds the inner output variable name
|
||||
if("${out}" STREQUAL "${_inner_out}")
|
||||
set(${out} "FALSE" PARENT_SCOPE)
|
||||
else()
|
||||
set(${out} "TRUE" PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
set(mylist a b c)
|
||||
list(TRANSFORM mylist APPLY inner_name_is_different OUTPUT_VARIABLE output)
|
||||
if(NOT output STREQUAL "TRUE;TRUE;TRUE")
|
||||
message(FATAL_ERROR "TRANSFORM(APPLY nested smoke) is \"${output}\", expected \"TRUE;TRUE;TRUE\"")
|
||||
endif()
|
||||
|
||||
100
Tests/RunCMake/list/TRANSFORM-PREDICATE.cmake
Normal file
100
Tests/RunCMake/list/TRANSFORM-PREDICATE.cmake
Normal file
@@ -0,0 +1,100 @@
|
||||
# Predicate: returns TRUE for items starting with "b"
|
||||
function(starts_with_b input result)
|
||||
if(input MATCHES "^b")
|
||||
set(${result} TRUE PARENT_SCOPE)
|
||||
else()
|
||||
set(${result} FALSE PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Predicate as macro
|
||||
macro(is_short input result)
|
||||
string(LENGTH "${input}" _len)
|
||||
if(_len LESS 4)
|
||||
set(${result} TRUE)
|
||||
else()
|
||||
set(${result} FALSE)
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
set(mylist alpha bravo charlie bravo_two delta)
|
||||
|
||||
# Basic PREDICATE with TOUPPER - only items starting with "b" are uppercased
|
||||
list(TRANSFORM mylist TOUPPER PREDICATE starts_with_b OUTPUT_VARIABLE output)
|
||||
if(NOT output STREQUAL "alpha;BRAVO;charlie;BRAVO_TWO;delta")
|
||||
message(FATAL_ERROR "TRANSFORM(TOUPPER PREDICATE) is \"${output}\", expected \"alpha;BRAVO;charlie;BRAVO_TWO;delta\"")
|
||||
endif()
|
||||
|
||||
# Verify original list unchanged (OUTPUT_VARIABLE)
|
||||
if(NOT mylist STREQUAL "alpha;bravo;charlie;bravo_two;delta")
|
||||
message(FATAL_ERROR "Original list modified: \"${mylist}\"")
|
||||
endif()
|
||||
|
||||
# PREDICATE in-place
|
||||
list(TRANSFORM mylist TOUPPER PREDICATE starts_with_b)
|
||||
if(NOT mylist STREQUAL "alpha;BRAVO;charlie;BRAVO_TWO;delta")
|
||||
message(FATAL_ERROR "TRANSFORM(TOUPPER PREDICATE in-place) is \"${mylist}\", expected \"alpha;BRAVO;charlie;BRAVO_TWO;delta\"")
|
||||
endif()
|
||||
|
||||
# PREDICATE with macro
|
||||
set(mylist ab cde fg hijklm no)
|
||||
list(TRANSFORM mylist TOUPPER PREDICATE is_short OUTPUT_VARIABLE output)
|
||||
if(NOT output STREQUAL "AB;CDE;FG;hijklm;NO")
|
||||
message(FATAL_ERROR "TRANSFORM(TOUPPER PREDICATE macro) is \"${output}\", expected \"AB;CDE;FG;hijklm;NO\"")
|
||||
endif()
|
||||
|
||||
# PREDICATE combined with APPLY
|
||||
function(add_prefix in out)
|
||||
set(${out} "prefix_${in}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
set(mylist alpha bravo charlie delta)
|
||||
list(TRANSFORM mylist APPLY add_prefix PREDICATE starts_with_b OUTPUT_VARIABLE output)
|
||||
if(NOT output STREQUAL "alpha;prefix_bravo;charlie;delta")
|
||||
message(FATAL_ERROR "TRANSFORM(APPLY PREDICATE) is \"${output}\", expected \"alpha;prefix_bravo;charlie;delta\"")
|
||||
endif()
|
||||
|
||||
# PREDICATE on empty list
|
||||
set(empty_list "")
|
||||
list(TRANSFORM empty_list TOUPPER PREDICATE starts_with_b OUTPUT_VARIABLE output)
|
||||
if(NOT output STREQUAL "")
|
||||
message(FATAL_ERROR "TRANSFORM(PREDICATE empty) is \"${output}\", expected \"\"")
|
||||
endif()
|
||||
|
||||
# PREDICATE where nothing matches (all elements unchanged)
|
||||
set(mylist alpha charlie delta)
|
||||
list(TRANSFORM mylist TOUPPER PREDICATE starts_with_b OUTPUT_VARIABLE output)
|
||||
if(NOT output STREQUAL "alpha;charlie;delta")
|
||||
message(FATAL_ERROR "TRANSFORM(PREDICATE no-match) is \"${output}\", expected \"alpha;charlie;delta\"")
|
||||
endif()
|
||||
|
||||
# PREDICATE where everything matches
|
||||
set(mylist bravo bronze bull)
|
||||
list(TRANSFORM mylist TOUPPER PREDICATE starts_with_b OUTPUT_VARIABLE output)
|
||||
if(NOT output STREQUAL "BRAVO;BRONZE;BULL")
|
||||
message(FATAL_ERROR "TRANSFORM(PREDICATE all-match) is \"${output}\", expected \"BRAVO;BRONZE;BULL\"")
|
||||
endif()
|
||||
|
||||
# Recursive PREDICATE: a PREDICATE function that itself calls list(TRANSFORM APPLY).
|
||||
# The inner function returns the output variable name it was given.
|
||||
function(return_pred_var_name in out)
|
||||
set(${out} "${out}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# The outer predicate triggers a nested APPLY and verifies that the inner
|
||||
# output variable name differs from its own result variable.
|
||||
function(inner_pred_name_is_different input result)
|
||||
set(_inner x)
|
||||
list(TRANSFORM _inner APPLY return_pred_var_name OUTPUT_VARIABLE _inner_names)
|
||||
if("${result}" STREQUAL "${_inner_names}")
|
||||
set(${result} FALSE PARENT_SCOPE)
|
||||
else()
|
||||
set(${result} TRUE PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
set(mylist x y z)
|
||||
list(TRANSFORM mylist TOUPPER PREDICATE inner_pred_name_is_different OUTPUT_VARIABLE output)
|
||||
if(NOT output STREQUAL "X;Y;Z")
|
||||
message(FATAL_ERROR "TRANSFORM(PREDICATE nested smoke) is \"${output}\", expected \"X;Y;Z\"")
|
||||
endif()
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
^CMake Error at TRANSFORM-Selector-PREDICATE-NoArguments\.cmake:2 \(list\):
|
||||
list sub-command TRANSFORM, selector PREDICATE expects 'function name'
|
||||
argument\.
|
||||
Call Stack \(most recent call first\):
|
||||
CMakeLists\.txt:3 \(include\)$
|
||||
@@ -0,0 +1,2 @@
|
||||
set(mylist alpha bravo charlie)
|
||||
list(TRANSFORM mylist TOUPPER PREDICATE)
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
^CMake Error at TRANSFORM-Selector-PREDICATE-NoOutput\.cmake:6 \(list\):
|
||||
list sub-command TRANSFORM, selector PREDICATE: function "bad_predicate"
|
||||
did not set the output variable\.
|
||||
Call Stack \(most recent call first\):
|
||||
CMakeLists\.txt:3 \(include\)$
|
||||
@@ -0,0 +1,6 @@
|
||||
function(bad_predicate in out)
|
||||
# Deliberately does NOT set ${out}
|
||||
endfunction()
|
||||
|
||||
set(mylist alpha bravo charlie)
|
||||
list(TRANSFORM mylist TOUPPER PREDICATE bad_predicate)
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
^CMake Error at TRANSFORM-Selector-PREDICATE-UnknownFunction\.cmake:2 \(list\):
|
||||
list sub-command TRANSFORM, selector PREDICATE: unknown function
|
||||
"no_such_function"\.
|
||||
Call Stack \(most recent call first\):
|
||||
CMakeLists\.txt:3 \(include\)$
|
||||
@@ -0,0 +1,2 @@
|
||||
set(mylist alpha bravo charlie)
|
||||
list(TRANSFORM mylist TOUPPER PREDICATE no_such_function)
|
||||
Reference in New Issue
Block a user