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
diff --git a/Help/command/list.rst b/Help/command/list.rst index 88516f0..a84e2cb 100644 --- a/Help/command/list.rst +++ b/Help/command/list.rst
@@ -19,7 +19,7 @@ `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 @@ 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 @@ 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 ^^^^^^^^
diff --git a/Help/release/dev/list-PREDICATE.rst b/Help/release/dev/list-PREDICATE.rst new file mode 100644 index 0000000..324bd07 --- /dev/null +++ b/Help/release/dev/list-PREDICATE.rst
@@ -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.
diff --git a/Source/cmList.cxx b/Source/cmList.cxx index 1c76664..4dfeaa2 100644 --- a/Source/cmList.cxx +++ b/Source/cmList.cxx
@@ -81,6 +81,92 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ } // 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 @@ std::string output = *result; // Clean up - this->Makefile->RemoveDefinition(outputVar); + this->Makefile->RemoveDefinition(this->OutputVar); return output; } @@ -646,6 +768,7 @@ private: std::string FunctionName; cmMakefile* Makefile = nullptr; + std::string OutputVar; }; // Descriptor of action @@ -827,6 +950,14 @@ 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 @@ 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;
diff --git a/Source/cmList.h b/Source/cmList.h index febc2c5f..b34a6ca 100644 --- a/Source/cmList.h +++ b/Source/cmList.h
@@ -821,8 +821,11 @@ 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 @@ struct AT; struct FOR; struct REGEX; + struct PREDICATE; virtual ~TransformSelector() = default; @@ -911,6 +915,12 @@ 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:
diff --git a/Source/cmListCommand.cxx b/Source/cmListCommand.cxx index 06eba11..e5338d2 100644 --- a/Source/cmListCommand.cxx +++ b/Source/cmListCommand.cxx
@@ -520,6 +520,7 @@ 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 @@ 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 @@ 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 @@ } 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
diff --git a/Tests/RunCMake/list/FILTER-PREDICATE-Exclude-stderr.txt b/Tests/RunCMake/list/FILTER-PREDICATE-Exclude-stderr.txt new file mode 100644 index 0000000..d9ba38d --- /dev/null +++ b/Tests/RunCMake/list/FILTER-PREDICATE-Exclude-stderr.txt
@@ -0,0 +1,2 @@ +^mylist was: FILTER_THIS_BIT;DO_NOT_FILTER_THIS;thisisanitem;FILTER_THIS_THING +mylist is: DO_NOT_FILTER_THIS;thisisanitem$
diff --git a/Tests/RunCMake/list/FILTER-PREDICATE-Exclude.cmake b/Tests/RunCMake/list/FILTER-PREDICATE-Exclude.cmake new file mode 100644 index 0000000..7b1f349 --- /dev/null +++ b/Tests/RunCMake/list/FILTER-PREDICATE-Exclude.cmake
@@ -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()
diff --git a/Tests/RunCMake/list/FILTER-PREDICATE-Include-stderr.txt b/Tests/RunCMake/list/FILTER-PREDICATE-Include-stderr.txt new file mode 100644 index 0000000..5e5280d --- /dev/null +++ b/Tests/RunCMake/list/FILTER-PREDICATE-Include-stderr.txt
@@ -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$
diff --git a/Tests/RunCMake/list/FILTER-PREDICATE-Include.cmake b/Tests/RunCMake/list/FILTER-PREDICATE-Include.cmake new file mode 100644 index 0000000..412ff25 --- /dev/null +++ b/Tests/RunCMake/list/FILTER-PREDICATE-Include.cmake
@@ -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()
diff --git a/Tests/RunCMake/list/FILTER-PREDICATE-NoOutput-result.txt b/Tests/RunCMake/list/FILTER-PREDICATE-NoOutput-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/list/FILTER-PREDICATE-NoOutput-result.txt
@@ -0,0 +1 @@ +1
diff --git a/Tests/RunCMake/list/FILTER-PREDICATE-NoOutput-stderr.txt b/Tests/RunCMake/list/FILTER-PREDICATE-NoOutput-stderr.txt new file mode 100644 index 0000000..2fbc148 --- /dev/null +++ b/Tests/RunCMake/list/FILTER-PREDICATE-NoOutput-stderr.txt
@@ -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\)$
diff --git a/Tests/RunCMake/list/FILTER-PREDICATE-NoOutput.cmake b/Tests/RunCMake/list/FILTER-PREDICATE-NoOutput.cmake new file mode 100644 index 0000000..f1f4621 --- /dev/null +++ b/Tests/RunCMake/list/FILTER-PREDICATE-NoOutput.cmake
@@ -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)
diff --git a/Tests/RunCMake/list/FILTER-PREDICATE-TooManyArguments-result.txt b/Tests/RunCMake/list/FILTER-PREDICATE-TooManyArguments-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/list/FILTER-PREDICATE-TooManyArguments-result.txt
@@ -0,0 +1 @@ +1
diff --git a/Tests/RunCMake/list/FILTER-PREDICATE-TooManyArguments-stderr.txt b/Tests/RunCMake/list/FILTER-PREDICATE-TooManyArguments-stderr.txt new file mode 100644 index 0000000..e3c7a10 --- /dev/null +++ b/Tests/RunCMake/list/FILTER-PREDICATE-TooManyArguments-stderr.txt
@@ -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\)$
diff --git a/Tests/RunCMake/list/FILTER-PREDICATE-TooManyArguments.cmake b/Tests/RunCMake/list/FILTER-PREDICATE-TooManyArguments.cmake new file mode 100644 index 0000000..87a8532 --- /dev/null +++ b/Tests/RunCMake/list/FILTER-PREDICATE-TooManyArguments.cmake
@@ -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)
diff --git a/Tests/RunCMake/list/FILTER-PREDICATE-UnknownFunction-result.txt b/Tests/RunCMake/list/FILTER-PREDICATE-UnknownFunction-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/list/FILTER-PREDICATE-UnknownFunction-result.txt
@@ -0,0 +1 @@ +1
diff --git a/Tests/RunCMake/list/FILTER-PREDICATE-UnknownFunction-stderr.txt b/Tests/RunCMake/list/FILTER-PREDICATE-UnknownFunction-stderr.txt new file mode 100644 index 0000000..2b6234b --- /dev/null +++ b/Tests/RunCMake/list/FILTER-PREDICATE-UnknownFunction-stderr.txt
@@ -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\)$
diff --git a/Tests/RunCMake/list/FILTER-PREDICATE-UnknownFunction.cmake b/Tests/RunCMake/list/FILTER-PREDICATE-UnknownFunction.cmake new file mode 100644 index 0000000..48461bd --- /dev/null +++ b/Tests/RunCMake/list/FILTER-PREDICATE-UnknownFunction.cmake
@@ -0,0 +1,2 @@ +set(mylist alpha bravo charlie) +list(FILTER mylist INCLUDE PREDICATE no_such_function)
diff --git a/Tests/RunCMake/list/RunCMakeTest.cmake b/Tests/RunCMake/list/RunCMakeTest.cmake index dd4264c..1fd86a9 100644 --- a/Tests/RunCMake/list/RunCMakeTest.cmake +++ b/Tests/RunCMake/list/RunCMakeTest.cmake
@@ -37,6 +37,11 @@ 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-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-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-PREPEND) run_cmake(TRANSFORM-REPLACE) run_cmake(TRANSFORM-APPLY) +run_cmake(TRANSFORM-PREDICATE) run_cmake(CMP0186) # argument tests
diff --git a/Tests/RunCMake/list/TRANSFORM-APPLY-UnknownFunction-result.txt b/Tests/RunCMake/list/TRANSFORM-APPLY-UnknownFunction-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/list/TRANSFORM-APPLY-UnknownFunction-result.txt
@@ -0,0 +1 @@ +1
diff --git a/Tests/RunCMake/list/TRANSFORM-APPLY-UnknownFunction-stderr.txt b/Tests/RunCMake/list/TRANSFORM-APPLY-UnknownFunction-stderr.txt new file mode 100644 index 0000000..cb59954 --- /dev/null +++ b/Tests/RunCMake/list/TRANSFORM-APPLY-UnknownFunction-stderr.txt
@@ -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\)$
diff --git a/Tests/RunCMake/list/TRANSFORM-APPLY-UnknownFunction.cmake b/Tests/RunCMake/list/TRANSFORM-APPLY-UnknownFunction.cmake new file mode 100644 index 0000000..5275439 --- /dev/null +++ b/Tests/RunCMake/list/TRANSFORM-APPLY-UnknownFunction.cmake
@@ -0,0 +1,2 @@ +set(mylist alpha bravo charlie) +list(TRANSFORM mylist APPLY no_such_function)
diff --git a/Tests/RunCMake/list/TRANSFORM-APPLY.cmake b/Tests/RunCMake/list/TRANSFORM-APPLY.cmake index 14fe360..68e1528 100644 --- a/Tests/RunCMake/list/TRANSFORM-APPLY.cmake +++ b/Tests/RunCMake/list/TRANSFORM-APPLY.cmake
@@ -104,3 +104,28 @@ 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()
diff --git a/Tests/RunCMake/list/TRANSFORM-PREDICATE.cmake b/Tests/RunCMake/list/TRANSFORM-PREDICATE.cmake new file mode 100644 index 0000000..103f021 --- /dev/null +++ b/Tests/RunCMake/list/TRANSFORM-PREDICATE.cmake
@@ -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()
diff --git a/Tests/RunCMake/list/TRANSFORM-Selector-PREDICATE-NoArguments-result.txt b/Tests/RunCMake/list/TRANSFORM-Selector-PREDICATE-NoArguments-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/list/TRANSFORM-Selector-PREDICATE-NoArguments-result.txt
@@ -0,0 +1 @@ +1
diff --git a/Tests/RunCMake/list/TRANSFORM-Selector-PREDICATE-NoArguments-stderr.txt b/Tests/RunCMake/list/TRANSFORM-Selector-PREDICATE-NoArguments-stderr.txt new file mode 100644 index 0000000..22c4133 --- /dev/null +++ b/Tests/RunCMake/list/TRANSFORM-Selector-PREDICATE-NoArguments-stderr.txt
@@ -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\)$
diff --git a/Tests/RunCMake/list/TRANSFORM-Selector-PREDICATE-NoArguments.cmake b/Tests/RunCMake/list/TRANSFORM-Selector-PREDICATE-NoArguments.cmake new file mode 100644 index 0000000..f263d8b --- /dev/null +++ b/Tests/RunCMake/list/TRANSFORM-Selector-PREDICATE-NoArguments.cmake
@@ -0,0 +1,2 @@ +set(mylist alpha bravo charlie) +list(TRANSFORM mylist TOUPPER PREDICATE)
diff --git a/Tests/RunCMake/list/TRANSFORM-Selector-PREDICATE-NoOutput-result.txt b/Tests/RunCMake/list/TRANSFORM-Selector-PREDICATE-NoOutput-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/list/TRANSFORM-Selector-PREDICATE-NoOutput-result.txt
@@ -0,0 +1 @@ +1
diff --git a/Tests/RunCMake/list/TRANSFORM-Selector-PREDICATE-NoOutput-stderr.txt b/Tests/RunCMake/list/TRANSFORM-Selector-PREDICATE-NoOutput-stderr.txt new file mode 100644 index 0000000..87310b2 --- /dev/null +++ b/Tests/RunCMake/list/TRANSFORM-Selector-PREDICATE-NoOutput-stderr.txt
@@ -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\)$
diff --git a/Tests/RunCMake/list/TRANSFORM-Selector-PREDICATE-NoOutput.cmake b/Tests/RunCMake/list/TRANSFORM-Selector-PREDICATE-NoOutput.cmake new file mode 100644 index 0000000..73b18c2 --- /dev/null +++ b/Tests/RunCMake/list/TRANSFORM-Selector-PREDICATE-NoOutput.cmake
@@ -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)
diff --git a/Tests/RunCMake/list/TRANSFORM-Selector-PREDICATE-UnknownFunction-result.txt b/Tests/RunCMake/list/TRANSFORM-Selector-PREDICATE-UnknownFunction-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/list/TRANSFORM-Selector-PREDICATE-UnknownFunction-result.txt
@@ -0,0 +1 @@ +1
diff --git a/Tests/RunCMake/list/TRANSFORM-Selector-PREDICATE-UnknownFunction-stderr.txt b/Tests/RunCMake/list/TRANSFORM-Selector-PREDICATE-UnknownFunction-stderr.txt new file mode 100644 index 0000000..21bea51 --- /dev/null +++ b/Tests/RunCMake/list/TRANSFORM-Selector-PREDICATE-UnknownFunction-stderr.txt
@@ -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\)$
diff --git a/Tests/RunCMake/list/TRANSFORM-Selector-PREDICATE-UnknownFunction.cmake b/Tests/RunCMake/list/TRANSFORM-Selector-PREDICATE-UnknownFunction.cmake new file mode 100644 index 0000000..d497288 --- /dev/null +++ b/Tests/RunCMake/list/TRANSFORM-Selector-PREDICATE-UnknownFunction.cmake
@@ -0,0 +1,2 @@ +set(mylist alpha bravo charlie) +list(TRANSFORM mylist TOUPPER PREDICATE no_such_function)