Browse Source

Merge topic 'genexp-no-eval'

634079b86d cmGeneratorExpressionEvaluator: Short-circuit boolean operators

Acked-by: Kitware Robot <[email protected]>
Acked-by: scivision <[email protected]>
Acked-by: Pavel Solodovnikov <[email protected]>
Merge-request: !8791
Brad King 2 years ago
parent
commit
b8151299de

+ 11 - 0
Help/manual/cmake-generator-expressions.7.rst

@@ -195,6 +195,12 @@ Two forms of conditional generator expressions are supported:
   if ``condition`` is ``0``.  Any other value for ``condition`` results in an
   error.
 
+    .. versionadded:: 3.28
+
+    This generator expression short-circuits such that generator expressions in
+    ``false_string`` will not evaluate when ``condition`` is ``1``, and generator
+    expressions in ``true_string`` will not evaluate when condition is ``0``.
+
 Typically, the ``condition`` is itself a generator expression.  For instance,
 the following expression expands to ``DEBUG_MODE`` when the ``Debug``
 configuration is used, and the empty string for all other configurations:
@@ -252,6 +258,11 @@ The common boolean logic operators are supported:
   ``condition`` must be ``0`` or ``1``.  The result of the expression is
   ``0`` if ``condition`` is ``1``, else ``1``.
 
+  .. versionadded:: 3.28
+
+  Logical operators short-circuit such that generator expressions in the
+  arguments list will not be evaluated once a return value can be determined.
+
 .. _`Comparison Expressions`:
 
 Primary Comparison Expressions

+ 5 - 0
Help/release/dev/genexp-no-eval.rst

@@ -0,0 +1,5 @@
+genexp-no-eval
+--------------
+
+* :manual:`generator expressions <cmake-generator-expressions(7)>`
+  short-circuit to avoid unnecessary evaluation of parameters.

+ 6 - 4
Source/cmGeneratorExpressionEvaluator.cxx

@@ -153,10 +153,12 @@ std::string GeneratorExpressionContent::EvaluateParameters(
         return std::string();
       }
       std::string parameter;
-      for (const auto& pExprEval : *pit) {
-        parameter += pExprEval->Evaluate(context, dagChecker);
-        if (context->HadError) {
-          return std::string();
+      if (node->ShouldEvaluateNextParameter(parameters, parameter)) {
+        for (const auto& pExprEval : *pit) {
+          parameter += pExprEval->Evaluate(context, dagChecker);
+          if (context->HadError) {
+            return std::string();
+          }
         }
       }
       parameters.push_back(std::move(parameter));

+ 17 - 0
Source/cmGeneratorExpressionNode.cxx

@@ -128,6 +128,16 @@ struct BooleanOpNode : public cmGeneratorExpressionNode
 
   int NumExpectedParameters() const override { return OneOrMoreParameters; }
 
+  bool ShouldEvaluateNextParameter(const std::vector<std::string>& parameters,
+                                   std::string& def_value) const override
+  {
+    if (!parameters.empty() && parameters[0] == failureVal) {
+      def_value = failureVal;
+      return false;
+    }
+    return true;
+  }
+
   std::string Evaluate(const std::vector<std::string>& parameters,
                        cmGeneratorExpressionContext* context,
                        const GeneratorExpressionContent* content,
@@ -195,6 +205,13 @@ static const struct IfNode : public cmGeneratorExpressionNode
 
   int NumExpectedParameters() const override { return 3; }
 
+  bool ShouldEvaluateNextParameter(const std::vector<std::string>& parameters,
+                                   std::string&) const override
+  {
+    return (parameters.empty() ||
+            parameters[0] != cmStrCat(parameters.size() - 1, ""));
+  }
+
   std::string Evaluate(const std::vector<std::string>& parameters,
                        cmGeneratorExpressionContext* context,
                        const GeneratorExpressionContent* content,

+ 6 - 0
Source/cmGeneratorExpressionNode.h

@@ -33,6 +33,12 @@ struct cmGeneratorExpressionNode
 
   virtual int NumExpectedParameters() const { return 1; }
 
+  virtual bool ShouldEvaluateNextParameter(const std::vector<std::string>&,
+                                           std::string&) const
+  {
+    return true;
+  }
+
   virtual std::string Evaluate(
     const std::vector<std::string>& parameters,
     cmGeneratorExpressionContext* context,

+ 1 - 0
Tests/RunCMake/CMakeLists.txt

@@ -399,6 +399,7 @@ add_RunCMake_test(GenEx-PATH)
 add_RunCMake_test(GenEx-PATH_EQUAL)
 add_RunCMake_test(GenEx-LIST)
 add_RunCMake_test(GeneratorExpression)
+add_RunCMake_test(GeneratorExpressionShortCircuit)
 add_RunCMake_test(GeneratorInstance)
 add_RunCMake_test(GeneratorPlatform)
 if(XCODE_VERSION)

+ 1 - 0
Tests/RunCMake/GeneratorExpressionShortCircuit/BadAND-result.txt

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

+ 8 - 0
Tests/RunCMake/GeneratorExpressionShortCircuit/BadAND-stderr.txt

@@ -0,0 +1,8 @@
+CMake Error at BadAND.cmake:2 \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<0>
+
+  \$<0> expression requires a parameter.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)

+ 4 - 0
Tests/RunCMake/GeneratorExpressionShortCircuit/BadAND.cmake

@@ -0,0 +1,4 @@
+set(error $<0>)
+add_custom_target(check ALL COMMAND check
+  $<AND:1,${error}>
+)

+ 1 - 0
Tests/RunCMake/GeneratorExpressionShortCircuit/BadIF-result.txt

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

+ 8 - 0
Tests/RunCMake/GeneratorExpressionShortCircuit/BadIF-stderr.txt

@@ -0,0 +1,8 @@
+CMake Error at BadIF.cmake:2 \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<0>
+
+  \$<0> expression requires a parameter.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)

+ 4 - 0
Tests/RunCMake/GeneratorExpressionShortCircuit/BadIF.cmake

@@ -0,0 +1,4 @@
+set(error $<0>)
+add_custom_target(check ALL COMMAND check
+  $<IF:0,1,${error}>
+)

+ 1 - 0
Tests/RunCMake/GeneratorExpressionShortCircuit/BadOR-result.txt

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

+ 8 - 0
Tests/RunCMake/GeneratorExpressionShortCircuit/BadOR-stderr.txt

@@ -0,0 +1,8 @@
+CMake Error at BadOR.cmake:2 \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<0>
+
+  \$<0> expression requires a parameter.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)

+ 4 - 0
Tests/RunCMake/GeneratorExpressionShortCircuit/BadOR.cmake

@@ -0,0 +1,4 @@
+set(error $<0>)
+add_custom_target(check ALL COMMAND check
+  $<OR:0,${error}>
+)

+ 3 - 0
Tests/RunCMake/GeneratorExpressionShortCircuit/CMakeLists.txt

@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.26)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)

+ 4 - 0
Tests/RunCMake/GeneratorExpressionShortCircuit/GoodAND.cmake

@@ -0,0 +1,4 @@
+set(error $<0>)
+add_custom_target(check ALL COMMAND check
+  $<AND:0,${error}>
+)

+ 5 - 0
Tests/RunCMake/GeneratorExpressionShortCircuit/GoodIF.cmake

@@ -0,0 +1,5 @@
+set(error $<0>)
+add_custom_target(check ALL
+  COMMAND check $<IF:1,1,${error}>
+  COMMAND Check $<IF:0,${error},1>
+)

+ 4 - 0
Tests/RunCMake/GeneratorExpressionShortCircuit/GoodOR.cmake

@@ -0,0 +1,4 @@
+set(error $<0>)
+add_custom_target(check ALL COMMAND check
+  $<OR:1,${error}>
+)

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

@@ -0,0 +1,9 @@
+include(RunCMake)
+
+run_cmake(GoodIF)
+run_cmake(GoodAND)
+run_cmake(GoodOR)
+
+run_cmake(BadIF)
+run_cmake(BadAND)
+run_cmake(BadOR)