mirror of
https://github.com/Kitware/CMake.git
synced 2026-08-04 14:50:23 +00:00
Merge topic 'list-predicate'
15340d6a96list(FILTER): Add PREDICATE modec7af6e94d8list(TRANSFORM): Add PREDICATE selector0f30e0fe1cHelp: Fix indentation of list(FILTER) docs Acked-by: Kitware Robot <kwrobot@kitware.com> Merge-request: !11945
This commit is contained in:
@@ -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
|
||||
^^^^^^^^
|
||||
|
||||
10
Help/release/dev/list-PREDICATE.rst
Normal file
10
Help/release/dev/list-PREDICATE.rst
Normal 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.
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
2
Tests/RunCMake/list/FILTER-PREDICATE-Exclude-stderr.txt
Normal file
2
Tests/RunCMake/list/FILTER-PREDICATE-Exclude-stderr.txt
Normal 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$
|
||||
50
Tests/RunCMake/list/FILTER-PREDICATE-Exclude.cmake
Normal file
50
Tests/RunCMake/list/FILTER-PREDICATE-Exclude.cmake
Normal 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()
|
||||
2
Tests/RunCMake/list/FILTER-PREDICATE-Include-stderr.txt
Normal file
2
Tests/RunCMake/list/FILTER-PREDICATE-Include-stderr.txt
Normal 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$
|
||||
51
Tests/RunCMake/list/FILTER-PREDICATE-Include.cmake
Normal file
51
Tests/RunCMake/list/FILTER-PREDICATE-Include.cmake
Normal 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()
|
||||
1
Tests/RunCMake/list/FILTER-PREDICATE-NoOutput-result.txt
Normal file
1
Tests/RunCMake/list/FILTER-PREDICATE-NoOutput-result.txt
Normal file
@@ -0,0 +1 @@
|
||||
1
|
||||
5
Tests/RunCMake/list/FILTER-PREDICATE-NoOutput-stderr.txt
Normal file
5
Tests/RunCMake/list/FILTER-PREDICATE-NoOutput-stderr.txt
Normal 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\)$
|
||||
6
Tests/RunCMake/list/FILTER-PREDICATE-NoOutput.cmake
Normal file
6
Tests/RunCMake/list/FILTER-PREDICATE-NoOutput.cmake
Normal 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)
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -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\)$
|
||||
@@ -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)
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -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\)$
|
||||
@@ -0,0 +1,2 @@
|
||||
set(mylist alpha bravo charlie)
|
||||
list(FILTER mylist INCLUDE PREDICATE no_such_function)
|
||||
@@ -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
|
||||
|
||||
@@ -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