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"
4
5 #include <algorithm>
6 #include <cassert>
7 #include <cstdio>
8 #include <functional>
9 #include <iterator>
10 #include <set>
11 #include <sstream>
12 #include <stdexcept>
13 #include <utility>
14 #include <vector>
15
16 #include <cm/memory>
17 #include <cmext/algorithm>
18 #include <cmext/string_view>
19
20 #include "cmsys/RegularExpression.hxx"
21
22 #include "cmAlgorithms.h"
23 #include "cmExecutionStatus.h"
24 #include "cmGeneratorExpression.h"
25 #include "cmMakefile.h"
26 #include "cmMessageType.h"
27 #include "cmPolicies.h"
28 #include "cmRange.h"
29 #include "cmStringAlgorithms.h"
30 #include "cmStringReplaceHelper.h"
31 #include "cmSubcommandTable.h"
32 #include "cmSystemTools.h"
33 #include "cmValue.h"
34
35 namespace {
36
GetIndexArg(const std::string & arg,int * idx,cmMakefile & mf)37 bool GetIndexArg(const std::string& arg, int* idx, cmMakefile& mf)
38 {
39 long value;
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.
45 std::string warn =
46 cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0121),
47 " Invalid list index \"", arg, "\".");
48 mf.IssueMessage(MessageType::AUTHOR_WARNING, warn);
49 CM_FALLTHROUGH;
50 }
51 case cmPolicies::OLD:
52 // OLD behavior is to allow compatibility, so just ignore the
53 // situation.
54 break;
55 case cmPolicies::NEW:
56 return false;
57 case cmPolicies::REQUIRED_IF_USED:
58 case cmPolicies::REQUIRED_ALWAYS:
59 std::string msg =
60 cmStrCat(cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0121),
61 " Invalid list index \"", arg, "\".");
62 mf.IssueMessage(MessageType::FATAL_ERROR, msg);
63 break;
64 }
65 }
66
67 // Truncation is happening here, but it had always been happening here.
68 *idx = static_cast<int>(value);
69
70 return true;
71 }
72
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);
77
GetListString(std::string & listString,const std::string & var,const cmMakefile & makefile)78 bool GetListString(std::string& listString, const std::string& var,
79 const cmMakefile& makefile)
80 {
81 // get the old value
82 cmValue cacheValue = makefile.GetDefinition(var);
83 if (!cacheValue) {
84 return false;
85 }
86 listString = *cacheValue;
87 return true;
88 }
89
GetList(std::vector<std::string> & list,const std::string & var,const cmMakefile & makefile)90 bool GetList(std::vector<std::string>& list, const std::string& var,
91 const cmMakefile& makefile)
92 {
93 std::string listString;
94 if (!GetListString(listString, var, makefile)) {
95 return false;
96 }
97 // if the size of the list
98 if (listString.empty()) {
99 return true;
100 }
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())) {
105 return true;
106 }
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
113 // empty values
114 list.clear();
115 cmExpandList(listString, list);
116 std::string warn =
117 cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0007),
118 " List has value = [", listString, "].");
119 makefile.IssueMessage(MessageType::AUTHOR_WARNING, warn);
120 return true;
121 }
122 case cmPolicies::OLD:
123 // OLD behavior is to allow compatibility, so recall
124 // ExpandListArgument without the true which will remove
125 // empty values
126 list.clear();
127 cmExpandList(listString, list);
128 return true;
129 case cmPolicies::NEW:
130 return true;
131 case cmPolicies::REQUIRED_IF_USED:
132 case cmPolicies::REQUIRED_ALWAYS:
133 makefile.IssueMessage(
134 MessageType::FATAL_ERROR,
135 cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0007));
136 return false;
137 }
138 return true;
139 }
140
HandleLengthCommand(std::vector<std::string> const & args,cmExecutionStatus & status)141 bool HandleLengthCommand(std::vector<std::string> const& args,
142 cmExecutionStatus& status)
143 {
144 if (args.size() != 3) {
145 status.SetError("sub-command LENGTH requires two arguments.");
146 return false;
147 }
148
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();
157 char buffer[1024];
158 sprintf(buffer, "%d", static_cast<int>(length));
159
160 status.GetMakefile().AddDefinition(variableName, buffer);
161 return true;
162 }
163
HandleGetCommand(std::vector<std::string> const & args,cmExecutionStatus & status)164 bool HandleGetCommand(std::vector<std::string> const& args,
165 cmExecutionStatus& status)
166 {
167 if (args.size() < 4) {
168 status.SetError("sub-command GET requires at least three arguments.");
169 return false;
170 }
171
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");
178 return true;
179 }
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");
183 return false;
184 }
185
186 std::string value;
187 size_t cc;
188 const char* sep = "";
189 size_t nitem = varArgsExpanded.size();
190 for (cc = 2; cc < args.size() - 1; cc++) {
191 int item;
192 if (!GetIndexArg(args[cc], &item, status.GetMakefile())) {
193 status.SetError(cmStrCat("index: ", args[cc], " is not a valid index"));
194 return false;
195 }
196 value += sep;
197 sep = ";";
198 if (item < 0) {
199 item = static_cast<int>(nitem) + item;
200 }
201 if (item < 0 || nitem <= static_cast<size_t>(item)) {
202 status.SetError(cmStrCat("index: ", item, " out of range (-", nitem,
203 ", ", nitem - 1, ")"));
204 return false;
205 }
206 value += varArgsExpanded[item];
207 }
208
209 status.GetMakefile().AddDefinition(variableName, value);
210 return true;
211 }
212
HandleAppendCommand(std::vector<std::string> const & args,cmExecutionStatus & status)213 bool HandleAppendCommand(std::vector<std::string> const& args,
214 cmExecutionStatus& status)
215 {
216 assert(args.size() >= 2);
217
218 // Skip if nothing to append.
219 if (args.size() < 3) {
220 return true;
221 }
222
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);
228
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 ";"
231 auto const offset =
232 std::string::size_type(listString.empty() || args.empty());
233 listString += &";"[offset] + cmJoin(cmMakeRange(args).advance(2), ";");
234
235 makefile.AddDefinition(listName, listString);
236 return true;
237 }
238
HandlePrependCommand(std::vector<std::string> const & args,cmExecutionStatus & status)239 bool HandlePrependCommand(std::vector<std::string> const& args,
240 cmExecutionStatus& status)
241 {
242 assert(args.size() >= 2);
243
244 // Skip if nothing to prepend.
245 if (args.size() < 3) {
246 return true;
247 }
248
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);
254
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 ";"
257 auto const offset =
258 std::string::size_type(listString.empty() || args.empty());
259 listString.insert(0,
260 cmJoin(cmMakeRange(args).advance(2), ";") + &";"[offset]);
261
262 makefile.AddDefinition(listName, listString);
263 return true;
264 }
265
HandlePopBackCommand(std::vector<std::string> const & args,cmExecutionStatus & status)266 bool HandlePopBackCommand(std::vector<std::string> const& args,
267 cmExecutionStatus& status)
268 {
269 assert(args.size() >= 2);
270
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);
280 }
281 return true;
282 }
283
284 if (!varArgsExpanded.empty()) {
285 if (ai == args.cend()) {
286 // No variables are given... Just remove one element.
287 varArgsExpanded.pop_back();
288 } else {
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();
294 }
295 // Undefine the rest variables if the list gets empty earlier...
296 for (; ai != args.cend(); ++ai) {
297 makefile.RemoveDefinition(*ai);
298 }
299 }
300
301 makefile.AddDefinition(listName, cmJoin(varArgsExpanded, ";"));
302
303 } else if (ai !=
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);
308 }
309 }
310
311 return true;
312 }
313
HandlePopFrontCommand(std::vector<std::string> const & args,cmExecutionStatus & status)314 bool HandlePopFrontCommand(std::vector<std::string> const& args,
315 cmExecutionStatus& status)
316 {
317 assert(args.size() >= 2);
318
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);
328 }
329 return true;
330 }
331
332 if (!varArgsExpanded.empty()) {
333 if (ai == args.cend()) {
334 // No variables are given... Just remove one element.
335 varArgsExpanded.erase(varArgsExpanded.begin());
336 } else {
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);
342 }
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);
347 }
348 }
349
350 makefile.AddDefinition(listName, cmJoin(varArgsExpanded, ";"));
351
352 } else if (ai !=
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);
357 }
358 }
359
360 return true;
361 }
362
HandleFindCommand(std::vector<std::string> const & args,cmExecutionStatus & status)363 bool HandleFindCommand(std::vector<std::string> const& args,
364 cmExecutionStatus& status)
365 {
366 if (args.size() != 4) {
367 status.SetError("sub-command FIND requires three arguments.");
368 return false;
369 }
370
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");
377 return true;
378 }
379
380 auto it = std::find(varArgsExpanded.begin(), varArgsExpanded.end(), args[2]);
381 if (it != varArgsExpanded.end()) {
382 status.GetMakefile().AddDefinition(
383 variableName,
384 std::to_string(std::distance(varArgsExpanded.begin(), it)));
385 return true;
386 }
387
388 status.GetMakefile().AddDefinition(variableName, "-1");
389 return true;
390 }
391
HandleInsertCommand(std::vector<std::string> const & args,cmExecutionStatus & status)392 bool HandleInsertCommand(std::vector<std::string> const& args,
393 cmExecutionStatus& status)
394 {
395 if (args.size() < 4) {
396 status.SetError("sub-command INSERT requires at least three arguments.");
397 return false;
398 }
399
400 const std::string& listName = args[1];
401
402 // expand the variable
403 int item;
404 if (!GetIndexArg(args[2], &item, status.GetMakefile())) {
405 status.SetError(cmStrCat("index: ", args[2], " is not a valid index"));
406 return false;
407 }
408 std::vector<std::string> varArgsExpanded;
409 if ((!GetList(varArgsExpanded, listName, status.GetMakefile()) ||
410 varArgsExpanded.empty()) &&
411 item != 0) {
412 status.SetError(cmStrCat("index: ", item, " out of range (0, 0)"));
413 return false;
414 }
415
416 if (!varArgsExpanded.empty()) {
417 size_t nitem = varArgsExpanded.size();
418 if (item < 0) {
419 item = static_cast<int>(nitem) + item;
420 }
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(), ")"));
425 return false;
426 }
427 }
428
429 varArgsExpanded.insert(varArgsExpanded.begin() + item, args.begin() + 3,
430 args.end());
431
432 std::string value = cmJoin(varArgsExpanded, ";");
433 status.GetMakefile().AddDefinition(listName, value);
434 return true;
435 }
436
HandleJoinCommand(std::vector<std::string> const & args,cmExecutionStatus & status)437 bool HandleJoinCommand(std::vector<std::string> const& args,
438 cmExecutionStatus& status)
439 {
440 if (args.size() != 4) {
441 status.SetError(cmStrCat("sub-command JOIN requires three arguments (",
442 args.size() - 1, " found)."));
443 return false;
444 }
445
446 const std::string& listName = args[1];
447 const std::string& glue = args[2];
448 const std::string& variableName = args[3];
449
450 // expand the variable
451 std::vector<std::string> varArgsExpanded;
452 if (!GetList(varArgsExpanded, listName, status.GetMakefile())) {
453 status.GetMakefile().AddDefinition(variableName, "");
454 return true;
455 }
456
457 std::string value =
458 cmJoin(cmMakeRange(varArgsExpanded.begin(), varArgsExpanded.end()), glue);
459
460 status.GetMakefile().AddDefinition(variableName, value);
461 return true;
462 }
463
HandleRemoveItemCommand(std::vector<std::string> const & args,cmExecutionStatus & status)464 bool HandleRemoveItemCommand(std::vector<std::string> const& args,
465 cmExecutionStatus& status)
466 {
467 assert(args.size() >= 2);
468
469 if (args.size() == 2) {
470 return true;
471 }
472
473 const std::string& listName = args[1];
474 // expand the variable
475 std::vector<std::string> varArgsExpanded;
476 if (!GetList(varArgsExpanded, listName, status.GetMakefile())) {
477 return true;
478 }
479
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();
484
485 auto argsEnd =
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);
490 return true;
491 }
492
HandleReverseCommand(std::vector<std::string> const & args,cmExecutionStatus & status)493 bool HandleReverseCommand(std::vector<std::string> const& args,
494 cmExecutionStatus& status)
495 {
496 assert(args.size() >= 2);
497 if (args.size() > 2) {
498 status.SetError("sub-command REVERSE only takes one argument.");
499 return false;
500 }
501
502 const std::string& listName = args[1];
503 // expand the variable
504 std::vector<std::string> varArgsExpanded;
505 if (!GetList(varArgsExpanded, listName, status.GetMakefile())) {
506 return true;
507 }
508
509 std::string value = cmJoin(cmReverseRange(varArgsExpanded), ";");
510
511 status.GetMakefile().AddDefinition(listName, value);
512 return true;
513 }
514
HandleRemoveDuplicatesCommand(std::vector<std::string> const & args,cmExecutionStatus & status)515 bool HandleRemoveDuplicatesCommand(std::vector<std::string> const& args,
516 cmExecutionStatus& status)
517 {
518 assert(args.size() >= 2);
519 if (args.size() > 2) {
520 status.SetError("sub-command REMOVE_DUPLICATES only takes one argument.");
521 return false;
522 }
523
524 const std::string& listName = args[1];
525 // expand the variable
526 std::vector<std::string> varArgsExpanded;
527 if (!GetList(varArgsExpanded, listName, status.GetMakefile())) {
528 return true;
529 }
530
531 auto argsEnd = cmRemoveDuplicates(varArgsExpanded);
532 auto argsBegin = varArgsExpanded.cbegin();
533 std::string value = cmJoin(cmMakeRange(argsBegin, argsEnd), ";");
534
535 status.GetMakefile().AddDefinition(listName, value);
536 return true;
537 }
538
539 // Helpers for list(TRANSFORM <list> ...)
540 using transform_type = std::function<std::string(const std::string&)>;
541
542 class transform_error : public std::runtime_error
543 {
544 public:
transform_error(const std::string & error)545 transform_error(const std::string& error)
546 : std::runtime_error(error)
547 {
548 }
549 };
550
551 class TransformSelector
552 {
553 public:
554 virtual ~TransformSelector() = default;
555
556 std::string Tag;
557
558 virtual bool Validate(std::size_t count = 0) = 0;
559
560 virtual bool InSelection(const std::string&) = 0;
561
Transform(std::vector<std::string> & list,const transform_type & transform)562 virtual void Transform(std::vector<std::string>& list,
563 const transform_type& transform)
564 {
565 std::transform(list.begin(), list.end(), list.begin(), transform);
566 }
567
568 protected:
TransformSelector(std::string && tag)569 TransformSelector(std::string&& tag)
570 : Tag(std::move(tag))
571 {
572 }
573 };
574 class TransformNoSelector : public TransformSelector
575 {
576 public:
TransformNoSelector()577 TransformNoSelector()
578 : TransformSelector("NO SELECTOR")
579 {
580 }
581
Validate(std::size_t)582 bool Validate(std::size_t) override { return true; }
583
InSelection(const std::string &)584 bool InSelection(const std::string&) override { return true; }
585 };
586 class TransformSelectorRegex : public TransformSelector
587 {
588 public:
TransformSelectorRegex(const std::string & regex)589 TransformSelectorRegex(const std::string& regex)
590 : TransformSelector("REGEX")
591 , Regex(regex)
592 {
593 }
594
Validate(std::size_t)595 bool Validate(std::size_t) override { return this->Regex.is_valid(); }
596
InSelection(const std::string & value)597 bool InSelection(const std::string& value) override
598 {
599 return this->Regex.find(value);
600 }
601
602 cmsys::RegularExpression Regex;
603 };
604 class TransformSelectorIndexes : public TransformSelector
605 {
606 public:
607 std::vector<int> Indexes;
608
InSelection(const std::string &)609 bool InSelection(const std::string&) override { return true; }
610
Transform(std::vector<std::string> & list,const transform_type & transform)611 void Transform(std::vector<std::string>& list,
612 const transform_type& transform) override
613 {
614 this->Validate(list.size());
615
616 for (auto index : this->Indexes) {
617 list[index] = transform(list[index]);
618 }
619 }
620
621 protected:
TransformSelectorIndexes(std::string && tag)622 TransformSelectorIndexes(std::string&& tag)
623 : TransformSelector(std::move(tag))
624 {
625 }
TransformSelectorIndexes(std::string && tag,std::vector<int> && indexes)626 TransformSelectorIndexes(std::string&& tag, std::vector<int>&& indexes)
627 : TransformSelector(std::move(tag))
628 , Indexes(indexes)
629 {
630 }
631
NormalizeIndex(int index,std::size_t count)632 int NormalizeIndex(int index, std::size_t count)
633 {
634 if (index < 0) {
635 index = static_cast<int>(count) + index;
636 }
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, ")."));
641 }
642 return index;
643 }
644 };
645 class TransformSelectorAt : public TransformSelectorIndexes
646 {
647 public:
TransformSelectorAt(std::vector<int> && indexes)648 TransformSelectorAt(std::vector<int>&& indexes)
649 : TransformSelectorIndexes("AT", std::move(indexes))
650 {
651 }
652
Validate(std::size_t count)653 bool Validate(std::size_t count) override
654 {
655 decltype(this->Indexes) indexes;
656
657 for (auto index : this->Indexes) {
658 indexes.push_back(this->NormalizeIndex(index, count));
659 }
660 this->Indexes = std::move(indexes);
661
662 return true;
663 }
664 };
665 class TransformSelectorFor : public TransformSelectorIndexes
666 {
667 public:
TransformSelectorFor(int start,int stop,int step)668 TransformSelectorFor(int start, int stop, int step)
669 : TransformSelectorIndexes("FOR")
670 , Start(start)
671 , Stop(stop)
672 , Step(step)
673 {
674 }
675
Validate(std::size_t count)676 bool Validate(std::size_t count) override
677 {
678 this->Start = this->NormalizeIndex(this->Start, count);
679 this->Stop = this->NormalizeIndex(this->Stop, count);
680
681 // compute indexes
682 auto size = (this->Stop - this->Start + 1) / this->Step;
683 if ((this->Stop - this->Start + 1) % this->Step != 0) {
684 size += 1;
685 }
686
687 this->Indexes.resize(size);
688 auto start = this->Start;
689 auto step = this->Step;
690 std::generate(this->Indexes.begin(), this->Indexes.end(),
691 [&start, step]() -> int {
692 auto r = start;
693 start += step;
694 return r;
695 });
696
697 return true;
698 }
699
700 private:
701 int Start, Stop, Step;
702 };
703
704 class TransformAction
705 {
706 public:
707 virtual ~TransformAction() = default;
708
709 virtual std::string Transform(const std::string& input) = 0;
710 };
711 class TransformReplace : public TransformAction
712 {
713 public:
TransformReplace(const std::vector<std::string> & arguments,cmMakefile * makefile)714 TransformReplace(const std::vector<std::string>& arguments,
715 cmMakefile* makefile)
716 : ReplaceHelper(arguments[0], arguments[1], makefile)
717 {
718 makefile->ClearMatches();
719
720 if (!this->ReplaceHelper.IsRegularExpressionValid()) {
721 throw transform_error(
722 cmStrCat("sub-command TRANSFORM, action REPLACE: Failed to compile "
723 "regex \"",
724 arguments[0], "\"."));
725 }
726 if (!this->ReplaceHelper.IsReplaceExpressionValid()) {
727 throw transform_error(cmStrCat("sub-command TRANSFORM, action REPLACE: ",
728 this->ReplaceHelper.GetError(), "."));
729 }
730 }
731
Transform(const std::string & input)732 std::string Transform(const std::string& input) override
733 {
734 // Scan through the input for all matches.
735 std::string output;
736
737 if (!this->ReplaceHelper.Replace(input, output)) {
738 throw transform_error(cmStrCat("sub-command TRANSFORM, action REPLACE: ",
739 this->ReplaceHelper.GetError(), "."));
740 }
741
742 return output;
743 }
744
745 private:
746 cmStringReplaceHelper ReplaceHelper;
747 };
748
HandleTransformCommand(std::vector<std::string> const & args,cmExecutionStatus & status)749 bool HandleTransformCommand(std::vector<std::string> const& args,
750 cmExecutionStatus& status)
751 {
752 if (args.size() < 3) {
753 status.SetError(
754 "sub-command TRANSFORM requires an action to be specified.");
755 return false;
756 }
757
758 // Structure collecting all elements of the command
759 struct Command
760 {
761 Command(const std::string& listName)
762 : ListName(listName)
763 , OutputName(listName)
764 {
765 }
766
767 std::string Name;
768 std::string ListName;
769 std::vector<std::string> Arguments;
770 std::unique_ptr<TransformAction> Action;
771 std::unique_ptr<TransformSelector> Selector;
772 std::string OutputName;
773 } command(args[1]);
774
775 // Descriptor of action
776 // Arity: number of arguments required for the action
777 // Transform: lambda function implementing the action
778 struct ActionDescriptor
779 {
780 ActionDescriptor(std::string name)
781 : Name(std::move(name))
782 {
783 }
784 ActionDescriptor(std::string name, int arity, transform_type transform)
785 : Name(std::move(name))
786 , Arity(arity)
787 #if defined(__GNUC__) && __GNUC__ == 6 && defined(__aarch64__)
788 // std::function move constructor miscompiles on this architecture
789 , Transform(transform)
790 #else
791 , Transform(std::move(transform))
792 #endif
793 {
794 }
795
796 operator const std::string&() const { return this->Name; }
797
798 std::string Name;
799 int Arity = 0;
800 transform_type Transform;
801 };
802
803 // Build a set of supported actions.
804 std::set<ActionDescriptor,
805 std::function<bool(const std::string&, const std::string&)>>
806 descriptors(
807 [](const std::string& x, const std::string& y) { return x < y; });
808 descriptors = { { "APPEND", 1,
809 [&command](const std::string& s) -> std::string {
810 if (command.Selector->InSelection(s)) {
811 return s + command.Arguments[0];
812 }
813
814 return s;
815 } },
816 { "PREPEND", 1,
817 [&command](const std::string& s) -> std::string {
818 if (command.Selector->InSelection(s)) {
819 return command.Arguments[0] + s;
820 }
821
822 return s;
823 } },
824 { "TOUPPER", 0,
825 [&command](const std::string& s) -> std::string {
826 if (command.Selector->InSelection(s)) {
827 return cmSystemTools::UpperCase(s);
828 }
829
830 return s;
831 } },
832 { "TOLOWER", 0,
833 [&command](const std::string& s) -> std::string {
834 if (command.Selector->InSelection(s)) {
835 return cmSystemTools::LowerCase(s);
836 }
837
838 return s;
839 } },
840 { "STRIP", 0,
841 [&command](const std::string& s) -> std::string {
842 if (command.Selector->InSelection(s)) {
843 return cmTrimWhitespace(s);
844 }
845
846 return s;
847 } },
848 { "GENEX_STRIP", 0,
849 [&command](const std::string& s) -> std::string {
850 if (command.Selector->InSelection(s)) {
851 return cmGeneratorExpression::Preprocess(
852 s,
853 cmGeneratorExpression::StripAllGeneratorExpressions);
854 }
855
856 return s;
857 } },
858 { "REPLACE", 2,
859 [&command](const std::string& s) -> std::string {
860 if (command.Selector->InSelection(s)) {
861 return command.Action->Transform(s);
862 }
863
864 return s;
865 } } };
866
867 using size_type = std::vector<std::string>::size_type;
868 size_type index = 2;
869
870 // Parse all possible function parameters
871 auto descriptor = descriptors.find(args[index]);
872
873 if (descriptor == descriptors.end()) {
874 status.SetError(
875 cmStrCat(" sub-command TRANSFORM, ", args[index], " invalid action."));
876 return false;
877 }
878
879 // Action arguments
880 index += 1;
881 if (args.size() < index + descriptor->Arity) {
882 status.SetError(cmStrCat("sub-command TRANSFORM, action ",
883 descriptor->Name, " expects ", descriptor->Arity,
884 " argument(s)."));
885 return false;
886 }
887
888 command.Name = descriptor->Name;
889 index += descriptor->Arity;
890 if (descriptor->Arity > 0) {
891 command.Arguments =
892 std::vector<std::string>(args.begin() + 3, args.begin() + index);
893 }
894
895 if (command.Name == "REPLACE") {
896 try {
897 command.Action = cm::make_unique<TransformReplace>(
898 command.Arguments, &status.GetMakefile());
899 } catch (const transform_error& e) {
900 status.SetError(e.what());
901 return false;
902 }
903 }
904
905 const std::string REGEX{ "REGEX" };
906 const std::string AT{ "AT" };
907 const std::string FOR{ "FOR" };
908 const std::string OUTPUT_VARIABLE{ "OUTPUT_VARIABLE" };
909
910 // handle optional arguments
911 while (args.size() > index) {
912 if ((args[index] == REGEX || args[index] == AT || args[index] == FOR) &&
913 command.Selector) {
914 status.SetError(
915 cmStrCat("sub-command TRANSFORM, selector already specified (",
916 command.Selector->Tag, ")."));
917
918 return false;
919 }
920
921 // REGEX selector
922 if (args[index] == REGEX) {
923 if (args.size() == ++index) {
924 status.SetError("sub-command TRANSFORM, selector REGEX expects "
925 "'regular expression' argument.");
926 return false;
927 }
928
929 command.Selector = cm::make_unique<TransformSelectorRegex>(args[index]);
930 if (!command.Selector->Validate()) {
931 status.SetError(
932 cmStrCat("sub-command TRANSFORM, selector REGEX failed to compile "
933 "regex \"",
934 args[index], "\"."));
935 return false;
936 }
937
938 index += 1;
939 continue;
940 }
941
942 // AT selector
943 if (args[index] == AT) {
944 // get all specified indexes
945 std::vector<int> indexes;
946 while (args.size() > ++index) {
947 std::size_t pos;
948 int value;
949
950 try {
951 value = std::stoi(args[index], &pos);
952 if (pos != args[index].length()) {
953 // this is not a number, stop processing
954 break;
955 }
956 indexes.push_back(value);
957 } catch (const std::invalid_argument&) {
958 // this is not a number, stop processing
959 break;
960 }
961 }
962
963 if (indexes.empty()) {
964 status.SetError(
965 "sub-command TRANSFORM, selector AT expects at least one "
966 "numeric value.");
967 return false;
968 }
969
970 command.Selector =
971 cm::make_unique<TransformSelectorAt>(std::move(indexes));
972
973 continue;
974 }
975
976 // FOR selector
977 if (args[index] == FOR) {
978 if (args.size() <= ++index + 1) {
979 status.SetError(
980 "sub-command TRANSFORM, selector FOR expects, at least,"
981 " two arguments.");
982 return false;
983 }
984
985 int start = 0;
986 int stop = 0;
987 int step = 1;
988 bool valid = true;
989 try {
990 std::size_t pos;
991
992 start = std::stoi(args[index], &pos);
993 if (pos != args[index].length()) {
994 // this is not a number
995 valid = false;
996 } else {
997 stop = std::stoi(args[++index], &pos);
998 if (pos != args[index].length()) {
999 // this is not a number
1000 valid = false;
1001 }
1002 }
1003 } catch (const std::invalid_argument&) {
1004 // this is not numbers
1005 valid = false;
1006 }
1007 if (!valid) {
1008 status.SetError("sub-command TRANSFORM, selector FOR expects, "
1009 "at least, two numeric values.");
1010 return false;
1011 }
1012 // try to read a third numeric value for step
1013 if (args.size() > ++index) {
1014 try {
1015 std::size_t pos;
1016
1017 step = std::stoi(args[index], &pos);
1018 if (pos != args[index].length()) {
1019 // this is not a number
1020 step = 1;
1021 } else {
1022 index += 1;
1023 }
1024 } catch (const std::invalid_argument&) {
1025 // this is not number, ignore exception
1026 }
1027 }
1028
1029 if (step < 0) {
1030 status.SetError("sub-command TRANSFORM, selector FOR expects "
1031 "non negative numeric value for <step>.");
1032 }
1033
1034 command.Selector =
1035 cm::make_unique<TransformSelectorFor>(start, stop, step);
1036
1037 continue;
1038 }
1039
1040 // output variable
1041 if (args[index] == OUTPUT_VARIABLE) {
1042 if (args.size() == ++index) {
1043 status.SetError("sub-command TRANSFORM, OUTPUT_VARIABLE "
1044 "expects variable name argument.");
1045 return false;
1046 }
1047
1048 command.OutputName = args[index++];
1049 continue;
1050 }
1051
1052 status.SetError(cmStrCat("sub-command TRANSFORM, '",
1053 cmJoin(cmMakeRange(args).advance(index), " "),
1054 "': unexpected argument(s)."));
1055 return false;
1056 }
1057
1058 // expand the list variable
1059 std::vector<std::string> varArgsExpanded;
1060 if (!GetList(varArgsExpanded, command.ListName, status.GetMakefile())) {
1061 status.GetMakefile().AddDefinition(command.OutputName, "");
1062 return true;
1063 }
1064
1065 if (!command.Selector) {
1066 // no selector specified, apply transformation to all elements
1067 command.Selector = cm::make_unique<TransformNoSelector>();
1068 }
1069
1070 try {
1071 command.Selector->Transform(varArgsExpanded, descriptor->Transform);
1072 } catch (const transform_error& e) {
1073 status.SetError(e.what());
1074 return false;
1075 }
1076
1077 status.GetMakefile().AddDefinition(command.OutputName,
1078 cmJoin(varArgsExpanded, ";"));
1079
1080 return true;
1081 }
1082
1083 class cmStringSorter
1084 {
1085 public:
1086 enum class Order
1087 {
1088 UNINITIALIZED,
1089 ASCENDING,
1090 DESCENDING,
1091 };
1092
1093 enum class Compare
1094 {
1095 UNINITIALIZED,
1096 STRING,
1097 FILE_BASENAME,
1098 NATURAL,
1099 };
1100 enum class CaseSensitivity
1101 {
1102 UNINITIALIZED,
1103 SENSITIVE,
1104 INSENSITIVE,
1105 };
1106
1107 protected:
1108 using StringFilter = std::string (*)(const std::string&);
GetCompareFilter(Compare compare)1109 StringFilter GetCompareFilter(Compare compare)
1110 {
1111 return (compare == Compare::FILE_BASENAME) ? cmSystemTools::GetFilenameName
1112 : nullptr;
1113 }
1114
GetCaseFilter(CaseSensitivity sensitivity)1115 StringFilter GetCaseFilter(CaseSensitivity sensitivity)
1116 {
1117 return (sensitivity == CaseSensitivity::INSENSITIVE)
1118 ? cmSystemTools::LowerCase
1119 : nullptr;
1120 }
1121
1122 using ComparisonFunction =
1123 std::function<bool(const std::string&, const std::string&)>;
GetComparisonFunction(Compare compare)1124 ComparisonFunction GetComparisonFunction(Compare compare)
1125 {
1126 if (compare == Compare::NATURAL) {
1127 return std::function<bool(const std::string&, const std::string&)>(
1128 [](const std::string& x, const std::string& y) {
1129 return cmSystemTools::strverscmp(x, y) < 0;
1130 });
1131 }
1132 return std::function<bool(const std::string&, const std::string&)>(
1133 [](const std::string& x, const std::string& y) { return x < y; });
1134 }
1135
1136 public:
cmStringSorter(Compare compare,CaseSensitivity caseSensitivity,Order desc=Order::ASCENDING)1137 cmStringSorter(Compare compare, CaseSensitivity caseSensitivity,
1138 Order desc = Order::ASCENDING)
1139 : filters{ this->GetCompareFilter(compare),
1140 this->GetCaseFilter(caseSensitivity) }
1141 , sortMethod(this->GetComparisonFunction(compare))
1142 , descending(desc == Order::DESCENDING)
1143 {
1144 }
1145
ApplyFilter(const std::string & argument)1146 std::string ApplyFilter(const std::string& argument)
1147 {
1148 std::string result = argument;
1149 for (auto filter : this->filters) {
1150 if (filter != nullptr) {
1151 result = filter(result);
1152 }
1153 }
1154 return result;
1155 }
1156
operator ()(const std::string & a,const std::string & b)1157 bool operator()(const std::string& a, const std::string& b)
1158 {
1159 std::string af = this->ApplyFilter(a);
1160 std::string bf = this->ApplyFilter(b);
1161 bool result;
1162 if (this->descending) {
1163 result = this->sortMethod(bf, af);
1164 } else {
1165 result = this->sortMethod(af, bf);
1166 }
1167 return result;
1168 }
1169
1170 protected:
1171 StringFilter filters[2] = { nullptr, nullptr };
1172 ComparisonFunction sortMethod;
1173 bool descending;
1174 };
1175
HandleSortCommand(std::vector<std::string> const & args,cmExecutionStatus & status)1176 bool HandleSortCommand(std::vector<std::string> const& args,
1177 cmExecutionStatus& status)
1178 {
1179 assert(args.size() >= 2);
1180 if (args.size() > 8) {
1181 status.SetError("sub-command SORT only takes up to six arguments.");
1182 return false;
1183 }
1184
1185 auto sortCompare = cmStringSorter::Compare::UNINITIALIZED;
1186 auto sortCaseSensitivity = cmStringSorter::CaseSensitivity::UNINITIALIZED;
1187 auto sortOrder = cmStringSorter::Order::UNINITIALIZED;
1188
1189 size_t argumentIndex = 2;
1190 const std::string messageHint = "sub-command SORT ";
1191
1192 while (argumentIndex < args.size()) {
1193 const std::string option = args[argumentIndex++];
1194 if (option == "COMPARE") {
1195 if (sortCompare != cmStringSorter::Compare::UNINITIALIZED) {
1196 std::string error = cmStrCat(messageHint, "option \"", option,
1197 "\" has been specified multiple times.");
1198 status.SetError(error);
1199 return false;
1200 }
1201 if (argumentIndex < args.size()) {
1202 const std::string argument = args[argumentIndex++];
1203 if (argument == "STRING") {
1204 sortCompare = cmStringSorter::Compare::STRING;
1205 } else if (argument == "FILE_BASENAME") {
1206 sortCompare = cmStringSorter::Compare::FILE_BASENAME;
1207 } else if (argument == "NATURAL") {
1208 sortCompare = cmStringSorter::Compare::NATURAL;
1209 } else {
1210 std::string error =
1211 cmStrCat(messageHint, "value \"", argument, "\" for option \"",
1212 option, "\" is invalid.");
1213 status.SetError(error);
1214 return false;
1215 }
1216 } else {
1217 status.SetError(cmStrCat(messageHint, "missing argument for option \"",
1218 option, "\"."));
1219 return false;
1220 }
1221 } else if (option == "CASE") {
1222 if (sortCaseSensitivity !=
1223 cmStringSorter::CaseSensitivity::UNINITIALIZED) {
1224 status.SetError(cmStrCat(messageHint, "option \"", option,
1225 "\" has been specified multiple times."));
1226 return false;
1227 }
1228 if (argumentIndex < args.size()) {
1229 const std::string argument = args[argumentIndex++];
1230 if (argument == "SENSITIVE") {
1231 sortCaseSensitivity = cmStringSorter::CaseSensitivity::SENSITIVE;
1232 } else if (argument == "INSENSITIVE") {
1233 sortCaseSensitivity = cmStringSorter::CaseSensitivity::INSENSITIVE;
1234 } else {
1235 status.SetError(cmStrCat(messageHint, "value \"", argument,
1236 "\" for option \"", option,
1237 "\" is invalid."));
1238 return false;
1239 }
1240 } else {
1241 status.SetError(cmStrCat(messageHint, "missing argument for option \"",
1242 option, "\"."));
1243 return false;
1244 }
1245 } else if (option == "ORDER") {
1246
1247 if (sortOrder != cmStringSorter::Order::UNINITIALIZED) {
1248 status.SetError(cmStrCat(messageHint, "option \"", option,
1249 "\" has been specified multiple times."));
1250 return false;
1251 }
1252 if (argumentIndex < args.size()) {
1253 const std::string argument = args[argumentIndex++];
1254 if (argument == "ASCENDING") {
1255 sortOrder = cmStringSorter::Order::ASCENDING;
1256 } else if (argument == "DESCENDING") {
1257 sortOrder = cmStringSorter::Order::DESCENDING;
1258 } else {
1259 status.SetError(cmStrCat(messageHint, "value \"", argument,
1260 "\" for option \"", option,
1261 "\" is invalid."));
1262 return false;
1263 }
1264 } else {
1265 status.SetError(cmStrCat(messageHint, "missing argument for option \"",
1266 option, "\"."));
1267 return false;
1268 }
1269 } else {
1270 status.SetError(
1271 cmStrCat(messageHint, "option \"", option, "\" is unknown."));
1272 return false;
1273 }
1274 }
1275 // set Default Values if Option is not given
1276 if (sortCompare == cmStringSorter::Compare::UNINITIALIZED) {
1277 sortCompare = cmStringSorter::Compare::STRING;
1278 }
1279 if (sortCaseSensitivity == cmStringSorter::CaseSensitivity::UNINITIALIZED) {
1280 sortCaseSensitivity = cmStringSorter::CaseSensitivity::SENSITIVE;
1281 }
1282 if (sortOrder == cmStringSorter::Order::UNINITIALIZED) {
1283 sortOrder = cmStringSorter::Order::ASCENDING;
1284 }
1285
1286 const std::string& listName = args[1];
1287 // expand the variable
1288 std::vector<std::string> varArgsExpanded;
1289 if (!GetList(varArgsExpanded, listName, status.GetMakefile())) {
1290 return true;
1291 }
1292
1293 if ((sortCompare == cmStringSorter::Compare::STRING) &&
1294 (sortCaseSensitivity == cmStringSorter::CaseSensitivity::SENSITIVE) &&
1295 (sortOrder == cmStringSorter::Order::ASCENDING)) {
1296 std::sort(varArgsExpanded.begin(), varArgsExpanded.end());
1297 } else {
1298 cmStringSorter sorter(sortCompare, sortCaseSensitivity, sortOrder);
1299 std::sort(varArgsExpanded.begin(), varArgsExpanded.end(), sorter);
1300 }
1301
1302 std::string value = cmJoin(varArgsExpanded, ";");
1303 status.GetMakefile().AddDefinition(listName, value);
1304 return true;
1305 }
1306
HandleSublistCommand(std::vector<std::string> const & args,cmExecutionStatus & status)1307 bool HandleSublistCommand(std::vector<std::string> const& args,
1308 cmExecutionStatus& status)
1309 {
1310 if (args.size() != 5) {
1311 status.SetError(cmStrCat("sub-command SUBLIST requires four arguments (",
1312 args.size() - 1, " found)."));
1313 return false;
1314 }
1315
1316 const std::string& listName = args[1];
1317 const std::string& variableName = args.back();
1318
1319 // expand the variable
1320 std::vector<std::string> varArgsExpanded;
1321 if (!GetList(varArgsExpanded, listName, status.GetMakefile()) ||
1322 varArgsExpanded.empty()) {
1323 status.GetMakefile().AddDefinition(variableName, "");
1324 return true;
1325 }
1326
1327 int start;
1328 int length;
1329 if (!GetIndexArg(args[2], &start, status.GetMakefile())) {
1330 status.SetError(cmStrCat("index: ", args[2], " is not a valid index"));
1331 return false;
1332 }
1333 if (!GetIndexArg(args[3], &length, status.GetMakefile())) {
1334 status.SetError(cmStrCat("index: ", args[3], " is not a valid index"));
1335 return false;
1336 }
1337
1338 using size_type = decltype(varArgsExpanded)::size_type;
1339
1340 if (start < 0 || size_type(start) >= varArgsExpanded.size()) {
1341 status.SetError(cmStrCat("begin index: ", start, " is out of range 0 - ",
1342 varArgsExpanded.size() - 1));
1343 return false;
1344 }
1345 if (length < -1) {
1346 status.SetError(cmStrCat("length: ", length, " should be -1 or greater"));
1347 return false;
1348 }
1349
1350 const size_type end =
1351 (length == -1 || size_type(start + length) > varArgsExpanded.size())
1352 ? varArgsExpanded.size()
1353 : size_type(start + length);
1354 std::vector<std::string> sublist(varArgsExpanded.begin() + start,
1355 varArgsExpanded.begin() + end);
1356 status.GetMakefile().AddDefinition(variableName, cmJoin(sublist, ";"));
1357 return true;
1358 }
1359
HandleRemoveAtCommand(std::vector<std::string> const & args,cmExecutionStatus & status)1360 bool HandleRemoveAtCommand(std::vector<std::string> const& args,
1361 cmExecutionStatus& status)
1362 {
1363 if (args.size() < 3) {
1364 status.SetError("sub-command REMOVE_AT requires at least "
1365 "two arguments.");
1366 return false;
1367 }
1368
1369 const std::string& listName = args[1];
1370 // expand the variable
1371 std::vector<std::string> varArgsExpanded;
1372 if (!GetList(varArgsExpanded, listName, status.GetMakefile()) ||
1373 varArgsExpanded.empty()) {
1374 std::ostringstream str;
1375 str << "index: ";
1376 for (size_t i = 1; i < args.size(); ++i) {
1377 str << args[i];
1378 if (i != args.size() - 1) {
1379 str << ", ";
1380 }
1381 }
1382 str << " out of range (0, 0)";
1383 status.SetError(str.str());
1384 return false;
1385 }
1386
1387 size_t cc;
1388 std::vector<size_t> removed;
1389 size_t nitem = varArgsExpanded.size();
1390 for (cc = 2; cc < args.size(); ++cc) {
1391 int item;
1392 if (!GetIndexArg(args[cc], &item, status.GetMakefile())) {
1393 status.SetError(cmStrCat("index: ", args[cc], " is not a valid index"));
1394 return false;
1395 }
1396 if (item < 0) {
1397 item = static_cast<int>(nitem) + item;
1398 }
1399 if (item < 0 || nitem <= static_cast<size_t>(item)) {
1400 status.SetError(cmStrCat("index: ", item, " out of range (-", nitem,
1401 ", ", nitem - 1, ")"));
1402 return false;
1403 }
1404 removed.push_back(static_cast<size_t>(item));
1405 }
1406
1407 std::sort(removed.begin(), removed.end());
1408 auto remEnd = std::unique(removed.begin(), removed.end());
1409 auto remBegin = removed.begin();
1410
1411 auto argsEnd =
1412 cmRemoveIndices(varArgsExpanded, cmMakeRange(remBegin, remEnd));
1413 auto argsBegin = varArgsExpanded.cbegin();
1414 std::string value = cmJoin(cmMakeRange(argsBegin, argsEnd), ";");
1415
1416 status.GetMakefile().AddDefinition(listName, value);
1417 return true;
1418 }
1419
HandleFilterCommand(std::vector<std::string> const & args,cmExecutionStatus & status)1420 bool HandleFilterCommand(std::vector<std::string> const& args,
1421 cmExecutionStatus& status)
1422 {
1423 if (args.size() < 2) {
1424 status.SetError("sub-command FILTER requires a list to be specified.");
1425 return false;
1426 }
1427
1428 if (args.size() < 3) {
1429 status.SetError(
1430 "sub-command FILTER requires an operator to be specified.");
1431 return false;
1432 }
1433
1434 if (args.size() < 4) {
1435 status.SetError("sub-command FILTER requires a mode to be specified.");
1436 return false;
1437 }
1438
1439 const std::string& op = args[2];
1440 bool includeMatches;
1441 if (op == "INCLUDE") {
1442 includeMatches = true;
1443 } else if (op == "EXCLUDE") {
1444 includeMatches = false;
1445 } else {
1446 status.SetError("sub-command FILTER does not recognize operator " + op);
1447 return false;
1448 }
1449
1450 const std::string& listName = args[1];
1451 // expand the variable
1452 std::vector<std::string> varArgsExpanded;
1453 if (!GetList(varArgsExpanded, listName, status.GetMakefile())) {
1454 return true;
1455 }
1456
1457 const std::string& mode = args[3];
1458 if (mode == "REGEX") {
1459 if (args.size() != 5) {
1460 status.SetError("sub-command FILTER, mode REGEX "
1461 "requires five arguments.");
1462 return false;
1463 }
1464 return FilterRegex(args, includeMatches, listName, varArgsExpanded,
1465 status);
1466 }
1467
1468 status.SetError("sub-command FILTER does not recognize mode " + mode);
1469 return false;
1470 }
1471
1472 class MatchesRegex
1473 {
1474 public:
MatchesRegex(cmsys::RegularExpression & in_regex,bool in_includeMatches)1475 MatchesRegex(cmsys::RegularExpression& in_regex, bool in_includeMatches)
1476 : regex(in_regex)
1477 , includeMatches(in_includeMatches)
1478 {
1479 }
1480
operator ()(const std::string & target)1481 bool operator()(const std::string& target)
1482 {
1483 return this->regex.find(target) ^ this->includeMatches;
1484 }
1485
1486 private:
1487 cmsys::RegularExpression& regex;
1488 const bool includeMatches;
1489 };
1490
FilterRegex(std::vector<std::string> const & args,bool includeMatches,std::string const & listName,std::vector<std::string> & varArgsExpanded,cmExecutionStatus & status)1491 bool FilterRegex(std::vector<std::string> const& args, bool includeMatches,
1492 std::string const& listName,
1493 std::vector<std::string>& varArgsExpanded,
1494 cmExecutionStatus& status)
1495 {
1496 const std::string& pattern = args[4];
1497 cmsys::RegularExpression regex(pattern);
1498 if (!regex.is_valid()) {
1499 std::string error =
1500 cmStrCat("sub-command FILTER, mode REGEX failed to compile regex \"",
1501 pattern, "\".");
1502 status.SetError(error);
1503 return false;
1504 }
1505
1506 auto argsBegin = varArgsExpanded.begin();
1507 auto argsEnd = varArgsExpanded.end();
1508 auto newArgsEnd =
1509 std::remove_if(argsBegin, argsEnd, MatchesRegex(regex, includeMatches));
1510
1511 std::string value = cmJoin(cmMakeRange(argsBegin, newArgsEnd), ";");
1512 status.GetMakefile().AddDefinition(listName, value);
1513 return true;
1514 }
1515
1516 } // namespace
1517
cmListCommand(std::vector<std::string> const & args,cmExecutionStatus & status)1518 bool cmListCommand(std::vector<std::string> const& args,
1519 cmExecutionStatus& status)
1520 {
1521 if (args.size() < 2) {
1522 status.SetError("must be called with at least two arguments.");
1523 return false;
1524 }
1525
1526 static cmSubcommandTable const subcommand{
1527 { "LENGTH"_s, HandleLengthCommand },
1528 { "GET"_s, HandleGetCommand },
1529 { "APPEND"_s, HandleAppendCommand },
1530 { "PREPEND"_s, HandlePrependCommand },
1531 { "POP_BACK"_s, HandlePopBackCommand },
1532 { "POP_FRONT"_s, HandlePopFrontCommand },
1533 { "FIND"_s, HandleFindCommand },
1534 { "INSERT"_s, HandleInsertCommand },
1535 { "JOIN"_s, HandleJoinCommand },
1536 { "REMOVE_AT"_s, HandleRemoveAtCommand },
1537 { "REMOVE_ITEM"_s, HandleRemoveItemCommand },
1538 { "REMOVE_DUPLICATES"_s, HandleRemoveDuplicatesCommand },
1539 { "TRANSFORM"_s, HandleTransformCommand },
1540 { "SORT"_s, HandleSortCommand },
1541 { "SUBLIST"_s, HandleSublistCommand },
1542 { "REVERSE"_s, HandleReverseCommand },
1543 { "FILTER"_s, HandleFilterCommand },
1544 };
1545
1546 return subcommand(args[0], args, status);
1547 }
1548