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