cmForEachCommand.cxx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  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. #include <algorithm>
  5. #include <cstddef>
  6. // NOTE The declaration of `std::abs` has moved to `cmath` since C++17
  7. // See https://en.cppreference.com/w/cpp/numeric/math/abs
  8. // ALERT But IWYU used to lint `#include`s do not "understand"
  9. // conditional compilation (i.e. `#if __cplusplus >= 201703L`)
  10. #include <cstdlib>
  11. #include <utility>
  12. #include <cm/memory>
  13. #include <cm/string_view>
  14. #include "cm_static_string_view.hxx"
  15. #include "cmExecutionStatus.h"
  16. #include "cmFunctionBlocker.h"
  17. #include "cmListFileCache.h"
  18. #include "cmMakefile.h"
  19. #include "cmMessageType.h"
  20. #include "cmRange.h"
  21. #include "cmStringAlgorithms.h"
  22. #include "cmSystemTools.h"
  23. namespace {
  24. class cmForEachFunctionBlocker : public cmFunctionBlocker
  25. {
  26. public:
  27. cmForEachFunctionBlocker(cmMakefile* mf);
  28. ~cmForEachFunctionBlocker() override;
  29. cm::string_view StartCommandName() const override { return "foreach"_s; }
  30. cm::string_view EndCommandName() const override { return "endforeach"_s; }
  31. bool ArgumentsMatch(cmListFileFunction const& lff,
  32. cmMakefile& mf) const override;
  33. bool Replay(std::vector<cmListFileFunction> functions,
  34. cmExecutionStatus& inStatus) override;
  35. std::vector<std::string> Args;
  36. private:
  37. cmMakefile* Makefile;
  38. };
  39. cmForEachFunctionBlocker::cmForEachFunctionBlocker(cmMakefile* mf)
  40. : Makefile(mf)
  41. {
  42. this->Makefile->PushLoopBlock();
  43. }
  44. cmForEachFunctionBlocker::~cmForEachFunctionBlocker()
  45. {
  46. this->Makefile->PopLoopBlock();
  47. }
  48. bool cmForEachFunctionBlocker::ArgumentsMatch(cmListFileFunction const& lff,
  49. cmMakefile& mf) const
  50. {
  51. std::vector<std::string> expandedArguments;
  52. mf.ExpandArguments(lff.Arguments, expandedArguments);
  53. return expandedArguments.empty() ||
  54. expandedArguments.front() == this->Args.front();
  55. }
  56. bool cmForEachFunctionBlocker::Replay(
  57. std::vector<cmListFileFunction> functions, cmExecutionStatus& inStatus)
  58. {
  59. cmMakefile& mf = inStatus.GetMakefile();
  60. // at end of for each execute recorded commands
  61. // store the old value
  62. std::string oldDef;
  63. if (mf.GetDefinition(this->Args.front())) {
  64. oldDef = mf.GetDefinition(this->Args.front());
  65. }
  66. for (std::string const& arg : cmMakeRange(this->Args).advance(1)) {
  67. // set the variable to the loop value
  68. mf.AddDefinition(this->Args.front(), arg);
  69. // Invoke all the functions that were collected in the block.
  70. for (cmListFileFunction const& func : functions) {
  71. cmExecutionStatus status(mf);
  72. mf.ExecuteCommand(func, status);
  73. if (status.GetReturnInvoked()) {
  74. inStatus.SetReturnInvoked();
  75. // restore the variable to its prior value
  76. mf.AddDefinition(this->Args.front(), oldDef);
  77. return true;
  78. }
  79. if (status.GetBreakInvoked()) {
  80. // restore the variable to its prior value
  81. mf.AddDefinition(this->Args.front(), oldDef);
  82. return true;
  83. }
  84. if (status.GetContinueInvoked()) {
  85. break;
  86. }
  87. if (cmSystemTools::GetFatalErrorOccured()) {
  88. return true;
  89. }
  90. }
  91. }
  92. // restore the variable to its prior value
  93. mf.AddDefinition(this->Args.front(), oldDef);
  94. return true;
  95. }
  96. bool HandleInMode(std::vector<std::string> const& args, cmMakefile& makefile)
  97. {
  98. auto fb = cm::make_unique<cmForEachFunctionBlocker>(&makefile);
  99. fb->Args.push_back(args.front());
  100. enum Doing
  101. {
  102. DoingNone,
  103. DoingLists,
  104. DoingItems
  105. };
  106. Doing doing = DoingNone;
  107. for (std::string const& arg : cmMakeRange(args).advance(2)) {
  108. if (arg == "LISTS") {
  109. doing = DoingLists;
  110. } else if (arg == "ITEMS") {
  111. doing = DoingItems;
  112. } else if (doing == DoingLists) {
  113. auto const& value = makefile.GetSafeDefinition(arg);
  114. if (!value.empty()) {
  115. cmExpandList(value, fb->Args, true);
  116. }
  117. } else if (doing == DoingItems) {
  118. fb->Args.push_back(arg);
  119. } else {
  120. makefile.IssueMessage(MessageType::FATAL_ERROR,
  121. cmStrCat("Unknown argument:\n", " ", arg, "\n"));
  122. return true;
  123. }
  124. }
  125. makefile.AddFunctionBlocker(std::move(fb));
  126. return true;
  127. }
  128. } // anonymous namespace
  129. bool cmForEachCommand(std::vector<std::string> const& args,
  130. cmExecutionStatus& status)
  131. {
  132. if (args.empty()) {
  133. status.SetError("called with incorrect number of arguments");
  134. return false;
  135. }
  136. if (args.size() > 1 && args[1] == "IN") {
  137. return HandleInMode(args, status.GetMakefile());
  138. }
  139. // create a function blocker
  140. auto fb = cm::make_unique<cmForEachFunctionBlocker>(&status.GetMakefile());
  141. if (args.size() > 1) {
  142. if (args[1] == "RANGE") {
  143. int start = 0;
  144. int stop = 0;
  145. int step = 0;
  146. if (args.size() == 3) {
  147. stop = std::stoi(args[2]);
  148. }
  149. if (args.size() == 4) {
  150. start = std::stoi(args[2]);
  151. stop = std::stoi(args[3]);
  152. }
  153. if (args.size() == 5) {
  154. start = std::stoi(args[2]);
  155. stop = std::stoi(args[3]);
  156. step = std::stoi(args[4]);
  157. }
  158. if (step == 0) {
  159. if (start > stop) {
  160. step = -1;
  161. } else {
  162. step = 1;
  163. }
  164. }
  165. if ((start > stop && step > 0) || (start < stop && step < 0) ||
  166. step == 0) {
  167. status.SetError(
  168. cmStrCat("called with incorrect range specification: start ", start,
  169. ", stop ", stop, ", step ", step));
  170. return false;
  171. }
  172. // Calculate expected iterations count and reserve enough space
  173. // in the `fb->Args` vector. The first item is the iteration variable
  174. // name...
  175. const std::size_t iter_cnt = 2u +
  176. int(start < stop) * (stop - start) / std::abs(step) +
  177. int(start > stop) * (start - stop) / std::abs(step);
  178. fb->Args.resize(iter_cnt);
  179. fb->Args.front() = args.front();
  180. auto cc = start;
  181. auto generator = [&cc, step]() -> std::string {
  182. auto result = std::to_string(cc);
  183. cc += step;
  184. return result;
  185. };
  186. // Fill the `range` vector w/ generated string values
  187. // (starting from 2nd position)
  188. std::generate(++fb->Args.begin(), fb->Args.end(), generator);
  189. } else {
  190. fb->Args = args;
  191. }
  192. } else {
  193. fb->Args = args;
  194. }
  195. status.GetMakefile().AddFunctionBlocker(std::move(fb));
  196. return true;
  197. }