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"
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`)
20 #include <cm/optional>
21 #include <cm/string_view>
22 #include <cmext/string_view>
24 #include "cmExecutionStatus.h"
25 #include "cmFunctionBlocker.h"
26 #include "cmListFileCache.h"
27 #include "cmMakefile.h"
28 #include "cmMessageType.h"
29 #include "cmPolicies.h"
31 #include "cmStringAlgorithms.h"
32 #include "cmSystemTools.h"
36 class cmForEachFunctionBlocker : public cmFunctionBlocker
39 explicit cmForEachFunctionBlocker(cmMakefile* mf);
40 ~cmForEachFunctionBlocker() override;
42 cm::string_view StartCommandName() const override { return "foreach"_s; }
43 cm::string_view EndCommandName() const override { return "endforeach"_s; }
45 bool ArgumentsMatch(cmListFileFunction const& lff,
46 cmMakefile& mf) const override;
48 bool Replay(std::vector<cmListFileFunction> functions,
49 cmExecutionStatus& inStatus) override;
51 void SetIterationVarsCount(const std::size_t varsCount)
53 this->IterationVarsCount = varsCount;
55 void SetZipLists() { this->ZipLists = true; }
57 std::vector<std::string> Args;
66 bool ReplayItems(std::vector<cmListFileFunction> const& functions,
67 cmExecutionStatus& inStatus);
69 bool ReplayZipLists(std::vector<cmListFileFunction> const& functions,
70 cmExecutionStatus& inStatus);
72 InvokeResult invoke(std::vector<cmListFileFunction> const& functions,
73 cmExecutionStatus& inStatus, cmMakefile& mf);
76 std::size_t IterationVarsCount = 0u;
77 bool ZipLists = false;
80 cmForEachFunctionBlocker::cmForEachFunctionBlocker(cmMakefile* mf)
83 this->Makefile->PushLoopBlock();
86 cmForEachFunctionBlocker::~cmForEachFunctionBlocker()
88 this->Makefile->PopLoopBlock();
91 bool cmForEachFunctionBlocker::ArgumentsMatch(cmListFileFunction const& lff,
94 std::vector<std::string> expandedArguments;
95 mf.ExpandArguments(lff.Arguments(), expandedArguments);
96 return expandedArguments.empty() ||
97 expandedArguments.front() == this->Args.front();
100 bool cmForEachFunctionBlocker::Replay(
101 std::vector<cmListFileFunction> functions, cmExecutionStatus& inStatus)
103 return this->ZipLists ? this->ReplayZipLists(functions, inStatus)
104 : this->ReplayItems(functions, inStatus);
107 bool cmForEachFunctionBlocker::ReplayItems(
108 std::vector<cmListFileFunction> const& functions,
109 cmExecutionStatus& inStatus)
111 assert("Unexpected number of iteration variables" &&
112 this->IterationVarsCount == 1);
114 auto& mf = inStatus.GetMakefile();
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());
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);
139 // restore the variable to its prior value
140 mf.AddDefinition(this->Args.front(), *oldDef);
142 mf.RemoveDefinition(this->Args.front());
149 bool cmForEachFunctionBlocker::ReplayZipLists(
150 std::vector<cmListFileFunction> const& functions,
151 cmExecutionStatus& inStatus)
153 assert("Unexpected number of iteration variables" &&
154 this->IterationVarsCount >= 1);
156 auto& mf = inStatus.GetMakefile();
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);
170 maxItems = std::max(maxItems, items.size());
171 values.emplace_back(std::move(items));
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));
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() + "_";
191 iterationVars.begin(), iterationVars.end(),
192 [&]() -> std::string { return iter_var_prefix + std::to_string(i++); });
194 assert("Sanity check" && iterationVars.size() == values.size());
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));
205 oldDefs.emplace(varName, cm::nullopt);
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());
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());
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]);
229 mf.RemoveDefinition(iterationVars[j]);
232 // Invoke all the functions that were collected in the block.
233 auto r = this->invoke(functions, inStatus, mf);
240 // Restore the variables to its prior value
242 for (auto const& p : oldDefs) {
244 mf.AddDefinition(p.first, *p.second);
246 mf.RemoveDefinition(p.first);
253 auto cmForEachFunctionBlocker::invoke(
254 std::vector<cmListFileFunction> const& functions,
255 cmExecutionStatus& inStatus, cmMakefile& mf) -> InvokeResult
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();
267 if (status.GetBreakInvoked()) {
271 if (status.GetContinueInvoked()) {
274 if (cmSystemTools::GetFatalErrorOccurred()) {
275 result.Restore = false;
283 bool HandleInMode(std::vector<std::string> const& args,
284 std::vector<std::string>::const_iterator kwInIter,
285 cmMakefile& makefile)
287 assert("A valid iterator expected" && kwInIter != args.end());
289 auto fb = cm::make_unique<cmForEachFunctionBlocker>(&makefile);
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);
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");
313 if (varsCount != 1u) {
314 makefile.IssueMessage(
315 MessageType::FATAL_ERROR,
316 "ITEMS or LISTS require exactly one iteration variable");
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");
327 if (varsCount != 1u) {
328 makefile.IssueMessage(
329 MessageType::FATAL_ERROR,
330 "ITEMS or LISTS require exactly one iteration variable");
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");
341 doing = DoingZipLists;
344 } else if (doing == DoingLists) {
345 auto const& value = makefile.GetSafeDefinition(arg);
346 if (!value.empty()) {
347 cmExpandList(value, fb->Args, true);
350 } else if (doing == DoingItems || doing == DoingZipLists) {
351 fb->Args.push_back(arg);
354 makefile.IssueMessage(MessageType::FATAL_ERROR,
355 cmStrCat("Unknown argument:\n", " ", arg, "\n"));
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)));
372 makefile.AddFunctionBlocker(std::move(fb));
377 bool TryParseInteger(cmExecutionStatus& status, const std::string& str, int& i)
381 } catch (std::invalid_argument&) {
382 std::ostringstream e;
383 e << "Invalid integer: '" << str << "'";
384 status.SetError(e.str());
385 cmSystemTools::SetFatalErrorOccurred();
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();
398 } // anonymous namespace
400 bool cmForEachCommand(std::vector<std::string> const& args,
401 cmExecutionStatus& status)
404 status.SetError("called with incorrect number of arguments");
407 auto kwInIter = std::find(args.begin(), args.end(), "IN");
408 if (kwInIter != args.end()) {
409 return HandleInMode(args, kwInIter, status.GetMakefile());
412 // create a function blocker
413 auto fb = cm::make_unique<cmForEachFunctionBlocker>(&status.GetMakefile());
414 if (args.size() > 1) {
415 if (args[1] == "RANGE") {
419 if (args.size() == 3) {
420 if (!TryParseInteger(status, args[2], stop)) {
424 if (args.size() == 4) {
425 if (!TryParseInteger(status, args[2], start)) {
428 if (!TryParseInteger(status, args[3], stop)) {
432 if (args.size() == 5) {
433 if (!TryParseInteger(status, args[2], start)) {
436 if (!TryParseInteger(status, args[3], stop)) {
439 if (!TryParseInteger(status, args[4], step)) {
450 if ((start > stop && step > 0) || (start < stop && step < 0) ||
453 cmStrCat("called with incorrect range specification: start ", start,
454 ", stop ", stop, ", step ", step));
455 cmSystemTools::SetFatalErrorOccurred();
459 // Calculate expected iterations count and reserve enough space
460 // in the `fb->Args` vector. The first item is the iteration variable
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();
468 auto generator = [&cc, step]() -> std::string {
469 auto result = std::to_string(cc);
473 // Fill the `range` vector w/ generated string values
474 // (starting from 2nd position)
475 std::generate(++fb->Args.begin(), fb->Args.end(), generator);
483 fb->SetIterationVarsCount(1u);
484 status.GetMakefile().AddFunctionBlocker(std::move(fb));