Imported Upstream version 3.25.0
[platform/upstream/cmake.git] / Source / cmWhileCommand.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 "cmWhileCommand.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 "cmPolicies.h"
21 #include "cmStringAlgorithms.h"
22 #include "cmSystemTools.h"
23 #include "cmake.h"
24
25 class cmWhileFunctionBlocker : public cmFunctionBlocker
26 {
27 public:
28   cmWhileFunctionBlocker(cmMakefile* mf, std::vector<cmListFileArgument> args);
29   ~cmWhileFunctionBlocker() override;
30
31   cm::string_view StartCommandName() const override { return "while"_s; }
32   cm::string_view EndCommandName() const override { return "endwhile"_s; }
33
34   bool ArgumentsMatch(cmListFileFunction const& lff,
35                       cmMakefile& mf) const override;
36
37   bool Replay(std::vector<cmListFileFunction> functions,
38               cmExecutionStatus& inStatus) override;
39
40 private:
41   cmMakefile* Makefile;
42   std::vector<cmListFileArgument> Args;
43 };
44
45 cmWhileFunctionBlocker::cmWhileFunctionBlocker(
46   cmMakefile* const mf, std::vector<cmListFileArgument> args)
47   : Makefile{ mf }
48   , Args{ std::move(args) }
49 {
50   this->Makefile->PushLoopBlock();
51 }
52
53 cmWhileFunctionBlocker::~cmWhileFunctionBlocker()
54 {
55   this->Makefile->PopLoopBlock();
56 }
57
58 bool cmWhileFunctionBlocker::ArgumentsMatch(cmListFileFunction const& lff,
59                                             cmMakefile&) const
60 {
61   return lff.Arguments().empty() || lff.Arguments() == this->Args;
62 }
63
64 bool cmWhileFunctionBlocker::Replay(std::vector<cmListFileFunction> functions,
65                                     cmExecutionStatus& inStatus)
66 {
67   auto& mf = inStatus.GetMakefile();
68
69   cmListFileBacktrace whileBT =
70     mf.GetBacktrace().Push(this->GetStartingContext());
71
72   std::vector<cmExpandedCommandArgument> expandedArguments;
73   // At least same size expected for `expandedArguments` as `Args`
74   expandedArguments.reserve(this->Args.size());
75
76   auto expandArgs = [&mf](std::vector<cmListFileArgument> const& args,
77                           std::vector<cmExpandedCommandArgument>& out)
78     -> std::vector<cmExpandedCommandArgument>& {
79     out.clear();
80     mf.ExpandArguments(args, out);
81     return out;
82   };
83
84   // For compatibility with projects that do not set CMP0130 to NEW,
85   // we tolerate condition errors that evaluate to false.
86   bool enforceError = true;
87   std::string errorString;
88   MessageType messageType;
89
90   for (cmConditionEvaluator conditionEvaluator(mf, whileBT);
91        (enforceError = /* enforce condition errors that evaluate to true */
92         conditionEvaluator.IsTrue(expandArgs(this->Args, expandedArguments),
93                                   errorString, messageType));) {
94     // Invoke all the functions that were collected in the block.
95     for (cmListFileFunction const& fn : functions) {
96       cmExecutionStatus status(mf);
97       mf.ExecuteCommand(fn, status);
98       if (status.GetReturnInvoked()) {
99         inStatus.SetReturnInvoked(status.GetReturnVariables());
100         return true;
101       }
102       if (status.GetBreakInvoked()) {
103         return true;
104       }
105       if (status.GetContinueInvoked()) {
106         break;
107       }
108       if (cmSystemTools::GetFatalErrorOccurred()) {
109         return true;
110       }
111     }
112   }
113
114   if (!errorString.empty() && !enforceError) {
115     // This error should only be enforced if CMP0130 is NEW.
116     switch (mf.GetPolicyStatus(cmPolicies::CMP0130)) {
117       case cmPolicies::WARN:
118         // Convert the error to a warning and enforce it.
119         messageType = MessageType::AUTHOR_WARNING;
120         enforceError = true;
121         break;
122       case cmPolicies::OLD:
123         // OLD behavior is to silently ignore the error.
124         break;
125       case cmPolicies::REQUIRED_ALWAYS:
126       case cmPolicies::REQUIRED_IF_USED:
127       case cmPolicies::NEW:
128         // NEW behavior is to enforce the error.
129         enforceError = true;
130         break;
131     }
132   }
133
134   if (!errorString.empty() && enforceError) {
135     std::string err = "while() given incorrect arguments:\n ";
136     for (auto const& i : expandedArguments) {
137       err += " ";
138       err += cmOutputConverter::EscapeForCMake(i.GetValue());
139     }
140     err += "\n";
141     err += errorString;
142     if (mf.GetPolicyStatus(cmPolicies::CMP0130) == cmPolicies::WARN) {
143       err =
144         cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0130), '\n', err);
145     }
146     mf.GetCMakeInstance()->IssueMessage(messageType, err, whileBT);
147     if (messageType == MessageType::FATAL_ERROR) {
148       cmSystemTools::SetFatalErrorOccurred();
149     }
150   }
151
152   return true;
153 }
154
155 bool cmWhileCommand(std::vector<cmListFileArgument> const& args,
156                     cmExecutionStatus& status)
157 {
158   if (args.empty()) {
159     status.SetError("called with incorrect number of arguments");
160     return false;
161   }
162
163   // create a function blocker
164   auto& makefile = status.GetMakefile();
165   makefile.AddFunctionBlocker(
166     cm::make_unique<cmWhileFunctionBlocker>(&makefile, args));
167
168   return true;
169 }