0da72b15d374eb54ef2f5d02c45d38f39b1a0849
[platform/upstream/cmake.git] / Source / cmIfCommand.cxx
1 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2    file Copyright.txt 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 {
27   std::string err = "given arguments:\n ";
28   for (cmExpandedCommandArgument const& i : args) {
29     err += " ";
30     err += cmOutputConverter::EscapeForCMake(i.GetValue());
31   }
32   err += "\n";
33   return err;
34 }
35
36 class cmIfFunctionBlocker : public cmFunctionBlocker
37 {
38 public:
39   cm::string_view StartCommandName() const override { return "if"_s; }
40   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 {
57   return lff.Arguments().empty() || lff.Arguments() == this->Args;
58 }
59
60 bool cmIfFunctionBlocker::Replay(std::vector<cmListFileFunction> functions,
61                                  cmExecutionStatus& inStatus)
62 {
63   cmMakefile& mf = inStatus.GetMakefile();
64   // execute the functions for the true parts of the if statement
65   int scopeDepth = 0;
66   for (cmListFileFunction const& func : functions) {
67     // keep track of scope depth
68     if (func.LowerCaseName() == "if") {
69       scopeDepth++;
70     }
71     if (func.LowerCaseName() == "endif") {
72       scopeDepth--;
73     }
74     // watch for our state change
75     if (scopeDepth == 0 && func.LowerCaseName() == "else") {
76       cmListFileBacktrace elseBT = mf.GetBacktrace().Push(
77         cmListFileContext{ func.OriginalName(),
78                            this->GetStartingContext().FilePath, func.Line() });
79
80       if (this->ElseSeen) {
81         mf.GetCMakeInstance()->IssueMessage(
82           MessageType::FATAL_ERROR,
83           "A duplicate ELSE command was found inside an IF block.", elseBT);
84         cmSystemTools::SetFatalErrorOccurred();
85         return true;
86       }
87
88       this->IsBlocking = this->HasRun;
89       this->HasRun = true;
90       this->ElseSeen = true;
91
92       // if trace is enabled, print a (trivially) evaluated "else"
93       // statement
94       if (!this->IsBlocking && mf.GetCMakeInstance()->GetTrace()) {
95         mf.PrintCommandTrace(func, elseBT,
96                              cmMakefile::CommandMissingFromStack::Yes);
97       }
98     } else if (scopeDepth == 0 && func.LowerCaseName() == "elseif") {
99       cmListFileBacktrace elseifBT = mf.GetBacktrace().Push(
100         cmListFileContext{ func.OriginalName(),
101                            this->GetStartingContext().FilePath, func.Line() });
102       if (this->ElseSeen) {
103         mf.GetCMakeInstance()->IssueMessage(
104           MessageType::FATAL_ERROR,
105           "An ELSEIF command was found after an ELSE command.", elseifBT);
106         cmSystemTools::SetFatalErrorOccurred();
107         return true;
108       }
109
110       if (this->HasRun) {
111         this->IsBlocking = true;
112       } else {
113         // if trace is enabled, print the evaluated "elseif" statement
114         if (mf.GetCMakeInstance()->GetTrace()) {
115           mf.PrintCommandTrace(func, elseifBT,
116                                cmMakefile::CommandMissingFromStack::Yes);
117         }
118
119         std::string errorString;
120
121         std::vector<cmExpandedCommandArgument> expandedArguments;
122         mf.ExpandArguments(func.Arguments(), expandedArguments);
123
124         MessageType messType;
125
126         cmConditionEvaluator conditionEvaluator(mf, elseifBT);
127
128         bool isTrue =
129           conditionEvaluator.IsTrue(expandedArguments, errorString, messType);
130
131         if (!errorString.empty()) {
132           std::string err =
133             cmStrCat(cmIfCommandError(expandedArguments), errorString);
134           mf.GetCMakeInstance()->IssueMessage(messType, err, elseifBT);
135           if (messType == MessageType::FATAL_ERROR) {
136             cmSystemTools::SetFatalErrorOccurred();
137             return true;
138           }
139         }
140
141         if (isTrue) {
142           this->IsBlocking = false;
143           this->HasRun = true;
144         }
145       }
146     }
147
148     // should we execute?
149     else if (!this->IsBlocking) {
150       cmExecutionStatus status(mf);
151       mf.ExecuteCommand(func, status);
152       if (status.GetReturnInvoked()) {
153         inStatus.SetReturnInvoked();
154         return true;
155       }
156       if (status.GetBreakInvoked()) {
157         inStatus.SetBreakInvoked();
158         return true;
159       }
160       if (status.GetContinueInvoked()) {
161         inStatus.SetContinueInvoked();
162         return true;
163       }
164     }
165   }
166   return true;
167 }
168
169 //=========================================================================
170 bool cmIfCommand(std::vector<cmListFileArgument> const& args,
171                  cmExecutionStatus& inStatus)
172 {
173   cmMakefile& makefile = inStatus.GetMakefile();
174   std::string errorString;
175
176   std::vector<cmExpandedCommandArgument> expandedArguments;
177   makefile.ExpandArguments(args, expandedArguments);
178
179   MessageType status;
180
181   cmConditionEvaluator conditionEvaluator(makefile, makefile.GetBacktrace());
182
183   bool isTrue =
184     conditionEvaluator.IsTrue(expandedArguments, errorString, status);
185
186   if (!errorString.empty()) {
187     std::string err =
188       cmStrCat("if ", cmIfCommandError(expandedArguments), errorString);
189     if (status == MessageType::FATAL_ERROR) {
190       makefile.IssueMessage(MessageType::FATAL_ERROR, err);
191       cmSystemTools::SetFatalErrorOccurred();
192       return true;
193     }
194     makefile.IssueMessage(status, err);
195   }
196
197   {
198     auto fb = cm::make_unique<cmIfFunctionBlocker>();
199     // if is isn't true block the commands
200     fb->IsBlocking = !isTrue;
201     if (isTrue) {
202       fb->HasRun = true;
203     }
204     fb->Args = args;
205     makefile.AddFunctionBlocker(std::move(fb));
206   }
207
208   return true;
209 }