Merge topic 'list-predicate'

15340d6a96 list(FILTER): Add PREDICATE mode
c7af6e94d8 list(TRANSFORM): Add PREDICATE selector
0f30e0fe1c Help: Fix indentation of list(FILTER) docs

Acked-by: Kitware Robot <kwrobot@kitware.com>
Merge-request: !11945
This commit is contained in:
Brad King
2026-05-05 14:57:33 +00:00
committed by Kitware Robot
33 changed files with 611 additions and 40 deletions

View File

@@ -19,7 +19,7 @@ Synopsis
`Modification`_
list(`APPEND`_ <list> [<element>...])
list(`FILTER`_ <list> {INCLUDE | EXCLUDE} REGEX <regex>)
list(`FILTER`_ <list> <INCLUDE|EXCLUDE> <MODE>)
list(`INSERT`_ <list> <index> [<element>...])
list(`POP_BACK`_ <list> [<out-var>...])
list(`POP_FRONT`_ <list> [<out-var>...])
@@ -120,15 +120,54 @@ Modification
that empty list.
.. signature::
list(FILTER <list> <INCLUDE|EXCLUDE> REGEX <regular_expression>)
list(FILTER <list> <INCLUDE|EXCLUDE> <MODE>)
.. versionadded:: 3.6
.. versionadded:: 3.6
Includes or removes items from the list that match the mode's pattern.
In ``REGEX`` mode, items will be matched against the given regular expression.
Includes or removes items from the list that match the mode's pattern.
For more information on regular expressions look under
:ref:`string(REGEX) <Regex Specification>`.
``<MODE>`` must be one of the following:
``REGEX``
Items will be matched against the given regular expression.
.. code-block:: cmake
list(FILTER <list> <INCLUDE|EXCLUDE> REGEX <regular_expression>)
For more information on regular expressions look under
:ref:`string(REGEX) <Regex Specification>`.
``PREDICATE``
Specify a user-defined callable as a predicate.
.. code-block:: cmake
list(FILTER <list> <INCLUDE|EXCLUDE> PREDICATE <function>)
.. versionadded:: 4.4
``<function>`` is a user-defined :command:`function` that acts as a
unary predicate. The callable must accept exactly two parameters: the
input value and the name of an output variable. The callable must set the
output variable to a boolean value in the calling scope.
The output variable is interpreted using standard CMake boolean evaluation.
If the callable does not set the output variable, it is an error.
Example:
.. code-block:: cmake
function(file_exists path result)
if(EXISTS "${path}")
set(${result} TRUE PARENT_SCOPE)
else()
set(${result} FALSE PARENT_SCOPE)
endif()
endfunction()
set(candidate_files main.c missing.c utils.c)
list(FILTER candidate_files INCLUDE PREDICATE file_exists)
.. signature::
list(INSERT <list> <element_index> <element> [<element> ...])
@@ -316,6 +355,39 @@ For more information on regular expressions look under
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
^^^^^^^^

View File

@@ -0,0 +1,10 @@
list-PREDICATE
--------------
* The :command:`list(TRANSFORM)` command gained a new ``PREDICATE`` selector
that invokes a user-defined callable to decide which elements are
transformed.
* The :command:`list(FILTER)` command gained a new ``PREDICATE`` mode
that invokes a user-defined callable to decide which elements are
included or excluded, complementing the existing ``REGEX`` mode.

View File

@@ -81,6 +81,92 @@ private:
cmsys::RegularExpression& Regex;
bool const IncludeMatches;
};
// Hash of call site (FilePath:Line) for unique variable names across recursive
// calls.
std::string OutputVarFor(cm::string_view prefix, cmMakefile& makefile)
{
cmListFileContext context = makefile.GetBacktrace().Top();
std::size_t hash =
std::hash<std::string>{}(cmStrCat(context.FilePath, ":", context.Line));
return cmStrCat(prefix, hash, "_");
}
class PredicateEvaluator
{
public:
PredicateEvaluator(std::string functionName, cmMakefile& makefile,
std::string errorPrefix = "sub-command TRANSFORM, "
"selector PREDICATE")
: FunctionName(std::move(functionName))
, Makefile(&makefile)
, ErrorPrefix(std::move(errorPrefix))
, OutputVar(OutputVarFor("_cmake_predicate_out_", makefile))
{
if (!makefile.GetState()->GetCommand(this->FunctionName)) {
throw cmList::transform_error(cmStrCat(this->ErrorPrefix,
": unknown function \"",
this->FunctionName, "\"."));
}
}
bool operator()(std::string const& value)
{
this->Makefile->RemoveDefinition(this->OutputVar);
cmListFileContext context = this->Makefile->GetBacktrace().Top();
std::vector<cmListFileArgument> funcArgs;
funcArgs.emplace_back(value, cmListFileArgument::Quoted, context.Line);
funcArgs.emplace_back(this->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(this->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(this->OutputVar);
return boolResult;
}
private:
std::string FunctionName;
cmMakefile* Makefile = nullptr;
std::string ErrorPrefix;
std::string OutputVar;
};
class MatchesPredicate
{
public:
MatchesPredicate(PredicateEvaluator& evaluator, cmList::FilterMode mode)
: Evaluator(evaluator)
, IncludeMatches(mode == cmList::FilterMode::INCLUDE)
{
}
bool operator()(std::string const& target)
{
return this->Evaluator(target) ^ this->IncludeMatches;
}
private:
PredicateEvaluator& Evaluator;
bool IncludeMatches;
};
}
cmList& cmList::filter(cm::string_view pattern, FilterMode mode)
@@ -99,6 +185,23 @@ cmList& cmList::filter(cm::string_view pattern, FilterMode mode)
return *this;
}
cmList& cmList::filter(std::string const& functionName, FilterMode mode,
cmMakefile& makefile)
{
try {
PredicateEvaluator evaluator(functionName, makefile,
"sub-command FILTER, mode PREDICATE");
auto it = std::remove_if(this->Values.begin(), this->Values.end(),
MatchesPredicate{ evaluator, mode });
this->Values.erase(it, this->Values.end());
} catch (transform_error& e) {
throw std::invalid_argument(e.what());
}
return *this;
}
namespace {
class StringSorter
{
@@ -270,6 +373,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:
@@ -580,6 +703,7 @@ public:
TransformAction::Initialize(selector);
this->FunctionName = functionName;
this->Makefile = &makefile;
this->OutputVar = OutputVarFor("_cmake_transform_apply_out_", makefile);
// Validate: command must exist
if (!makefile.GetState()->GetCommand(this->FunctionName)) {
@@ -603,17 +727,15 @@ public:
return s;
}
// Use a unique output variable name to avoid collisions
std::string const outputVar = "_list_transform_apply_out_";
// Unset the output variable before calling
this->Makefile->RemoveDefinition(outputVar);
this->Makefile->RemoveDefinition(this->OutputVar);
// Build the function call: functionName(s, outputVar)
cmListFileContext context = this->Makefile->GetBacktrace().Top();
std::vector<cmListFileArgument> funcArgs;
funcArgs.emplace_back(s, cmListFileArgument::Quoted, context.Line);
funcArgs.emplace_back(outputVar, cmListFileArgument::Quoted, context.Line);
funcArgs.emplace_back(this->OutputVar, cmListFileArgument::Quoted,
context.Line);
cmListFileFunction func{ this->FunctionName, context.Line, context.Line,
std::move(funcArgs) };
@@ -626,7 +748,7 @@ public:
}
// Read back the output variable
cmValue result = this->Makefile->GetDefinition(outputVar);
cmValue result = this->Makefile->GetDefinition(this->OutputVar);
if (!result) {
throw transform_error(
cmStrCat("sub-command TRANSFORM, action APPLY: function \"",
@@ -638,7 +760,7 @@ public:
std::string output = *result;
// Clean up
this->Makefile->RemoveDefinition(outputVar);
this->Makefile->RemoveDefinition(this->OutputVar);
return output;
}
@@ -646,6 +768,7 @@ public:
private:
std::string FunctionName;
cmMakefile* Makefile = nullptr;
std::string OutputVar;
};
// Descriptor of action
@@ -827,6 +950,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)
{
@@ -897,16 +1028,19 @@ cmList& cmList::transform(TransformAction action, std::string const& arg,
cmMakefile& makefile,
std::unique_ptr<TransformSelector> selector)
{
auto descriptor = TransformConfigure(action, selector, 1);
// Validate action and arity via the static registry.
TransformConfigure(action, selector, 1);
auto* applyAction =
static_cast<TransformActionApply*>(descriptor->Transform.get());
applyAction->Initialize(static_cast<::TransformSelector*>(selector.get()),
arg, makefile);
// Create a local instance rather than reusing the singleton from
// Descriptors. A user function invoked by APPLY may itself call
// list(TRANSFORM ... APPLY ...), which would clobber a shared instance.
TransformActionApply applyAction;
applyAction.Initialize(static_cast<::TransformSelector*>(selector.get()),
arg, makefile);
static_cast<::TransformSelector&>(*selector).Transform(
this->Values, [&descriptor](std::string const& s) -> std::string {
return (*descriptor->Transform)(s);
this->Values, [&applyAction](std::string const& s) -> std::string {
return applyAction(s);
});
return *this;

View File

@@ -821,8 +821,11 @@ public:
EXCLUDE
};
// Includes or removes items from the list
// Throw std::invalid_argument if regular expression is invalid
// Throw std::invalid_argument if regular expression is invalid or predicate
// function is unknown / does not set its output variable
cmList& filter(cm::string_view regex, FilterMode mode);
cmList& filter(std::string const& functionName, FilterMode mode,
cmMakefile& makefile);
cmList& reverse()
{
@@ -886,6 +889,7 @@ public:
struct AT;
struct FOR;
struct REGEX;
struct PREDICATE;
virtual ~TransformSelector() = default;
@@ -911,6 +915,12 @@ public:
template <typename Type>
static std::unique_ptr<TransformSelector> New(std::string&&);
// NewPREDICATE is public (unlike NewAT/NewFOR/NewREGEX) because it takes
// a cmMakefile& parameter that cannot be dispatched through the existing
// New<Type>() templates.
static std::unique_ptr<TransformSelector> NewPREDICATE(
std::string const& functionName, cmMakefile& makefile);
cmMakefile* Makefile = nullptr;
private:

View File

@@ -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) {
@@ -951,25 +968,46 @@ bool HandleFilterCommand(std::vector<std::string> const& args,
}
std::string const& mode = args[3];
if (mode != "REGEX") {
status.SetError("sub-command FILTER does not recognize mode " + mode);
return false;
}
if (args.size() != 5) {
status.SetError("sub-command FILTER, mode REGEX "
"requires five arguments.");
return false;
}
std::string const& pattern = args[4];
if (mode == "REGEX") {
if (args.size() != 5) {
status.SetError("sub-command FILTER, mode REGEX "
"requires five arguments.");
return false;
}
std::string const& pattern = args[4];
try {
status.GetMakefile().AddDefinition(
listName, list->filter(pattern, filterMode).to_string());
return true;
} catch (std::invalid_argument& e) {
status.SetError(e.what());
return false;
try {
status.GetMakefile().AddDefinition(
listName, list->filter(pattern, filterMode).to_string());
return true;
} catch (std::invalid_argument& e) {
status.SetError(e.what());
return false;
}
}
if (mode == "PREDICATE") {
if (args.size() != 5) {
status.SetError("sub-command FILTER, mode PREDICATE "
"requires five arguments.");
return false;
}
std::string const& functionName = args[4];
try {
status.GetMakefile().AddDefinition(
listName,
list->filter(functionName, filterMode, status.GetMakefile())
.to_string());
return true;
} catch (std::invalid_argument& e) {
status.SetError(e.what());
return false;
}
}
status.SetError("sub-command FILTER does not recognize mode " + mode);
return false;
}
} // namespace

View File

@@ -0,0 +1,2 @@
^mylist was: FILTER_THIS_BIT;DO_NOT_FILTER_THIS;thisisanitem;FILTER_THIS_THING
mylist is: DO_NOT_FILTER_THIS;thisisanitem$

View File

@@ -0,0 +1,50 @@
# Predicate: returns TRUE for items starting with "FILTER_THIS_"
function(starts_with_filter input result)
if(input MATCHES "^FILTER_THIS_")
set(${result} TRUE PARENT_SCOPE)
else()
set(${result} FALSE PARENT_SCOPE)
endif()
endfunction()
set(mylist FILTER_THIS_BIT DO_NOT_FILTER_THIS thisisanitem FILTER_THIS_THING)
message("mylist was: ${mylist}")
list(FILTER mylist EXCLUDE PREDICATE starts_with_filter)
message("mylist is: ${mylist}")
# EXCLUDE with macro predicate
macro(is_short input result)
string(LENGTH "${input}" _len)
if(_len LESS 6)
set(${result} TRUE)
else()
set(${result} FALSE)
endif()
endmacro()
set(mylist ab cdefgh ij klmnop qr)
list(FILTER mylist EXCLUDE PREDICATE is_short)
if(NOT mylist STREQUAL "cdefgh;klmnop")
message(FATAL_ERROR "FILTER(EXCLUDE PREDICATE macro) is \"${mylist}\", expected \"cdefgh;klmnop\"")
endif()
# EXCLUDE on empty list
set(empty_list "")
list(FILTER empty_list EXCLUDE PREDICATE starts_with_filter)
if(NOT empty_list STREQUAL "")
message(FATAL_ERROR "FILTER(EXCLUDE PREDICATE empty) is \"${empty_list}\", expected \"\"")
endif()
# EXCLUDE where nothing matches (all elements kept)
set(mylist alpha bravo charlie)
list(FILTER mylist EXCLUDE PREDICATE starts_with_filter)
if(NOT mylist STREQUAL "alpha;bravo;charlie")
message(FATAL_ERROR "FILTER(EXCLUDE PREDICATE no-match) is \"${mylist}\", expected \"alpha;bravo;charlie\"")
endif()
# EXCLUDE where everything matches (all elements removed)
set(mylist FILTER_THIS_A FILTER_THIS_B FILTER_THIS_C)
list(FILTER mylist EXCLUDE PREDICATE starts_with_filter)
if(NOT mylist STREQUAL "")
message(FATAL_ERROR "FILTER(EXCLUDE PREDICATE all-match) is \"${mylist}\", expected \"\"")
endif()

View File

@@ -0,0 +1,2 @@
^mylist was: FILTER_THIS_BIT;DO_NOT_FILTER_THIS;thisisanitem;FILTER_THIS_THING
mylist is: FILTER_THIS_BIT;FILTER_THIS_THING$

View File

@@ -0,0 +1,51 @@
# Predicate: returns TRUE for items starting with "FILTER_THIS_"
function(starts_with_filter input result)
if(input MATCHES "^FILTER_THIS_")
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 6)
set(${result} TRUE)
else()
set(${result} FALSE)
endif()
endmacro()
set(mylist FILTER_THIS_BIT DO_NOT_FILTER_THIS thisisanitem FILTER_THIS_THING)
message("mylist was: ${mylist}")
list(FILTER mylist INCLUDE PREDICATE starts_with_filter)
message("mylist is: ${mylist}")
# INCLUDE with macro predicate
set(mylist ab cdefgh ij klmnop qr)
list(FILTER mylist INCLUDE PREDICATE is_short)
if(NOT mylist STREQUAL "ab;ij;qr")
message(FATAL_ERROR "FILTER(INCLUDE PREDICATE macro) is \"${mylist}\", expected \"ab;ij;qr\"")
endif()
# INCLUDE on empty list
set(empty_list "")
list(FILTER empty_list INCLUDE PREDICATE starts_with_filter)
if(NOT empty_list STREQUAL "")
message(FATAL_ERROR "FILTER(INCLUDE PREDICATE empty) is \"${empty_list}\", expected \"\"")
endif()
# INCLUDE where nothing matches (all elements removed)
set(mylist alpha bravo charlie)
list(FILTER mylist INCLUDE PREDICATE starts_with_filter)
if(NOT mylist STREQUAL "")
message(FATAL_ERROR "FILTER(INCLUDE PREDICATE no-match) is \"${mylist}\", expected \"\"")
endif()
# INCLUDE where everything matches
set(mylist FILTER_THIS_A FILTER_THIS_B FILTER_THIS_C)
list(FILTER mylist INCLUDE PREDICATE starts_with_filter)
if(NOT mylist STREQUAL "FILTER_THIS_A;FILTER_THIS_B;FILTER_THIS_C")
message(FATAL_ERROR "FILTER(INCLUDE PREDICATE all-match) is \"${mylist}\", expected \"FILTER_THIS_A;FILTER_THIS_B;FILTER_THIS_C\"")
endif()

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,5 @@
^CMake Error at FILTER-PREDICATE-NoOutput\.cmake:6 \(list\):
list sub-command FILTER, mode PREDICATE: function "bad_predicate" did not
set the output variable\.
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)$

View File

@@ -0,0 +1,6 @@
function(bad_predicate in out)
# Deliberately does NOT set ${out}
endfunction()
set(mylist alpha bravo charlie)
list(FILTER mylist INCLUDE PREDICATE bad_predicate)

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,4 @@
^CMake Error at FILTER-PREDICATE-TooManyArguments\.cmake:6 \(list\):
list sub-command FILTER, mode PREDICATE requires five arguments\.
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)$

View File

@@ -0,0 +1,6 @@
function(my_predicate in out)
set(${out} TRUE PARENT_SCOPE)
endfunction()
set(mylist alpha bravo charlie)
list(FILTER mylist INCLUDE PREDICATE my_predicate extra_arg)

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,5 @@
^CMake Error at FILTER-PREDICATE-UnknownFunction\.cmake:2 \(list\):
list sub-command FILTER, mode PREDICATE: unknown function
"no_such_function"\.
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)$

View File

@@ -0,0 +1,2 @@
set(mylist alpha bravo charlie)
list(FILTER mylist INCLUDE PREDICATE no_such_function)

View File

@@ -37,6 +37,11 @@ run_cmake(FILTER-REGEX-InvalidMode)
run_cmake(FILTER-REGEX-InvalidOperator)
run_cmake(FILTER-REGEX-Valid0)
run_cmake(FILTER-REGEX-Valid1)
run_cmake(FILTER-PREDICATE-UnknownFunction)
run_cmake(FILTER-PREDICATE-NoOutput)
run_cmake(FILTER-PREDICATE-TooManyArguments)
run_cmake(FILTER-PREDICATE-Include)
run_cmake(FILTER-PREDICATE-Exclude)
run_cmake(JOIN-NoArguments)
run_cmake(JOIN-NoVariable)
@@ -65,6 +70,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 +87,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 +102,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

View File

@@ -0,0 +1 @@
1

View File

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

View File

@@ -0,0 +1,2 @@
set(mylist alpha bravo charlie)
list(TRANSFORM mylist APPLY no_such_function)

View File

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

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

View File

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

View File

@@ -0,0 +1,2 @@
set(mylist alpha bravo charlie)
list(TRANSFORM mylist TOUPPER PREDICATE)

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,2 @@
set(mylist alpha bravo charlie)
list(TRANSFORM mylist TOUPPER PREDICATE no_such_function)