cmConditionEvaluator.cxx 27 KB
Newer Older
1 2
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
3
#include "cmConditionEvaluator.h"
4

5
#include "cmsys/RegularExpression.hxx"
6 7 8 9 10
#include <algorithm>
#include <sstream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
wahikihiki's avatar
wahikihiki committed
11
#include <utility>
12

13
#include "cmAlgorithms.h"
14
#include "cmMakefile.h"
15
#include "cmMessageType.h"
16
#include "cmState.h"
17
#include "cmSystemTools.h"
18
#include "cmake.h"
19

20 21 22
class cmCommand;
class cmTest;

23 24 25 26 27 28
static std::string const keyAND = "AND";
static std::string const keyCOMMAND = "COMMAND";
static std::string const keyDEFINED = "DEFINED";
static std::string const keyEQUAL = "EQUAL";
static std::string const keyEXISTS = "EXISTS";
static std::string const keyGREATER = "GREATER";
29
static std::string const keyGREATER_EQUAL = "GREATER_EQUAL";
30 31 32 33 34 35
static std::string const keyIN_LIST = "IN_LIST";
static std::string const keyIS_ABSOLUTE = "IS_ABSOLUTE";
static std::string const keyIS_DIRECTORY = "IS_DIRECTORY";
static std::string const keyIS_NEWER_THAN = "IS_NEWER_THAN";
static std::string const keyIS_SYMLINK = "IS_SYMLINK";
static std::string const keyLESS = "LESS";
36
static std::string const keyLESS_EQUAL = "LESS_EQUAL";
37 38 39 40 41 42 43 44
static std::string const keyMATCHES = "MATCHES";
static std::string const keyNOT = "NOT";
static std::string const keyOR = "OR";
static std::string const keyParenL = "(";
static std::string const keyParenR = ")";
static std::string const keyPOLICY = "POLICY";
static std::string const keySTREQUAL = "STREQUAL";
static std::string const keySTRGREATER = "STRGREATER";
45
static std::string const keySTRGREATER_EQUAL = "STRGREATER_EQUAL";
46
static std::string const keySTRLESS = "STRLESS";
47
static std::string const keySTRLESS_EQUAL = "STRLESS_EQUAL";
48 49 50 51
static std::string const keyTARGET = "TARGET";
static std::string const keyTEST = "TEST";
static std::string const keyVERSION_EQUAL = "VERSION_EQUAL";
static std::string const keyVERSION_GREATER = "VERSION_GREATER";
52
static std::string const keyVERSION_GREATER_EQUAL = "VERSION_GREATER_EQUAL";
53
static std::string const keyVERSION_LESS = "VERSION_LESS";
54
static std::string const keyVERSION_LESS_EQUAL = "VERSION_LESS_EQUAL";
55

56
cmConditionEvaluator::cmConditionEvaluator(cmMakefile& makefile,
wahikihiki's avatar
wahikihiki committed
57 58
                                           cmListFileContext context,
                                           cmListFileBacktrace bt)
59
  : Makefile(makefile)
wahikihiki's avatar
wahikihiki committed
60 61
  , ExecutionContext(std::move(context))
  , Backtrace(std::move(bt))
62 63 64 65
  , Policy12Status(makefile.GetPolicyStatus(cmPolicies::CMP0012))
  , Policy54Status(makefile.GetPolicyStatus(cmPolicies::CMP0054))
  , Policy57Status(makefile.GetPolicyStatus(cmPolicies::CMP0057))
  , Policy64Status(makefile.GetPolicyStatus(cmPolicies::CMP0064))
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
{
}

//=========================================================================
// order of operations,
// 1.   ( )   -- parenthetical groups
// 2.  IS_DIRECTORY EXISTS COMMAND DEFINED etc predicates
// 3. MATCHES LESS GREATER EQUAL STRLESS STRGREATER STREQUAL etc binary ops
// 4. NOT
// 5. AND OR
//
// There is an issue on whether the arguments should be values of references,
// for example IF (FOO AND BAR) should that compare the strings FOO and BAR
// or should it really do IF (${FOO} AND ${BAR}) Currently IS_DIRECTORY
// EXISTS COMMAND and DEFINED all take values. EQUAL, LESS and GREATER can
// take numeric values or variable names. STRLESS and STRGREATER take
// variable names but if the variable name is not found it will use the name
// directly. AND OR take variables or the values 0 or 1.

bool cmConditionEvaluator::IsTrue(
86
  const std::vector<cmExpandedCommandArgument>& args, std::string& errorString,
87
  MessageType& status)
88
{
89
  errorString.clear();
90 91

  // handle empty invocation
92
  if (args.empty()) {
93
    return false;
94
  }
95 96

  // store the reduced args in this vector
97
  cmArgumentList newArgs(args.begin(), args.end());
98 99 100 101

  // now loop through the arguments and see if we can reduce any of them
  // we do this multiple times. Once for each level of precedence
  // parens
102
  if (!this->HandleLevel0(newArgs, errorString, status)) {
103
    return false;
104 105 106
  }
  // predicates
  if (!this->HandleLevel1(newArgs, errorString, status)) {
107
    return false;
108
  }
109
  // binary ops
110
  if (!this->HandleLevel2(newArgs, errorString, status)) {
111
    return false;
112
  }
113 114

  // NOT
115
  if (!this->HandleLevel3(newArgs, errorString, status)) {
116
    return false;
117
  }
118
  // AND OR
119
  if (!this->HandleLevel4(newArgs, errorString, status)) {
120
    return false;
121
  }
122 123

  // now at the end there should only be one argument left
124
  if (newArgs.size() != 1) {
125
    errorString = "Unknown arguments specified";
126
    status = MessageType::FATAL_ERROR;
127
    return false;
128
  }
129

130 131
  return this->GetBooleanValueWithAutoDereference(newArgs.front(), errorString,
                                                  status, true);
132 133
}

134 135 136 137
//=========================================================================
const char* cmConditionEvaluator::GetDefinitionIfUnquoted(
  cmExpandedCommandArgument const& argument) const
{
138 139 140
  if ((this->Policy54Status != cmPolicies::WARN &&
       this->Policy54Status != cmPolicies::OLD) &&
      argument.WasQuoted()) {
Daniel Pfeifer's avatar
Daniel Pfeifer committed
141
    return nullptr;
142
  }
143 144 145

  const char* def = this->Makefile.GetDefinition(argument.GetValue());

146 147 148 149
  if (def && argument.WasQuoted() &&
      this->Policy54Status == cmPolicies::WARN) {
    if (!this->Makefile.HasCMP0054AlreadyBeenReported(
          this->ExecutionContext)) {
150
      std::ostringstream e;
151
      e << (cmPolicies::GetPolicyWarning(cmPolicies::CMP0054)) << "\n";
152 153 154 155 156 157
      e << "Quoted variables like \"" << argument.GetValue()
        << "\" will no longer be dereferenced "
           "when the policy is set to NEW.  "
           "Since the policy is not set the OLD behavior will be used.";

      this->Makefile.GetCMakeInstance()->IssueMessage(
158
        MessageType::AUTHOR_WARNING, e.str(), this->Backtrace);
159
    }
160
  }
161 162 163 164

  return def;
}

165 166
//=========================================================================
const char* cmConditionEvaluator::GetVariableOrString(
167
  const cmExpandedCommandArgument& argument) const
168
{
169
  const char* def = this->GetDefinitionIfUnquoted(argument);
170

171
  if (!def) {
172
    def = argument.c_str();
173
  }
174 175 176 177

  return def;
}

178 179
//=========================================================================
bool cmConditionEvaluator::IsKeyword(std::string const& keyword,
180
                                     cmExpandedCommandArgument& argument) const
181
{
182 183 184
  if ((this->Policy54Status != cmPolicies::WARN &&
       this->Policy54Status != cmPolicies::OLD) &&
      argument.WasQuoted()) {
185
    return false;
186
  }
187 188 189

  bool isKeyword = argument.GetValue() == keyword;

190 191 192 193
  if (isKeyword && argument.WasQuoted() &&
      this->Policy54Status == cmPolicies::WARN) {
    if (!this->Makefile.HasCMP0054AlreadyBeenReported(
          this->ExecutionContext)) {
194
      std::ostringstream e;
195
      e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0054) << "\n";
196 197 198 199 200 201
      e << "Quoted keywords like \"" << argument.GetValue()
        << "\" will no longer be interpreted as keywords "
           "when the policy is set to NEW.  "
           "Since the policy is not set the OLD behavior will be used.";

      this->Makefile.GetCMakeInstance()->IssueMessage(
202
        MessageType::AUTHOR_WARNING, e.str(), this->Backtrace);
203
    }
204
  }
205 206 207 208

  return isKeyword;
}

209 210
//=========================================================================
bool cmConditionEvaluator::GetBooleanValue(
211
  cmExpandedCommandArgument& arg) const
212 213
{
  // Check basic constants.
214
  if (arg == "0") {
215
    return false;
216 217
  }
  if (arg == "1") {
218
    return true;
219
  }
220 221

  // Check named constants.
222
  if (cmSystemTools::IsOn(arg.c_str())) {
223
    return true;
224 225
  }
  if (cmSystemTools::IsOff(arg.c_str())) {
226
    return false;
227
  }
228 229

  // Check for numbers.
230
  if (!arg.empty()) {
231 232
    char* end;
    double d = strtod(arg.c_str(), &end);
233
    if (*end == '\0') {
234
      // The whole string is a number.  Use C conversion to bool.
235
      return static_cast<bool>(d);
236
    }
237
  }
238 239

  // Check definition.
240
  const char* def = this->GetDefinitionIfUnquoted(arg);
241 242 243 244 245 246
  return !cmSystemTools::IsOff(def);
}

//=========================================================================
// Boolean value behavior from CMake 2.6.4 and below.
bool cmConditionEvaluator::GetBooleanValueOld(
247
  cmExpandedCommandArgument const& arg, bool one) const
248
{
249
  if (one) {
250
    // Old IsTrue behavior for single argument.
251 252
    if (arg == "0") {
      return false;
Daniel Pfeifer's avatar
Daniel Pfeifer committed
253 254
    }
    if (arg == "1") {
255
      return true;
256
    }
257
    const char* def = this->GetDefinitionIfUnquoted(arg);
258 259
    return !cmSystemTools::IsOff(def);
  }
Daniel Pfeifer's avatar
Daniel Pfeifer committed
260 261 262 263 264 265
  // Old GetVariableOrNumber behavior.
  const char* def = this->GetDefinitionIfUnquoted(arg);
  if (!def && atoi(arg.c_str())) {
    def = arg.c_str();
  }
  return !cmSystemTools::IsOff(def);
266 267 268 269 270
}

//=========================================================================
// returns the resulting boolean value
bool cmConditionEvaluator::GetBooleanValueWithAutoDereference(
271
  cmExpandedCommandArgument& newArg, std::string& errorString,
272
  MessageType& status, bool oneArg) const
273 274
{
  // Use the policy if it is set.
275
  if (this->Policy12Status == cmPolicies::NEW) {
276
    return GetBooleanValue(newArg);
Daniel Pfeifer's avatar
Daniel Pfeifer committed
277 278
  }
  if (this->Policy12Status == cmPolicies::OLD) {
279
    return GetBooleanValueOld(newArg, oneArg);
280
  }
281 282 283 284

  // Check policy only if old and new results differ.
  bool newResult = this->GetBooleanValue(newArg);
  bool oldResult = this->GetBooleanValueOld(newArg, oneArg);
285 286
  if (newResult != oldResult) {
    switch (this->Policy12Status) {
287
      case cmPolicies::WARN:
288 289 290
        errorString = "An argument named \"" + newArg.GetValue() +
          "\" appears in a conditional statement.  " +
          cmPolicies::GetPolicyWarning(cmPolicies::CMP0012);
291
        status = MessageType::AUTHOR_WARNING;
292
        CM_FALLTHROUGH;
293 294 295
      case cmPolicies::OLD:
        return oldResult;
      case cmPolicies::REQUIRED_IF_USED:
296 297 298 299
      case cmPolicies::REQUIRED_ALWAYS: {
        errorString = "An argument named \"" + newArg.GetValue() +
          "\" appears in a conditional statement.  " +
          cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0012);
300
        status = MessageType::FATAL_ERROR;
301
      }
302 303 304
      case cmPolicies::NEW:
        break;
    }
305
  }
306 307 308 309
  return newResult;
}

//=========================================================================
310 311 312
void cmConditionEvaluator::IncrementArguments(
  cmArgumentList& newArgs, cmArgumentList::iterator& argP1,
  cmArgumentList::iterator& argP2) const
313
{
314
  if (argP1 != newArgs.end()) {
315 316
    argP1++;
    argP2 = argP1;
317
    if (argP1 != newArgs.end()) {
318 319
      argP2++;
    }
320
  }
321 322 323 324
}

//=========================================================================
// helper function to reduce code duplication
325 326 327 328
void cmConditionEvaluator::HandlePredicate(
  bool value, int& reducible, cmArgumentList::iterator& arg,
  cmArgumentList& newArgs, cmArgumentList::iterator& argP1,
  cmArgumentList::iterator& argP2) const
329
{
330
  if (value) {
331
    *arg = cmExpandedCommandArgument("1", true);
332
  } else {
333
    *arg = cmExpandedCommandArgument("0", true);
334
  }
335 336
  newArgs.erase(argP1);
  argP1 = arg;
337
  this->IncrementArguments(newArgs, argP1, argP2);
338 339 340 341 342
  reducible = 1;
}

//=========================================================================
// helper function to reduce code duplication
343 344 345 346 347
void cmConditionEvaluator::HandleBinaryOp(bool value, int& reducible,
                                          cmArgumentList::iterator& arg,
                                          cmArgumentList& newArgs,
                                          cmArgumentList::iterator& argP1,
                                          cmArgumentList::iterator& argP2)
348
{
349
  if (value) {
350
    *arg = cmExpandedCommandArgument("1", true);
351
  } else {
352
    *arg = cmExpandedCommandArgument("0", true);
353
  }
354 355 356
  newArgs.erase(argP2);
  newArgs.erase(argP1);
  argP1 = arg;
357
  this->IncrementArguments(newArgs, argP1, argP2);
358 359 360 361 362
  reducible = 1;
}

//=========================================================================
// level 0 processes parenthetical expressions
363 364
bool cmConditionEvaluator::HandleLevel0(cmArgumentList& newArgs,
                                        std::string& errorString,
365
                                        MessageType& status)
366 367
{
  int reducible;
368
  do {
369
    reducible = 0;
370
    cmArgumentList::iterator arg = newArgs.begin();
371 372
    while (arg != newArgs.end()) {
      if (IsKeyword(keyParenL, *arg)) {
373
        // search for the closing paren for this opening one
374
        cmArgumentList::iterator argClose;
375 376 377
        argClose = arg;
        argClose++;
        unsigned int depth = 1;
378 379 380 381 382 383
        while (argClose != newArgs.end() && depth) {
          if (this->IsKeyword(keyParenL, *argClose)) {
            depth++;
          }
          if (this->IsKeyword(keyParenR, *argClose)) {
            depth--;
384
          }
385 386 387
          argClose++;
        }
        if (depth) {
388
          errorString = "mismatched parenthesis in condition";
389
          status = MessageType::FATAL_ERROR;
390
          return false;
391
        }
392
        // store the reduced args in this vector
393
        std::vector<cmExpandedCommandArgument> newArgs2;
394 395

        // copy to the list structure
396
        cmArgumentList::iterator argP1 = arg;
397
        argP1++;
398
        cmAppend(newArgs2, argP1, argClose);
399 400 401
        newArgs2.pop_back();
        // now recursively invoke IsTrue to handle the values inside the
        // parenthetical expression
402 403
        bool value = this->IsTrue(newArgs2, errorString, status);
        if (value) {
404
          *arg = cmExpandedCommandArgument("1", true);
405
        } else {
406
          *arg = cmExpandedCommandArgument("0", true);
407
        }
408 409 410
        argP1 = arg;
        argP1++;
        // remove the now evaluated parenthetical expression
411
        newArgs.erase(argP1, argClose);
412
      }
413
      ++arg;
414
    }
415
  } while (reducible);
416 417 418 419 420
  return true;
}

//=========================================================================
// level one handles most predicates except for NOT
421
bool cmConditionEvaluator::HandleLevel1(cmArgumentList& newArgs, std::string&,
422
                                        MessageType&)
423 424
{
  int reducible;
425
  do {
426
    reducible = 0;
427 428 429
    cmArgumentList::iterator arg = newArgs.begin();
    cmArgumentList::iterator argP1;
    cmArgumentList::iterator argP2;
430
    while (arg != newArgs.end()) {
431
      argP1 = arg;
432
      this->IncrementArguments(newArgs, argP1, argP2);
433
      // does a file exist
434 435 436 437
      if (this->IsKeyword(keyEXISTS, *arg) && argP1 != newArgs.end()) {
        this->HandlePredicate(cmSystemTools::FileExists(argP1->c_str()),
                              reducible, arg, newArgs, argP1, argP2);
      }
438
      // does a directory with this name exist
439 440 441 442
      if (this->IsKeyword(keyIS_DIRECTORY, *arg) && argP1 != newArgs.end()) {
        this->HandlePredicate(cmSystemTools::FileIsDirectory(argP1->c_str()),
                              reducible, arg, newArgs, argP1, argP2);
      }
443
      // does a symlink with this name exist
444 445 446 447
      if (this->IsKeyword(keyIS_SYMLINK, *arg) && argP1 != newArgs.end()) {
        this->HandlePredicate(cmSystemTools::FileIsSymlink(argP1->c_str()),
                              reducible, arg, newArgs, argP1, argP2);
      }
448
      // is the given path an absolute path ?
449 450 451 452
      if (this->IsKeyword(keyIS_ABSOLUTE, *arg) && argP1 != newArgs.end()) {
        this->HandlePredicate(cmSystemTools::FileIsFullPath(argP1->c_str()),
                              reducible, arg, newArgs, argP1, argP2);
      }
453
      // does a command exist
454
      if (this->IsKeyword(keyCOMMAND, *arg) && argP1 != newArgs.end()) {
455
        cmCommand* command =
456
          this->Makefile.GetState()->GetCommand(argP1->c_str());
Daniel Pfeifer's avatar
Daniel Pfeifer committed
457
        this->HandlePredicate(command != nullptr, reducible, arg, newArgs,
458 459
                              argP1, argP2);
      }
460
      // does a policy exist
461
      if (this->IsKeyword(keyPOLICY, *arg) && argP1 != newArgs.end()) {
462
        cmPolicies::PolicyID pid;
463 464 465
        this->HandlePredicate(cmPolicies::GetPolicyID(argP1->c_str(), pid),
                              reducible, arg, newArgs, argP1, argP2);
      }
466
      // does a target exist
467
      if (this->IsKeyword(keyTARGET, *arg) && argP1 != newArgs.end()) {
468
        this->HandlePredicate(
Daniel Pfeifer's avatar
Daniel Pfeifer committed
469
          this->Makefile.FindTargetToUse(argP1->GetValue()) != nullptr,
470
          reducible, arg, newArgs, argP1, argP2);
471
      }
472
      // does a test exist
473 474 475
      if (this->Policy64Status != cmPolicies::OLD &&
          this->Policy64Status != cmPolicies::WARN) {
        if (this->IsKeyword(keyTEST, *arg) && argP1 != newArgs.end()) {
476
          const cmTest* haveTest = this->Makefile.GetTest(argP1->c_str());
Daniel Pfeifer's avatar
Daniel Pfeifer committed
477 478
          this->HandlePredicate(haveTest != nullptr, reducible, arg, newArgs,
                                argP1, argP2);
479
        }
480 481
      } else if (this->Policy64Status == cmPolicies::WARN &&
                 this->IsKeyword(keyTEST, *arg)) {
482 483 484
        std::ostringstream e;
        e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0064) << "\n";
        e << "TEST will be interpreted as an operator "
485 486
             "when the policy is set to NEW.  "
             "Since the policy is not set the OLD behavior will be used.";
487

488
        this->Makefile.IssueMessage(MessageType::AUTHOR_WARNING, e.str());
489
      }
490
      // is a variable defined
491
      if (this->IsKeyword(keyDEFINED, *arg) && argP1 != newArgs.end()) {
492
        size_t argP1len = argP1->GetValue().size();
493
        bool bdef = false;
494 495 496
        if (argP1len > 4 && argP1->GetValue().substr(0, 4) == "ENV{" &&
            argP1->GetValue().operator[](argP1len - 1) == '}') {
          std::string env = argP1->GetValue().substr(4, argP1len - 5);
497
          bdef = cmSystemTools::HasEnv(env);
498 499 500 501 502 503
        } else if (argP1len > 6 &&
                   argP1->GetValue().substr(0, 6) == "CACHE{" &&
                   argP1->GetValue().operator[](argP1len - 1) == '}') {
          std::string cache = argP1->GetValue().substr(6, argP1len - 7);
          bdef =
            this->Makefile.GetState()->GetCacheEntryValue(cache) != nullptr;
504
        } else {
505
          bdef = this->Makefile.IsDefinitionSet(argP1->GetValue());
506
        }
507
        this->HandlePredicate(bdef, reducible, arg, newArgs, argP1, argP2);
508
      }
509
      ++arg;
510
    }
511
  } while (reducible);
512 513 514 515 516
  return true;
}

//=========================================================================
// level two handles most binary operations except for AND  OR
517 518
bool cmConditionEvaluator::HandleLevel2(cmArgumentList& newArgs,
                                        std::string& errorString,
519
                                        MessageType& status)
520 521
{
  int reducible;
522
  std::string def_buf;
523 524 525
  const char* def;
  const char* def2;
  do {
526
    reducible = 0;
527 528 529
    cmArgumentList::iterator arg = newArgs.begin();
    cmArgumentList::iterator argP1;
    cmArgumentList::iterator argP2;
530
    while (arg != newArgs.end()) {
531
      argP1 = arg;
532
      this->IncrementArguments(newArgs, argP1, argP2);
533
      if (argP1 != newArgs.end() && argP2 != newArgs.end() &&
534
          IsKeyword(keyMATCHES, *argP1)) {
535
        def = this->GetVariableOrString(*arg);
536
        if (def != arg->c_str() // yes, we compare the pointer value
537
            && cmHasLiteralPrefix(arg->GetValue(), "CMAKE_MATCH_")) {
538 539 540 541
          // The string to match is owned by our match result variables.
          // Move it to our own buffer before clearing them.
          def_buf = def;
          def = def_buf.c_str();
542
        }
543
        const char* rex = argP2->c_str();
544
        this->Makefile.ClearMatches();
545
        cmsys::RegularExpression regEntry;
546
        if (!regEntry.compile(rex)) {
547
          std::ostringstream error;
548 549
          error << "Regular expression \"" << rex << "\" cannot compile";
          errorString = error.str();
550
          status = MessageType::FATAL_ERROR;
551
          return false;
552 553
        }
        if (regEntry.find(def)) {
554
          this->Makefile.StoreMatches(regEntry);
555
          *arg = cmExpandedCommandArgument("1", true);
556
        } else {
557
          *arg = cmExpandedCommandArgument("0", true);
558
        }
559 560 561
        newArgs.erase(argP2);
        newArgs.erase(argP1);
        argP1 = arg;
562
        this->IncrementArguments(newArgs, argP1, argP2);
563
        reducible = 1;
564
      }
565

566
      if (argP1 != newArgs.end() && this->IsKeyword(keyMATCHES, *arg)) {
567
        *arg = cmExpandedCommandArgument("0", true);
568 569
        newArgs.erase(argP1);
        argP1 = arg;
570
        this->IncrementArguments(newArgs, argP1, argP2);
571
        reducible = 1;
572
      }
573 574

      if (argP1 != newArgs.end() && argP2 != newArgs.end() &&
575
          (this->IsKeyword(keyLESS, *argP1) ||
576
           this->IsKeyword(keyLESS_EQUAL, *argP1) ||
577
           this->IsKeyword(keyGREATER, *argP1) ||
578
           this->IsKeyword(keyGREATER_EQUAL, *argP1) ||
579
           this->IsKeyword(keyEQUAL, *argP1))) {
580 581 582 583 584
        def = this->GetVariableOrString(*arg);
        def2 = this->GetVariableOrString(*argP2);
        double lhs;
        double rhs;
        bool result;
585
        if (sscanf(def, "%lg", &lhs) != 1 || sscanf(def2, "%lg", &rhs) != 1) {
586
          result = false;
587
        } else if (*(argP1) == keyLESS) {
588
          result = (lhs < rhs);
589 590
        } else if (*(argP1) == keyLESS_EQUAL) {
          result = (lhs <= rhs);
591
        } else if (*(argP1) == keyGREATER) {
592
          result = (lhs > rhs);
593 594
        } else if (*(argP1) == keyGREATER_EQUAL) {
          result = (lhs >= rhs);
595
        } else {
596 597
          result = (lhs == rhs);
        }
598 599
        this->HandleBinaryOp(result, reducible, arg, newArgs, argP1, argP2);
      }
600 601

      if (argP1 != newArgs.end() && argP2 != newArgs.end() &&
602
          (this->IsKeyword(keySTRLESS, *argP1) ||
603 604 605 606
           this->IsKeyword(keySTRLESS_EQUAL, *argP1) ||
           this->IsKeyword(keySTRGREATER, *argP1) ||
           this->IsKeyword(keySTRGREATER_EQUAL, *argP1) ||
           this->IsKeyword(keySTREQUAL, *argP1))) {
607 608
        def = this->GetVariableOrString(*arg);
        def2 = this->GetVariableOrString(*argP2);
609
        int val = strcmp(def, def2);
610
        bool result;
611
        if (*(argP1) == keySTRLESS) {
612
          result = (val < 0);
613 614
        } else if (*(argP1) == keySTRLESS_EQUAL) {
          result = (val <= 0);
615
        } else if (*(argP1) == keySTRGREATER) {
616
          result = (val > 0);
617 618
        } else if (*(argP1) == keySTRGREATER_EQUAL) {
          result = (val >= 0);
619 620
        } else // strequal
        {
621 622
          result = (val == 0);
        }
623 624
        this->HandleBinaryOp(result, reducible, arg, newArgs, argP1, argP2);
      }
625 626

      if (argP1 != newArgs.end() && argP2 != newArgs.end() &&
627
          (this->IsKeyword(keyVERSION_LESS, *argP1) ||
628
           this->IsKeyword(keyVERSION_LESS_EQUAL, *argP1) ||
629
           this->IsKeyword(keyVERSION_GREATER, *argP1) ||
630
           this->IsKeyword(keyVERSION_GREATER_EQUAL, *argP1) ||
631
           this->IsKeyword(keyVERSION_EQUAL, *argP1))) {
632 633
        def = this->GetVariableOrString(*arg);
        def2 = this->GetVariableOrString(*argP2);
634
        cmSystemTools::CompareOp op;
635
        if (*argP1 == keyVERSION_LESS) {
636
          op = cmSystemTools::OP_LESS;
637 638
        } else if (*argP1 == keyVERSION_LESS_EQUAL) {
          op = cmSystemTools::OP_LESS_EQUAL;
639
        } else if (*argP1 == keyVERSION_GREATER) {
640
          op = cmSystemTools::OP_GREATER;
641 642 643 644
        } else if (*argP1 == keyVERSION_GREATER_EQUAL) {
          op = cmSystemTools::OP_GREATER_EQUAL;
        } else { // version_equal
          op = cmSystemTools::OP_EQUAL;
645
        }
646 647 648
        bool result = cmSystemTools::VersionCompare(op, def, def2);
        this->HandleBinaryOp(result, reducible, arg, newArgs, argP1, argP2);
      }
649 650 651

      // is file A newer than file B
      if (argP1 != newArgs.end() && argP2 != newArgs.end() &&
652 653 654 655
          this->IsKeyword(keyIS_NEWER_THAN, *argP1)) {
        int fileIsNewer = 0;
        bool success = cmSystemTools::FileTimeCompare(
          arg->GetValue(), (argP2)->GetValue(), &fileIsNewer);
656
        this->HandleBinaryOp(
657 658
          (!success || fileIsNewer == 1 || fileIsNewer == 0), reducible, arg,
          newArgs, argP1, argP2);
659
      }
660

661
      if (argP1 != newArgs.end() && argP2 != newArgs.end() &&
662 663 664
          this->IsKeyword(keyIN_LIST, *argP1)) {
        if (this->Policy57Status != cmPolicies::OLD &&
            this->Policy57Status != cmPolicies::WARN) {
665 666 667 668 669
          bool result = false;

          def = this->GetVariableOrString(*arg);
          def2 = this->Makefile.GetDefinition(argP2->GetValue());

670
          if (def2) {
671 672 673 674 675
            std::vector<std::string> list;
            cmSystemTools::ExpandListArgument(def2, list, true);

            result = std::find(list.begin(), list.end(), def) != list.end();
          }
676 677 678 679 680 681 682 683 684

          this->HandleBinaryOp(result, reducible, arg, newArgs, argP1, argP2);
        } else if (this->Policy57Status == cmPolicies::WARN) {
          std::ostringstream e;
          e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0057) << "\n";
          e << "IN_LIST will be interpreted as an operator "
               "when the policy is set to NEW.  "
               "Since the policy is not set the OLD behavior will be used.";

685
          this->Makefile.IssueMessage(MessageType::AUTHOR_WARNING, e.str());
686
        }
687
      }
688

689 690
      ++arg;
    }
691
  } while (reducible);
692 693 694 695 696
  return true;
}

//=========================================================================
// level 3 handles NOT
697 698
bool cmConditionEvaluator::HandleLevel3(cmArgumentList& newArgs,
                                        std::string& errorString,
699
                                        MessageType& status)
700 701
{
  int reducible;
702
  do {
703
    reducible = 0;
704 705 706
    cmArgumentList::iterator arg = newArgs.begin();
    cmArgumentList::iterator argP1;
    cmArgumentList::iterator argP2;
707
    while (arg != newArgs.end()) {
708
      argP1 = arg;
709 710 711 712
      IncrementArguments(newArgs, argP1, argP2);
      if (argP1 != newArgs.end() && IsKeyword(keyNOT, *arg)) {
        bool rhs = this->GetBooleanValueWithAutoDereference(
          *argP1, errorString, status);
713 714
        this->HandlePredicate(!rhs, reducible, arg, newArgs, argP1, argP2);
      }
715
      ++arg;
716
    }
717
  } while (reducible);
718 719 720 721 722
  return true;
}

//=========================================================================
// level 4 handles AND OR
723 724
bool cmConditionEvaluator::HandleLevel4(cmArgumentList& newArgs,
                                        std::string& errorString,
725
                                        MessageType& status)
726 727 728 729
{
  int reducible;
  bool lhs;
  bool rhs;
730
  do {
731
    reducible = 0;
732 733 734
    cmArgumentList::iterator arg = newArgs.begin();
    cmArgumentList::iterator argP1;
    cmArgumentList::iterator argP2;
735
    while (arg != newArgs.end()) {
736
      argP1 = arg;
737
      IncrementArguments(newArgs, argP1, argP2);
738
      if (argP1 != newArgs.end() && IsKeyword(keyAND, *argP1) &&
739 740 741 742 743 744 745 746
          argP2 != newArgs.end()) {
        lhs =
          this->GetBooleanValueWithAutoDereference(*arg, errorString, status);
        rhs = this->GetBooleanValueWithAutoDereference(*argP2, errorString,
                                                       status);
        this->HandleBinaryOp((lhs && rhs), reducible, arg, newArgs, argP1,
                             argP2);
      }
747

748
      if (argP1 != newArgs.end() && this->IsKeyword(keyOR, *argP1) &&
749 750 751 752 753 754 755
          argP2 != newArgs.end()) {
        lhs =
          this->GetBooleanValueWithAutoDereference(*arg, errorString, status);
        rhs = this->GetBooleanValueWithAutoDereference(*argP2, errorString,
                                                       status);
        this->HandleBinaryOp((lhs || rhs), reducible, arg, newArgs, argP1,
                             argP2);
756
      }
757
      ++arg;
758
    }
759
  } while (reducible);
760 761
  return true;
}