diff options
author | Brad King <brad.king@kitware.com> | 2019-12-09 16:25:23 (GMT) |
---|---|---|
committer | Kitware Robot <kwrobot@kitware.com> | 2019-12-09 16:25:35 (GMT) |
commit | f62c674202045f877eaed9da0f9cbe2046452b40 (patch) | |
tree | 691535f0ad6788dfbec850ef0c72ffaa09d5cb54 /Source | |
parent | 17cb41926725a232393796bcd157d86b74fd6ad3 (diff) | |
parent | d30468a2f69f80c4f7d9d16b68f347bc3815dacc (diff) | |
download | CMake-f62c674202045f877eaed9da0f9cbe2046452b40.zip CMake-f62c674202045f877eaed9da0f9cbe2046452b40.tar.gz CMake-f62c674202045f877eaed9da0f9cbe2046452b40.tar.bz2 |
Merge topic 'foreach-ZIP_LISTS'
d30468a2f6 foreach: Allow multiple iteration variables for `ZIP_LIST` mode
f3e51a2b1d foreach: Introduce `IN ZIP_LISTS` mode
Acked-by: Kitware Robot <kwrobot@kitware.com>
Merge-request: !4021
Diffstat (limited to 'Source')
-rw-r--r-- | Source/cmForEachCommand.cxx | 275 |
1 files changed, 243 insertions, 32 deletions
diff --git a/Source/cmForEachCommand.cxx b/Source/cmForEachCommand.cxx index 91cd4ef..ac48287 100644 --- a/Source/cmForEachCommand.cxx +++ b/Source/cmForEachCommand.cxx @@ -3,12 +3,15 @@ #include "cmForEachCommand.h" #include <algorithm> +#include <cassert> #include <cstddef> // NOTE The declaration of `std::abs` has moved to `cmath` since C++17 // See https://en.cppreference.com/w/cpp/numeric/math/abs // ALERT But IWYU used to lint `#include`s do not "understand" // conditional compilation (i.e. `#if __cplusplus >= 201703L`) #include <cstdlib> +#include <iterator> +#include <map> #include <utility> #include <cm/memory> @@ -29,7 +32,7 @@ namespace { class cmForEachFunctionBlocker : public cmFunctionBlocker { public: - cmForEachFunctionBlocker(cmMakefile* mf); + explicit cmForEachFunctionBlocker(cmMakefile* mf); ~cmForEachFunctionBlocker() override; cm::string_view StartCommandName() const override { return "foreach"_s; } @@ -41,10 +44,33 @@ public: bool Replay(std::vector<cmListFileFunction> functions, cmExecutionStatus& inStatus) override; + void SetIterationVarsCount(const std::size_t varsCount) + { + this->IterationVarsCount = varsCount; + } + void SetZipLists() { this->ZipLists = true; } + std::vector<std::string> Args; private: + struct InvokeResult + { + bool Restore; + bool Break; + }; + + bool ReplayItems(std::vector<cmListFileFunction> const& functions, + cmExecutionStatus& inStatus); + + bool ReplayZipLists(std::vector<cmListFileFunction> const& functions, + cmExecutionStatus& inStatus); + + InvokeResult invoke(std::vector<cmListFileFunction> const& functions, + cmExecutionStatus& inStatus, cmMakefile& mf); + cmMakefile* Makefile; + std::size_t IterationVarsCount = 0u; + bool ZipLists = false; }; cmForEachFunctionBlocker::cmForEachFunctionBlocker(cmMakefile* mf) @@ -70,70 +96,240 @@ bool cmForEachFunctionBlocker::ArgumentsMatch(cmListFileFunction const& lff, bool cmForEachFunctionBlocker::Replay( std::vector<cmListFileFunction> functions, cmExecutionStatus& inStatus) { - cmMakefile& mf = inStatus.GetMakefile(); - // at end of for each execute recorded commands + return this->ZipLists ? this->ReplayZipLists(functions, inStatus) + : this->ReplayItems(functions, inStatus); +} + +bool cmForEachFunctionBlocker::ReplayItems( + std::vector<cmListFileFunction> const& functions, + cmExecutionStatus& inStatus) +{ + assert("Unexpected number of iteration variables" && + this->IterationVarsCount == 1); + + auto& mf = inStatus.GetMakefile(); + + // At end of for each execute recorded commands // store the old value std::string oldDef; if (mf.GetDefinition(this->Args.front())) { oldDef = mf.GetDefinition(this->Args.front()); } + auto restore = false; for (std::string const& arg : cmMakeRange(this->Args).advance(1)) { - // set the variable to the loop value + // Set the variable to the loop value mf.AddDefinition(this->Args.front(), arg); // Invoke all the functions that were collected in the block. - for (cmListFileFunction const& func : functions) { - cmExecutionStatus status(mf); - mf.ExecuteCommand(func, status); - if (status.GetReturnInvoked()) { - inStatus.SetReturnInvoked(); - // restore the variable to its prior value - mf.AddDefinition(this->Args.front(), oldDef); - return true; - } - if (status.GetBreakInvoked()) { - // restore the variable to its prior value - mf.AddDefinition(this->Args.front(), oldDef); - return true; - } - if (status.GetContinueInvoked()) { - break; - } - if (cmSystemTools::GetFatalErrorOccured()) { - return true; + auto r = this->invoke(functions, inStatus, mf); + restore = r.Restore; + if (r.Break) { + break; + } + } + + if (restore) { + // restore the variable to its prior value + mf.AddDefinition(this->Args.front(), oldDef); + } + return true; +} + +bool cmForEachFunctionBlocker::ReplayZipLists( + std::vector<cmListFileFunction> const& functions, + cmExecutionStatus& inStatus) +{ + assert("Unexpected number of iteration variables" && + this->IterationVarsCount >= 1); + + auto& mf = inStatus.GetMakefile(); + + // Expand the list of list-variables into a list of lists of strings + std::vector<std::vector<std::string>> values; + values.reserve(this->Args.size() - this->IterationVarsCount); + // Also track the longest list size + std::size_t maxItems = 0u; + for (auto const& var : + cmMakeRange(this->Args).advance(this->IterationVarsCount)) { + std::vector<std::string> items; + auto const& value = mf.GetSafeDefinition(var); + if (!value.empty()) { + cmExpandList(value, items, true); + } + maxItems = std::max(maxItems, items.size()); + values.emplace_back(std::move(items)); + } + + // Form the list of iteration variables + std::vector<std::string> iterationVars; + if (this->IterationVarsCount > 1) { + // If multiple iteration variables has given, + // just copy them to the `iterationVars` list. + iterationVars.reserve(values.size()); + std::copy(this->Args.begin(), + this->Args.begin() + this->IterationVarsCount, + std::back_inserter(iterationVars)); + } else { + // In case of the only iteration variable, + // generate names as `var_name_N`, + // where `N` is the count of lists to zip + iterationVars.resize(values.size()); + const auto iter_var_prefix = this->Args.front() + "_"; + auto i = 0u; + std::generate( + iterationVars.begin(), iterationVars.end(), + [&]() -> std::string { return iter_var_prefix + std::to_string(i++); }); + } + assert("Sanity check" && iterationVars.size() == values.size()); + + // Store old values for iteration variables + std::map<std::string, std::string> oldDefs; + for (auto i = 0u; i < values.size(); ++i) { + if (mf.GetDefinition(iterationVars[i])) { + oldDefs.emplace(iterationVars[i], mf.GetDefinition(iterationVars[i])); + } + } + + // Form a vector of current positions in all lists (Ok, vectors) of values + std::vector<decltype(values)::value_type::iterator> positions; + positions.reserve(values.size()); + std::transform( + values.begin(), values.end(), std::back_inserter(positions), + // Set the initial position to the beginning of every list + [](decltype(values)::value_type& list) { return list.begin(); }); + assert("Sanity check" && positions.size() == values.size()); + + auto restore = false; + // Iterate over all the lists simulateneously + for (auto i = 0u; i < maxItems; ++i) { + // Declare iteration variables + for (auto j = 0u; j < values.size(); ++j) { + // Define (or not) the iteration variable if the current position + // still not at the end... + if (positions[j] != values[j].end()) { + mf.AddDefinition(iterationVars[j], *positions[j]); + ++positions[j]; + } else { + mf.RemoveDefinition(iterationVars[j]); } } + // Invoke all the functions that were collected in the block. + auto r = this->invoke(functions, inStatus, mf); + restore = r.Restore; + if (r.Break) { + break; + } } - // restore the variable to its prior value - mf.AddDefinition(this->Args.front(), oldDef); + // Restore the variables to its prior value + if (restore) { + for (auto const& p : oldDefs) { + mf.AddDefinition(p.first, p.second); + } + } return true; } -bool HandleInMode(std::vector<std::string> const& args, cmMakefile& makefile) +auto cmForEachFunctionBlocker::invoke( + std::vector<cmListFileFunction> const& functions, + cmExecutionStatus& inStatus, cmMakefile& mf) -> InvokeResult +{ + InvokeResult result = { true, false }; + // Invoke all the functions that were collected in the block. + for (cmListFileFunction const& func : functions) { + cmExecutionStatus status(mf); + mf.ExecuteCommand(func, status); + if (status.GetReturnInvoked()) { + inStatus.SetReturnInvoked(); + result.Break = true; + break; + } + if (status.GetBreakInvoked()) { + result.Break = true; + break; + } + if (status.GetContinueInvoked()) { + break; + } + if (cmSystemTools::GetFatalErrorOccured()) { + result.Restore = false; + result.Break = true; + break; + } + } + return result; +} + +bool HandleInMode(std::vector<std::string> const& args, + std::vector<std::string>::const_iterator kwInIter, + cmMakefile& makefile) { + assert("A valid iterator expected" && kwInIter != args.end()); + auto fb = cm::make_unique<cmForEachFunctionBlocker>(&makefile); - fb->Args.push_back(args.front()); + + // Copy iteration variable names first + std::copy(args.begin(), kwInIter, std::back_inserter(fb->Args)); + // Remember the count of given iteration variable names + const auto varsCount = fb->Args.size(); + fb->SetIterationVarsCount(varsCount); enum Doing { DoingNone, DoingLists, - DoingItems + DoingItems, + DoingZipLists }; Doing doing = DoingNone; - for (std::string const& arg : cmMakeRange(args).advance(2)) { + // Iterate over arguments past the "IN" keyword + for (std::string const& arg : cmMakeRange(++kwInIter, args.end())) { if (arg == "LISTS") { + if (doing == DoingZipLists) { + makefile.IssueMessage(MessageType::FATAL_ERROR, + "ZIP_LISTS can not be used with LISTS or ITEMS"); + return true; + } + if (varsCount != 1u) { + makefile.IssueMessage( + MessageType::FATAL_ERROR, + "ITEMS or LISTS require exactly one iteration variable"); + return true; + } doing = DoingLists; + } else if (arg == "ITEMS") { + if (doing == DoingZipLists) { + makefile.IssueMessage(MessageType::FATAL_ERROR, + "ZIP_LISTS can not be used with LISTS or ITEMS"); + return true; + } + if (varsCount != 1u) { + makefile.IssueMessage( + MessageType::FATAL_ERROR, + "ITEMS or LISTS require exactly one iteration variable"); + return true; + } doing = DoingItems; + + } else if (arg == "ZIP_LISTS") { + if (doing != DoingNone) { + makefile.IssueMessage(MessageType::FATAL_ERROR, + "ZIP_LISTS can not be used with LISTS or ITEMS"); + return true; + } + doing = DoingZipLists; + fb->SetZipLists(); + } else if (doing == DoingLists) { auto const& value = makefile.GetSafeDefinition(arg); if (!value.empty()) { cmExpandList(value, fb->Args, true); } - } else if (doing == DoingItems) { + + } else if (doing == DoingItems || doing == DoingZipLists) { fb->Args.push_back(arg); + } else { makefile.IssueMessage(MessageType::FATAL_ERROR, cmStrCat("Unknown argument:\n", " ", arg, "\n")); @@ -141,6 +337,18 @@ bool HandleInMode(std::vector<std::string> const& args, cmMakefile& makefile) } } + // If `ZIP_LISTS` given and variables count more than 1, + // make sure the given lists count matches variables... + if (doing == DoingZipLists && varsCount > 1u && + (2u * varsCount) != fb->Args.size()) { + makefile.IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Expected ", std::to_string(varsCount), + " list variables, but given ", + std::to_string(fb->Args.size() - varsCount))); + return true; + } + makefile.AddFunctionBlocker(std::move(fb)); return true; @@ -155,8 +363,9 @@ bool cmForEachCommand(std::vector<std::string> const& args, status.SetError("called with incorrect number of arguments"); return false; } - if (args.size() > 1 && args[1] == "IN") { - return HandleInMode(args, status.GetMakefile()); + auto kwInIter = std::find(args.begin(), args.end(), "IN"); + if (kwInIter != args.end()) { + return HandleInMode(args, kwInIter, status.GetMakefile()); } // create a function blocker @@ -216,6 +425,8 @@ bool cmForEachCommand(std::vector<std::string> const& args, } else { fb->Args = args; } + + fb->SetIterationVarsCount(1u); status.GetMakefile().AddFunctionBlocker(std::move(fb)); return true; |