/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmListCommand.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cmsys/RegularExpression.hxx" #include "cmAlgorithms.h" #include "cmExecutionStatus.h" #include "cmGeneratorExpression.h" #include "cmMakefile.h" #include "cmMessageType.h" #include "cmPolicies.h" #include "cmRange.h" #include "cmStringAlgorithms.h" #include "cmStringReplaceHelper.h" #include "cmSubcommandTable.h" #include "cmSystemTools.h" #include "cmValue.h" namespace { bool GetIndexArg(const std::string& arg, int* idx, cmMakefile& mf) { long value; if (!cmStrToLong(arg, &value)) { switch (mf.GetPolicyStatus(cmPolicies::CMP0121)) { case cmPolicies::WARN: { // Default is to warn and use old behavior OLD behavior is to allow // compatibility, so issue a warning and use the previous behavior. std::string warn = cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0121), " Invalid list index \"", arg, "\"."); mf.IssueMessage(MessageType::AUTHOR_WARNING, warn); break; } case cmPolicies::OLD: // OLD behavior is to allow compatibility, so just ignore the // situation. break; case cmPolicies::NEW: return false; case cmPolicies::REQUIRED_IF_USED: case cmPolicies::REQUIRED_ALWAYS: std::string msg = cmStrCat(cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0121), " Invalid list index \"", arg, "\"."); mf.IssueMessage(MessageType::FATAL_ERROR, msg); break; } } // Truncation is happening here, but it had always been happening here. *idx = static_cast(value); return true; } bool FilterRegex(std::vector const& args, bool includeMatches, std::string const& listName, std::vector& varArgsExpanded, cmExecutionStatus& status); bool GetListString(std::string& listString, const std::string& var, const cmMakefile& makefile) { // get the old value cmValue cacheValue = makefile.GetDefinition(var); if (!cacheValue) { return false; } listString = *cacheValue; return true; } bool GetList(std::vector& list, const std::string& var, const cmMakefile& makefile) { std::string listString; if (!GetListString(listString, var, makefile)) { return false; } // if the size of the list if (listString.empty()) { return true; } // expand the variable into a list cmExpandList(listString, list, true); // if no empty elements then just return if (!cm::contains(list, std::string())) { return true; } // if we have empty elements we need to check policy CMP0007 switch (makefile.GetPolicyStatus(cmPolicies::CMP0007)) { case cmPolicies::WARN: { // Default is to warn and use old behavior // OLD behavior is to allow compatibility, so recall // ExpandListArgument without the true which will remove // empty values list.clear(); cmExpandList(listString, list); std::string warn = cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0007), " List has value = [", listString, "]."); makefile.IssueMessage(MessageType::AUTHOR_WARNING, warn); return true; } case cmPolicies::OLD: // OLD behavior is to allow compatibility, so recall // ExpandListArgument without the true which will remove // empty values list.clear(); cmExpandList(listString, list); return true; case cmPolicies::NEW: return true; case cmPolicies::REQUIRED_IF_USED: case cmPolicies::REQUIRED_ALWAYS: makefile.IssueMessage( MessageType::FATAL_ERROR, cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0007)); return false; } return true; } bool HandleLengthCommand(std::vector const& args, cmExecutionStatus& status) { if (args.size() != 3) { status.SetError("sub-command LENGTH requires two arguments."); return false; } const std::string& listName = args[1]; const std::string& variableName = args.back(); std::vector varArgsExpanded; // do not check the return value here // if the list var is not found varArgsExpanded will have size 0 // and we will return 0 GetList(varArgsExpanded, listName, status.GetMakefile()); size_t length = varArgsExpanded.size(); char buffer[1024]; sprintf(buffer, "%d", static_cast(length)); status.GetMakefile().AddDefinition(variableName, buffer); return true; } bool HandleGetCommand(std::vector const& args, cmExecutionStatus& status) { if (args.size() < 4) { status.SetError("sub-command GET requires at least three arguments."); return false; } const std::string& listName = args[1]; const std::string& variableName = args.back(); // expand the variable std::vector varArgsExpanded; if (!GetList(varArgsExpanded, listName, status.GetMakefile())) { status.GetMakefile().AddDefinition(variableName, "NOTFOUND"); return true; } // FIXME: Add policy to make non-existing lists an error like empty lists. if (varArgsExpanded.empty()) { status.SetError("GET given empty list"); return false; } std::string value; size_t cc; const char* sep = ""; size_t nitem = varArgsExpanded.size(); for (cc = 2; cc < args.size() - 1; cc++) { int item; if (!GetIndexArg(args[cc], &item, status.GetMakefile())) { status.SetError(cmStrCat("index: ", args[cc], " is not a valid index")); return false; } value += sep; sep = ";"; if (item < 0) { item = static_cast(nitem) + item; } if (item < 0 || nitem <= static_cast(item)) { status.SetError(cmStrCat("index: ", item, " out of range (-", nitem, ", ", nitem - 1, ")")); return false; } value += varArgsExpanded[item]; } status.GetMakefile().AddDefinition(variableName, value); return true; } bool HandleAppendCommand(std::vector const& args, cmExecutionStatus& status) { assert(args.size() >= 2); // Skip if nothing to append. if (args.size() < 3) { return true; } cmMakefile& makefile = status.GetMakefile(); std::string const& listName = args[1]; // expand the variable std::string listString; GetListString(listString, listName, makefile); // If `listString` or `args` is empty, no need to append `;`, // then index is going to be `1` and points to the end-of-string ";" auto const offset = std::string::size_type(listString.empty() || args.empty()); listString += &";"[offset] + cmJoin(cmMakeRange(args).advance(2), ";"); makefile.AddDefinition(listName, listString); return true; } bool HandlePrependCommand(std::vector const& args, cmExecutionStatus& status) { assert(args.size() >= 2); // Skip if nothing to prepend. if (args.size() < 3) { return true; } cmMakefile& makefile = status.GetMakefile(); std::string const& listName = args[1]; // expand the variable std::string listString; GetListString(listString, listName, makefile); // If `listString` or `args` is empty, no need to append `;`, // then `offset` is going to be `1` and points to the end-of-string ";" auto const offset = std::string::size_type(listString.empty() || args.empty()); listString.insert(0, cmJoin(cmMakeRange(args).advance(2), ";") + &";"[offset]); makefile.AddDefinition(listName, listString); return true; } bool HandlePopBackCommand(std::vector const& args, cmExecutionStatus& status) { assert(args.size() >= 2); cmMakefile& makefile = status.GetMakefile(); auto ai = args.cbegin(); ++ai; // Skip subcommand name std::string const& listName = *ai++; std::vector varArgsExpanded; if (!GetList(varArgsExpanded, listName, makefile)) { // Can't get the list definition... undefine any vars given after. for (; ai != args.cend(); ++ai) { makefile.RemoveDefinition(*ai); } return true; } if (!varArgsExpanded.empty()) { if (ai == args.cend()) { // No variables are given... Just remove one element. varArgsExpanded.pop_back(); } else { // Ok, assign elements to be removed to the given variables for (; !varArgsExpanded.empty() && ai != args.cend(); ++ai) { assert(!ai->empty()); makefile.AddDefinition(*ai, varArgsExpanded.back()); varArgsExpanded.pop_back(); } // Undefine the rest variables if the list gets empty earlier... for (; ai != args.cend(); ++ai) { makefile.RemoveDefinition(*ai); } } makefile.AddDefinition(listName, cmJoin(varArgsExpanded, ";")); } else if (ai != args.cend()) { // The list is empty, but some args were given // Need to *undefine* 'em all, cuz there are no items to assign... for (; ai != args.cend(); ++ai) { makefile.RemoveDefinition(*ai); } } return true; } bool HandlePopFrontCommand(std::vector const& args, cmExecutionStatus& status) { assert(args.size() >= 2); cmMakefile& makefile = status.GetMakefile(); auto ai = args.cbegin(); ++ai; // Skip subcommand name std::string const& listName = *ai++; std::vector varArgsExpanded; if (!GetList(varArgsExpanded, listName, makefile)) { // Can't get the list definition... undefine any vars given after. for (; ai != args.cend(); ++ai) { makefile.RemoveDefinition(*ai); } return true; } if (!varArgsExpanded.empty()) { if (ai == args.cend()) { // No variables are given... Just remove one element. varArgsExpanded.erase(varArgsExpanded.begin()); } else { // Ok, assign elements to be removed to the given variables auto vi = varArgsExpanded.begin(); for (; vi != varArgsExpanded.end() && ai != args.cend(); ++ai, ++vi) { assert(!ai->empty()); makefile.AddDefinition(*ai, *vi); } varArgsExpanded.erase(varArgsExpanded.begin(), vi); // Undefine the rest variables if the list gets empty earlier... for (; ai != args.cend(); ++ai) { makefile.RemoveDefinition(*ai); } } makefile.AddDefinition(listName, cmJoin(varArgsExpanded, ";")); } else if (ai != args.cend()) { // The list is empty, but some args were given // Need to *undefine* 'em all, cuz there are no items to assign... for (; ai != args.cend(); ++ai) { makefile.RemoveDefinition(*ai); } } return true; } bool HandleFindCommand(std::vector const& args, cmExecutionStatus& status) { if (args.size() != 4) { status.SetError("sub-command FIND requires three arguments."); return false; } const std::string& listName = args[1]; const std::string& variableName = args.back(); // expand the variable std::vector varArgsExpanded; if (!GetList(varArgsExpanded, listName, status.GetMakefile())) { status.GetMakefile().AddDefinition(variableName, "-1"); return true; } auto it = std::find(varArgsExpanded.begin(), varArgsExpanded.end(), args[2]); if (it != varArgsExpanded.end()) { status.GetMakefile().AddDefinition( variableName, std::to_string(std::distance(varArgsExpanded.begin(), it))); return true; } status.GetMakefile().AddDefinition(variableName, "-1"); return true; } bool HandleInsertCommand(std::vector const& args, cmExecutionStatus& status) { if (args.size() < 4) { status.SetError("sub-command INSERT requires at least three arguments."); return false; } const std::string& listName = args[1]; // expand the variable int item; if (!GetIndexArg(args[2], &item, status.GetMakefile())) { status.SetError(cmStrCat("index: ", args[2], " is not a valid index")); return false; } std::vector varArgsExpanded; if ((!GetList(varArgsExpanded, listName, status.GetMakefile()) || varArgsExpanded.empty()) && item != 0) { status.SetError(cmStrCat("index: ", item, " out of range (0, 0)")); return false; } if (!varArgsExpanded.empty()) { size_t nitem = varArgsExpanded.size(); if (item < 0) { item = static_cast(nitem) + item; } if (item < 0 || nitem < static_cast(item)) { status.SetError(cmStrCat("index: ", item, " out of range (-", varArgsExpanded.size(), ", ", varArgsExpanded.size(), ")")); return false; } } varArgsExpanded.insert(varArgsExpanded.begin() + item, args.begin() + 3, args.end()); std::string value = cmJoin(varArgsExpanded, ";"); status.GetMakefile().AddDefinition(listName, value); return true; } bool HandleJoinCommand(std::vector const& args, cmExecutionStatus& status) { if (args.size() != 4) { status.SetError(cmStrCat("sub-command JOIN requires three arguments (", args.size() - 1, " found).")); return false; } const std::string& listName = args[1]; const std::string& glue = args[2]; const std::string& variableName = args[3]; // expand the variable std::vector varArgsExpanded; if (!GetList(varArgsExpanded, listName, status.GetMakefile())) { status.GetMakefile().AddDefinition(variableName, ""); return true; } std::string value = cmJoin(cmMakeRange(varArgsExpanded.begin(), varArgsExpanded.end()), glue); status.GetMakefile().AddDefinition(variableName, value); return true; } bool HandleRemoveItemCommand(std::vector const& args, cmExecutionStatus& status) { assert(args.size() >= 2); if (args.size() == 2) { return true; } const std::string& listName = args[1]; // expand the variable std::vector varArgsExpanded; if (!GetList(varArgsExpanded, listName, status.GetMakefile())) { return true; } std::vector remove(args.begin() + 2, args.end()); std::sort(remove.begin(), remove.end()); auto remEnd = std::unique(remove.begin(), remove.end()); auto remBegin = remove.begin(); auto argsEnd = cmRemoveMatching(varArgsExpanded, cmMakeRange(remBegin, remEnd)); auto argsBegin = varArgsExpanded.cbegin(); std::string value = cmJoin(cmMakeRange(argsBegin, argsEnd), ";"); status.GetMakefile().AddDefinition(listName, value); return true; } bool HandleReverseCommand(std::vector const& args, cmExecutionStatus& status) { assert(args.size() >= 2); if (args.size() > 2) { status.SetError("sub-command REVERSE only takes one argument."); return false; } const std::string& listName = args[1]; // expand the variable std::vector varArgsExpanded; if (!GetList(varArgsExpanded, listName, status.GetMakefile())) { return true; } std::string value = cmJoin(cmReverseRange(varArgsExpanded), ";"); status.GetMakefile().AddDefinition(listName, value); return true; } bool HandleRemoveDuplicatesCommand(std::vector const& args, cmExecutionStatus& status) { assert(args.size() >= 2); if (args.size() > 2) { status.SetError("sub-command REMOVE_DUPLICATES only takes one argument."); return false; } const std::string& listName = args[1]; // expand the variable std::vector varArgsExpanded; if (!GetList(varArgsExpanded, listName, status.GetMakefile())) { return true; } auto argsEnd = cmRemoveDuplicates(varArgsExpanded); auto argsBegin = varArgsExpanded.cbegin(); std::string value = cmJoin(cmMakeRange(argsBegin, argsEnd), ";"); status.GetMakefile().AddDefinition(listName, value); return true; } // Helpers for list(TRANSFORM ...) using transform_type = std::function; class transform_error : public std::runtime_error { public: transform_error(const std::string& error) : std::runtime_error(error) { } }; class TransformSelector { public: virtual ~TransformSelector() = default; std::string Tag; virtual bool Validate(std::size_t count = 0) = 0; virtual bool InSelection(const std::string&) = 0; virtual void Transform(std::vector& list, const transform_type& transform) { std::transform(list.begin(), list.end(), list.begin(), transform); } protected: TransformSelector(std::string&& tag) : Tag(std::move(tag)) { } }; class TransformNoSelector : public TransformSelector { public: TransformNoSelector() : TransformSelector("NO SELECTOR") { } bool Validate(std::size_t) override { return true; } bool InSelection(const std::string&) override { return true; } }; class TransformSelectorRegex : public TransformSelector { public: TransformSelectorRegex(const std::string& regex) : TransformSelector("REGEX") , Regex(regex) { } bool Validate(std::size_t) override { return this->Regex.is_valid(); } bool InSelection(const std::string& value) override { return this->Regex.find(value); } cmsys::RegularExpression Regex; }; class TransformSelectorIndexes : public TransformSelector { public: std::vector Indexes; bool InSelection(const std::string&) override { return true; } void Transform(std::vector& list, const transform_type& transform) override { this->Validate(list.size()); for (auto index : this->Indexes) { list[index] = transform(list[index]); } } protected: TransformSelectorIndexes(std::string&& tag) : TransformSelector(std::move(tag)) { } TransformSelectorIndexes(std::string&& tag, std::vector&& indexes) : TransformSelector(std::move(tag)) , Indexes(indexes) { } int NormalizeIndex(int index, std::size_t count) { if (index < 0) { index = static_cast(count) + index; } if (index < 0 || count <= static_cast(index)) { throw transform_error(cmStrCat( "sub-command TRANSFORM, selector ", this->Tag, ", index: ", index, " out of range (-", count, ", ", count - 1, ").")); } return index; } }; class TransformSelectorAt : public TransformSelectorIndexes { public: TransformSelectorAt(std::vector&& indexes) : TransformSelectorIndexes("AT", std::move(indexes)) { } bool Validate(std::size_t count) override { decltype(this->Indexes) indexes; for (auto index : this->Indexes) { indexes.push_back(this->NormalizeIndex(index, count)); } this->Indexes = std::move(indexes); return true; } }; class TransformSelectorFor : public TransformSelectorIndexes { public: TransformSelectorFor(int start, int stop, int step) : TransformSelectorIndexes("FOR") , Start(start) , Stop(stop) , Step(step) { } bool Validate(std::size_t count) override { this->Start = this->NormalizeIndex(this->Start, count); this->Stop = this->NormalizeIndex(this->Stop, count); // compute indexes auto size = (this->Stop - this->Start + 1) / this->Step; if ((this->Stop - this->Start + 1) % this->Step != 0) { size += 1; } this->Indexes.resize(size); auto start = this->Start; auto step = this->Step; std::generate(this->Indexes.begin(), this->Indexes.end(), [&start, step]() -> int { auto r = start; start += step; return r; }); return true; } private: int Start, Stop, Step; }; class TransformAction { public: virtual ~TransformAction() = default; virtual std::string Transform(const std::string& input) = 0; }; class TransformReplace : public TransformAction { public: TransformReplace(const std::vector& arguments, cmMakefile* makefile) : ReplaceHelper(arguments[0], arguments[1], makefile) { makefile->ClearMatches(); if (!this->ReplaceHelper.IsRegularExpressionValid()) { throw transform_error( cmStrCat("sub-command TRANSFORM, action REPLACE: Failed to compile " "regex \"", arguments[0], "\".")); } if (!this->ReplaceHelper.IsReplaceExpressionValid()) { throw transform_error(cmStrCat("sub-command TRANSFORM, action REPLACE: ", this->ReplaceHelper.GetError(), ".")); } } std::string Transform(const std::string& input) override { // Scan through the input for all matches. std::string output; if (!this->ReplaceHelper.Replace(input, output)) { throw transform_error(cmStrCat("sub-command TRANSFORM, action REPLACE: ", this->ReplaceHelper.GetError(), ".")); } return output; } private: cmStringReplaceHelper ReplaceHelper; }; bool HandleTransformCommand(std::vector const& args, cmExecutionStatus& status) { if (args.size() < 3) { status.SetError( "sub-command TRANSFORM requires an action to be specified."); return false; } // Structure collecting all elements of the command struct Command { Command(const std::string& listName) : ListName(listName) , OutputName(listName) { } std::string Name; std::string ListName; std::vector Arguments; std::unique_ptr Action; std::unique_ptr Selector; std::string OutputName; } command(args[1]); // Descriptor of action // Arity: number of arguments required for the action // Transform: lambda function implementing the action struct ActionDescriptor { ActionDescriptor(std::string name) : Name(std::move(name)) { } ActionDescriptor(std::string name, int arity, transform_type transform) : Name(std::move(name)) , Arity(arity) #if defined(__GNUC__) && __GNUC__ == 6 && defined(__aarch64__) // std::function move constructor miscompiles on this architecture , Transform(transform) #else , Transform(std::move(transform)) #endif { } operator const std::string&() const { return this->Name; } std::string Name; int Arity = 0; transform_type Transform; }; // Build a set of supported actions. std::set> descriptors( [](const std::string& x, const std::string& y) { return x < y; }); descriptors = { { "APPEND", 1, [&command](const std::string& s) -> std::string { if (command.Selector->InSelection(s)) { return s + command.Arguments[0]; } return s; } }, { "PREPEND", 1, [&command](const std::string& s) -> std::string { if (command.Selector->InSelection(s)) { return command.Arguments[0] + s; } return s; } }, { "TOUPPER", 0, [&command](const std::string& s) -> std::string { if (command.Selector->InSelection(s)) { return cmSystemTools::UpperCase(s); } return s; } }, { "TOLOWER", 0, [&command](const std::string& s) -> std::string { if (command.Selector->InSelection(s)) { return cmSystemTools::LowerCase(s); } return s; } }, { "STRIP", 0, [&command](const std::string& s) -> std::string { if (command.Selector->InSelection(s)) { return cmTrimWhitespace(s); } return s; } }, { "GENEX_STRIP", 0, [&command](const std::string& s) -> std::string { if (command.Selector->InSelection(s)) { return cmGeneratorExpression::Preprocess( s, cmGeneratorExpression::StripAllGeneratorExpressions); } return s; } }, { "REPLACE", 2, [&command](const std::string& s) -> std::string { if (command.Selector->InSelection(s)) { return command.Action->Transform(s); } return s; } } }; using size_type = std::vector::size_type; size_type index = 2; // Parse all possible function parameters auto descriptor = descriptors.find(args[index]); if (descriptor == descriptors.end()) { status.SetError( cmStrCat(" sub-command TRANSFORM, ", args[index], " invalid action.")); return false; } // Action arguments index += 1; if (args.size() < index + descriptor->Arity) { status.SetError(cmStrCat("sub-command TRANSFORM, action ", descriptor->Name, " expects ", descriptor->Arity, " argument(s).")); return false; } command.Name = descriptor->Name; index += descriptor->Arity; if (descriptor->Arity > 0) { command.Arguments = std::vector(args.begin() + 3, args.begin() + index); } if (command.Name == "REPLACE") { try { command.Action = cm::make_unique( command.Arguments, &status.GetMakefile()); } catch (const transform_error& e) { status.SetError(e.what()); return false; } } const std::string REGEX{ "REGEX" }; const std::string AT{ "AT" }; const std::string FOR{ "FOR" }; const std::string OUTPUT_VARIABLE{ "OUTPUT_VARIABLE" }; // handle optional arguments while (args.size() > index) { if ((args[index] == REGEX || args[index] == AT || args[index] == FOR) && command.Selector) { status.SetError( cmStrCat("sub-command TRANSFORM, selector already specified (", command.Selector->Tag, ").")); return false; } // REGEX selector if (args[index] == REGEX) { if (args.size() == ++index) { status.SetError("sub-command TRANSFORM, selector REGEX expects " "'regular expression' argument."); return false; } command.Selector = cm::make_unique(args[index]); if (!command.Selector->Validate()) { status.SetError( cmStrCat("sub-command TRANSFORM, selector REGEX failed to compile " "regex \"", args[index], "\".")); return false; } index += 1; continue; } // AT selector if (args[index] == AT) { // get all specified indexes std::vector indexes; while (args.size() > ++index) { std::size_t pos; int value; try { value = std::stoi(args[index], &pos); if (pos != args[index].length()) { // this is not a number, stop processing break; } indexes.push_back(value); } catch (const std::invalid_argument&) { // this is not a number, stop processing break; } } if (indexes.empty()) { status.SetError( "sub-command TRANSFORM, selector AT expects at least one " "numeric value."); return false; } command.Selector = cm::make_unique(std::move(indexes)); continue; } // FOR selector if (args[index] == FOR) { if (args.size() <= ++index + 1) { status.SetError( "sub-command TRANSFORM, selector FOR expects, at least," " two arguments."); return false; } int start = 0; int stop = 0; int step = 1; bool valid = true; try { std::size_t pos; start = std::stoi(args[index], &pos); if (pos != args[index].length()) { // this is not a number valid = false; } else { stop = std::stoi(args[++index], &pos); if (pos != args[index].length()) { // this is not a number valid = false; } } } catch (const std::invalid_argument&) { // this is not numbers valid = false; } if (!valid) { status.SetError("sub-command TRANSFORM, selector FOR expects, " "at least, two numeric values."); return false; } // try to read a third numeric value for step if (args.size() > ++index) { try { std::size_t pos; step = std::stoi(args[index], &pos); if (pos != args[index].length()) { // this is not a number step = 1; } else { index += 1; } } catch (const std::invalid_argument&) { // this is not number, ignore exception } } if (step < 0) { status.SetError("sub-command TRANSFORM, selector FOR expects " "non negative numeric value for ."); } command.Selector = cm::make_unique(start, stop, step); continue; } // output variable if (args[index] == OUTPUT_VARIABLE) { if (args.size() == ++index) { status.SetError("sub-command TRANSFORM, OUTPUT_VARIABLE " "expects variable name argument."); return false; } command.OutputName = args[index++]; continue; } status.SetError(cmStrCat("sub-command TRANSFORM, '", cmJoin(cmMakeRange(args).advance(index), " "), "': unexpected argument(s).")); return false; } // expand the list variable std::vector varArgsExpanded; if (!GetList(varArgsExpanded, command.ListName, status.GetMakefile())) { status.GetMakefile().AddDefinition(command.OutputName, ""); return true; } if (!command.Selector) { // no selector specified, apply transformation to all elements command.Selector = cm::make_unique(); } try { command.Selector->Transform(varArgsExpanded, descriptor->Transform); } catch (const transform_error& e) { status.SetError(e.what()); return false; } status.GetMakefile().AddDefinition(command.OutputName, cmJoin(varArgsExpanded, ";")); return true; } class cmStringSorter { public: enum class Order { UNINITIALIZED, ASCENDING, DESCENDING, }; enum class Compare { UNINITIALIZED, STRING, FILE_BASENAME, NATURAL, }; enum class CaseSensitivity { UNINITIALIZED, SENSITIVE, INSENSITIVE, }; protected: using StringFilter = std::string (*)(const std::string&); StringFilter GetCompareFilter(Compare compare) { return (compare == Compare::FILE_BASENAME) ? cmSystemTools::GetFilenameName : nullptr; } StringFilter GetCaseFilter(CaseSensitivity sensitivity) { return (sensitivity == CaseSensitivity::INSENSITIVE) ? cmSystemTools::LowerCase : nullptr; } using ComparisonFunction = std::function; ComparisonFunction GetComparisonFunction(Compare compare) { if (compare == Compare::NATURAL) { return std::function( [](const std::string& x, const std::string& y) { return cmSystemTools::strverscmp(x, y) < 0; }); } return std::function( [](const std::string& x, const std::string& y) { return x < y; }); } public: cmStringSorter(Compare compare, CaseSensitivity caseSensitivity, Order desc = Order::ASCENDING) : filters{ this->GetCompareFilter(compare), this->GetCaseFilter(caseSensitivity) } , sortMethod(this->GetComparisonFunction(compare)) , descending(desc == Order::DESCENDING) { } std::string ApplyFilter(const std::string& argument) { std::string result = argument; for (auto filter : this->filters) { if (filter != nullptr) { result = filter(result); } } return result; } bool operator()(const std::string& a, const std::string& b) { std::string af = this->ApplyFilter(a); std::string bf = this->ApplyFilter(b); bool result; if (this->descending) { result = this->sortMethod(bf, af); } else { result = this->sortMethod(af, bf); } return result; } protected: StringFilter filters[2] = { nullptr, nullptr }; ComparisonFunction sortMethod; bool descending; }; bool HandleSortCommand(std::vector const& args, cmExecutionStatus& status) { assert(args.size() >= 2); if (args.size() > 8) { status.SetError("sub-command SORT only takes up to six arguments."); return false; } auto sortCompare = cmStringSorter::Compare::UNINITIALIZED; auto sortCaseSensitivity = cmStringSorter::CaseSensitivity::UNINITIALIZED; auto sortOrder = cmStringSorter::Order::UNINITIALIZED; size_t argumentIndex = 2; const std::string messageHint = "sub-command SORT "; while (argumentIndex < args.size()) { const std::string option = args[argumentIndex++]; if (option == "COMPARE") { if (sortCompare != cmStringSorter::Compare::UNINITIALIZED) { std::string error = cmStrCat(messageHint, "option \"", option, "\" has been specified multiple times."); status.SetError(error); return false; } if (argumentIndex < args.size()) { const std::string argument = args[argumentIndex++]; if (argument == "STRING") { sortCompare = cmStringSorter::Compare::STRING; } else if (argument == "FILE_BASENAME") { sortCompare = cmStringSorter::Compare::FILE_BASENAME; } else if (argument == "NATURAL") { sortCompare = cmStringSorter::Compare::NATURAL; } else { std::string error = cmStrCat(messageHint, "value \"", argument, "\" for option \"", option, "\" is invalid."); status.SetError(error); return false; } } else { status.SetError(cmStrCat(messageHint, "missing argument for option \"", option, "\".")); return false; } } else if (option == "CASE") { if (sortCaseSensitivity != cmStringSorter::CaseSensitivity::UNINITIALIZED) { status.SetError(cmStrCat(messageHint, "option \"", option, "\" has been specified multiple times.")); return false; } if (argumentIndex < args.size()) { const std::string argument = args[argumentIndex++]; if (argument == "SENSITIVE") { sortCaseSensitivity = cmStringSorter::CaseSensitivity::SENSITIVE; } else if (argument == "INSENSITIVE") { sortCaseSensitivity = cmStringSorter::CaseSensitivity::INSENSITIVE; } else { status.SetError(cmStrCat(messageHint, "value \"", argument, "\" for option \"", option, "\" is invalid.")); return false; } } else { status.SetError(cmStrCat(messageHint, "missing argument for option \"", option, "\".")); return false; } } else if (option == "ORDER") { if (sortOrder != cmStringSorter::Order::UNINITIALIZED) { status.SetError(cmStrCat(messageHint, "option \"", option, "\" has been specified multiple times.")); return false; } if (argumentIndex < args.size()) { const std::string argument = args[argumentIndex++]; if (argument == "ASCENDING") { sortOrder = cmStringSorter::Order::ASCENDING; } else if (argument == "DESCENDING") { sortOrder = cmStringSorter::Order::DESCENDING; } else { status.SetError(cmStrCat(messageHint, "value \"", argument, "\" for option \"", option, "\" is invalid.")); return false; } } else { status.SetError(cmStrCat(messageHint, "missing argument for option \"", option, "\".")); return false; } } else { status.SetError( cmStrCat(messageHint, "option \"", option, "\" is unknown.")); return false; } } // set Default Values if Option is not given if (sortCompare == cmStringSorter::Compare::UNINITIALIZED) { sortCompare = cmStringSorter::Compare::STRING; } if (sortCaseSensitivity == cmStringSorter::CaseSensitivity::UNINITIALIZED) { sortCaseSensitivity = cmStringSorter::CaseSensitivity::SENSITIVE; } if (sortOrder == cmStringSorter::Order::UNINITIALIZED) { sortOrder = cmStringSorter::Order::ASCENDING; } const std::string& listName = args[1]; // expand the variable std::vector varArgsExpanded; if (!GetList(varArgsExpanded, listName, status.GetMakefile())) { return true; } if ((sortCompare == cmStringSorter::Compare::STRING) && (sortCaseSensitivity == cmStringSorter::CaseSensitivity::SENSITIVE) && (sortOrder == cmStringSorter::Order::ASCENDING)) { std::sort(varArgsExpanded.begin(), varArgsExpanded.end()); } else { cmStringSorter sorter(sortCompare, sortCaseSensitivity, sortOrder); std::sort(varArgsExpanded.begin(), varArgsExpanded.end(), sorter); } std::string value = cmJoin(varArgsExpanded, ";"); status.GetMakefile().AddDefinition(listName, value); return true; } bool HandleSublistCommand(std::vector const& args, cmExecutionStatus& status) { if (args.size() != 5) { status.SetError(cmStrCat("sub-command SUBLIST requires four arguments (", args.size() - 1, " found).")); return false; } const std::string& listName = args[1]; const std::string& variableName = args.back(); // expand the variable std::vector varArgsExpanded; if (!GetList(varArgsExpanded, listName, status.GetMakefile()) || varArgsExpanded.empty()) { status.GetMakefile().AddDefinition(variableName, ""); return true; } int start; int length; if (!GetIndexArg(args[2], &start, status.GetMakefile())) { status.SetError(cmStrCat("index: ", args[2], " is not a valid index")); return false; } if (!GetIndexArg(args[3], &length, status.GetMakefile())) { status.SetError(cmStrCat("index: ", args[3], " is not a valid index")); return false; } using size_type = decltype(varArgsExpanded)::size_type; if (start < 0 || size_type(start) >= varArgsExpanded.size()) { status.SetError(cmStrCat("begin index: ", start, " is out of range 0 - ", varArgsExpanded.size() - 1)); return false; } if (length < -1) { status.SetError(cmStrCat("length: ", length, " should be -1 or greater")); return false; } const size_type end = (length == -1 || size_type(start + length) > varArgsExpanded.size()) ? varArgsExpanded.size() : size_type(start + length); std::vector sublist(varArgsExpanded.begin() + start, varArgsExpanded.begin() + end); status.GetMakefile().AddDefinition(variableName, cmJoin(sublist, ";")); return true; } bool HandleRemoveAtCommand(std::vector const& args, cmExecutionStatus& status) { if (args.size() < 3) { status.SetError("sub-command REMOVE_AT requires at least " "two arguments."); return false; } const std::string& listName = args[1]; // expand the variable std::vector varArgsExpanded; if (!GetList(varArgsExpanded, listName, status.GetMakefile()) || varArgsExpanded.empty()) { std::ostringstream str; str << "index: "; for (size_t i = 1; i < args.size(); ++i) { str << args[i]; if (i != args.size() - 1) { str << ", "; } } str << " out of range (0, 0)"; status.SetError(str.str()); return false; } size_t cc; std::vector removed; size_t nitem = varArgsExpanded.size(); for (cc = 2; cc < args.size(); ++cc) { int item; if (!GetIndexArg(args[cc], &item, status.GetMakefile())) { status.SetError(cmStrCat("index: ", args[cc], " is not a valid index")); return false; } if (item < 0) { item = static_cast(nitem) + item; } if (item < 0 || nitem <= static_cast(item)) { status.SetError(cmStrCat("index: ", item, " out of range (-", nitem, ", ", nitem - 1, ")")); return false; } removed.push_back(static_cast(item)); } std::sort(removed.begin(), removed.end()); auto remEnd = std::unique(removed.begin(), removed.end()); auto remBegin = removed.begin(); auto argsEnd = cmRemoveIndices(varArgsExpanded, cmMakeRange(remBegin, remEnd)); auto argsBegin = varArgsExpanded.cbegin(); std::string value = cmJoin(cmMakeRange(argsBegin, argsEnd), ";"); status.GetMakefile().AddDefinition(listName, value); return true; } bool HandleFilterCommand(std::vector const& args, cmExecutionStatus& status) { if (args.size() < 2) { status.SetError("sub-command FILTER requires a list to be specified."); return false; } if (args.size() < 3) { status.SetError( "sub-command FILTER requires an operator to be specified."); return false; } if (args.size() < 4) { status.SetError("sub-command FILTER requires a mode to be specified."); return false; } const std::string& op = args[2]; bool includeMatches; if (op == "INCLUDE") { includeMatches = true; } else if (op == "EXCLUDE") { includeMatches = false; } else { status.SetError("sub-command FILTER does not recognize operator " + op); return false; } const std::string& listName = args[1]; // expand the variable std::vector varArgsExpanded; if (!GetList(varArgsExpanded, listName, status.GetMakefile())) { return true; } const std::string& mode = args[3]; if (mode == "REGEX") { if (args.size() != 5) { status.SetError("sub-command FILTER, mode REGEX " "requires five arguments."); return false; } return FilterRegex(args, includeMatches, listName, varArgsExpanded, status); } status.SetError("sub-command FILTER does not recognize mode " + mode); return false; } class MatchesRegex { public: MatchesRegex(cmsys::RegularExpression& in_regex, bool in_includeMatches) : regex(in_regex) , includeMatches(in_includeMatches) { } bool operator()(const std::string& target) { return this->regex.find(target) ^ this->includeMatches; } private: cmsys::RegularExpression& regex; const bool includeMatches; }; bool FilterRegex(std::vector const& args, bool includeMatches, std::string const& listName, std::vector& varArgsExpanded, cmExecutionStatus& status) { const std::string& pattern = args[4]; cmsys::RegularExpression regex(pattern); if (!regex.is_valid()) { std::string error = cmStrCat("sub-command FILTER, mode REGEX failed to compile regex \"", pattern, "\"."); status.SetError(error); return false; } auto argsBegin = varArgsExpanded.begin(); auto argsEnd = varArgsExpanded.end(); auto newArgsEnd = std::remove_if(argsBegin, argsEnd, MatchesRegex(regex, includeMatches)); std::string value = cmJoin(cmMakeRange(argsBegin, newArgsEnd), ";"); status.GetMakefile().AddDefinition(listName, value); return true; } } // namespace bool cmListCommand(std::vector const& args, cmExecutionStatus& status) { if (args.size() < 2) { status.SetError("must be called with at least two arguments."); return false; } static cmSubcommandTable const subcommand{ { "LENGTH"_s, HandleLengthCommand }, { "GET"_s, HandleGetCommand }, { "APPEND"_s, HandleAppendCommand }, { "PREPEND"_s, HandlePrependCommand }, { "POP_BACK"_s, HandlePopBackCommand }, { "POP_FRONT"_s, HandlePopFrontCommand }, { "FIND"_s, HandleFindCommand }, { "INSERT"_s, HandleInsertCommand }, { "JOIN"_s, HandleJoinCommand }, { "REMOVE_AT"_s, HandleRemoveAtCommand }, { "REMOVE_ITEM"_s, HandleRemoveItemCommand }, { "REMOVE_DUPLICATES"_s, HandleRemoveDuplicatesCommand }, { "TRANSFORM"_s, HandleTransformCommand }, { "SORT"_s, HandleSortCommand }, { "SUBLIST"_s, HandleSublistCommand }, { "REVERSE"_s, HandleReverseCommand }, { "FILTER"_s, HandleFilterCommand }, }; return subcommand(args[0], args, status); }