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 "cmListCommand.h"
17 #include <cmext/algorithm>
18 #include <cmext/string_view>
20 #include "cmsys/RegularExpression.hxx"
22 #include "cmAlgorithms.h"
23 #include "cmExecutionStatus.h"
24 #include "cmGeneratorExpression.h"
25 #include "cmMakefile.h"
26 #include "cmMessageType.h"
27 #include "cmPolicies.h"
29 #include "cmStringAlgorithms.h"
30 #include "cmStringReplaceHelper.h"
31 #include "cmSubcommandTable.h"
32 #include "cmSystemTools.h"
37 bool GetIndexArg(const std::string& arg, int* idx, cmMakefile& mf)
40 if (!cmStrToLong(arg, &value)) {
41 switch (mf.GetPolicyStatus(cmPolicies::CMP0121)) {
42 case cmPolicies::WARN: {
43 // Default is to warn and use old behavior OLD behavior is to allow
44 // compatibility, so issue a warning and use the previous behavior.
46 cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0121),
47 " Invalid list index \"", arg, "\".");
48 mf.IssueMessage(MessageType::AUTHOR_WARNING, warn);
52 // OLD behavior is to allow compatibility, so just ignore the
57 case cmPolicies::REQUIRED_IF_USED:
58 case cmPolicies::REQUIRED_ALWAYS:
60 cmStrCat(cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0121),
61 " Invalid list index \"", arg, "\".");
62 mf.IssueMessage(MessageType::FATAL_ERROR, msg);
67 // Truncation is happening here, but it had always been happening here.
68 *idx = static_cast<int>(value);
73 bool FilterRegex(std::vector<std::string> const& args, bool includeMatches,
74 std::string const& listName,
75 std::vector<std::string>& varArgsExpanded,
76 cmExecutionStatus& status);
78 bool GetListString(std::string& listString, const std::string& var,
79 const cmMakefile& makefile)
82 cmValue cacheValue = makefile.GetDefinition(var);
86 listString = *cacheValue;
90 bool GetList(std::vector<std::string>& list, const std::string& var,
91 const cmMakefile& makefile)
93 std::string listString;
94 if (!GetListString(listString, var, makefile)) {
97 // if the size of the list
98 if (listString.empty()) {
101 // expand the variable into a list
102 cmExpandList(listString, list, true);
103 // if no empty elements then just return
104 if (!cm::contains(list, std::string())) {
107 // if we have empty elements we need to check policy CMP0007
108 switch (makefile.GetPolicyStatus(cmPolicies::CMP0007)) {
109 case cmPolicies::WARN: {
110 // Default is to warn and use old behavior
111 // OLD behavior is to allow compatibility, so recall
112 // ExpandListArgument without the true which will remove
115 cmExpandList(listString, list);
117 cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0007),
118 " List has value = [", listString, "].");
119 makefile.IssueMessage(MessageType::AUTHOR_WARNING, warn);
122 case cmPolicies::OLD:
123 // OLD behavior is to allow compatibility, so recall
124 // ExpandListArgument without the true which will remove
127 cmExpandList(listString, list);
129 case cmPolicies::NEW:
131 case cmPolicies::REQUIRED_IF_USED:
132 case cmPolicies::REQUIRED_ALWAYS:
133 makefile.IssueMessage(
134 MessageType::FATAL_ERROR,
135 cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0007));
141 bool HandleLengthCommand(std::vector<std::string> const& args,
142 cmExecutionStatus& status)
144 if (args.size() != 3) {
145 status.SetError("sub-command LENGTH requires two arguments.");
149 const std::string& listName = args[1];
150 const std::string& variableName = args.back();
151 std::vector<std::string> varArgsExpanded;
152 // do not check the return value here
153 // if the list var is not found varArgsExpanded will have size 0
154 // and we will return 0
155 GetList(varArgsExpanded, listName, status.GetMakefile());
156 size_t length = varArgsExpanded.size();
158 snprintf(buffer, sizeof(buffer), "%d", static_cast<int>(length));
160 status.GetMakefile().AddDefinition(variableName, buffer);
164 bool HandleGetCommand(std::vector<std::string> const& args,
165 cmExecutionStatus& status)
167 if (args.size() < 4) {
168 status.SetError("sub-command GET requires at least three arguments.");
172 const std::string& listName = args[1];
173 const std::string& variableName = args.back();
174 // expand the variable
175 std::vector<std::string> varArgsExpanded;
176 if (!GetList(varArgsExpanded, listName, status.GetMakefile())) {
177 status.GetMakefile().AddDefinition(variableName, "NOTFOUND");
180 // FIXME: Add policy to make non-existing lists an error like empty lists.
181 if (varArgsExpanded.empty()) {
182 status.SetError("GET given empty list");
188 const char* sep = "";
189 size_t nitem = varArgsExpanded.size();
190 for (cc = 2; cc < args.size() - 1; cc++) {
192 if (!GetIndexArg(args[cc], &item, status.GetMakefile())) {
193 status.SetError(cmStrCat("index: ", args[cc], " is not a valid index"));
199 item = static_cast<int>(nitem) + item;
201 if (item < 0 || nitem <= static_cast<size_t>(item)) {
202 status.SetError(cmStrCat("index: ", item, " out of range (-", nitem,
203 ", ", nitem - 1, ")"));
206 value += varArgsExpanded[item];
209 status.GetMakefile().AddDefinition(variableName, value);
213 bool HandleAppendCommand(std::vector<std::string> const& args,
214 cmExecutionStatus& status)
216 assert(args.size() >= 2);
218 // Skip if nothing to append.
219 if (args.size() < 3) {
223 cmMakefile& makefile = status.GetMakefile();
224 std::string const& listName = args[1];
225 // expand the variable
226 std::string listString;
227 GetListString(listString, listName, makefile);
229 // If `listString` or `args` is empty, no need to append `;`,
230 // then index is going to be `1` and points to the end-of-string ";"
232 static_cast<std::string::size_type>(listString.empty() || args.empty());
233 listString += &";"[offset] + cmJoin(cmMakeRange(args).advance(2), ";");
235 makefile.AddDefinition(listName, listString);
239 bool HandlePrependCommand(std::vector<std::string> const& args,
240 cmExecutionStatus& status)
242 assert(args.size() >= 2);
244 // Skip if nothing to prepend.
245 if (args.size() < 3) {
249 cmMakefile& makefile = status.GetMakefile();
250 std::string const& listName = args[1];
251 // expand the variable
252 std::string listString;
253 GetListString(listString, listName, makefile);
255 // If `listString` or `args` is empty, no need to append `;`,
256 // then `offset` is going to be `1` and points to the end-of-string ";"
258 static_cast<std::string::size_type>(listString.empty() || args.empty());
260 cmJoin(cmMakeRange(args).advance(2), ";") + &";"[offset]);
262 makefile.AddDefinition(listName, listString);
266 bool HandlePopBackCommand(std::vector<std::string> const& args,
267 cmExecutionStatus& status)
269 assert(args.size() >= 2);
271 cmMakefile& makefile = status.GetMakefile();
272 auto ai = args.cbegin();
273 ++ai; // Skip subcommand name
274 std::string const& listName = *ai++;
275 std::vector<std::string> varArgsExpanded;
276 if (!GetList(varArgsExpanded, listName, makefile)) {
277 // Can't get the list definition... undefine any vars given after.
278 for (; ai != args.cend(); ++ai) {
279 makefile.RemoveDefinition(*ai);
284 if (!varArgsExpanded.empty()) {
285 if (ai == args.cend()) {
286 // No variables are given... Just remove one element.
287 varArgsExpanded.pop_back();
289 // Ok, assign elements to be removed to the given variables
290 for (; !varArgsExpanded.empty() && ai != args.cend(); ++ai) {
291 assert(!ai->empty());
292 makefile.AddDefinition(*ai, varArgsExpanded.back());
293 varArgsExpanded.pop_back();
295 // Undefine the rest variables if the list gets empty earlier...
296 for (; ai != args.cend(); ++ai) {
297 makefile.RemoveDefinition(*ai);
301 makefile.AddDefinition(listName, cmJoin(varArgsExpanded, ";"));
304 args.cend()) { // The list is empty, but some args were given
305 // Need to *undefine* 'em all, cuz there are no items to assign...
306 for (; ai != args.cend(); ++ai) {
307 makefile.RemoveDefinition(*ai);
314 bool HandlePopFrontCommand(std::vector<std::string> const& args,
315 cmExecutionStatus& status)
317 assert(args.size() >= 2);
319 cmMakefile& makefile = status.GetMakefile();
320 auto ai = args.cbegin();
321 ++ai; // Skip subcommand name
322 std::string const& listName = *ai++;
323 std::vector<std::string> varArgsExpanded;
324 if (!GetList(varArgsExpanded, listName, makefile)) {
325 // Can't get the list definition... undefine any vars given after.
326 for (; ai != args.cend(); ++ai) {
327 makefile.RemoveDefinition(*ai);
332 if (!varArgsExpanded.empty()) {
333 if (ai == args.cend()) {
334 // No variables are given... Just remove one element.
335 varArgsExpanded.erase(varArgsExpanded.begin());
337 // Ok, assign elements to be removed to the given variables
338 auto vi = varArgsExpanded.begin();
339 for (; vi != varArgsExpanded.end() && ai != args.cend(); ++ai, ++vi) {
340 assert(!ai->empty());
341 makefile.AddDefinition(*ai, *vi);
343 varArgsExpanded.erase(varArgsExpanded.begin(), vi);
344 // Undefine the rest variables if the list gets empty earlier...
345 for (; ai != args.cend(); ++ai) {
346 makefile.RemoveDefinition(*ai);
350 makefile.AddDefinition(listName, cmJoin(varArgsExpanded, ";"));
353 args.cend()) { // The list is empty, but some args were given
354 // Need to *undefine* 'em all, cuz there are no items to assign...
355 for (; ai != args.cend(); ++ai) {
356 makefile.RemoveDefinition(*ai);
363 bool HandleFindCommand(std::vector<std::string> const& args,
364 cmExecutionStatus& status)
366 if (args.size() != 4) {
367 status.SetError("sub-command FIND requires three arguments.");
371 const std::string& listName = args[1];
372 const std::string& variableName = args.back();
373 // expand the variable
374 std::vector<std::string> varArgsExpanded;
375 if (!GetList(varArgsExpanded, listName, status.GetMakefile())) {
376 status.GetMakefile().AddDefinition(variableName, "-1");
380 auto it = std::find(varArgsExpanded.begin(), varArgsExpanded.end(), args[2]);
381 if (it != varArgsExpanded.end()) {
382 status.GetMakefile().AddDefinition(
384 std::to_string(std::distance(varArgsExpanded.begin(), it)));
388 status.GetMakefile().AddDefinition(variableName, "-1");
392 bool HandleInsertCommand(std::vector<std::string> const& args,
393 cmExecutionStatus& status)
395 if (args.size() < 4) {
396 status.SetError("sub-command INSERT requires at least three arguments.");
400 const std::string& listName = args[1];
402 // expand the variable
404 if (!GetIndexArg(args[2], &item, status.GetMakefile())) {
405 status.SetError(cmStrCat("index: ", args[2], " is not a valid index"));
408 std::vector<std::string> varArgsExpanded;
409 if ((!GetList(varArgsExpanded, listName, status.GetMakefile()) ||
410 varArgsExpanded.empty()) &&
412 status.SetError(cmStrCat("index: ", item, " out of range (0, 0)"));
416 if (!varArgsExpanded.empty()) {
417 size_t nitem = varArgsExpanded.size();
419 item = static_cast<int>(nitem) + item;
421 if (item < 0 || nitem < static_cast<size_t>(item)) {
422 status.SetError(cmStrCat("index: ", item, " out of range (-",
423 varArgsExpanded.size(), ", ",
424 varArgsExpanded.size(), ")"));
429 varArgsExpanded.insert(varArgsExpanded.begin() + item, args.begin() + 3,
432 std::string value = cmJoin(varArgsExpanded, ";");
433 status.GetMakefile().AddDefinition(listName, value);
437 bool HandleJoinCommand(std::vector<std::string> const& args,
438 cmExecutionStatus& status)
440 if (args.size() != 4) {
441 status.SetError(cmStrCat("sub-command JOIN requires three arguments (",
442 args.size() - 1, " found)."));
446 const std::string& listName = args[1];
447 const std::string& glue = args[2];
448 const std::string& variableName = args[3];
450 // expand the variable
451 std::vector<std::string> varArgsExpanded;
452 if (!GetList(varArgsExpanded, listName, status.GetMakefile())) {
453 status.GetMakefile().AddDefinition(variableName, "");
458 cmJoin(cmMakeRange(varArgsExpanded.begin(), varArgsExpanded.end()), glue);
460 status.GetMakefile().AddDefinition(variableName, value);
464 bool HandleRemoveItemCommand(std::vector<std::string> const& args,
465 cmExecutionStatus& status)
467 assert(args.size() >= 2);
469 if (args.size() == 2) {
473 const std::string& listName = args[1];
474 // expand the variable
475 std::vector<std::string> varArgsExpanded;
476 if (!GetList(varArgsExpanded, listName, status.GetMakefile())) {
480 std::vector<std::string> remove(args.begin() + 2, args.end());
481 std::sort(remove.begin(), remove.end());
482 auto remEnd = std::unique(remove.begin(), remove.end());
483 auto remBegin = remove.begin();
486 cmRemoveMatching(varArgsExpanded, cmMakeRange(remBegin, remEnd));
487 auto argsBegin = varArgsExpanded.cbegin();
488 std::string value = cmJoin(cmMakeRange(argsBegin, argsEnd), ";");
489 status.GetMakefile().AddDefinition(listName, value);
493 bool HandleReverseCommand(std::vector<std::string> const& args,
494 cmExecutionStatus& status)
496 assert(args.size() >= 2);
497 if (args.size() > 2) {
498 status.SetError("sub-command REVERSE only takes one argument.");
502 const std::string& listName = args[1];
503 // expand the variable
504 std::vector<std::string> varArgsExpanded;
505 if (!GetList(varArgsExpanded, listName, status.GetMakefile())) {
509 std::string value = cmJoin(cmReverseRange(varArgsExpanded), ";");
511 status.GetMakefile().AddDefinition(listName, value);
515 bool HandleRemoveDuplicatesCommand(std::vector<std::string> const& args,
516 cmExecutionStatus& status)
518 assert(args.size() >= 2);
519 if (args.size() > 2) {
520 status.SetError("sub-command REMOVE_DUPLICATES only takes one argument.");
524 const std::string& listName = args[1];
525 // expand the variable
526 std::vector<std::string> varArgsExpanded;
527 if (!GetList(varArgsExpanded, listName, status.GetMakefile())) {
531 auto argsEnd = cmRemoveDuplicates(varArgsExpanded);
532 auto argsBegin = varArgsExpanded.cbegin();
533 std::string value = cmJoin(cmMakeRange(argsBegin, argsEnd), ";");
535 status.GetMakefile().AddDefinition(listName, value);
539 // Helpers for list(TRANSFORM <list> ...)
540 using transform_type = std::function<std::string(const std::string&)>;
542 class transform_error : public std::runtime_error
545 transform_error(const std::string& error)
546 : std::runtime_error(error)
551 class TransformSelector
554 virtual ~TransformSelector() = default;
558 virtual bool Validate(std::size_t count = 0) = 0;
560 virtual bool InSelection(const std::string&) = 0;
562 virtual void Transform(std::vector<std::string>& list,
563 const transform_type& transform)
565 std::transform(list.begin(), list.end(), list.begin(), transform);
569 TransformSelector(std::string&& tag)
570 : Tag(std::move(tag))
574 class TransformNoSelector : public TransformSelector
577 TransformNoSelector()
578 : TransformSelector("NO SELECTOR")
582 bool Validate(std::size_t) override { return true; }
584 bool InSelection(const std::string&) override { return true; }
586 class TransformSelectorRegex : public TransformSelector
589 TransformSelectorRegex(const std::string& regex)
590 : TransformSelector("REGEX")
595 bool Validate(std::size_t) override { return this->Regex.is_valid(); }
597 bool InSelection(const std::string& value) override
599 return this->Regex.find(value);
602 cmsys::RegularExpression Regex;
604 class TransformSelectorIndexes : public TransformSelector
607 std::vector<int> Indexes;
609 bool InSelection(const std::string&) override { return true; }
611 void Transform(std::vector<std::string>& list,
612 const transform_type& transform) override
614 this->Validate(list.size());
616 for (auto index : this->Indexes) {
617 list[index] = transform(list[index]);
622 TransformSelectorIndexes(std::string&& tag)
623 : TransformSelector(std::move(tag))
626 TransformSelectorIndexes(std::string&& tag, std::vector<int>&& indexes)
627 : TransformSelector(std::move(tag))
632 int NormalizeIndex(int index, std::size_t count)
635 index = static_cast<int>(count) + index;
637 if (index < 0 || count <= static_cast<std::size_t>(index)) {
638 throw transform_error(cmStrCat(
639 "sub-command TRANSFORM, selector ", this->Tag, ", index: ", index,
640 " out of range (-", count, ", ", count - 1, ")."));
645 class TransformSelectorAt : public TransformSelectorIndexes
648 TransformSelectorAt(std::vector<int>&& indexes)
649 : TransformSelectorIndexes("AT", std::move(indexes))
653 bool Validate(std::size_t count) override
655 decltype(this->Indexes) indexes;
657 for (auto index : this->Indexes) {
658 indexes.push_back(this->NormalizeIndex(index, count));
660 this->Indexes = std::move(indexes);
665 class TransformSelectorFor : public TransformSelectorIndexes
668 TransformSelectorFor(int start, int stop, int step)
669 : TransformSelectorIndexes("FOR")
676 bool Validate(std::size_t count) override
678 this->Start = this->NormalizeIndex(this->Start, count);
679 this->Stop = this->NormalizeIndex(this->Stop, count);
681 // Does stepping move us further from the end?
682 if (this->Start > this->Stop) {
683 throw transform_error(
684 cmStrCat("sub-command TRANSFORM, selector FOR "
685 "expects <start> to be no greater than <stop> (",
686 this->Start, " > ", this->Stop, ")"));
690 auto size = (this->Stop - this->Start + 1) / this->Step;
691 if ((this->Stop - this->Start + 1) % this->Step != 0) {
695 this->Indexes.resize(size);
696 auto start = this->Start;
697 auto step = this->Step;
698 std::generate(this->Indexes.begin(), this->Indexes.end(),
699 [&start, step]() -> int {
709 int Start, Stop, Step;
712 class TransformAction
715 virtual ~TransformAction() = default;
717 virtual std::string Transform(const std::string& input) = 0;
719 class TransformReplace : public TransformAction
722 TransformReplace(const std::vector<std::string>& arguments,
723 cmMakefile* makefile)
724 : ReplaceHelper(arguments[0], arguments[1], makefile)
726 makefile->ClearMatches();
728 if (!this->ReplaceHelper.IsRegularExpressionValid()) {
729 throw transform_error(
730 cmStrCat("sub-command TRANSFORM, action REPLACE: Failed to compile "
732 arguments[0], "\"."));
734 if (!this->ReplaceHelper.IsReplaceExpressionValid()) {
735 throw transform_error(cmStrCat("sub-command TRANSFORM, action REPLACE: ",
736 this->ReplaceHelper.GetError(), "."));
740 std::string Transform(const std::string& input) override
742 // Scan through the input for all matches.
745 if (!this->ReplaceHelper.Replace(input, output)) {
746 throw transform_error(cmStrCat("sub-command TRANSFORM, action REPLACE: ",
747 this->ReplaceHelper.GetError(), "."));
754 cmStringReplaceHelper ReplaceHelper;
757 bool HandleTransformCommand(std::vector<std::string> const& args,
758 cmExecutionStatus& status)
760 if (args.size() < 3) {
762 "sub-command TRANSFORM requires an action to be specified.");
766 // Structure collecting all elements of the command
769 Command(const std::string& listName)
771 , OutputName(listName)
776 std::string ListName;
777 std::vector<std::string> Arguments;
778 std::unique_ptr<TransformAction> Action;
779 std::unique_ptr<TransformSelector> Selector;
780 std::string OutputName;
783 // Descriptor of action
784 // Arity: number of arguments required for the action
785 // Transform: lambda function implementing the action
786 struct ActionDescriptor
788 ActionDescriptor(std::string name)
789 : Name(std::move(name))
792 ActionDescriptor(std::string name, int arity, transform_type transform)
793 : Name(std::move(name))
795 #if defined(__GNUC__) && __GNUC__ == 6 && defined(__aarch64__)
796 // std::function move constructor miscompiles on this architecture
797 , Transform(transform)
799 , Transform(std::move(transform))
804 operator const std::string&() const { return this->Name; }
808 transform_type Transform;
811 // Build a set of supported actions.
812 std::set<ActionDescriptor,
813 std::function<bool(const std::string&, const std::string&)>>
815 [](const std::string& x, const std::string& y) { return x < y; });
816 descriptors = { { "APPEND", 1,
817 [&command](const std::string& s) -> std::string {
818 if (command.Selector->InSelection(s)) {
819 return s + command.Arguments[0];
825 [&command](const std::string& s) -> std::string {
826 if (command.Selector->InSelection(s)) {
827 return command.Arguments[0] + s;
833 [&command](const std::string& s) -> std::string {
834 if (command.Selector->InSelection(s)) {
835 return cmSystemTools::UpperCase(s);
841 [&command](const std::string& s) -> std::string {
842 if (command.Selector->InSelection(s)) {
843 return cmSystemTools::LowerCase(s);
849 [&command](const std::string& s) -> std::string {
850 if (command.Selector->InSelection(s)) {
851 return cmTrimWhitespace(s);
857 [&command](const std::string& s) -> std::string {
858 if (command.Selector->InSelection(s)) {
859 return cmGeneratorExpression::Preprocess(
861 cmGeneratorExpression::StripAllGeneratorExpressions);
867 [&command](const std::string& s) -> std::string {
868 if (command.Selector->InSelection(s)) {
869 return command.Action->Transform(s);
875 using size_type = std::vector<std::string>::size_type;
878 // Parse all possible function parameters
879 auto descriptor = descriptors.find(args[index]);
881 if (descriptor == descriptors.end()) {
883 cmStrCat(" sub-command TRANSFORM, ", args[index], " invalid action."));
889 if (args.size() < index + descriptor->Arity) {
890 status.SetError(cmStrCat("sub-command TRANSFORM, action ",
891 descriptor->Name, " expects ", descriptor->Arity,
896 command.Name = descriptor->Name;
897 index += descriptor->Arity;
898 if (descriptor->Arity > 0) {
900 std::vector<std::string>(args.begin() + 3, args.begin() + index);
903 if (command.Name == "REPLACE") {
905 command.Action = cm::make_unique<TransformReplace>(
906 command.Arguments, &status.GetMakefile());
907 } catch (const transform_error& e) {
908 status.SetError(e.what());
913 const std::string REGEX{ "REGEX" };
914 const std::string AT{ "AT" };
915 const std::string FOR{ "FOR" };
916 const std::string OUTPUT_VARIABLE{ "OUTPUT_VARIABLE" };
918 // handle optional arguments
919 while (args.size() > index) {
920 if ((args[index] == REGEX || args[index] == AT || args[index] == FOR) &&
923 cmStrCat("sub-command TRANSFORM, selector already specified (",
924 command.Selector->Tag, ")."));
930 if (args[index] == REGEX) {
931 if (args.size() == ++index) {
932 status.SetError("sub-command TRANSFORM, selector REGEX expects "
933 "'regular expression' argument.");
937 command.Selector = cm::make_unique<TransformSelectorRegex>(args[index]);
938 if (!command.Selector->Validate()) {
940 cmStrCat("sub-command TRANSFORM, selector REGEX failed to compile "
942 args[index], "\"."));
951 if (args[index] == AT) {
952 // get all specified indexes
953 std::vector<int> indexes;
954 while (args.size() > ++index) {
959 value = std::stoi(args[index], &pos);
960 if (pos != args[index].length()) {
961 // this is not a number, stop processing
964 indexes.push_back(value);
965 } catch (const std::invalid_argument&) {
966 // this is not a number, stop processing
971 if (indexes.empty()) {
973 "sub-command TRANSFORM, selector AT expects at least one "
979 cm::make_unique<TransformSelectorAt>(std::move(indexes));
985 if (args[index] == FOR) {
986 if (args.size() <= ++index + 1) {
988 "sub-command TRANSFORM, selector FOR expects, at least,"
1000 start = std::stoi(args[index], &pos);
1001 if (pos != args[index].length()) {
1002 // this is not a number
1005 stop = std::stoi(args[++index], &pos);
1006 if (pos != args[index].length()) {
1007 // this is not a number
1011 } catch (const std::invalid_argument&) {
1012 // this is not numbers
1016 status.SetError("sub-command TRANSFORM, selector FOR expects, "
1017 "at least, two numeric values.");
1020 // try to read a third numeric value for step
1021 if (args.size() > ++index) {
1025 step = std::stoi(args[index], &pos);
1026 if (pos != args[index].length()) {
1027 // this is not a number
1032 } catch (const std::invalid_argument&) {
1033 // this is not number, ignore exception
1038 status.SetError("sub-command TRANSFORM, selector FOR expects "
1039 "positive numeric value for <step>.");
1044 cm::make_unique<TransformSelectorFor>(start, stop, step);
1050 if (args[index] == OUTPUT_VARIABLE) {
1051 if (args.size() == ++index) {
1052 status.SetError("sub-command TRANSFORM, OUTPUT_VARIABLE "
1053 "expects variable name argument.");
1057 command.OutputName = args[index++];
1061 status.SetError(cmStrCat("sub-command TRANSFORM, '",
1062 cmJoin(cmMakeRange(args).advance(index), " "),
1063 "': unexpected argument(s)."));
1067 // expand the list variable
1068 std::vector<std::string> varArgsExpanded;
1069 if (!GetList(varArgsExpanded, command.ListName, status.GetMakefile())) {
1070 status.GetMakefile().AddDefinition(command.OutputName, "");
1074 if (!command.Selector) {
1075 // no selector specified, apply transformation to all elements
1076 command.Selector = cm::make_unique<TransformNoSelector>();
1080 command.Selector->Transform(varArgsExpanded, descriptor->Transform);
1081 } catch (const transform_error& e) {
1082 status.SetError(e.what());
1086 status.GetMakefile().AddDefinition(command.OutputName,
1087 cmJoin(varArgsExpanded, ";"));
1092 class cmStringSorter
1109 enum class CaseSensitivity
1117 using StringFilter = std::string (*)(const std::string&);
1118 StringFilter GetCompareFilter(Compare compare)
1120 return (compare == Compare::FILE_BASENAME) ? cmSystemTools::GetFilenameName
1124 StringFilter GetCaseFilter(CaseSensitivity sensitivity)
1126 return (sensitivity == CaseSensitivity::INSENSITIVE)
1127 ? cmSystemTools::LowerCase
1131 using ComparisonFunction =
1132 std::function<bool(const std::string&, const std::string&)>;
1133 ComparisonFunction GetComparisonFunction(Compare compare)
1135 if (compare == Compare::NATURAL) {
1136 return std::function<bool(const std::string&, const std::string&)>(
1137 [](const std::string& x, const std::string& y) {
1138 return cmSystemTools::strverscmp(x, y) < 0;
1141 return std::function<bool(const std::string&, const std::string&)>(
1142 [](const std::string& x, const std::string& y) { return x < y; });
1146 cmStringSorter(Compare compare, CaseSensitivity caseSensitivity,
1147 Order desc = Order::ASCENDING)
1148 : filters{ this->GetCompareFilter(compare),
1149 this->GetCaseFilter(caseSensitivity) }
1150 , sortMethod(this->GetComparisonFunction(compare))
1151 , descending(desc == Order::DESCENDING)
1155 std::string ApplyFilter(const std::string& argument)
1157 std::string result = argument;
1158 for (auto filter : this->filters) {
1159 if (filter != nullptr) {
1160 result = filter(result);
1166 bool operator()(const std::string& a, const std::string& b)
1168 std::string af = this->ApplyFilter(a);
1169 std::string bf = this->ApplyFilter(b);
1171 if (this->descending) {
1172 result = this->sortMethod(bf, af);
1174 result = this->sortMethod(af, bf);
1180 StringFilter filters[2] = { nullptr, nullptr };
1181 ComparisonFunction sortMethod;
1185 bool HandleSortCommand(std::vector<std::string> const& args,
1186 cmExecutionStatus& status)
1188 assert(args.size() >= 2);
1189 if (args.size() > 8) {
1190 status.SetError("sub-command SORT only takes up to six arguments.");
1194 auto sortCompare = cmStringSorter::Compare::UNINITIALIZED;
1195 auto sortCaseSensitivity = cmStringSorter::CaseSensitivity::UNINITIALIZED;
1196 auto sortOrder = cmStringSorter::Order::UNINITIALIZED;
1198 size_t argumentIndex = 2;
1199 const std::string messageHint = "sub-command SORT ";
1201 while (argumentIndex < args.size()) {
1202 std::string const& option = args[argumentIndex++];
1203 if (option == "COMPARE") {
1204 if (sortCompare != cmStringSorter::Compare::UNINITIALIZED) {
1205 std::string error = cmStrCat(messageHint, "option \"", option,
1206 "\" has been specified multiple times.");
1207 status.SetError(error);
1210 if (argumentIndex < args.size()) {
1211 std::string const& argument = args[argumentIndex++];
1212 if (argument == "STRING") {
1213 sortCompare = cmStringSorter::Compare::STRING;
1214 } else if (argument == "FILE_BASENAME") {
1215 sortCompare = cmStringSorter::Compare::FILE_BASENAME;
1216 } else if (argument == "NATURAL") {
1217 sortCompare = cmStringSorter::Compare::NATURAL;
1220 cmStrCat(messageHint, "value \"", argument, "\" for option \"",
1221 option, "\" is invalid.");
1222 status.SetError(error);
1226 status.SetError(cmStrCat(messageHint, "missing argument for option \"",
1230 } else if (option == "CASE") {
1231 if (sortCaseSensitivity !=
1232 cmStringSorter::CaseSensitivity::UNINITIALIZED) {
1233 status.SetError(cmStrCat(messageHint, "option \"", option,
1234 "\" has been specified multiple times."));
1237 if (argumentIndex < args.size()) {
1238 std::string const& argument = args[argumentIndex++];
1239 if (argument == "SENSITIVE") {
1240 sortCaseSensitivity = cmStringSorter::CaseSensitivity::SENSITIVE;
1241 } else if (argument == "INSENSITIVE") {
1242 sortCaseSensitivity = cmStringSorter::CaseSensitivity::INSENSITIVE;
1244 status.SetError(cmStrCat(messageHint, "value \"", argument,
1245 "\" for option \"", option,
1250 status.SetError(cmStrCat(messageHint, "missing argument for option \"",
1254 } else if (option == "ORDER") {
1256 if (sortOrder != cmStringSorter::Order::UNINITIALIZED) {
1257 status.SetError(cmStrCat(messageHint, "option \"", option,
1258 "\" has been specified multiple times."));
1261 if (argumentIndex < args.size()) {
1262 std::string const& argument = args[argumentIndex++];
1263 if (argument == "ASCENDING") {
1264 sortOrder = cmStringSorter::Order::ASCENDING;
1265 } else if (argument == "DESCENDING") {
1266 sortOrder = cmStringSorter::Order::DESCENDING;
1268 status.SetError(cmStrCat(messageHint, "value \"", argument,
1269 "\" for option \"", option,
1274 status.SetError(cmStrCat(messageHint, "missing argument for option \"",
1280 cmStrCat(messageHint, "option \"", option, "\" is unknown."));
1284 // set Default Values if Option is not given
1285 if (sortCompare == cmStringSorter::Compare::UNINITIALIZED) {
1286 sortCompare = cmStringSorter::Compare::STRING;
1288 if (sortCaseSensitivity == cmStringSorter::CaseSensitivity::UNINITIALIZED) {
1289 sortCaseSensitivity = cmStringSorter::CaseSensitivity::SENSITIVE;
1291 if (sortOrder == cmStringSorter::Order::UNINITIALIZED) {
1292 sortOrder = cmStringSorter::Order::ASCENDING;
1295 const std::string& listName = args[1];
1296 // expand the variable
1297 std::vector<std::string> varArgsExpanded;
1298 if (!GetList(varArgsExpanded, listName, status.GetMakefile())) {
1302 if ((sortCompare == cmStringSorter::Compare::STRING) &&
1303 (sortCaseSensitivity == cmStringSorter::CaseSensitivity::SENSITIVE) &&
1304 (sortOrder == cmStringSorter::Order::ASCENDING)) {
1305 std::sort(varArgsExpanded.begin(), varArgsExpanded.end());
1307 cmStringSorter sorter(sortCompare, sortCaseSensitivity, sortOrder);
1308 std::sort(varArgsExpanded.begin(), varArgsExpanded.end(), sorter);
1311 std::string value = cmJoin(varArgsExpanded, ";");
1312 status.GetMakefile().AddDefinition(listName, value);
1316 bool HandleSublistCommand(std::vector<std::string> const& args,
1317 cmExecutionStatus& status)
1319 if (args.size() != 5) {
1320 status.SetError(cmStrCat("sub-command SUBLIST requires four arguments (",
1321 args.size() - 1, " found)."));
1325 const std::string& listName = args[1];
1326 const std::string& variableName = args.back();
1328 // expand the variable
1329 std::vector<std::string> varArgsExpanded;
1330 if (!GetList(varArgsExpanded, listName, status.GetMakefile()) ||
1331 varArgsExpanded.empty()) {
1332 status.GetMakefile().AddDefinition(variableName, "");
1338 if (!GetIndexArg(args[2], &start, status.GetMakefile())) {
1339 status.SetError(cmStrCat("index: ", args[2], " is not a valid index"));
1342 if (!GetIndexArg(args[3], &length, status.GetMakefile())) {
1343 status.SetError(cmStrCat("index: ", args[3], " is not a valid index"));
1347 using size_type = decltype(varArgsExpanded)::size_type;
1349 if (start < 0 || static_cast<size_type>(start) >= varArgsExpanded.size()) {
1350 status.SetError(cmStrCat("begin index: ", start, " is out of range 0 - ",
1351 varArgsExpanded.size() - 1));
1355 status.SetError(cmStrCat("length: ", length, " should be -1 or greater"));
1359 const size_type end =
1361 static_cast<size_type>(start + length) > varArgsExpanded.size())
1362 ? varArgsExpanded.size()
1363 : static_cast<size_type>(start + length);
1364 std::vector<std::string> sublist(varArgsExpanded.begin() + start,
1365 varArgsExpanded.begin() + end);
1366 status.GetMakefile().AddDefinition(variableName, cmJoin(sublist, ";"));
1370 bool HandleRemoveAtCommand(std::vector<std::string> const& args,
1371 cmExecutionStatus& status)
1373 if (args.size() < 3) {
1374 status.SetError("sub-command REMOVE_AT requires at least "
1379 const std::string& listName = args[1];
1380 // expand the variable
1381 std::vector<std::string> varArgsExpanded;
1382 if (!GetList(varArgsExpanded, listName, status.GetMakefile()) ||
1383 varArgsExpanded.empty()) {
1384 std::ostringstream str;
1386 for (size_t i = 1; i < args.size(); ++i) {
1388 if (i != args.size() - 1) {
1392 str << " out of range (0, 0)";
1393 status.SetError(str.str());
1398 std::vector<size_t> removed;
1399 size_t nitem = varArgsExpanded.size();
1400 for (cc = 2; cc < args.size(); ++cc) {
1402 if (!GetIndexArg(args[cc], &item, status.GetMakefile())) {
1403 status.SetError(cmStrCat("index: ", args[cc], " is not a valid index"));
1407 item = static_cast<int>(nitem) + item;
1409 if (item < 0 || nitem <= static_cast<size_t>(item)) {
1410 status.SetError(cmStrCat("index: ", item, " out of range (-", nitem,
1411 ", ", nitem - 1, ")"));
1414 removed.push_back(static_cast<size_t>(item));
1417 std::sort(removed.begin(), removed.end());
1418 auto remEnd = std::unique(removed.begin(), removed.end());
1419 auto remBegin = removed.begin();
1422 cmRemoveIndices(varArgsExpanded, cmMakeRange(remBegin, remEnd));
1423 auto argsBegin = varArgsExpanded.cbegin();
1424 std::string value = cmJoin(cmMakeRange(argsBegin, argsEnd), ";");
1426 status.GetMakefile().AddDefinition(listName, value);
1430 bool HandleFilterCommand(std::vector<std::string> const& args,
1431 cmExecutionStatus& status)
1433 if (args.size() < 2) {
1434 status.SetError("sub-command FILTER requires a list to be specified.");
1438 if (args.size() < 3) {
1440 "sub-command FILTER requires an operator to be specified.");
1444 if (args.size() < 4) {
1445 status.SetError("sub-command FILTER requires a mode to be specified.");
1449 const std::string& op = args[2];
1450 bool includeMatches;
1451 if (op == "INCLUDE") {
1452 includeMatches = true;
1453 } else if (op == "EXCLUDE") {
1454 includeMatches = false;
1456 status.SetError("sub-command FILTER does not recognize operator " + op);
1460 const std::string& listName = args[1];
1461 // expand the variable
1462 std::vector<std::string> varArgsExpanded;
1463 if (!GetList(varArgsExpanded, listName, status.GetMakefile())) {
1467 const std::string& mode = args[3];
1468 if (mode == "REGEX") {
1469 if (args.size() != 5) {
1470 status.SetError("sub-command FILTER, mode REGEX "
1471 "requires five arguments.");
1474 return FilterRegex(args, includeMatches, listName, varArgsExpanded,
1478 status.SetError("sub-command FILTER does not recognize mode " + mode);
1485 MatchesRegex(cmsys::RegularExpression& in_regex, bool in_includeMatches)
1487 , includeMatches(in_includeMatches)
1491 bool operator()(const std::string& target)
1493 return this->regex.find(target) ^ this->includeMatches;
1497 cmsys::RegularExpression& regex;
1498 const bool includeMatches;
1501 bool FilterRegex(std::vector<std::string> const& args, bool includeMatches,
1502 std::string const& listName,
1503 std::vector<std::string>& varArgsExpanded,
1504 cmExecutionStatus& status)
1506 const std::string& pattern = args[4];
1507 cmsys::RegularExpression regex(pattern);
1508 if (!regex.is_valid()) {
1510 cmStrCat("sub-command FILTER, mode REGEX failed to compile regex \"",
1512 status.SetError(error);
1516 auto argsBegin = varArgsExpanded.begin();
1517 auto argsEnd = varArgsExpanded.end();
1519 std::remove_if(argsBegin, argsEnd, MatchesRegex(regex, includeMatches));
1521 std::string value = cmJoin(cmMakeRange(argsBegin, newArgsEnd), ";");
1522 status.GetMakefile().AddDefinition(listName, value);
1528 bool cmListCommand(std::vector<std::string> const& args,
1529 cmExecutionStatus& status)
1531 if (args.size() < 2) {
1532 status.SetError("must be called with at least two arguments.");
1536 static cmSubcommandTable const subcommand{
1537 { "LENGTH"_s, HandleLengthCommand },
1538 { "GET"_s, HandleGetCommand },
1539 { "APPEND"_s, HandleAppendCommand },
1540 { "PREPEND"_s, HandlePrependCommand },
1541 { "POP_BACK"_s, HandlePopBackCommand },
1542 { "POP_FRONT"_s, HandlePopFrontCommand },
1543 { "FIND"_s, HandleFindCommand },
1544 { "INSERT"_s, HandleInsertCommand },
1545 { "JOIN"_s, HandleJoinCommand },
1546 { "REMOVE_AT"_s, HandleRemoveAtCommand },
1547 { "REMOVE_ITEM"_s, HandleRemoveItemCommand },
1548 { "REMOVE_DUPLICATES"_s, HandleRemoveDuplicatesCommand },
1549 { "TRANSFORM"_s, HandleTransformCommand },
1550 { "SORT"_s, HandleSortCommand },
1551 { "SUBLIST"_s, HandleSublistCommand },
1552 { "REVERSE"_s, HandleReverseCommand },
1553 { "FILTER"_s, HandleFilterCommand },
1556 return subcommand(args[0], args, status);