Coverage Report

Created: 2026-02-09 06:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmIfCommand.cxx
Line
Count
Source
1
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2
   file LICENSE.rst or https://cmake.org/licensing for details.  */
3
#include "cmIfCommand.h"
4
5
#include <string>
6
#include <utility>
7
8
#include <cm/memory>
9
#include <cm/string_view>
10
#include <cmext/string_view>
11
12
#include "cmConditionEvaluator.h"
13
#include "cmExecutionStatus.h"
14
#include "cmExpandedCommandArgument.h"
15
#include "cmFunctionBlocker.h"
16
#include "cmListFileCache.h"
17
#include "cmMakefile.h"
18
#include "cmMessageType.h"
19
#include "cmOutputConverter.h"
20
#include "cmStringAlgorithms.h"
21
#include "cmSystemTools.h"
22
#include "cmake.h"
23
24
static std::string cmIfCommandError(
25
  std::vector<cmExpandedCommandArgument> const& args)
26
0
{
27
0
  std::string err = "given arguments:\n ";
28
0
  for (cmExpandedCommandArgument const& i : args) {
29
0
    err += " ";
30
0
    err += cmOutputConverter::EscapeForCMake(i.GetValue());
31
0
  }
32
0
  err += "\n";
33
0
  return err;
34
0
}
35
36
class cmIfFunctionBlocker : public cmFunctionBlocker
37
{
38
public:
39
0
  cm::string_view StartCommandName() const override { return "if"_s; }
40
0
  cm::string_view EndCommandName() const override { return "endif"_s; }
41
42
  bool ArgumentsMatch(cmListFileFunction const& lff,
43
                      cmMakefile&) const override;
44
45
  bool Replay(std::vector<cmListFileFunction> functions,
46
              cmExecutionStatus& inStatus) override;
47
48
  std::vector<cmListFileArgument> Args;
49
  bool IsBlocking;
50
  bool HasRun = false;
51
  bool ElseSeen = false;
52
};
53
54
bool cmIfFunctionBlocker::ArgumentsMatch(cmListFileFunction const& lff,
55
                                         cmMakefile&) const
56
0
{
57
0
  return lff.Arguments().empty() || lff.Arguments() == this->Args;
58
0
}
59
60
bool cmIfFunctionBlocker::Replay(std::vector<cmListFileFunction> functions,
61
                                 cmExecutionStatus& inStatus)
62
0
{
63
0
  cmMakefile& mf = inStatus.GetMakefile();
64
  // execute the functions for the true parts of the if statement
65
0
  int scopeDepth = 0;
66
0
  for (cmListFileFunction const& func : functions) {
67
    // keep track of scope depth
68
0
    if (func.LowerCaseName() == "if") {
69
0
      scopeDepth++;
70
0
    }
71
0
    if (func.LowerCaseName() == "endif") {
72
0
      scopeDepth--;
73
0
    }
74
    // watch for our state change
75
0
    if (scopeDepth == 0 && func.LowerCaseName() == "else") {
76
0
      cmListFileBacktrace elseBT = mf.GetBacktrace().Push(
77
0
        cmListFileContext{ func.OriginalName(),
78
0
                           this->GetStartingContext().FilePath, func.Line() });
79
80
0
      if (this->ElseSeen) {
81
0
        mf.GetCMakeInstance()->IssueMessage(
82
0
          MessageType::FATAL_ERROR,
83
0
          "A duplicate ELSE command was found inside an IF block.", elseBT);
84
0
        cmSystemTools::SetFatalErrorOccurred();
85
0
        return true;
86
0
      }
87
88
0
      this->IsBlocking = this->HasRun;
89
0
      this->HasRun = true;
90
0
      this->ElseSeen = true;
91
92
      // if trace is enabled, print a (trivially) evaluated "else"
93
      // statement
94
0
      if (!this->IsBlocking && mf.GetCMakeInstance()->GetTrace()) {
95
0
        mf.PrintCommandTrace(func, elseBT,
96
0
                             cmMakefile::CommandMissingFromStack::Yes);
97
0
      }
98
0
    } else if (scopeDepth == 0 && func.LowerCaseName() == "elseif") {
99
0
      cmListFileBacktrace elseifBT = mf.GetBacktrace().Push(
100
0
        cmListFileContext{ func.OriginalName(),
101
0
                           this->GetStartingContext().FilePath, func.Line() });
102
0
      if (this->ElseSeen) {
103
0
        mf.GetCMakeInstance()->IssueMessage(
104
0
          MessageType::FATAL_ERROR,
105
0
          "An ELSEIF command was found after an ELSE command.", elseifBT);
106
0
        cmSystemTools::SetFatalErrorOccurred();
107
0
        return true;
108
0
      }
109
110
0
      if (func.Arguments().empty()) {
111
0
        mf.GetCMakeInstance()->IssueMessage(
112
0
          MessageType::AUTHOR_WARNING,
113
0
          "ELSEIF called with no arguments, it will be skipped. ", elseifBT);
114
0
      }
115
116
0
      if (this->HasRun) {
117
0
        this->IsBlocking = true;
118
0
      } else {
119
        // if trace is enabled, print the evaluated "elseif" statement
120
0
        if (mf.GetCMakeInstance()->GetTrace()) {
121
0
          mf.PrintCommandTrace(func, elseifBT,
122
0
                               cmMakefile::CommandMissingFromStack::Yes);
123
0
        }
124
125
0
        std::string errorString;
126
127
0
        std::vector<cmExpandedCommandArgument> expandedArguments;
128
0
        mf.ExpandArguments(func.Arguments(), expandedArguments);
129
130
0
        MessageType messType;
131
132
0
        cmConditionEvaluator conditionEvaluator(mf, elseifBT);
133
134
0
        bool isTrue =
135
0
          conditionEvaluator.IsTrue(expandedArguments, errorString, messType);
136
137
0
        if (!errorString.empty()) {
138
0
          std::string err =
139
0
            cmStrCat(cmIfCommandError(expandedArguments), errorString);
140
0
          mf.GetCMakeInstance()->IssueMessage(messType, err, elseifBT);
141
0
          if (messType == MessageType::FATAL_ERROR) {
142
0
            cmSystemTools::SetFatalErrorOccurred();
143
0
            return true;
144
0
          }
145
0
        }
146
147
0
        if (isTrue) {
148
0
          this->IsBlocking = false;
149
0
          this->HasRun = true;
150
0
        }
151
0
      }
152
0
    }
153
154
    // should we execute?
155
0
    else if (!this->IsBlocking) {
156
0
      cmExecutionStatus status(mf);
157
0
      mf.ExecuteCommand(func, status);
158
0
      if (status.GetReturnInvoked()) {
159
0
        inStatus.SetReturnInvoked(status.GetReturnVariables());
160
0
        return true;
161
0
      }
162
0
      if (status.GetBreakInvoked()) {
163
0
        inStatus.SetBreakInvoked();
164
0
        return true;
165
0
      }
166
0
      if (status.GetContinueInvoked()) {
167
0
        inStatus.SetContinueInvoked();
168
0
        return true;
169
0
      }
170
0
      if (status.HasExitCode()) {
171
0
        inStatus.SetExitCode(status.GetExitCode());
172
0
        return true;
173
0
      }
174
0
    }
175
0
  }
176
0
  return true;
177
0
}
178
179
//=========================================================================
180
bool cmIfCommand(std::vector<cmListFileArgument> const& args,
181
                 cmExecutionStatus& inStatus)
182
0
{
183
0
  cmMakefile& makefile = inStatus.GetMakefile();
184
0
  std::string errorString;
185
186
0
  std::vector<cmExpandedCommandArgument> expandedArguments;
187
0
  makefile.ExpandArguments(args, expandedArguments);
188
189
0
  MessageType status;
190
191
0
  cmConditionEvaluator conditionEvaluator(makefile, makefile.GetBacktrace());
192
193
0
  bool isTrue =
194
0
    conditionEvaluator.IsTrue(expandedArguments, errorString, status);
195
196
0
  if (!errorString.empty()) {
197
0
    std::string err =
198
0
      cmStrCat("if ", cmIfCommandError(expandedArguments), errorString);
199
0
    if (status == MessageType::FATAL_ERROR) {
200
0
      makefile.IssueMessage(MessageType::FATAL_ERROR, err);
201
0
      cmSystemTools::SetFatalErrorOccurred();
202
0
      return true;
203
0
    }
204
0
    makefile.IssueMessage(status, err);
205
0
  }
206
207
0
  {
208
0
    auto fb = cm::make_unique<cmIfFunctionBlocker>();
209
    // if is isn't true block the commands
210
0
    fb->IsBlocking = !isTrue;
211
0
    if (isTrue) {
212
0
      fb->HasRun = true;
213
0
    }
214
0
    fb->Args = args;
215
0
    makefile.AddFunctionBlocker(std::move(fb));
216
0
  }
217
218
0
  return true;
219
0
}