Browse Source

GenEx: add new expressions for string comparisons

Marc Chevrier 1 month ago
parent
commit
7564cbae12
26 changed files with 382 additions and 18 deletions
  1. 52 17
      Help/manual/cmake-generator-expressions.7.rst
  2. 5 0
      Help/release/dev/GenEx-string-comparisons.rst
  3. 81 1
      Source/cmGeneratorExpressionNode.cxx
  4. 1 0
      Tests/RunCMake/GeneratorExpression/BadStrGreater-result.txt
  5. 40 0
      Tests/RunCMake/GeneratorExpression/BadStrGreater-stderr.txt
  6. 6 0
      Tests/RunCMake/GeneratorExpression/BadStrGreater.cmake
  7. 1 0
      Tests/RunCMake/GeneratorExpression/BadStrGreaterEqual-result.txt
  8. 40 0
      Tests/RunCMake/GeneratorExpression/BadStrGreaterEqual-stderr.txt
  9. 6 0
      Tests/RunCMake/GeneratorExpression/BadStrGreaterEqual.cmake
  10. 1 0
      Tests/RunCMake/GeneratorExpression/BadStrLess-result.txt
  11. 40 0
      Tests/RunCMake/GeneratorExpression/BadStrLess-stderr.txt
  12. 6 0
      Tests/RunCMake/GeneratorExpression/BadStrLess.cmake
  13. 1 0
      Tests/RunCMake/GeneratorExpression/BadStrLessEqual-result.txt
  14. 40 0
      Tests/RunCMake/GeneratorExpression/BadStrLessEqual-stderr.txt
  15. 6 0
      Tests/RunCMake/GeneratorExpression/BadStrLessEqual.cmake
  16. 9 0
      Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake
  17. 6 0
      Tests/RunCMake/GeneratorExpression/STREQUAL-check.cmake
  18. 3 0
      Tests/RunCMake/GeneratorExpression/STREQUAL.cmake
  19. 6 0
      Tests/RunCMake/GeneratorExpression/STRGREATER-check.cmake
  20. 3 0
      Tests/RunCMake/GeneratorExpression/STRGREATER.cmake
  21. 6 0
      Tests/RunCMake/GeneratorExpression/STRGREATER_EQUAL-check.cmake
  22. 4 0
      Tests/RunCMake/GeneratorExpression/STRGREATER_EQUAL.cmake
  23. 6 0
      Tests/RunCMake/GeneratorExpression/STRLESS-check.cmake
  24. 3 0
      Tests/RunCMake/GeneratorExpression/STRLESS.cmake
  25. 6 0
      Tests/RunCMake/GeneratorExpression/STRLESS_EQUAL-check.cmake
  26. 4 0
      Tests/RunCMake/GeneratorExpression/STRLESS_EQUAL.cmake

+ 52 - 17
Help/manual/cmake-generator-expressions.7.rst

@@ -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
 sections further below.
 
-String 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>
+Numeric Comparisons
+^^^^^^^^^^^^^^^^^^^
 
 .. genex:: $<EQUAL:value1,value2>
 
@@ -330,10 +318,57 @@ Version Comparisons
 
   ``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
-----------------------
+^^^^^^^^^^^^^^^^^^^^^^
 
 .. genex:: $<LOWER_CASE:string>
 
@@ -343,7 +378,7 @@ String Transformations
 
   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
   same behavior as :command:`string(MAKE_C_IDENTIFIER)`.

+ 5 - 0
Help/release/dev/GenEx-string-comparisons.rst

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

+ 81 - 1
Source/cmGeneratorExpressionNode.cxx

@@ -26,6 +26,7 @@
 #include "cmsys/String.h"
 
 #include "cmCMakePath.h"
+#include "cmCMakeString.hxx"
 #include "cmComputeLinkInformation.h"
 #include "cmGenExContext.h"
 #include "cmGenExEvaluation.h"
@@ -265,9 +266,84 @@ static const struct StrEqualNode : public cmGeneratorExpressionNode
     GeneratorExpressionContent const* /*content*/,
     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;
+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
 {
@@ -4872,6 +4948,10 @@ cmGeneratorExpressionNode const* cmGeneratorExpressionNode::GetNode(
     { "TARGET_BUNDLE_DIR_NAME", &targetBundleDirNameNode },
     { "TARGET_BUNDLE_CONTENT_DIR", &targetBundleContentDirNode },
     { "STREQUAL", &strEqualNode },
+    { "STRLESS", &strLessNode },
+    { "STRLESS_EQUAL", &strLessEqualNode },
+    { "STRGREATER", &strGreaterNode },
+    { "STRGREATER_EQUAL", &strGreaterEqualNode },
     { "EQUAL", &equalNode },
     { "IN_LIST", &inListNode },
     { "FILTER", &filterNode },

+ 1 - 0
Tests/RunCMake/GeneratorExpression/BadStrGreater-result.txt

@@ -0,0 +1 @@
+1

+ 40 - 0
Tests/RunCMake/GeneratorExpression/BadStrGreater-stderr.txt

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

+ 6 - 0
Tests/RunCMake/GeneratorExpression/BadStrGreater.cmake

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

+ 1 - 0
Tests/RunCMake/GeneratorExpression/BadStrGreaterEqual-result.txt

@@ -0,0 +1 @@
+1

+ 40 - 0
Tests/RunCMake/GeneratorExpression/BadStrGreaterEqual-stderr.txt

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

+ 6 - 0
Tests/RunCMake/GeneratorExpression/BadStrGreaterEqual.cmake

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

+ 1 - 0
Tests/RunCMake/GeneratorExpression/BadStrLess-result.txt

@@ -0,0 +1 @@
+1

+ 40 - 0
Tests/RunCMake/GeneratorExpression/BadStrLess-stderr.txt

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

+ 6 - 0
Tests/RunCMake/GeneratorExpression/BadStrLess.cmake

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

+ 1 - 0
Tests/RunCMake/GeneratorExpression/BadStrLessEqual-result.txt

@@ -0,0 +1 @@
+1

+ 40 - 0
Tests/RunCMake/GeneratorExpression/BadStrLessEqual-stderr.txt

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

+ 6 - 0
Tests/RunCMake/GeneratorExpression/BadStrLessEqual.cmake

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

+ 9 - 0
Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake

@@ -7,6 +7,15 @@ run_cmake(BadOR)
 run_cmake(BadAND)
 run_cmake(BadNOT)
 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(BadTargetName)
 run_cmake(BadTargetTypeInterface)

+ 6 - 0
Tests/RunCMake/GeneratorExpression/STREQUAL-check.cmake

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

+ 3 - 0
Tests/RunCMake/GeneratorExpression/STREQUAL.cmake

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

+ 6 - 0
Tests/RunCMake/GeneratorExpression/STRGREATER-check.cmake

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

+ 3 - 0
Tests/RunCMake/GeneratorExpression/STRGREATER.cmake

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

+ 6 - 0
Tests/RunCMake/GeneratorExpression/STRGREATER_EQUAL-check.cmake

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

+ 4 - 0
Tests/RunCMake/GeneratorExpression/STRGREATER_EQUAL.cmake

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

+ 6 - 0
Tests/RunCMake/GeneratorExpression/STRLESS-check.cmake

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

+ 3 - 0
Tests/RunCMake/GeneratorExpression/STRLESS.cmake

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

+ 6 - 0
Tests/RunCMake/GeneratorExpression/STRLESS_EQUAL-check.cmake

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

+ 4 - 0
Tests/RunCMake/GeneratorExpression/STRLESS_EQUAL.cmake

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