Browse Source

Genex: Add policy to handle empty list items in $<IN_LIST:...>

The old behavior of $<IN_LIST:...> is inconsistent with that of
if(IN_LIST), in that it does not find an empty search item even if
the list contains empty items. This change adds a new policy to
correctly handle empty items and make the behavior more consistent
with if(IN_LIST).

Fixes: #18556
Kyle Edwards 7 years ago
parent
commit
b5f8113ca7

+ 1 - 0
Help/manual/cmake-policies.7.rst

@@ -57,6 +57,7 @@ Policies Introduced by CMake 3.14
 .. toctree::
    :maxdepth: 1
 
+   CMP0085: IN_LIST generator expression handles empty list items. </policy/CMP0085>
    CMP0084: The FindQt module does not exist for find_package(). </policy/CMP0084>
    CMP0083: Add PIE options when linking executable. </policy/CMP0083>
    CMP0082: Install rules from add_subdirectory() are interleaved with those in caller. </policy/CMP0082>

+ 21 - 0
Help/policy/CMP0085.rst

@@ -0,0 +1,21 @@
+CMP0085
+-------
+
+``$<IN_LIST:...>`` handles empty list items.
+
+In CMake 3.13 and lower, the ``$<IN_LIST:...>`` generator expression always
+returned ``0`` if the first argument was empty, even if the list contained an
+empty item. This behavior is inconsistent with the ``IN_LIST`` behavior of
+:command:`if`, which this generator expression is meant to emulate. CMake 3.14
+and later handles this case correctly.
+
+The ``OLD`` behavior of this policy is for ``$<IN_LIST:...>`` to always return
+``0`` if the first argument is empty. The ``NEW`` behavior is to return ``1``
+if the first argument is empty and the list contains an empty item.
+
+This policy was introduced in CMake version 3.14.  CMake version
+|release| warns when the policy is not set and uses ``OLD`` behavior.
+Use the :command:`cmake_policy` command to set it to ``OLD`` or ``NEW``
+explicitly.
+
+.. include:: DEPRECATED.txt

+ 5 - 0
Help/release/dev/genex-in_list-empty-args.rst

@@ -0,0 +1,5 @@
+genex-in_list-empty-args
+------------------------
+
+* The $<IN_LIST:...> generator expression now correctly handles an empty
+  argument. See :policy:`CMP0085` for details.

+ 30 - 5
Source/cmGeneratorExpressionNode.cxx

@@ -283,14 +283,39 @@ static const struct InListNode : public cmGeneratorExpressionNode
 
   std::string Evaluate(
     const std::vector<std::string>& parameters,
-    cmGeneratorExpressionContext* /*context*/,
+    cmGeneratorExpressionContext* context,
     const GeneratorExpressionContent* /*content*/,
     cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override
   {
-    std::vector<std::string> values;
-    cmSystemTools::ExpandListArgument(parameters[1], values);
-    if (values.empty()) {
-      return "0";
+    std::vector<std::string> values, checkValues;
+    bool check = false;
+    switch (context->LG->GetPolicyStatus(cmPolicies::CMP0085)) {
+      case cmPolicies::WARN:
+        if (parameters.front().empty()) {
+          check = true;
+          cmSystemTools::ExpandListArgument(parameters[1], checkValues, true);
+        }
+        CM_FALLTHROUGH;
+      case cmPolicies::OLD:
+        cmSystemTools::ExpandListArgument(parameters[1], values);
+        if (check && values != checkValues) {
+          std::ostringstream e;
+          e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0085)
+            << "\nSearch Item:\n  \"" << parameters.front()
+            << "\"\nList:\n  \"" << parameters[1] << "\"\n";
+          context->LG->GetCMakeInstance()->IssueMessage(
+            cmake::AUTHOR_WARNING, e.str(), context->Backtrace);
+          return "0";
+        }
+        if (values.empty()) {
+          return "0";
+        }
+        break;
+      case cmPolicies::REQUIRED_IF_USED:
+      case cmPolicies::REQUIRED_ALWAYS:
+      case cmPolicies::NEW:
+        cmSystemTools::ExpandListArgument(parameters[1], values, true);
+        break;
     }
 
     return std::find(values.cbegin(), values.cend(), parameters.front()) ==

+ 3 - 1
Source/cmPolicies.h

@@ -249,7 +249,9 @@ class cmMakefile;
          0, cmPolicies::WARN)                                                 \
   SELECT(POLICY, CMP0084,                                                     \
          "The FindQt module does not exist for find_package().", 3, 14, 0,    \
-         cmPolicies::WARN)
+         cmPolicies::WARN)                                                    \
+  SELECT(POLICY, CMP0085, "$<IN_LIST:...> handles empty list items.", 3, 14,  \
+         0, cmPolicies::WARN)
 
 #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
 #define CM_FOR_EACH_POLICY_ID(POLICY)                                         \

+ 6 - 0
Tests/RunCMake/GeneratorExpression/CMP0085-NEW-check.cmake

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

+ 4 - 0
Tests/RunCMake/GeneratorExpression/CMP0085-NEW.cmake

@@ -0,0 +1,4 @@
+cmake_policy(SET CMP0070 NEW)
+file(GENERATE OUTPUT CMP0085-NEW-generated.txt CONTENT
+  "$<IN_LIST:,>$<IN_LIST:,a>$<IN_LIST:,;a>$<IN_LIST:a,>$<IN_LIST:a,a>$<IN_LIST:a,;a>"
+  )

+ 6 - 0
Tests/RunCMake/GeneratorExpression/CMP0085-OLD-check.cmake

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

+ 4 - 0
Tests/RunCMake/GeneratorExpression/CMP0085-OLD.cmake

@@ -0,0 +1,4 @@
+cmake_policy(SET CMP0070 NEW)
+file(GENERATE OUTPUT CMP0085-OLD-generated.txt CONTENT
+  "$<IN_LIST:,>$<IN_LIST:,a>$<IN_LIST:,;a>$<IN_LIST:a,>$<IN_LIST:a,a>$<IN_LIST:a,;a>"
+  )

+ 6 - 0
Tests/RunCMake/GeneratorExpression/CMP0085-WARN-check.cmake

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

+ 33 - 0
Tests/RunCMake/GeneratorExpression/CMP0085-WARN-stderr.txt

@@ -0,0 +1,33 @@
+CMake Warning \(dev\) at CMP0085-WARN\.cmake:[0-9]+ \(file\):
+  Policy CMP0085 is not set: \$<IN_LIST:\.\.\.> handles empty list items\.  Run
+  "cmake --help-policy CMP0085" for policy details\.  Use the cmake_policy
+  command to set the policy and suppress this warning\.
+
+  Search Item:
+
+    ""
+
+  List:
+
+    ""
+
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
+This warning is for project developers\.  Use -Wno-dev to suppress it\.
+
+CMake Warning \(dev\) at CMP0085-WARN\.cmake:[0-9]+ \(file\):
+  Policy CMP0085 is not set: \$<IN_LIST:\.\.\.> handles empty list items\.  Run
+  "cmake --help-policy CMP0085" for policy details\.  Use the cmake_policy
+  command to set the policy and suppress this warning\.
+
+  Search Item:
+
+    ""
+
+  List:
+
+    ";a"
+
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
+This warning is for project developers\.  Use -Wno-dev to suppress it\.

+ 4 - 0
Tests/RunCMake/GeneratorExpression/CMP0085-WARN.cmake

@@ -0,0 +1,4 @@
+cmake_policy(SET CMP0070 NEW)
+file(GENERATE OUTPUT CMP0085-WARN-generated.txt CONTENT
+  "$<IN_LIST:,>$<IN_LIST:,a>$<IN_LIST:,;a>$<IN_LIST:a,>$<IN_LIST:a,a>$<IN_LIST:a,;a>"
+  )

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

@@ -61,3 +61,13 @@ if(LINKER_SUPPORTS_PDB)
 else()
   run_cmake(NonValidCompiler-TARGET_PDB_FILE)
 endif()
+
+set(RunCMake_TEST_OPTIONS -DCMAKE_POLICY_DEFAULT_CMP0085:STRING=OLD)
+run_cmake(CMP0085-OLD)
+unset(RunCMake_TEST_OPTIONS)
+
+run_cmake(CMP0085-WARN)
+
+set(RunCMake_TEST_OPTIONS -DCMAKE_POLICY_DEFAULT_CMP0085:STRING=NEW)
+run_cmake(CMP0085-NEW)
+unset(RunCMake_TEST_OPTIONS)