mirror of
https://gitlab.kitware.com/cmake/cmake.git
synced 2026-10-07 04:02:23 +03:00
list(SORT): Add COMPARATOR option for user-defined comparisons
Allow users to provide a custom comparison function for `list(SORT)`. The comparator is validated for strict weak ordering at runtime to produce a clear CMake error rather than a platform-dependent abort. Closes: #27761
This commit is contained in:
1 parent
bfeef792bb
commit
3dc32ac62b
31 files changed
+440
No files matched your search
@@ -399,6 +399,7 @@ Ordering
|
||||
|
||||
.. signature::
|
||||
list(SORT <list> [COMPARE <compare>] [CASE <case>] [ORDER <order>])
|
||||
list(SORT <list> COMPARATOR <function> [CASE <case>] [ORDER <order>])
|
||||
|
||||
Sorts the list in-place alphabetically.
|
||||
|
||||
@@ -447,3 +448,47 @@ Ordering
|
||||
|
||||
``DESCENDING``
|
||||
Sorts the list in descending order.
|
||||
|
||||
Instead of the built-in ``COMPARE`` methods, a user-defined comparison
|
||||
function may be used with the ``COMPARATOR`` keyword.
|
||||
|
||||
.. versionadded:: 4.4
|
||||
|
||||
``COMPARATOR`` is mutually exclusive with ``COMPARE``. The ``CASE``
|
||||
and ``ORDER`` options may still be used alongside ``COMPARATOR``:
|
||||
the ``CASE`` filter is applied to both values before they are passed to
|
||||
the callable, and ``ORDER DESCENDING`` reverses the comparison by
|
||||
swapping the two arguments.
|
||||
|
||||
The callable must accept exactly three parameters: two input values and
|
||||
the name of an output variable. It must set the output variable to a
|
||||
boolean value (``TRUE`` if the first value should come before the second,
|
||||
``FALSE`` otherwise) in the calling scope.
|
||||
If the callable does not set the output variable, it is an error.
|
||||
|
||||
The comparator must define a
|
||||
`strict weak ordering <https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings>`_.
|
||||
In particular:
|
||||
|
||||
* It must return ``FALSE`` when comparing an element to itself
|
||||
(irreflexivity).
|
||||
* If it returns ``TRUE`` for ``(a, b)``, it must return ``FALSE``
|
||||
for ``(b, a)`` (asymmetry).
|
||||
|
||||
An error is raised if the comparator violates these requirements.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
function(version_less a b result)
|
||||
if("${a}" VERSION_LESS "${b}")
|
||||
set(${result} TRUE PARENT_SCOPE)
|
||||
else()
|
||||
set(${result} FALSE PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
set(versions 3.1 1.2 2.0 1.10)
|
||||
list(SORT versions COMPARATOR version_less)
|
||||
# versions is now: 1.2;1.10;2.0;3.1
|
||||
@@ -0,0 +1,6 @@
|
||||
list-SORT-COMPARATOR
|
||||
--------------------
|
||||
|
||||
* The :command:`list(SORT)` command gained a new ``COMPARATOR`` keyword
|
||||
that invokes a user-defined :command:`function` to compare elements, complementing
|
||||
the built-in ``COMPARE`` methods.
|
||||
@@ -167,6 +167,60 @@ private:
|
||||
PredicateEvaluator& Evaluator;
|
||||
bool IncludeMatches;
|
||||
};
|
||||
|
||||
class ComparatorEvaluator
|
||||
{
|
||||
public:
|
||||
ComparatorEvaluator(std::string functionName, cmMakefile& makefile)
|
||||
: FunctionName(std::move(functionName))
|
||||
, Makefile(&makefile)
|
||||
, OutputVar(OutputVarFor("_cmake_comparator_out_", makefile))
|
||||
{
|
||||
if (!makefile.GetState()->GetCommand(this->FunctionName)) {
|
||||
throw cmList::transform_error(
|
||||
cmStrCat("sub-command SORT, COMPARATOR: unknown function \"",
|
||||
this->FunctionName, "\"."));
|
||||
}
|
||||
}
|
||||
|
||||
bool operator()(std::string const& a, std::string const& b)
|
||||
{
|
||||
this->Makefile->RemoveDefinition(this->OutputVar);
|
||||
|
||||
cmListFileContext context = this->Makefile->GetBacktrace().Top();
|
||||
std::vector<cmListFileArgument> funcArgs;
|
||||
funcArgs.emplace_back(a, cmListFileArgument::Quoted, context.Line);
|
||||
funcArgs.emplace_back(b, 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("sub-command SORT, COMPARATOR: function \"",
|
||||
this->FunctionName, "\" failed during execution."));
|
||||
}
|
||||
|
||||
cmValue result = this->Makefile->GetDefinition(this->OutputVar);
|
||||
if (!result) {
|
||||
throw cmList::transform_error(
|
||||
cmStrCat("sub-command SORT, COMPARATOR: 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 OutputVar;
|
||||
};
|
||||
}
|
||||
|
||||
cmList& cmList::filter(cm::string_view pattern, FilterMode mode)
|
||||
@@ -249,6 +303,13 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
StringSorter(cmList::SortConfiguration config, ComparisonFunction comparator)
|
||||
: Filters{ nullptr, this->GetCaseFilter(config.Case) }
|
||||
, SortMethod(std::move(comparator))
|
||||
, Descending(config.Order == OrderMode::DESCENDING)
|
||||
{
|
||||
}
|
||||
|
||||
std::string ApplyFilter(std::string const& argument)
|
||||
{
|
||||
std::string result = argument;
|
||||
@@ -308,6 +369,38 @@ cmList& cmList::sort(SortConfiguration cfg)
|
||||
return *this;
|
||||
}
|
||||
|
||||
cmList& cmList::sort(SortConfiguration cfg, cmMakefile& makefile)
|
||||
{
|
||||
SortConfiguration config{ cfg };
|
||||
|
||||
if (config.Order == SortConfiguration::OrderMode::DEFAULT) {
|
||||
config.Order = SortConfiguration::OrderMode::ASCENDING;
|
||||
}
|
||||
if (config.Case == SortConfiguration::CaseSensitivity::DEFAULT) {
|
||||
config.Case = SortConfiguration::CaseSensitivity::SENSITIVE;
|
||||
}
|
||||
|
||||
try {
|
||||
ComparatorEvaluator evaluator(config.ComparatorFunction, makefile);
|
||||
StringSorter sorter(
|
||||
config, [&evaluator](std::string const& a, std::string const& b) {
|
||||
bool result = evaluator(a, b);
|
||||
if (result && evaluator(b, a)) {
|
||||
throw cmList::transform_error(
|
||||
"sub-command SORT, COMPARATOR: function does not induce a strict "
|
||||
"weak ordering. The comparator returned TRUE for both (a, b) and "
|
||||
"(b, a).");
|
||||
}
|
||||
return result;
|
||||
});
|
||||
std::sort(this->Values.begin(), this->Values.end(), sorter);
|
||||
} catch (transform_error& e) {
|
||||
throw std::invalid_argument(e.what());
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
namespace {
|
||||
using transform_type = std::function<std::string(std::string const&)>;
|
||||
using transform_error = cmList::transform_error;
|
||||
|
||||
@@ -848,6 +848,7 @@ public:
|
||||
STRING,
|
||||
FILE_BASENAME,
|
||||
NATURAL,
|
||||
COMPARATOR,
|
||||
} Compare = CompareMethod::DEFAULT;
|
||||
|
||||
enum class CaseSensitivity
|
||||
@@ -857,6 +858,8 @@ public:
|
||||
INSENSITIVE,
|
||||
} Case = CaseSensitivity::DEFAULT;
|
||||
|
||||
std::string ComparatorFunction;
|
||||
|
||||
// declare the default constructor to work-around clang bug
|
||||
SortConfiguration();
|
||||
|
||||
@@ -869,6 +872,7 @@ public:
|
||||
}
|
||||
};
|
||||
cmList& sort(SortConfiguration config = SortConfiguration{});
|
||||
cmList& sort(SortConfiguration config, cmMakefile& makefile);
|
||||
|
||||
// exception raised on error during transform operations
|
||||
class transform_error : public std::runtime_error
|
||||
|
||||
@@ -726,6 +726,7 @@ bool HandleSortCommand(std::vector<std::string> const& args,
|
||||
|
||||
using SortConfig = cmList::SortConfiguration;
|
||||
SortConfig sortConfig;
|
||||
bool hasComparator = false;
|
||||
|
||||
size_t argumentIndex = 2;
|
||||
std::string const messageHint = "sub-command SORT ";
|
||||
@@ -733,6 +734,12 @@ bool HandleSortCommand(std::vector<std::string> const& args,
|
||||
while (argumentIndex < args.size()) {
|
||||
std::string const& option = args[argumentIndex++];
|
||||
if (option == "COMPARE") {
|
||||
if (hasComparator) {
|
||||
status.SetError(cmStrCat(messageHint,
|
||||
"option \"COMPARE\" is incompatible "
|
||||
"with \"COMPARATOR\"."));
|
||||
return false;
|
||||
}
|
||||
if (sortConfig.Compare != SortConfig::CompareMethod::DEFAULT) {
|
||||
std::string error = cmStrCat(messageHint, "option \"", option,
|
||||
"\" has been specified multiple times.");
|
||||
@@ -806,6 +813,27 @@ bool HandleSortCommand(std::vector<std::string> const& args,
|
||||
option, "\"."));
|
||||
return false;
|
||||
}
|
||||
} else if (option == "COMPARATOR") {
|
||||
if (hasComparator) {
|
||||
status.SetError(cmStrCat(messageHint, "option \"", option,
|
||||
"\" has been specified multiple times."));
|
||||
return false;
|
||||
}
|
||||
if (sortConfig.Compare != SortConfig::CompareMethod::DEFAULT) {
|
||||
status.SetError(cmStrCat(messageHint,
|
||||
"option \"COMPARATOR\" is incompatible "
|
||||
"with \"COMPARE\"."));
|
||||
return false;
|
||||
}
|
||||
if (argumentIndex < args.size()) {
|
||||
sortConfig.ComparatorFunction = args[argumentIndex++];
|
||||
sortConfig.Compare = SortConfig::CompareMethod::COMPARATOR;
|
||||
hasComparator = true;
|
||||
} else {
|
||||
status.SetError(cmStrCat(messageHint, "missing argument for option \"",
|
||||
option, "\"."));
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
status.SetError(
|
||||
cmStrCat(messageHint, "option \"", option, "\" is unknown."));
|
||||
@@ -821,6 +849,17 @@ bool HandleSortCommand(std::vector<std::string> const& args,
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hasComparator) {
|
||||
try {
|
||||
status.GetMakefile().AddDefinition(
|
||||
listName, list->sort(sortConfig, status.GetMakefile()).to_string());
|
||||
return true;
|
||||
} catch (std::invalid_argument& e) {
|
||||
status.SetError(e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
status.GetMakefile().AddDefinition(listName,
|
||||
list->sort(sortConfig).to_string());
|
||||
return true;
|
||||
|
||||
@@ -114,9 +114,18 @@ run_cmake(SORT-DuplicateOrderOption)
|
||||
run_cmake(SORT-DuplicateCompareOption)
|
||||
run_cmake(SORT-DuplicateCaseOption)
|
||||
run_cmake(SORT-NoCaseOption)
|
||||
run_cmake(SORT-COMPARATOR-UnknownFunction)
|
||||
run_cmake(SORT-COMPARATOR-NoOutput)
|
||||
run_cmake(SORT-COMPARATOR-CompareConflict)
|
||||
run_cmake(SORT-COMPARATOR-CompareConflictReverse)
|
||||
run_cmake(SORT-COMPARATOR-NoFunction)
|
||||
run_cmake(SORT-COMPARATOR-DuplicateOption)
|
||||
run_cmake(SORT-COMPARATOR-NotStrictWeak)
|
||||
run_cmake(SORT-COMPARATOR-NotStrictWeakLate)
|
||||
|
||||
# Successful tests
|
||||
run_cmake(SORT)
|
||||
run_cmake(SORT-COMPARATOR)
|
||||
|
||||
# argument tests
|
||||
run_cmake(PREPEND-NoArgs)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,4 @@
|
||||
^CMake Error at SORT-COMPARATOR-CompareConflict\.cmake:10 \(list\):
|
||||
list sub-command SORT option "COMPARATOR" is incompatible with "COMPARE"\.
|
||||
Call Stack \(most recent call first\):
|
||||
CMakeLists\.txt:3 \(include\)$
|
||||
@@ -0,0 +1,10 @@
|
||||
function(my_cmp a b out)
|
||||
if("${a}" STRLESS "${b}")
|
||||
set(${out} TRUE PARENT_SCOPE)
|
||||
else()
|
||||
set(${out} FALSE PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
set(mylist c a b)
|
||||
list(SORT mylist COMPARE STRING COMPARATOR my_cmp)
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,4 @@
|
||||
^CMake Error at SORT-COMPARATOR-CompareConflictReverse\.cmake:10 \(list\):
|
||||
list sub-command SORT option "COMPARE" is incompatible with "COMPARATOR"\.
|
||||
Call Stack \(most recent call first\):
|
||||
CMakeLists\.txt:3 \(include\)$
|
||||
@@ -0,0 +1,10 @@
|
||||
function(my_cmp a b out)
|
||||
if("${a}" STRLESS "${b}")
|
||||
set(${out} TRUE PARENT_SCOPE)
|
||||
else()
|
||||
set(${out} FALSE PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
set(mylist c a b)
|
||||
list(SORT mylist COMPARATOR my_cmp COMPARE STRING)
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
^CMake Error at SORT-COMPARATOR-DuplicateOption\.cmake:10 \(list\):
|
||||
list sub-command SORT option "COMPARATOR" has been specified multiple
|
||||
times\.
|
||||
Call Stack \(most recent call first\):
|
||||
CMakeLists\.txt:3 \(include\)$
|
||||
@@ -0,0 +1,10 @@
|
||||
function(my_cmp a b out)
|
||||
if("${a}" STRLESS "${b}")
|
||||
set(${out} TRUE PARENT_SCOPE)
|
||||
else()
|
||||
set(${out} FALSE PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
set(mylist c a b)
|
||||
list(SORT mylist COMPARATOR my_cmp COMPARATOR my_cmp)
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,4 @@
|
||||
^CMake Error at SORT-COMPARATOR-NoFunction\.cmake:2 \(list\):
|
||||
list sub-command SORT missing argument for option "COMPARATOR"\.
|
||||
Call Stack \(most recent call first\):
|
||||
CMakeLists\.txt:3 \(include\)$
|
||||
@@ -0,0 +1,2 @@
|
||||
set(mylist a b c)
|
||||
list(SORT mylist COMPARATOR)
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
^CMake Error at SORT-COMPARATOR-NoOutput\.cmake:6 \(list\):
|
||||
list sub-command SORT, COMPARATOR: function "bad_comparator" did not set
|
||||
the output variable\.
|
||||
Call Stack \(most recent call first\):
|
||||
CMakeLists\.txt:3 \(include\)$
|
||||
@@ -0,0 +1,6 @@
|
||||
function(bad_comparator a b out)
|
||||
# Deliberately does NOT set ${out}
|
||||
endfunction()
|
||||
|
||||
set(mylist c a b)
|
||||
list(SORT mylist COMPARATOR bad_comparator)
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
^CMake Error at SORT-COMPARATOR-NotStrictWeak\.cmake:6 \(list\):
|
||||
list sub-command SORT, COMPARATOR: function does not induce a strict weak
|
||||
ordering\. The comparator returned TRUE for both \(a, b\) and \(b, a\)\.
|
||||
Call Stack \(most recent call first\):
|
||||
CMakeLists\.txt:3 \(include\)$
|
||||
@@ -0,0 +1,6 @@
|
||||
function(reflexive_comparator a b out)
|
||||
set(${out} TRUE PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
set(mylist c a b)
|
||||
list(SORT mylist COMPARATOR reflexive_comparator)
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
^CMake Error at SORT-COMPARATOR-NotStrictWeakLate\.cmake:19 \(list\):
|
||||
list sub-command SORT, COMPARATOR: function does not induce a strict weak
|
||||
ordering\. The comparator returned TRUE for both \(a, b\) and \(b, a\)\.
|
||||
Call Stack \(most recent call first\):
|
||||
CMakeLists\.txt:3 \(include\)$
|
||||
@@ -0,0 +1,19 @@
|
||||
# Comparator that is correct for most pairs but violates strict weak ordering
|
||||
# when both elements start with "b".
|
||||
function(late_violation a b result)
|
||||
string(SUBSTRING "${a}" 0 1 a_first)
|
||||
string(SUBSTRING "${b}" 0 1 b_first)
|
||||
if(a_first STREQUAL "b" AND b_first STREQUAL "b")
|
||||
# Violates asymmetry: always returns TRUE for b-vs-b pairs
|
||||
set(${result} TRUE PARENT_SCOPE)
|
||||
else()
|
||||
if("${a}" STRLESS "${b}")
|
||||
set(${result} TRUE PARENT_SCOPE)
|
||||
else()
|
||||
set(${result} FALSE PARENT_SCOPE)
|
||||
endif()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
set(mylist "a" "c" "bb" "ba")
|
||||
list(SORT mylist COMPARATOR late_violation)
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,4 @@
|
||||
^CMake Error at SORT-COMPARATOR-UnknownFunction\.cmake:2 \(list\):
|
||||
list sub-command SORT, COMPARATOR: unknown function "no_such_function"\.
|
||||
Call Stack \(most recent call first\):
|
||||
CMakeLists\.txt:3 \(include\)$
|
||||
@@ -0,0 +1,2 @@
|
||||
set(mylist a b c)
|
||||
list(SORT mylist COMPARATOR no_such_function)
|
||||
@@ -0,0 +1,135 @@
|
||||
# Comparator: returns TRUE if a < b (string comparison)
|
||||
function(string_less a b result)
|
||||
if("${a}" STRLESS "${b}")
|
||||
set(${result} TRUE PARENT_SCOPE)
|
||||
else()
|
||||
set(${result} FALSE PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Comparator as macro
|
||||
macro(string_less_macro a b result)
|
||||
if(${a} STRLESS ${b})
|
||||
set(${result} TRUE)
|
||||
else()
|
||||
set(${result} FALSE)
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
# Comparator: string length comparison
|
||||
function(shorter_first a b result)
|
||||
string(LENGTH "${a}" len_a)
|
||||
string(LENGTH "${b}" len_b)
|
||||
if(len_a LESS len_b)
|
||||
set(${result} TRUE PARENT_SCOPE)
|
||||
else()
|
||||
set(${result} FALSE PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
## Basic COMPARATOR with function
|
||||
set(mylist c a b)
|
||||
list(SORT mylist COMPARATOR string_less)
|
||||
if(NOT mylist STREQUAL "a;b;c")
|
||||
message(FATAL_ERROR "SORT(COMPARATOR function) is \"${mylist}\", expected \"a;b;c\"")
|
||||
endif()
|
||||
|
||||
## COMPARATOR with macro
|
||||
set(mylist c a b)
|
||||
list(SORT mylist COMPARATOR string_less_macro)
|
||||
if(NOT mylist STREQUAL "a;b;c")
|
||||
message(FATAL_ERROR "SORT(COMPARATOR macro) is \"${mylist}\", expected \"a;b;c\"")
|
||||
endif()
|
||||
|
||||
## COMPARATOR with ORDER DESCENDING
|
||||
set(mylist c a b)
|
||||
list(SORT mylist COMPARATOR string_less ORDER DESCENDING)
|
||||
if(NOT mylist STREQUAL "c;b;a")
|
||||
message(FATAL_ERROR "SORT(COMPARATOR ORDER DESCENDING) is \"${mylist}\", expected \"c;b;a\"")
|
||||
endif()
|
||||
|
||||
## COMPARATOR with CASE INSENSITIVE
|
||||
# With CASE INSENSITIVE, values are lowercased before being passed to the comparator
|
||||
set(mylist "C" "a" "B")
|
||||
list(SORT mylist COMPARATOR string_less CASE INSENSITIVE)
|
||||
if(NOT mylist STREQUAL "a;B;C")
|
||||
message(FATAL_ERROR "SORT(COMPARATOR CASE INSENSITIVE) is \"${mylist}\", expected \"a;B;C\"")
|
||||
endif()
|
||||
|
||||
## COMPARATOR with CASE INSENSITIVE and ORDER DESCENDING
|
||||
set(mylist "C" "a" "B")
|
||||
list(SORT mylist COMPARATOR string_less CASE INSENSITIVE ORDER DESCENDING)
|
||||
if(NOT mylist STREQUAL "C;B;a")
|
||||
message(FATAL_ERROR "SORT(COMPARATOR CASE INSENSITIVE ORDER DESCENDING) is \"${mylist}\", expected \"C;B;a\"")
|
||||
endif()
|
||||
|
||||
## Custom comparator (sort by string length)
|
||||
set(mylist "bbb" "a" "cc")
|
||||
list(SORT mylist COMPARATOR shorter_first)
|
||||
if(NOT mylist STREQUAL "a;cc;bbb")
|
||||
message(FATAL_ERROR "SORT(COMPARATOR shorter_first) is \"${mylist}\", expected \"a;cc;bbb\"")
|
||||
endif()
|
||||
|
||||
## Custom comparator with ORDER DESCENDING (sort by length descending)
|
||||
set(mylist "bbb" "a" "cc")
|
||||
list(SORT mylist COMPARATOR shorter_first ORDER DESCENDING)
|
||||
if(NOT mylist STREQUAL "bbb;cc;a")
|
||||
message(FATAL_ERROR "SORT(COMPARATOR shorter_first ORDER DESCENDING) is \"${mylist}\", expected \"bbb;cc;a\"")
|
||||
endif()
|
||||
|
||||
## Empty list (no-op)
|
||||
set(empty_list "")
|
||||
list(SORT empty_list COMPARATOR string_less)
|
||||
if(NOT empty_list STREQUAL "")
|
||||
message(FATAL_ERROR "SORT(COMPARATOR empty) is \"${empty_list}\", expected \"\"")
|
||||
endif()
|
||||
|
||||
## Single element list (no-op)
|
||||
set(mylist "only")
|
||||
list(SORT mylist COMPARATOR string_less)
|
||||
if(NOT mylist STREQUAL "only")
|
||||
message(FATAL_ERROR "SORT(COMPARATOR single) is \"${mylist}\", expected \"only\"")
|
||||
endif()
|
||||
|
||||
## Already sorted list
|
||||
set(mylist a b c)
|
||||
list(SORT mylist COMPARATOR string_less)
|
||||
if(NOT mylist STREQUAL "a;b;c")
|
||||
message(FATAL_ERROR "SORT(COMPARATOR already-sorted) is \"${mylist}\", expected \"a;b;c\"")
|
||||
endif()
|
||||
|
||||
## Recursive COMPARATOR - verify inner and outer output variables are distinct
|
||||
# Inner comparator: checks that neither element equals its own output variable
|
||||
# name (which would mean the outer and inner names collided), then performs a
|
||||
# valid string comparison to satisfy strict weak ordering.
|
||||
function(check_outer_name a b out)
|
||||
if("${a}" STREQUAL "${out}")
|
||||
message(FATAL_ERROR "Recursive COMPARATOR: inner and outer output variable names are the same: ${out}")
|
||||
endif()
|
||||
if("${b}" STREQUAL "${out}")
|
||||
message(FATAL_ERROR "Recursive COMPARATOR: inner and outer output variable names are the same: ${out}")
|
||||
endif()
|
||||
if("${a}" STRLESS "${b}")
|
||||
set(${out} TRUE PARENT_SCOPE)
|
||||
else()
|
||||
set(${out} FALSE PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Outer comparator: passes its own output variable name into a nested sort
|
||||
# as a list element, so the inner comparator can compare the two names.
|
||||
function(nested_comparator a b out)
|
||||
set(_inner "${out}" dummy)
|
||||
list(SORT _inner COMPARATOR check_outer_name)
|
||||
if("${a}" STRLESS "${b}")
|
||||
set(${out} TRUE PARENT_SCOPE)
|
||||
else()
|
||||
set(${out} FALSE PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
set(mylist c a b)
|
||||
list(SORT mylist COMPARATOR nested_comparator)
|
||||
if(NOT mylist STREQUAL "a;b;c")
|
||||
message(FATAL_ERROR "SORT(COMPARATOR nested) is \"${mylist}\", expected \"a;b;c\"")
|
||||
endif()
|
||||
Reference in new issue
Block a user