b9400c95fd292e13cdf45d6fcff50bd74f720fdf
[platform/upstream/cmake.git] / Source / cmForEachCommand.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 "cmForEachCommand.h"
4
5 #include <algorithm>
6 #include <cassert>
7 #include <cstddef> // IWYU pragma: keep
8 // NOTE The declaration of `std::abs` has moved to `cmath` since C++17
9 // See https://en.cppreference.com/w/cpp/numeric/math/abs
10 // ALERT But IWYU used to lint `#include`s do not "understand"
11 // conditional compilation (i.e. `#if __cplusplus >= 201703L`)
12 #include <cstdlib>
13 #include <iterator>
14 #include <map>
15 #include <sstream>
16 #include <stdexcept>
17 #include <utility>
18
19 #include <cm/memory>
20 #include <cm/optional>
21 #include <cm/string_view>
22 #include <cmext/string_view>
23
24 #include "cmExecutionStatus.h"
25 #include "cmFunctionBlocker.h"
26 #include "cmListFileCache.h"
27 #include "cmMakefile.h"
28 #include "cmMessageType.h"
29 #include "cmPolicies.h"
30 #include "cmRange.h"
31 #include "cmStringAlgorithms.h"
32 #include "cmSystemTools.h"
33 #include "cmValue.h"
34
35 namespace {
36 class cmForEachFunctionBlocker : public cmFunctionBlocker
37 {
38 public:
39   explicit cmForEachFunctionBlocker(cmMakefile* mf);
40   ~cmForEachFunctionBlocker() override;
41
42   cm::string_view StartCommandName() const override { return "foreach"_s; }
43   cm::string_view EndCommandName() const override { return "endforeach"_s; }
44
45   bool ArgumentsMatch(cmListFileFunction const& lff,
46                       cmMakefile& mf) const override;
47
48   bool Replay(std::vector<cmListFileFunction> functions,
49               cmExecutionStatus& inStatus) override;
50
51   void SetIterationVarsCount(const std::size_t varsCount)
52   {
53     this->IterationVarsCount = varsCount;
54   }
55   void SetZipLists() { this->ZipLists = true; }
56
57   std::vector<std::string> Args;
58
59 private:
60   struct InvokeResult
61   {
62     bool Restore;
63     bool Break;
64   };
65
66   bool ReplayItems(std::vector<cmListFileFunction> const& functions,
67                    cmExecutionStatus& inStatus);
68
69   bool ReplayZipLists(std::vector<cmListFileFunction> const& functions,
70                       cmExecutionStatus& inStatus);
71
72   InvokeResult invoke(std::vector<cmListFileFunction> const& functions,
73                       cmExecutionStatus& inStatus, cmMakefile& mf);
74
75   cmMakefile* Makefile;
76   std::size_t IterationVarsCount = 0u;
77   bool ZipLists = false;
78 };
79
80 cmForEachFunctionBlocker::cmForEachFunctionBlocker(cmMakefile* mf)
81   : Makefile(mf)
82 {
83   this->Makefile->PushLoopBlock();
84 }
85
86 cmForEachFunctionBlocker::~cmForEachFunctionBlocker()
87 {
88   this->Makefile->PopLoopBlock();
89 }
90
91 bool cmForEachFunctionBlocker::ArgumentsMatch(cmListFileFunction const& lff,
92                                               cmMakefile& mf) const
93 {
94   std::vector<std::string> expandedArguments;
95   mf.ExpandArguments(lff.Arguments(), expandedArguments);
96   return expandedArguments.empty() ||
97     expandedArguments.front() == this->Args.front();
98 }
99
100 bool cmForEachFunctionBlocker::Replay(
101   std::vector<cmListFileFunction> functions, cmExecutionStatus& inStatus)
102 {
103   return this->ZipLists ? this->ReplayZipLists(functions, inStatus)
104                         : this->ReplayItems(functions, inStatus);
105 }
106
107 bool cmForEachFunctionBlocker::ReplayItems(
108   std::vector<cmListFileFunction> const& functions,
109   cmExecutionStatus& inStatus)
110 {
111   assert("Unexpected number of iteration variables" &&
112          this->IterationVarsCount == 1);
113
114   auto& mf = inStatus.GetMakefile();
115
116   // At end of for each execute recorded commands
117   // store the old value
118   cm::optional<std::string> oldDef;
119   if (mf.GetPolicyStatus(cmPolicies::CMP0124) != cmPolicies::NEW) {
120     oldDef = mf.GetSafeDefinition(this->Args.front());
121   } else if (mf.IsNormalDefinitionSet(this->Args.front())) {
122     oldDef = *mf.GetDefinition(this->Args.front());
123   }
124
125   auto restore = false;
126   for (std::string const& arg : cmMakeRange(this->Args).advance(1)) {
127     // Set the variable to the loop value
128     mf.AddDefinition(this->Args.front(), arg);
129     // Invoke all the functions that were collected in the block.
130     auto r = this->invoke(functions, inStatus, mf);
131     restore = r.Restore;
132     if (r.Break) {
133       break;
134     }
135   }
136
137   if (restore) {
138     if (oldDef) {
139       // restore the variable to its prior value
140       mf.AddDefinition(this->Args.front(), *oldDef);
141     } else {
142       mf.RemoveDefinition(this->Args.front());
143     }
144   }
145
146   return true;
147 }
148
149 bool cmForEachFunctionBlocker::ReplayZipLists(
150   std::vector<cmListFileFunction> const& functions,
151   cmExecutionStatus& inStatus)
152 {
153   assert("Unexpected number of iteration variables" &&
154          this->IterationVarsCount >= 1);
155
156   auto& mf = inStatus.GetMakefile();
157
158   // Expand the list of list-variables into a list of lists of strings
159   std::vector<std::vector<std::string>> values;
160   values.reserve(this->Args.size() - this->IterationVarsCount);
161   // Also track the longest list size
162   std::size_t maxItems = 0u;
163   for (auto const& var :
164        cmMakeRange(this->Args).advance(this->IterationVarsCount)) {
165     std::vector<std::string> items;
166     auto const& value = mf.GetSafeDefinition(var);
167     if (!value.empty()) {
168       cmExpandList(value, items, true);
169     }
170     maxItems = std::max(maxItems, items.size());
171     values.emplace_back(std::move(items));
172   }
173
174   // Form the list of iteration variables
175   std::vector<std::string> iterationVars;
176   if (this->IterationVarsCount > 1) {
177     // If multiple iteration variables has given,
178     // just copy them to the `iterationVars` list.
179     iterationVars.reserve(values.size());
180     std::copy(this->Args.begin(),
181               this->Args.begin() + this->IterationVarsCount,
182               std::back_inserter(iterationVars));
183   } else {
184     // In case of the only iteration variable,
185     // generate names as `var_name_N`,
186     // where `N` is the count of lists to zip
187     iterationVars.resize(values.size());
188     const auto iter_var_prefix = this->Args.front() + "_";
189     auto i = 0u;
190     std::generate(
191       iterationVars.begin(), iterationVars.end(),
192       [&]() -> std::string { return iter_var_prefix + std::to_string(i++); });
193   }
194   assert("Sanity check" && iterationVars.size() == values.size());
195
196   // Store old values for iteration variables
197   std::map<std::string, cm::optional<std::string>> oldDefs;
198   for (auto i = 0u; i < values.size(); ++i) {
199     const auto& varName = iterationVars[i];
200     if (mf.GetPolicyStatus(cmPolicies::CMP0124) != cmPolicies::NEW) {
201       oldDefs.emplace(varName, mf.GetSafeDefinition(varName));
202     } else if (mf.IsNormalDefinitionSet(varName)) {
203       oldDefs.emplace(varName, *mf.GetDefinition(varName));
204     } else {
205       oldDefs.emplace(varName, cm::nullopt);
206     }
207   }
208
209   // Form a vector of current positions in all lists (Ok, vectors) of values
210   std::vector<decltype(values)::value_type::iterator> positions;
211   positions.reserve(values.size());
212   std::transform(
213     values.begin(), values.end(), std::back_inserter(positions),
214     // Set the initial position to the beginning of every list
215     [](decltype(values)::value_type& list) { return list.begin(); });
216   assert("Sanity check" && positions.size() == values.size());
217
218   auto restore = false;
219   // Iterate over all the lists simulateneously
220   for (auto i = 0u; i < maxItems; ++i) {
221     // Declare iteration variables
222     for (auto j = 0u; j < values.size(); ++j) {
223       // Define (or not) the iteration variable if the current position
224       // still not at the end...
225       if (positions[j] != values[j].end()) {
226         mf.AddDefinition(iterationVars[j], *positions[j]);
227         ++positions[j];
228       } else {
229         mf.RemoveDefinition(iterationVars[j]);
230       }
231     }
232     // Invoke all the functions that were collected in the block.
233     auto r = this->invoke(functions, inStatus, mf);
234     restore = r.Restore;
235     if (r.Break) {
236       break;
237     }
238   }
239
240   // Restore the variables to its prior value
241   if (restore) {
242     for (auto const& p : oldDefs) {
243       if (p.second) {
244         mf.AddDefinition(p.first, *p.second);
245       } else {
246         mf.RemoveDefinition(p.first);
247       }
248     }
249   }
250   return true;
251 }
252
253 auto cmForEachFunctionBlocker::invoke(
254   std::vector<cmListFileFunction> const& functions,
255   cmExecutionStatus& inStatus, cmMakefile& mf) -> InvokeResult
256 {
257   InvokeResult result = { true, false };
258   // Invoke all the functions that were collected in the block.
259   for (cmListFileFunction const& func : functions) {
260     cmExecutionStatus status(mf);
261     mf.ExecuteCommand(func, status);
262     if (status.GetReturnInvoked()) {
263       inStatus.SetReturnInvoked();
264       result.Break = true;
265       break;
266     }
267     if (status.GetBreakInvoked()) {
268       result.Break = true;
269       break;
270     }
271     if (status.GetContinueInvoked()) {
272       break;
273     }
274     if (cmSystemTools::GetFatalErrorOccurred()) {
275       result.Restore = false;
276       result.Break = true;
277       break;
278     }
279   }
280   return result;
281 }
282
283 bool HandleInMode(std::vector<std::string> const& args,
284                   std::vector<std::string>::const_iterator kwInIter,
285                   cmMakefile& makefile)
286 {
287   assert("A valid iterator expected" && kwInIter != args.end());
288
289   auto fb = cm::make_unique<cmForEachFunctionBlocker>(&makefile);
290
291   // Copy iteration variable names first
292   std::copy(args.begin(), kwInIter, std::back_inserter(fb->Args));
293   // Remember the count of given iteration variable names
294   const auto varsCount = fb->Args.size();
295   fb->SetIterationVarsCount(varsCount);
296
297   enum Doing
298   {
299     DoingNone,
300     DoingLists,
301     DoingItems,
302     DoingZipLists
303   };
304   Doing doing = DoingNone;
305   // Iterate over arguments past the "IN" keyword
306   for (std::string const& arg : cmMakeRange(++kwInIter, args.end())) {
307     if (arg == "LISTS") {
308       if (doing == DoingZipLists) {
309         makefile.IssueMessage(MessageType::FATAL_ERROR,
310                               "ZIP_LISTS can not be used with LISTS or ITEMS");
311         return true;
312       }
313       if (varsCount != 1u) {
314         makefile.IssueMessage(
315           MessageType::FATAL_ERROR,
316           "ITEMS or LISTS require exactly one iteration variable");
317         return true;
318       }
319       doing = DoingLists;
320
321     } else if (arg == "ITEMS") {
322       if (doing == DoingZipLists) {
323         makefile.IssueMessage(MessageType::FATAL_ERROR,
324                               "ZIP_LISTS can not be used with LISTS or ITEMS");
325         return true;
326       }
327       if (varsCount != 1u) {
328         makefile.IssueMessage(
329           MessageType::FATAL_ERROR,
330           "ITEMS or LISTS require exactly one iteration variable");
331         return true;
332       }
333       doing = DoingItems;
334
335     } else if (arg == "ZIP_LISTS") {
336       if (doing != DoingNone) {
337         makefile.IssueMessage(MessageType::FATAL_ERROR,
338                               "ZIP_LISTS can not be used with LISTS or ITEMS");
339         return true;
340       }
341       doing = DoingZipLists;
342       fb->SetZipLists();
343
344     } else if (doing == DoingLists) {
345       auto const& value = makefile.GetSafeDefinition(arg);
346       if (!value.empty()) {
347         cmExpandList(value, fb->Args, true);
348       }
349
350     } else if (doing == DoingItems || doing == DoingZipLists) {
351       fb->Args.push_back(arg);
352
353     } else {
354       makefile.IssueMessage(MessageType::FATAL_ERROR,
355                             cmStrCat("Unknown argument:\n", "  ", arg, "\n"));
356       return true;
357     }
358   }
359
360   // If `ZIP_LISTS` given and variables count more than 1,
361   // make sure the given lists count matches variables...
362   if (doing == DoingZipLists && varsCount > 1u &&
363       (2u * varsCount) != fb->Args.size()) {
364     makefile.IssueMessage(
365       MessageType::FATAL_ERROR,
366       cmStrCat("Expected ", std::to_string(varsCount),
367                " list variables, but given ",
368                std::to_string(fb->Args.size() - varsCount)));
369     return true;
370   }
371
372   makefile.AddFunctionBlocker(std::move(fb));
373
374   return true;
375 }
376
377 bool TryParseInteger(cmExecutionStatus& status, const std::string& str, int& i)
378 {
379   try {
380     i = std::stoi(str);
381   } catch (std::invalid_argument&) {
382     std::ostringstream e;
383     e << "Invalid integer: '" << str << "'";
384     status.SetError(e.str());
385     cmSystemTools::SetFatalErrorOccurred();
386     return false;
387   } catch (std::out_of_range&) {
388     std::ostringstream e;
389     e << "Integer out of range: '" << str << "'";
390     status.SetError(e.str());
391     cmSystemTools::SetFatalErrorOccurred();
392     return false;
393   }
394
395   return true;
396 }
397
398 } // anonymous namespace
399
400 bool cmForEachCommand(std::vector<std::string> const& args,
401                       cmExecutionStatus& status)
402 {
403   if (args.empty()) {
404     status.SetError("called with incorrect number of arguments");
405     return false;
406   }
407   auto kwInIter = std::find(args.begin(), args.end(), "IN");
408   if (kwInIter != args.end()) {
409     return HandleInMode(args, kwInIter, status.GetMakefile());
410   }
411
412   // create a function blocker
413   auto fb = cm::make_unique<cmForEachFunctionBlocker>(&status.GetMakefile());
414   if (args.size() > 1) {
415     if (args[1] == "RANGE") {
416       int start = 0;
417       int stop = 0;
418       int step = 0;
419       if (args.size() == 3) {
420         if (!TryParseInteger(status, args[2], stop)) {
421           return false;
422         }
423       }
424       if (args.size() == 4) {
425         if (!TryParseInteger(status, args[2], start)) {
426           return false;
427         }
428         if (!TryParseInteger(status, args[3], stop)) {
429           return false;
430         }
431       }
432       if (args.size() == 5) {
433         if (!TryParseInteger(status, args[2], start)) {
434           return false;
435         }
436         if (!TryParseInteger(status, args[3], stop)) {
437           return false;
438         }
439         if (!TryParseInteger(status, args[4], step)) {
440           return false;
441         }
442       }
443       if (step == 0) {
444         if (start > stop) {
445           step = -1;
446         } else {
447           step = 1;
448         }
449       }
450       if ((start > stop && step > 0) || (start < stop && step < 0) ||
451           step == 0) {
452         status.SetError(
453           cmStrCat("called with incorrect range specification: start ", start,
454                    ", stop ", stop, ", step ", step));
455         cmSystemTools::SetFatalErrorOccurred();
456         return false;
457       }
458
459       // Calculate expected iterations count and reserve enough space
460       // in the `fb->Args` vector. The first item is the iteration variable
461       // name...
462       const std::size_t iter_cnt = 2u +
463         static_cast<int>(start < stop) * (stop - start) / std::abs(step) +
464         static_cast<int>(start > stop) * (start - stop) / std::abs(step);
465       fb->Args.resize(iter_cnt);
466       fb->Args.front() = args.front();
467       auto cc = start;
468       auto generator = [&cc, step]() -> std::string {
469         auto result = std::to_string(cc);
470         cc += step;
471         return result;
472       };
473       // Fill the `range` vector w/ generated string values
474       // (starting from 2nd position)
475       std::generate(++fb->Args.begin(), fb->Args.end(), generator);
476     } else {
477       fb->Args = args;
478     }
479   } else {
480     fb->Args = args;
481   }
482
483   fb->SetIterationVarsCount(1u);
484   status.GetMakefile().AddFunctionBlocker(std::move(fb));
485
486   return true;
487 }