/* 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 "cmExecutionStatus.h" #include "cmList.h" #include "cmMakefile.h" #include "cmMessageType.h" #include "cmPolicies.h" #include "cmRange.h" #include "cmStringAlgorithms.h" #include "cmSubcommandTable.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); CM_FALLTHROUGH; } 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 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; } cm::optional GetList(const std::string& var, const cmMakefile& makefile) { cm::optional list; std::string listString; if (!GetListString(listString, var, makefile)) { return list; } // if the size of the list if (listString.empty()) { list.emplace(); return list; } // expand the variable into a list list.emplace(listString, cmList::EmptyElements::Yes); // if no empty elements then just return if (!cm::contains(*list, std::string())) { return list; } // 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->assign(listString); std::string warn = cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0007), " List has value = [", listString, "]."); makefile.IssueMessage(MessageType::AUTHOR_WARNING, warn); return list; } case cmPolicies::OLD: // OLD behavior is to allow compatibility, so recall // ExpandListArgument without the true which will remove // empty values list->assign(listString); return list; case cmPolicies::NEW: return list; case cmPolicies::REQUIRED_IF_USED: case cmPolicies::REQUIRED_ALWAYS: makefile.IssueMessage( MessageType::FATAL_ERROR, cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0007)); return {}; } return list; } 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(); auto list = GetList(listName, status.GetMakefile()); status.GetMakefile().AddDefinition(variableName, std::to_string(list ? list->size() : 0)); 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 auto list = GetList(listName, status.GetMakefile()); if (!list) { status.GetMakefile().AddDefinition(variableName, "NOTFOUND"); return true; } // FIXME: Add policy to make non-existing lists an error like empty lists. if (list->empty()) { status.SetError("GET given empty list"); return false; } std::vector indexes; for (std::size_t cc = 2; cc < args.size() - 1; cc++) { int index; if (!GetIndexArg(args[cc], &index, status.GetMakefile())) { status.SetError(cmStrCat("index: ", args[cc], " is not a valid index")); return false; } indexes.push_back(index); } try { auto values = list->get_items(indexes.begin(), indexes.end()); status.GetMakefile().AddDefinition(variableName, values.to_string()); return true; } catch (std::out_of_range& e) { status.SetError(e.what()); return false; } } 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); makefile.AddDefinition( listName, cmList::append(listString, args.begin() + 2, args.end())); 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); makefile.AddDefinition( listName, cmList::prepend(listString, args.begin() + 2, args.end())); 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++; auto list = GetList(listName, makefile); if (!list) { // Can't get the list definition... undefine any vars given after. for (; ai != args.cend(); ++ai) { makefile.RemoveDefinition(*ai); } return true; } if (!list->empty()) { if (ai == args.cend()) { // No variables are given... Just remove one element. list->pop_back(); } else { // Ok, assign elements to be removed to the given variables for (; !list->empty() && ai != args.cend(); ++ai) { assert(!ai->empty()); makefile.AddDefinition(*ai, list->back()); list->pop_back(); } // Undefine the rest variables if the list gets empty earlier... for (; ai != args.cend(); ++ai) { makefile.RemoveDefinition(*ai); } } makefile.AddDefinition(listName, list->to_string()); } 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++; auto list = GetList(listName, makefile); if (!list) { // Can't get the list definition... undefine any vars given after. for (; ai != args.cend(); ++ai) { makefile.RemoveDefinition(*ai); } return true; } if (!list->empty()) { if (ai == args.cend()) { // No variables are given... Just remove one element. list->pop_front(); } else { // Ok, assign elements to be removed to the given variables auto vi = list->begin(); for (; vi != list->end() && ai != args.cend(); ++ai, ++vi) { assert(!ai->empty()); makefile.AddDefinition(*ai, *vi); } list->erase(list->begin(), vi); // Undefine the rest variables if the list gets empty earlier... for (; ai != args.cend(); ++ai) { makefile.RemoveDefinition(*ai); } } makefile.AddDefinition(listName, list->to_string()); } 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 auto list = GetList(listName, status.GetMakefile()); if (!list) { status.GetMakefile().AddDefinition(variableName, "-1"); return true; } auto index = list->find(args[2]); status.GetMakefile().AddDefinition( variableName, index == cmList::npos ? "-1" : std::to_string(index)); 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 index; if (!GetIndexArg(args[2], &index, status.GetMakefile())) { status.SetError(cmStrCat("index: ", args[2], " is not a valid index")); return false; } auto list = GetList(listName, status.GetMakefile()); if (!list) { list = cmList{}; } try { list->insert_items(index, args.begin() + 3, args.end()); status.GetMakefile().AddDefinition(listName, list->to_string()); return true; } catch (std::out_of_range& e) { status.SetError(e.what()); return false; } } 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 auto list = GetList(listName, status.GetMakefile()); if (!list) { status.GetMakefile().AddDefinition(variableName, ""); return true; } status.GetMakefile().AddDefinition(variableName, list->join(glue)); 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 auto list = GetList(listName, status.GetMakefile()); if (!list) { return true; } status.GetMakefile().AddDefinition( listName, list->remove_items(args.begin() + 2, args.end()).to_string()); 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 auto list = GetList(listName, status.GetMakefile()); if (!list) { return true; } status.GetMakefile().AddDefinition(listName, list->reverse().to_string()); 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 auto list = GetList(listName, status.GetMakefile()); if (!list) { return true; } status.GetMakefile().AddDefinition(listName, list->remove_duplicates().to_string()); return true; } 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; } // Descriptor of action // Action: enum value identifying action // Arity: number of arguments required for the action struct ActionDescriptor { ActionDescriptor(std::string name) : Name(std::move(name)) { } ActionDescriptor(std::string name, cmList::TransformAction action, int arity) : Name(std::move(name)) , Action(action) , Arity(arity) { } operator const std::string&() const { return this->Name; } std::string Name; cmList::TransformAction Action; int Arity = 0; }; // Build a set of supported actions. std::set> descriptors{ { { "APPEND", cmList::TransformAction::APPEND, 1 }, { "PREPEND", cmList::TransformAction::PREPEND, 1 }, { "TOUPPER", cmList::TransformAction::TOUPPER, 0 }, { "TOLOWER", cmList::TransformAction::TOLOWER, 0 }, { "STRIP", cmList::TransformAction::STRIP, 0 }, { "GENEX_STRIP", cmList::TransformAction::GENEX_STRIP, 0 }, { "REPLACE", cmList::TransformAction::REPLACE, 2 } }, [](const std::string& x, const std::string& y) { return x < y; } }; const std::string& listName = args[1]; // Parse all possible function parameters using size_type = std::vector::size_type; size_type index = 2; 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; } std::vector arguments; index += descriptor->Arity; if (descriptor->Arity > 0) { arguments = std::vector(args.begin() + 3, args.begin() + index); } const std::string REGEX{ "REGEX" }; const std::string AT{ "AT" }; const std::string FOR{ "FOR" }; const std::string OUTPUT_VARIABLE{ "OUTPUT_VARIABLE" }; std::unique_ptr selector; std::string outputName = listName; try { // handle optional arguments while (args.size() > index) { if ((args[index] == REGEX || args[index] == AT || args[index] == FOR) && selector) { status.SetError( cmStrCat("sub-command TRANSFORM, selector already specified (", selector->GetTag(), ").")); 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; } selector = cmList::TransformSelector::New( args[index]); 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; } selector = cmList::TransformSelector::New( 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; } cmList::index_type start = 0; cmList::index_type stop = 0; cmList::index_type 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 " "positive numeric value for ."); return false; } selector = cmList::TransformSelector::New( { 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; } outputName = args[index++]; continue; } status.SetError(cmStrCat("sub-command TRANSFORM, '", cmJoin(cmMakeRange(args).advance(index), " "), "': unexpected argument(s).")); return false; } // expand the list variable auto list = GetList(listName, status.GetMakefile()); if (!list) { status.GetMakefile().AddDefinition(outputName, ""); return true; } list->transform(descriptor->Action, arguments, std::move(selector)); status.GetMakefile().AddDefinition(outputName, list->to_string()); return true; } catch (cmList::transform_error& e) { status.SetError(e.what()); return false; } } 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; } using SortConfig = cmList::SortConfiguration; SortConfig sortConfig; size_t argumentIndex = 2; const std::string messageHint = "sub-command SORT "; while (argumentIndex < args.size()) { std::string const& option = args[argumentIndex++]; if (option == "COMPARE") { if (sortConfig.Compare != SortConfig::CompareMethod::DEFAULT) { std::string error = cmStrCat(messageHint, "option \"", option, "\" has been specified multiple times."); status.SetError(error); return false; } if (argumentIndex < args.size()) { std::string const& argument = args[argumentIndex++]; if (argument == "STRING") { sortConfig.Compare = SortConfig::CompareMethod::STRING; } else if (argument == "FILE_BASENAME") { sortConfig.Compare = SortConfig::CompareMethod::FILE_BASENAME; } else if (argument == "NATURAL") { sortConfig.Compare = SortConfig::CompareMethod::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 (sortConfig.Case != SortConfig::CaseSensitivity::DEFAULT) { status.SetError(cmStrCat(messageHint, "option \"", option, "\" has been specified multiple times.")); return false; } if (argumentIndex < args.size()) { std::string const& argument = args[argumentIndex++]; if (argument == "SENSITIVE") { sortConfig.Case = SortConfig::CaseSensitivity::SENSITIVE; } else if (argument == "INSENSITIVE") { sortConfig.Case = SortConfig::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 (sortConfig.Order != SortConfig::OrderMode::DEFAULT) { status.SetError(cmStrCat(messageHint, "option \"", option, "\" has been specified multiple times.")); return false; } if (argumentIndex < args.size()) { std::string const& argument = args[argumentIndex++]; if (argument == "ASCENDING") { sortConfig.Order = SortConfig::OrderMode::ASCENDING; } else if (argument == "DESCENDING") { sortConfig.Order = SortConfig::OrderMode::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; } } const std::string& listName = args[1]; // expand the variable auto list = GetList(listName, status.GetMakefile()); if (!list) { return true; } status.GetMakefile().AddDefinition(listName, list->sort(sortConfig).to_string()); 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 auto list = GetList(listName, status.GetMakefile()); if (!list || list->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; } if (start < 0) { status.SetError(cmStrCat("begin index: ", start, " is out of range 0 - ", list->size() - 1)); return false; } if (length < -1) { status.SetError(cmStrCat("length: ", length, " should be -1 or greater")); return false; } using size_type = cmList::size_type; try { auto sublist = list->sublist(static_cast(start), static_cast(length)); status.GetMakefile().AddDefinition(variableName, sublist.to_string()); return true; } catch (std::out_of_range& e) { status.SetError(e.what()); return false; } } 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 auto list = GetList(listName, status.GetMakefile()); if (!list || list->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; for (cc = 2; cc < args.size(); ++cc) { int index; if (!GetIndexArg(args[cc], &index, status.GetMakefile())) { status.SetError(cmStrCat("index: ", args[cc], " is not a valid index")); return false; } removed.push_back(index); } try { status.GetMakefile().AddDefinition( listName, list->remove_items(removed.begin(), removed.end()).to_string()); return true; } catch (std::out_of_range& e) { status.SetError(e.what()); return false; } } 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]; cmList::FilterMode filterMode; if (op == "INCLUDE") { filterMode = cmList::FilterMode::INCLUDE; } else if (op == "EXCLUDE") { filterMode = cmList::FilterMode::EXCLUDE; } else { status.SetError("sub-command FILTER does not recognize operator " + op); return false; } const std::string& listName = args[1]; // expand the variable auto list = GetList(listName, status.GetMakefile()); if (!list) { return true; } const std::string& mode = args[3]; if (mode != "REGEX") { status.SetError("sub-command FILTER does not recognize mode " + mode); return false; } if (args.size() != 5) { status.SetError("sub-command FILTER, mode REGEX " "requires five arguments."); return false; } const std::string& pattern = args[4]; try { status.GetMakefile().AddDefinition( listName, list->filter(pattern, filterMode).to_string()); return true; } catch (std::invalid_argument& e) { status.SetError(e.what()); return false; } } } // 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); }