GenEx: add new expressions for string comparisons

This commit is contained in:
Marc Chevrier
2025-10-13 15:13:24 +02:00
parent dab5e6ebb1
commit 7564cbae12
26 changed files with 382 additions and 18 deletions

View File

@@ -284,20 +284,8 @@ This section covers the primary and most widely used comparison types.
Other more specific comparison types are documented in their own separate Other more specific comparison types are documented in their own separate
sections further below. sections further below.
String Comparisons Numeric Comparisons
^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
.. genex:: $<STREQUAL:string1,string2>
``1`` if ``string1`` and ``string2`` are equal, else ``0``.
The comparison is case-sensitive. For a case-insensitive comparison,
combine with a :ref:`string transforming generator expression
<String Transforming Generator Expressions>`. For example, the following
evaluates to ``1`` if ``${foo}`` is any of ``BAR``, ``Bar``, ``bar``, etc.
.. code-block:: cmake
$<STREQUAL:$<UPPER_CASE:${foo}>,BAR>
.. genex:: $<EQUAL:value1,value2> .. genex:: $<EQUAL:value1,value2>
@@ -330,10 +318,57 @@ Version Comparisons
``1`` if ``v1`` is a version greater than or equal to ``v2``, else ``0``. ``1`` if ``v1`` is a version greater than or equal to ``v2``, else ``0``.
.. _`String Transforming Generator Expressions`: String Expressions
------------------
.. _`String Comparisons Generator Expressions`:
String Comparisons
^^^^^^^^^^^^^^^^^^
The comparisons are case-sensitive. For a case-insensitive comparison,
combine with a :ref:`string transforming generator expression
<String Transforming Generator Expressions>`. For example, the following
evaluates to ``1`` if ``${foo}`` is any of ``BAR``, ``Bar``, ``bar``, etc.
.. code-block:: cmake
$<STREQUAL:$<UPPER_CASE:${foo}>,BAR>
.. genex:: $<STREQUAL:string1,string2>
``1`` if ``string1`` and ``string2`` are lexicographically equal, else ``0``.
.. genex:: $<STRLESS:string1,string2>
.. versionadded:: 4.3
``1`` if ``string1`` is lexicographically less than ``string2``, else ``0``.
.. genex:: $<STRGREATER:string1,string2>
.. versionadded:: 4.3
``1`` if ``string1`` is lexicographically greater than ``string2``, else
``0``.
.. genex:: $<STRLESS_EQUAL:string1,string2>
.. versionadded:: 4.3
``1`` if ``string1`` is lexicographically less than or equal to ``string2``,
else ``0``.
.. genex:: $<STRGREATER_EQUAL:string1,string2>
.. versionadded:: 4.3
``1`` if ``string1`` is lexicographically greater than or equal to
``string2``, else ``0``.
String Transformations String Transformations
---------------------- ^^^^^^^^^^^^^^^^^^^^^^
.. genex:: $<LOWER_CASE:string> .. genex:: $<LOWER_CASE:string>
@@ -343,7 +378,7 @@ String Transformations
Content of ``string`` converted to upper case. Content of ``string`` converted to upper case.
.. genex:: $<MAKE_C_IDENTIFIER:...> .. genex:: $<MAKE_C_IDENTIFIER:string>
Content of ``...`` converted to a C identifier. The conversion follows the Content of ``...`` converted to a C identifier. The conversion follows the
same behavior as :command:`string(MAKE_C_IDENTIFIER)`. same behavior as :command:`string(MAKE_C_IDENTIFIER)`.

View File

@@ -0,0 +1,5 @@
GenEx-string-comparisons
------------------------
* CMake gains new :ref:`generator expressions
<String Comparisons Generator Expressions>` for string comparisons.

View File

@@ -26,6 +26,7 @@
#include "cmsys/String.h" #include "cmsys/String.h"
#include "cmCMakePath.h" #include "cmCMakePath.h"
#include "cmCMakeString.hxx"
#include "cmComputeLinkInformation.h" #include "cmComputeLinkInformation.h"
#include "cmGenExContext.h" #include "cmGenExContext.h"
#include "cmGenExEvaluation.h" #include "cmGenExEvaluation.h"
@@ -265,9 +266,84 @@ static const struct StrEqualNode : public cmGeneratorExpressionNode
GeneratorExpressionContent const* /*content*/, GeneratorExpressionContent const* /*content*/,
cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override
{ {
return parameters.front() == parameters[1] ? "1" : "0"; return cm::CMakeString{ parameters.front() }.Compare(
cm::CMakeString::CompOperator::EQUAL, parameters[1])
? "1"
: "0";
} }
} strEqualNode; } strEqualNode;
static const struct StrLessNode : public cmGeneratorExpressionNode
{
StrLessNode() {} // NOLINT(modernize-use-equals-default)
int NumExpectedParameters() const override { return 2; }
std::string Evaluate(
std::vector<std::string> const& parameters,
cm::GenEx::Evaluation* /*eval*/,
GeneratorExpressionContent const* /*content*/,
cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override
{
return cm::CMakeString{ parameters.front() }.Compare(
cm::CMakeString::CompOperator::LESS, parameters[1])
? "1"
: "0";
}
} strLessNode;
static const struct StrLessEqualNode : public cmGeneratorExpressionNode
{
StrLessEqualNode() {} // NOLINT(modernize-use-equals-default)
int NumExpectedParameters() const override { return 2; }
std::string Evaluate(
std::vector<std::string> const& parameters,
cm::GenEx::Evaluation* /*eval*/,
GeneratorExpressionContent const* /*content*/,
cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override
{
return cm::CMakeString{ parameters.front() }.Compare(
cm::CMakeString::CompOperator::LESS_EQUAL, parameters[1])
? "1"
: "0";
}
} strLessEqualNode;
static const struct StrGreaterNode : public cmGeneratorExpressionNode
{
StrGreaterNode() {} // NOLINT(modernize-use-equals-default)
int NumExpectedParameters() const override { return 2; }
std::string Evaluate(
std::vector<std::string> const& parameters,
cm::GenEx::Evaluation* /*eval*/,
GeneratorExpressionContent const* /*content*/,
cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override
{
return cm::CMakeString{ parameters.front() }.Compare(
cm::CMakeString::CompOperator::GREATER, parameters[1])
? "1"
: "0";
}
} strGreaterNode;
static const struct StrGreaterEqualNode : public cmGeneratorExpressionNode
{
StrGreaterEqualNode() {} // NOLINT(modernize-use-equals-default)
int NumExpectedParameters() const override { return 2; }
std::string Evaluate(
std::vector<std::string> const& parameters,
cm::GenEx::Evaluation* /*eval*/,
GeneratorExpressionContent const* /*content*/,
cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override
{
return cm::CMakeString{ parameters.front() }.Compare(
cm::CMakeString::CompOperator::GREATER_EQUAL, parameters[1])
? "1"
: "0";
}
} strGreaterEqualNode;
static const struct EqualNode : public cmGeneratorExpressionNode static const struct EqualNode : public cmGeneratorExpressionNode
{ {
@@ -4872,6 +4948,10 @@ cmGeneratorExpressionNode const* cmGeneratorExpressionNode::GetNode(
{ "TARGET_BUNDLE_DIR_NAME", &targetBundleDirNameNode }, { "TARGET_BUNDLE_DIR_NAME", &targetBundleDirNameNode },
{ "TARGET_BUNDLE_CONTENT_DIR", &targetBundleContentDirNode }, { "TARGET_BUNDLE_CONTENT_DIR", &targetBundleContentDirNode },
{ "STREQUAL", &strEqualNode }, { "STREQUAL", &strEqualNode },
{ "STRLESS", &strLessNode },
{ "STRLESS_EQUAL", &strLessEqualNode },
{ "STRGREATER", &strGreaterNode },
{ "STRGREATER_EQUAL", &strGreaterEqualNode },
{ "EQUAL", &equalNode }, { "EQUAL", &equalNode },
{ "IN_LIST", &inListNode }, { "IN_LIST", &inListNode },
{ "FILTER", &filterNode }, { "FILTER", &filterNode },

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,40 @@
CMake Error at BadStrGreater.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<STRGREATER>
\$<STRGREATER> expression requires 2 comma separated parameters, but got 0
instead.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
+CMake Error at BadStrGreater.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<STRGREATER:>
\$<STRGREATER> expression requires 2 comma separated parameters, but got 1
instead.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
+
CMake Error at BadStrGreater.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<STRGREATER:,,>
\$<STRGREATER> expression requires 2 comma separated parameters, but got 3
instead.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
+
CMake Error at BadStrGreater.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<STRGREATER:something,,>
\$<STRGREATER> expression requires 2 comma separated parameters, but got 3
instead.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
+
CMake Generate step failed\. Build files cannot be regenerated correctly\.$

View File

@@ -0,0 +1,6 @@
add_custom_target(check ALL COMMAND check
$<STRGREATER>
$<STRGREATER:>
$<STRGREATER:,,>
$<STRGREATER:something,,>
VERBATIM)

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,40 @@
CMake Error at BadStrGreaterEqual.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<STRGREATER_EQUAL>
\$<STRGREATER_EQUAL> expression requires 2 comma separated parameters, but
got 0 instead.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
+CMake Error at BadStrGreaterEqual.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<STRGREATER_EQUAL:>
\$<STRGREATER_EQUAL> expression requires 2 comma separated parameters, but
got 1 instead.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
+
CMake Error at BadStrGreaterEqual.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<STRGREATER_EQUAL:,,>
\$<STRGREATER_EQUAL> expression requires 2 comma separated parameters, but
got 3 instead.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
+
CMake Error at BadStrGreaterEqual.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<STRGREATER_EQUAL:something,,>
\$<STRGREATER_EQUAL> expression requires 2 comma separated parameters, but
got 3 instead.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
+
CMake Generate step failed\. Build files cannot be regenerated correctly\.$

View File

@@ -0,0 +1,6 @@
add_custom_target(check ALL COMMAND check
$<STRGREATER_EQUAL>
$<STRGREATER_EQUAL:>
$<STRGREATER_EQUAL:,,>
$<STRGREATER_EQUAL:something,,>
VERBATIM)

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,40 @@
CMake Error at BadStrLess.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<STRLESS>
\$<STRLESS> expression requires 2 comma separated parameters, but got 0
instead.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
+CMake Error at BadStrLess.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<STRLESS:>
\$<STRLESS> expression requires 2 comma separated parameters, but got 1
instead.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
+
CMake Error at BadStrLess.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<STRLESS:,,>
\$<STRLESS> expression requires 2 comma separated parameters, but got 3
instead.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
+
CMake Error at BadStrLess.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<STRLESS:something,,>
\$<STRLESS> expression requires 2 comma separated parameters, but got 3
instead.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
+
CMake Generate step failed\. Build files cannot be regenerated correctly\.$

View File

@@ -0,0 +1,6 @@
add_custom_target(check ALL COMMAND check
$<STRLESS>
$<STRLESS:>
$<STRLESS:,,>
$<STRLESS:something,,>
VERBATIM)

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,40 @@
CMake Error at BadStrLessEqual.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<STRLESS_EQUAL>
\$<STRLESS_EQUAL> expression requires 2 comma separated parameters, but got
0 instead.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
+CMake Error at BadStrLessEqual.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<STRLESS_EQUAL:>
\$<STRLESS_EQUAL> expression requires 2 comma separated parameters, but got
1 instead.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
+
CMake Error at BadStrLessEqual.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<STRLESS_EQUAL:,,>
\$<STRLESS_EQUAL> expression requires 2 comma separated parameters, but got
3 instead.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
+
CMake Error at BadStrLessEqual.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<STRLESS_EQUAL:something,,>
\$<STRLESS_EQUAL> expression requires 2 comma separated parameters, but got
3 instead.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
+
CMake Generate step failed\. Build files cannot be regenerated correctly\.$

View File

@@ -0,0 +1,6 @@
add_custom_target(check ALL COMMAND check
$<STRLESS_EQUAL>
$<STRLESS_EQUAL:>
$<STRLESS_EQUAL:,,>
$<STRLESS_EQUAL:something,,>
VERBATIM)

View File

@@ -7,6 +7,15 @@ run_cmake(BadOR)
run_cmake(BadAND) run_cmake(BadAND)
run_cmake(BadNOT) run_cmake(BadNOT)
run_cmake(BadStrEqual) run_cmake(BadStrEqual)
run_cmake(BadStrLess)
run_cmake(BadStrLessEqual)
run_cmake(BadStrGreater)
run_cmake(BadStrGreaterEqual)
run_cmake(STREQUAL)
run_cmake(STRLESS)
run_cmake(STRLESS_EQUAL)
run_cmake(STRGREATER)
run_cmake(STRGREATER_EQUAL)
run_cmake(BadZero) run_cmake(BadZero)
run_cmake(BadTargetName) run_cmake(BadTargetName)
run_cmake(BadTargetTypeInterface) run_cmake(BadTargetTypeInterface)

View File

@@ -0,0 +1,6 @@
file(READ "${RunCMake_TEST_BINARY_DIR}/STREQUAL-generated.txt" content)
set(expected "1:0")
if(NOT content STREQUAL expected)
set(RunCMake_TEST_FAILED "$<STREQUAL>: actual content:\n [[${content}]]\nbut expected:\n [[${expected}]]")
endif()

View File

@@ -0,0 +1,3 @@
cmake_policy(VERSION 4.2)
file(GENERATE OUTPUT "STREQUAL-generated.txt" CONTENT "$<STREQUAL:AA,AA>:$<STREQUAL:AA,BB>")

View File

@@ -0,0 +1,6 @@
file(READ "${RunCMake_TEST_BINARY_DIR}/STRGREATER-generated.txt" content)
set(expected "0:0:0:1")
if(NOT content STREQUAL expected)
set(RunCMake_TEST_FAILED "$<STRGREATER>: actual content:\n [[${content}]]\nbut expected:\n [[${expected}]]")
endif()

View File

@@ -0,0 +1,3 @@
cmake_policy(VERSION 4.2)
file(GENERATE OUTPUT "STRGREATER-generated.txt" CONTENT "$<STRGREATER:A,AA>:$<STRGREATER:AA,BB>:$<STRGREATER:AA,AA>:$<STRGREATER:BB,AA>")

View File

@@ -0,0 +1,6 @@
file(READ "${RunCMake_TEST_BINARY_DIR}/STRGREATER_EQUAL-generated.txt" content)
set(expected "0:0:1:1")
if(NOT content STREQUAL expected)
set(RunCMake_TEST_FAILED "$<STRGREATER_EQUAL>: actual content:\n [[${content}]]\nbut expected:\n [[${expected}]]")
endif()

View File

@@ -0,0 +1,4 @@
cmake_policy(VERSION 4.2)
file(GENERATE OUTPUT "STRGREATER_EQUAL-generated.txt"
CONTENT "$<STRGREATER_EQUAL:A,AA>:$<STRGREATER_EQUAL:AA,BB>:$<STRGREATER_EQUAL:AA,AA>:$<STRGREATER_EQUAL:BB,AA>")

View File

@@ -0,0 +1,6 @@
file(READ "${RunCMake_TEST_BINARY_DIR}/STRLESS-generated.txt" content)
set(expected "1:1:0:0")
if(NOT content STREQUAL expected)
set(RunCMake_TEST_FAILED "$<STRLESS>: actual content:\n [[${content}]]\nbut expected:\n [[${expected}]]")
endif()

View File

@@ -0,0 +1,3 @@
cmake_policy(VERSION 4.2)
file(GENERATE OUTPUT "STRLESS-generated.txt" CONTENT "$<STRLESS:A,AA>:$<STRLESS:AA,BB>:$<STRLESS:AA,AA>:$<STRLESS:BB,AA>")

View File

@@ -0,0 +1,6 @@
file(READ "${RunCMake_TEST_BINARY_DIR}/STRLESS_EQUAL-generated.txt" content)
set(expected "1:1:1:0")
if(NOT content STREQUAL expected)
set(RunCMake_TEST_FAILED "$<STRLESS_EQUAL>: actual content:\n [[${content}]]\nbut expected:\n [[${expected}]]")
endif()

View File

@@ -0,0 +1,4 @@
cmake_policy(VERSION 4.2)
file(GENERATE OUTPUT "STRLESS_EQUAL-generated.txt"
CONTENT "$<STRLESS_EQUAL:A,AA>:$<STRLESS_EQUAL:AA,BB>:$<STRLESS_EQUAL:AA,AA>:$<STRLESS_EQUAL:BB,AA>")