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 "cmFileCommand.h"
4
5 #include <algorithm>
6 #include <cassert>
7 #include <cctype>
8 #include <cmath>
9 #include <cstdio>
10 #include <cstdlib>
11 #include <map>
12 #include <set>
13 #include <sstream>
14 #include <utility>
15 #include <vector>
16
17 #include <cm/memory>
18 #include <cm/string_view>
19 #include <cmext/algorithm>
20 #include <cmext/string_view>
21
22 #include <cm3p/kwiml/int.h>
23
24 #include "cmsys/FStream.hxx"
25 #include "cmsys/Glob.hxx"
26 #include "cmsys/RegularExpression.hxx"
27
28 #include "cm_sys_stat.h"
29
30 #include "cmAlgorithms.h"
31 #include "cmArgumentParser.h"
32 #include "cmCMakePath.h"
33 #include "cmCryptoHash.h"
34 #include "cmELF.h"
35 #include "cmExecutionStatus.h"
36 #include "cmFSPermissions.h"
37 #include "cmFileCopier.h"
38 #include "cmFileInstaller.h"
39 #include "cmFileLockPool.h"
40 #include "cmFileTimes.h"
41 #include "cmGeneratedFileStream.h"
42 #include "cmGeneratorExpression.h"
43 #include "cmGlobalGenerator.h"
44 #include "cmHexFileConverter.h"
45 #include "cmListFileCache.h"
46 #include "cmMakefile.h"
47 #include "cmMessageType.h"
48 #include "cmNewLineStyle.h"
49 #include "cmPolicies.h"
50 #include "cmRange.h"
51 #include "cmRuntimeDependencyArchive.h"
52 #include "cmState.h"
53 #include "cmStringAlgorithms.h"
54 #include "cmSubcommandTable.h"
55 #include "cmSystemTools.h"
56 #include "cmTimestamp.h"
57 #include "cmValue.h"
58 #include "cmWorkingDirectory.h"
59 #include "cmake.h"
60
61 #if !defined(CMAKE_BOOTSTRAP)
62 # include <cm3p/curl/curl.h>
63
64 # include "cmCurl.h"
65 # include "cmFileLockResult.h"
66 #endif
67
68 namespace {
69
HandleWriteImpl(std::vector<std::string> const & args,bool append,cmExecutionStatus & status)70 bool HandleWriteImpl(std::vector<std::string> const& args, bool append,
71 cmExecutionStatus& status)
72 {
73 auto i = args.begin();
74
75 i++; // Get rid of subcommand
76
77 std::string fileName = *i;
78 if (!cmsys::SystemTools::FileIsFullPath(*i)) {
79 fileName =
80 cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', *i);
81 }
82
83 i++;
84
85 if (!status.GetMakefile().CanIWriteThisFile(fileName)) {
86 std::string e =
87 "attempted to write a file: " + fileName + " into a source directory.";
88 status.SetError(e);
89 cmSystemTools::SetFatalErrorOccured();
90 return false;
91 }
92 std::string dir = cmSystemTools::GetFilenamePath(fileName);
93 cmSystemTools::MakeDirectory(dir);
94
95 mode_t mode = 0;
96 bool writable = false;
97
98 // Set permissions to writable
99 if (cmSystemTools::GetPermissions(fileName, mode)) {
100 #if defined(_MSC_VER) || defined(__MINGW32__)
101 writable = (mode & S_IWRITE) != 0;
102 mode_t newMode = mode | S_IWRITE;
103 #else
104 writable = mode & S_IWUSR;
105 mode_t newMode = mode | S_IWUSR | S_IWGRP;
106 #endif
107 if (!writable) {
108 cmSystemTools::SetPermissions(fileName, newMode);
109 }
110 }
111 // If GetPermissions fails, pretend like it is ok. File open will fail if
112 // the file is not writable
113 cmsys::ofstream file(fileName.c_str(),
114 append ? std::ios::app : std::ios::out);
115 if (!file) {
116 std::string error =
117 cmStrCat("failed to open for writing (",
118 cmSystemTools::GetLastSystemError(), "):\n ", fileName);
119 status.SetError(error);
120 return false;
121 }
122 std::string message = cmJoin(cmMakeRange(i, args.end()), std::string());
123 file << message;
124 if (!file) {
125 std::string error =
126 cmStrCat("write failed (", cmSystemTools::GetLastSystemError(), "):\n ",
127 fileName);
128 status.SetError(error);
129 return false;
130 }
131 file.close();
132 if (mode && !writable) {
133 cmSystemTools::SetPermissions(fileName, mode);
134 }
135 return true;
136 }
137
HandleWriteCommand(std::vector<std::string> const & args,cmExecutionStatus & status)138 bool HandleWriteCommand(std::vector<std::string> const& args,
139 cmExecutionStatus& status)
140 {
141 return HandleWriteImpl(args, false, status);
142 }
143
HandleAppendCommand(std::vector<std::string> const & args,cmExecutionStatus & status)144 bool HandleAppendCommand(std::vector<std::string> const& args,
145 cmExecutionStatus& status)
146 {
147 return HandleWriteImpl(args, true, status);
148 }
149
HandleReadCommand(std::vector<std::string> const & args,cmExecutionStatus & status)150 bool HandleReadCommand(std::vector<std::string> const& args,
151 cmExecutionStatus& status)
152 {
153 if (args.size() < 3) {
154 status.SetError("READ must be called with at least two additional "
155 "arguments");
156 return false;
157 }
158
159 std::string const& fileNameArg = args[1];
160 std::string const& variable = args[2];
161
162 struct Arguments
163 {
164 std::string Offset;
165 std::string Limit;
166 bool Hex = false;
167 };
168
169 static auto const parser = cmArgumentParser<Arguments>{}
170 .Bind("OFFSET"_s, &Arguments::Offset)
171 .Bind("LIMIT"_s, &Arguments::Limit)
172 .Bind("HEX"_s, &Arguments::Hex);
173
174 Arguments const arguments = parser.Parse(cmMakeRange(args).advance(3));
175
176 std::string fileName = fileNameArg;
177 if (!cmsys::SystemTools::FileIsFullPath(fileName)) {
178 fileName = cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/',
179 fileNameArg);
180 }
181
182 // Open the specified file.
183 #if defined(_WIN32) || defined(__CYGWIN__)
184 cmsys::ifstream file(fileName.c_str(),
185 arguments.Hex ? (std::ios::binary | std::ios::in)
186 : std::ios::in);
187 #else
188 cmsys::ifstream file(fileName.c_str());
189 #endif
190
191 if (!file) {
192 std::string error =
193 cmStrCat("failed to open for reading (",
194 cmSystemTools::GetLastSystemError(), "):\n ", fileName);
195 status.SetError(error);
196 return false;
197 }
198
199 // is there a limit?
200 long sizeLimit = -1;
201 if (!arguments.Limit.empty()) {
202 sizeLimit = atoi(arguments.Limit.c_str());
203 }
204
205 // is there an offset?
206 long offset = 0;
207 if (!arguments.Offset.empty()) {
208 offset = atoi(arguments.Offset.c_str());
209 }
210
211 file.seekg(offset, std::ios::beg); // explicit ios::beg for IBM VisualAge 6
212
213 std::string output;
214
215 if (arguments.Hex) {
216 // Convert part of the file into hex code
217 char c;
218 while ((sizeLimit != 0) && (file.get(c))) {
219 char hex[4];
220 sprintf(hex, "%.2x", c & 0xff);
221 output += hex;
222 if (sizeLimit > 0) {
223 sizeLimit--;
224 }
225 }
226 } else {
227 std::string line;
228 bool has_newline = false;
229 while (
230 sizeLimit != 0 &&
231 cmSystemTools::GetLineFromStream(file, line, &has_newline, sizeLimit)) {
232 if (sizeLimit > 0) {
233 sizeLimit = sizeLimit - static_cast<long>(line.size());
234 if (has_newline) {
235 sizeLimit--;
236 }
237 if (sizeLimit < 0) {
238 sizeLimit = 0;
239 }
240 }
241 output += line;
242 if (has_newline) {
243 output += "\n";
244 }
245 }
246 }
247 status.GetMakefile().AddDefinition(variable, output);
248 return true;
249 }
250
HandleHashCommand(std::vector<std::string> const & args,cmExecutionStatus & status)251 bool HandleHashCommand(std::vector<std::string> const& args,
252 cmExecutionStatus& status)
253 {
254 #if !defined(CMAKE_BOOTSTRAP)
255 if (args.size() != 3) {
256 status.SetError(
257 cmStrCat(args[0], " requires a file name and output variable"));
258 return false;
259 }
260
261 std::unique_ptr<cmCryptoHash> hash(cmCryptoHash::New(args[0]));
262 if (hash) {
263 std::string out = hash->HashFile(args[1]);
264 if (!out.empty()) {
265 status.GetMakefile().AddDefinition(args[2], out);
266 return true;
267 }
268 status.SetError(cmStrCat(args[0], " failed to read file \"", args[1],
269 "\": ", cmSystemTools::GetLastSystemError()));
270 }
271 return false;
272 #else
273 status.SetError(cmStrCat(args[0], " not available during bootstrap"));
274 return false;
275 #endif
276 }
277
HandleStringsCommand(std::vector<std::string> const & args,cmExecutionStatus & status)278 bool HandleStringsCommand(std::vector<std::string> const& args,
279 cmExecutionStatus& status)
280 {
281 if (args.size() < 3) {
282 status.SetError("STRINGS requires a file name and output variable");
283 return false;
284 }
285
286 // Get the file to read.
287 std::string fileName = args[1];
288 if (!cmsys::SystemTools::FileIsFullPath(fileName)) {
289 fileName =
290 cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[1]);
291 }
292
293 // Get the variable in which to store the results.
294 std::string outVar = args[2];
295
296 // Parse the options.
297 enum
298 {
299 arg_none,
300 arg_limit_input,
301 arg_limit_output,
302 arg_limit_count,
303 arg_length_minimum,
304 arg_length_maximum,
305 arg_maximum,
306 arg_regex,
307 arg_encoding
308 };
309 unsigned int minlen = 0;
310 unsigned int maxlen = 0;
311 int limit_input = -1;
312 int limit_output = -1;
313 unsigned int limit_count = 0;
314 cmsys::RegularExpression regex;
315 bool have_regex = false;
316 bool newline_consume = false;
317 bool hex_conversion_enabled = true;
318 enum
319 {
320 encoding_none = cmsys::FStream::BOM_None,
321 encoding_utf8 = cmsys::FStream::BOM_UTF8,
322 encoding_utf16le = cmsys::FStream::BOM_UTF16LE,
323 encoding_utf16be = cmsys::FStream::BOM_UTF16BE,
324 encoding_utf32le = cmsys::FStream::BOM_UTF32LE,
325 encoding_utf32be = cmsys::FStream::BOM_UTF32BE
326 };
327 int encoding = encoding_none;
328 int arg_mode = arg_none;
329 for (unsigned int i = 3; i < args.size(); ++i) {
330 if (args[i] == "LIMIT_INPUT") {
331 arg_mode = arg_limit_input;
332 } else if (args[i] == "LIMIT_OUTPUT") {
333 arg_mode = arg_limit_output;
334 } else if (args[i] == "LIMIT_COUNT") {
335 arg_mode = arg_limit_count;
336 } else if (args[i] == "LENGTH_MINIMUM") {
337 arg_mode = arg_length_minimum;
338 } else if (args[i] == "LENGTH_MAXIMUM") {
339 arg_mode = arg_length_maximum;
340 } else if (args[i] == "REGEX") {
341 arg_mode = arg_regex;
342 } else if (args[i] == "NEWLINE_CONSUME") {
343 newline_consume = true;
344 arg_mode = arg_none;
345 } else if (args[i] == "NO_HEX_CONVERSION") {
346 hex_conversion_enabled = false;
347 arg_mode = arg_none;
348 } else if (args[i] == "ENCODING") {
349 arg_mode = arg_encoding;
350 } else if (arg_mode == arg_limit_input) {
351 if (sscanf(args[i].c_str(), "%d", &limit_input) != 1 ||
352 limit_input < 0) {
353 status.SetError(cmStrCat("STRINGS option LIMIT_INPUT value \"",
354 args[i], "\" is not an unsigned integer."));
355 return false;
356 }
357 arg_mode = arg_none;
358 } else if (arg_mode == arg_limit_output) {
359 if (sscanf(args[i].c_str(), "%d", &limit_output) != 1 ||
360 limit_output < 0) {
361 status.SetError(cmStrCat("STRINGS option LIMIT_OUTPUT value \"",
362 args[i], "\" is not an unsigned integer."));
363 return false;
364 }
365 arg_mode = arg_none;
366 } else if (arg_mode == arg_limit_count) {
367 int count;
368 if (sscanf(args[i].c_str(), "%d", &count) != 1 || count < 0) {
369 status.SetError(cmStrCat("STRINGS option LIMIT_COUNT value \"",
370 args[i], "\" is not an unsigned integer."));
371 return false;
372 }
373 limit_count = count;
374 arg_mode = arg_none;
375 } else if (arg_mode == arg_length_minimum) {
376 int len;
377 if (sscanf(args[i].c_str(), "%d", &len) != 1 || len < 0) {
378 status.SetError(cmStrCat("STRINGS option LENGTH_MINIMUM value \"",
379 args[i], "\" is not an unsigned integer."));
380 return false;
381 }
382 minlen = len;
383 arg_mode = arg_none;
384 } else if (arg_mode == arg_length_maximum) {
385 int len;
386 if (sscanf(args[i].c_str(), "%d", &len) != 1 || len < 0) {
387 status.SetError(cmStrCat("STRINGS option LENGTH_MAXIMUM value \"",
388 args[i], "\" is not an unsigned integer."));
389 return false;
390 }
391 maxlen = len;
392 arg_mode = arg_none;
393 } else if (arg_mode == arg_regex) {
394 if (!regex.compile(args[i])) {
395 status.SetError(cmStrCat("STRINGS option REGEX value \"", args[i],
396 "\" could not be compiled."));
397 return false;
398 }
399 have_regex = true;
400 arg_mode = arg_none;
401 } else if (arg_mode == arg_encoding) {
402 if (args[i] == "UTF-8") {
403 encoding = encoding_utf8;
404 } else if (args[i] == "UTF-16LE") {
405 encoding = encoding_utf16le;
406 } else if (args[i] == "UTF-16BE") {
407 encoding = encoding_utf16be;
408 } else if (args[i] == "UTF-32LE") {
409 encoding = encoding_utf32le;
410 } else if (args[i] == "UTF-32BE") {
411 encoding = encoding_utf32be;
412 } else {
413 status.SetError(cmStrCat("STRINGS option ENCODING \"", args[i],
414 "\" not recognized."));
415 return false;
416 }
417 arg_mode = arg_none;
418 } else {
419 status.SetError(
420 cmStrCat("STRINGS given unknown argument \"", args[i], "\""));
421 return false;
422 }
423 }
424
425 if (hex_conversion_enabled) {
426 // TODO: should work without temp file, but just on a memory buffer
427 std::string binaryFileName =
428 cmStrCat(status.GetMakefile().GetCurrentBinaryDirectory(),
429 "/CMakeFiles/FileCommandStringsBinaryFile");
430 if (cmHexFileConverter::TryConvert(fileName, binaryFileName)) {
431 fileName = binaryFileName;
432 }
433 }
434
435 // Open the specified file.
436 #if defined(_WIN32) || defined(__CYGWIN__)
437 cmsys::ifstream fin(fileName.c_str(), std::ios::in | std::ios::binary);
438 #else
439 cmsys::ifstream fin(fileName.c_str());
440 #endif
441 if (!fin) {
442 status.SetError(
443 cmStrCat("STRINGS file \"", fileName, "\" cannot be read."));
444 return false;
445 }
446
447 // If BOM is found and encoding was not specified, use the BOM
448 int bom_found = cmsys::FStream::ReadBOM(fin);
449 if (encoding == encoding_none && bom_found != cmsys::FStream::BOM_None) {
450 encoding = bom_found;
451 }
452
453 unsigned int bytes_rem = 0;
454 if (encoding == encoding_utf16le || encoding == encoding_utf16be) {
455 bytes_rem = 1;
456 }
457 if (encoding == encoding_utf32le || encoding == encoding_utf32be) {
458 bytes_rem = 3;
459 }
460
461 // Parse strings out of the file.
462 int output_size = 0;
463 std::vector<std::string> strings;
464 std::string s;
465 while ((!limit_count || strings.size() < limit_count) &&
466 (limit_input < 0 || static_cast<int>(fin.tellg()) < limit_input) &&
467 fin) {
468 std::string current_str;
469
470 int c = fin.get();
471 for (unsigned int i = 0; i < bytes_rem; ++i) {
472 int c1 = fin.get();
473 if (!fin) {
474 fin.putback(static_cast<char>(c1));
475 break;
476 }
477 c = (c << 8) | c1;
478 }
479 if (encoding == encoding_utf16le) {
480 c = ((c & 0xFF) << 8) | ((c & 0xFF00) >> 8);
481 } else if (encoding == encoding_utf32le) {
482 c = (((c & 0xFF) << 24) | ((c & 0xFF00) << 8) | ((c & 0xFF0000) >> 8) |
483 ((c & 0xFF000000) >> 24));
484 }
485
486 if (c == '\r') {
487 // Ignore CR character to make output always have UNIX newlines.
488 continue;
489 }
490
491 if (c >= 0 && c <= 0xFF &&
492 (isprint(c) || c == '\t' || (c == '\n' && newline_consume))) {
493 // This is an ASCII character that may be part of a string.
494 // Cast added to avoid compiler warning. Cast is ok because
495 // c is guaranteed to fit in char by the above if...
496 current_str += static_cast<char>(c);
497 } else if (encoding == encoding_utf8) {
498 // Check for UTF-8 encoded string (up to 4 octets)
499 static const unsigned char utf8_check_table[3][2] = {
500 { 0xE0, 0xC0 },
501 { 0xF0, 0xE0 },
502 { 0xF8, 0xF0 },
503 };
504
505 // how many octets are there?
506 unsigned int num_utf8_bytes = 0;
507 for (unsigned int j = 0; num_utf8_bytes == 0 && j < 3; j++) {
508 if ((c & utf8_check_table[j][0]) == utf8_check_table[j][1]) {
509 num_utf8_bytes = j + 2;
510 }
511 }
512
513 // get subsequent octets and check that they are valid
514 for (unsigned int j = 0; j < num_utf8_bytes; j++) {
515 if (j != 0) {
516 c = fin.get();
517 if (!fin || (c & 0xC0) != 0x80) {
518 fin.putback(static_cast<char>(c));
519 break;
520 }
521 }
522 current_str += static_cast<char>(c);
523 }
524
525 // if this was an invalid utf8 sequence, discard the data, and put
526 // back subsequent characters
527 if ((current_str.length() != num_utf8_bytes)) {
528 for (unsigned int j = 0; j < current_str.size() - 1; j++) {
529 fin.putback(current_str[current_str.size() - 1 - j]);
530 }
531 current_str.clear();
532 }
533 }
534
535 if (c == '\n' && !newline_consume) {
536 // The current line has been terminated. Check if the current
537 // string matches the requirements. The length may now be as
538 // low as zero since blank lines are allowed.
539 if (s.length() >= minlen && (!have_regex || regex.find(s))) {
540 output_size += static_cast<int>(s.size()) + 1;
541 if (limit_output >= 0 && output_size >= limit_output) {
542 s.clear();
543 break;
544 }
545 strings.push_back(s);
546 }
547
548 // Reset the string to empty.
549 s.clear();
550 } else if (current_str.empty()) {
551 // A non-string character has been found. Check if the current
552 // string matches the requirements. We require that the length
553 // be at least one no matter what the user specified.
554 if (s.length() >= minlen && !s.empty() &&
555 (!have_regex || regex.find(s))) {
556 output_size += static_cast<int>(s.size()) + 1;
557 if (limit_output >= 0 && output_size >= limit_output) {
558 s.clear();
559 break;
560 }
561 strings.push_back(s);
562 }
563
564 // Reset the string to empty.
565 s.clear();
566 } else {
567 s += current_str;
568 }
569
570 if (maxlen > 0 && s.size() == maxlen) {
571 // Terminate a string if the maximum length is reached.
572 if (s.length() >= minlen && (!have_regex || regex.find(s))) {
573 output_size += static_cast<int>(s.size()) + 1;
574 if (limit_output >= 0 && output_size >= limit_output) {
575 s.clear();
576 break;
577 }
578 strings.push_back(s);
579 }
580 s.clear();
581 }
582 }
583
584 // If there is a non-empty current string we have hit the end of the
585 // input file or the input size limit. Check if the current string
586 // matches the requirements.
587 if ((!limit_count || strings.size() < limit_count) && !s.empty() &&
588 s.length() >= minlen && (!have_regex || regex.find(s))) {
589 output_size += static_cast<int>(s.size()) + 1;
590 if (limit_output < 0 || output_size < limit_output) {
591 strings.push_back(s);
592 }
593 }
594
595 // Encode the result in a CMake list.
596 const char* sep = "";
597 std::string output;
598 for (std::string const& sr : strings) {
599 // Separate the strings in the output to make it a list.
600 output += sep;
601 sep = ";";
602
603 // Store the string in the output, but escape semicolons to
604 // make sure it is a list.
605 for (char i : sr) {
606 if (i == ';') {
607 output += '\\';
608 }
609 output += i;
610 }
611 }
612
613 // Save the output in a makefile variable.
614 status.GetMakefile().AddDefinition(outVar, output);
615 return true;
616 }
617
HandleGlobImpl(std::vector<std::string> const & args,bool recurse,cmExecutionStatus & status)618 bool HandleGlobImpl(std::vector<std::string> const& args, bool recurse,
619 cmExecutionStatus& status)
620 {
621 // File commands has at least one argument
622 assert(args.size() > 1);
623
624 auto i = args.begin();
625
626 i++; // Get rid of subcommand
627
628 std::string variable = *i;
629 i++;
630 cmsys::Glob g;
631 g.SetRecurse(recurse);
632
633 bool explicitFollowSymlinks = false;
634 cmPolicies::PolicyStatus policyStatus =
635 status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0009);
636 if (recurse) {
637 switch (policyStatus) {
638 case cmPolicies::REQUIRED_IF_USED:
639 case cmPolicies::REQUIRED_ALWAYS:
640 case cmPolicies::NEW:
641 g.RecurseThroughSymlinksOff();
642 break;
643 case cmPolicies::WARN:
644 CM_FALLTHROUGH;
645 case cmPolicies::OLD:
646 g.RecurseThroughSymlinksOn();
647 break;
648 }
649 }
650
651 cmake* cm = status.GetMakefile().GetCMakeInstance();
652 std::vector<std::string> files;
653 bool configureDepends = false;
654 bool warnConfigureLate = false;
655 bool warnFollowedSymlinks = false;
656 const cmake::WorkingMode workingMode = cm->GetWorkingMode();
657 while (i != args.end()) {
658 if (*i == "LIST_DIRECTORIES") {
659 ++i; // skip LIST_DIRECTORIES
660 if (i != args.end()) {
661 if (cmIsOn(*i)) {
662 g.SetListDirs(true);
663 g.SetRecurseListDirs(true);
664 } else if (cmIsOff(*i)) {
665 g.SetListDirs(false);
666 g.SetRecurseListDirs(false);
667 } else {
668 status.SetError("LIST_DIRECTORIES missing bool value.");
669 return false;
670 }
671 ++i;
672 } else {
673 status.SetError("LIST_DIRECTORIES missing bool value.");
674 return false;
675 }
676 } else if (*i == "FOLLOW_SYMLINKS") {
677 ++i; // skip FOLLOW_SYMLINKS
678 if (recurse) {
679 explicitFollowSymlinks = true;
680 g.RecurseThroughSymlinksOn();
681 if (i == args.end()) {
682 status.SetError(
683 "GLOB_RECURSE requires a glob expression after FOLLOW_SYMLINKS.");
684 return false;
685 }
686 }
687 } else if (*i == "RELATIVE") {
688 ++i; // skip RELATIVE
689 if (i == args.end()) {
690 status.SetError("GLOB requires a directory after the RELATIVE tag.");
691 return false;
692 }
693 g.SetRelative(i->c_str());
694 ++i;
695 if (i == args.end()) {
696 status.SetError(
697 "GLOB requires a glob expression after the directory.");
698 return false;
699 }
700 } else if (*i == "CONFIGURE_DEPENDS") {
701 // Generated build system depends on glob results
702 if (!configureDepends && warnConfigureLate) {
703 status.GetMakefile().IssueMessage(
704 MessageType::AUTHOR_WARNING,
705 "CONFIGURE_DEPENDS flag was given after a glob expression was "
706 "already evaluated.");
707 }
708 if (workingMode != cmake::NORMAL_MODE) {
709 status.GetMakefile().IssueMessage(
710 MessageType::FATAL_ERROR,
711 "CONFIGURE_DEPENDS is invalid for script and find package modes.");
712 return false;
713 }
714 configureDepends = true;
715 ++i;
716 if (i == args.end()) {
717 status.SetError(
718 "GLOB requires a glob expression after CONFIGURE_DEPENDS.");
719 return false;
720 }
721 } else {
722 std::string expr = *i;
723 if (!cmsys::SystemTools::FileIsFullPath(*i)) {
724 expr = status.GetMakefile().GetCurrentSourceDirectory();
725 // Handle script mode
726 if (!expr.empty()) {
727 expr += "/" + *i;
728 } else {
729 expr = *i;
730 }
731 }
732
733 cmsys::Glob::GlobMessages globMessages;
734 g.FindFiles(expr, &globMessages);
735
736 if (!globMessages.empty()) {
737 bool shouldExit = false;
738 for (cmsys::Glob::Message const& globMessage : globMessages) {
739 if (globMessage.type == cmsys::Glob::cyclicRecursion) {
740 status.GetMakefile().IssueMessage(
741 MessageType::AUTHOR_WARNING,
742 "Cyclic recursion detected while globbing for '" + *i + "':\n" +
743 globMessage.content);
744 } else if (globMessage.type == cmsys::Glob::error) {
745 status.GetMakefile().IssueMessage(
746 MessageType::FATAL_ERROR,
747 "Error has occurred while globbing for '" + *i + "' - " +
748 globMessage.content);
749 shouldExit = true;
750 } else if (cm->GetDebugOutput() || cm->GetTrace()) {
751 status.GetMakefile().IssueMessage(
752 MessageType::LOG,
753 cmStrCat("Globbing for\n ", *i, "\nEncountered an error:\n ",
754 globMessage.content));
755 }
756 }
757 if (shouldExit) {
758 return false;
759 }
760 }
761
762 if (recurse && !explicitFollowSymlinks &&
763 g.GetFollowedSymlinkCount() != 0) {
764 warnFollowedSymlinks = true;
765 }
766
767 std::vector<std::string>& foundFiles = g.GetFiles();
768 cm::append(files, foundFiles);
769
770 if (configureDepends) {
771 std::sort(foundFiles.begin(), foundFiles.end());
772 foundFiles.erase(std::unique(foundFiles.begin(), foundFiles.end()),
773 foundFiles.end());
774 cm->AddGlobCacheEntry(
775 recurse, (recurse ? g.GetRecurseListDirs() : g.GetListDirs()),
776 (recurse ? g.GetRecurseThroughSymlinks() : false),
777 (g.GetRelative() ? g.GetRelative() : ""), expr, foundFiles, variable,
778 status.GetMakefile().GetBacktrace());
779 } else {
780 warnConfigureLate = true;
781 }
782 ++i;
783 }
784 }
785
786 switch (policyStatus) {
787 case cmPolicies::REQUIRED_IF_USED:
788 case cmPolicies::REQUIRED_ALWAYS:
789 case cmPolicies::NEW:
790 // Correct behavior, yay!
791 break;
792 case cmPolicies::OLD:
793 // Probably not really the expected behavior, but the author explicitly
794 // asked for the old behavior... no warning.
795 case cmPolicies::WARN:
796 // Possibly unexpected old behavior *and* we actually traversed
797 // symlinks without being explicitly asked to: warn the author.
798 if (warnFollowedSymlinks) {
799 status.GetMakefile().IssueMessage(
800 MessageType::AUTHOR_WARNING,
801 cmPolicies::GetPolicyWarning(cmPolicies::CMP0009));
802 }
803 break;
804 }
805
806 std::sort(files.begin(), files.end());
807 files.erase(std::unique(files.begin(), files.end()), files.end());
808 status.GetMakefile().AddDefinition(variable, cmJoin(files, ";"));
809 return true;
810 }
811
HandleGlobCommand(std::vector<std::string> const & args,cmExecutionStatus & status)812 bool HandleGlobCommand(std::vector<std::string> const& args,
813 cmExecutionStatus& status)
814 {
815 return HandleGlobImpl(args, false, status);
816 }
817
HandleGlobRecurseCommand(std::vector<std::string> const & args,cmExecutionStatus & status)818 bool HandleGlobRecurseCommand(std::vector<std::string> const& args,
819 cmExecutionStatus& status)
820 {
821 return HandleGlobImpl(args, true, status);
822 }
823
HandleMakeDirectoryCommand(std::vector<std::string> const & args,cmExecutionStatus & status)824 bool HandleMakeDirectoryCommand(std::vector<std::string> const& args,
825 cmExecutionStatus& status)
826 {
827 // File command has at least one argument
828 assert(args.size() > 1);
829
830 std::string expr;
831 for (std::string const& arg :
832 cmMakeRange(args).advance(1)) // Get rid of subcommand
833 {
834 const std::string* cdir = &arg;
835 if (!cmsys::SystemTools::FileIsFullPath(arg)) {
836 expr =
837 cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', arg);
838 cdir = &expr;
839 }
840 if (!status.GetMakefile().CanIWriteThisFile(*cdir)) {
841 std::string e = "attempted to create a directory: " + *cdir +
842 " into a source directory.";
843 status.SetError(e);
844 cmSystemTools::SetFatalErrorOccured();
845 return false;
846 }
847 if (!cmSystemTools::MakeDirectory(*cdir)) {
848 std::string error = "problem creating directory: " + *cdir;
849 status.SetError(error);
850 return false;
851 }
852 }
853 return true;
854 }
855
HandleTouchImpl(std::vector<std::string> const & args,bool create,cmExecutionStatus & status)856 bool HandleTouchImpl(std::vector<std::string> const& args, bool create,
857 cmExecutionStatus& status)
858 {
859 // File command has at least one argument
860 assert(args.size() > 1);
861
862 for (std::string const& arg :
863 cmMakeRange(args).advance(1)) // Get rid of subcommand
864 {
865 std::string tfile = arg;
866 if (!cmsys::SystemTools::FileIsFullPath(tfile)) {
867 tfile =
868 cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', arg);
869 }
870 if (!status.GetMakefile().CanIWriteThisFile(tfile)) {
871 std::string e =
872 "attempted to touch a file: " + tfile + " in a source directory.";
873 status.SetError(e);
874 cmSystemTools::SetFatalErrorOccured();
875 return false;
876 }
877 if (!cmSystemTools::Touch(tfile, create)) {
878 std::string error = "problem touching file: " + tfile;
879 status.SetError(error);
880 return false;
881 }
882 }
883 return true;
884 }
885
HandleTouchCommand(std::vector<std::string> const & args,cmExecutionStatus & status)886 bool HandleTouchCommand(std::vector<std::string> const& args,
887 cmExecutionStatus& status)
888 {
889 return HandleTouchImpl(args, true, status);
890 }
891
HandleTouchNocreateCommand(std::vector<std::string> const & args,cmExecutionStatus & status)892 bool HandleTouchNocreateCommand(std::vector<std::string> const& args,
893 cmExecutionStatus& status)
894 {
895 return HandleTouchImpl(args, false, status);
896 }
897
HandleDifferentCommand(std::vector<std::string> const & args,cmExecutionStatus & status)898 bool HandleDifferentCommand(std::vector<std::string> const& args,
899 cmExecutionStatus& status)
900 {
901 /*
902 FILE(DIFFERENT <variable> FILES <lhs> <rhs>)
903 */
904
905 // Evaluate arguments.
906 const char* file_lhs = nullptr;
907 const char* file_rhs = nullptr;
908 const char* var = nullptr;
909 enum Doing
910 {
911 DoingNone,
912 DoingVar,
913 DoingFileLHS,
914 DoingFileRHS
915 };
916 Doing doing = DoingVar;
917 for (unsigned int i = 1; i < args.size(); ++i) {
918 if (args[i] == "FILES") {
919 doing = DoingFileLHS;
920 } else if (doing == DoingVar) {
921 var = args[i].c_str();
922 doing = DoingNone;
923 } else if (doing == DoingFileLHS) {
924 file_lhs = args[i].c_str();
925 doing = DoingFileRHS;
926 } else if (doing == DoingFileRHS) {
927 file_rhs = args[i].c_str();
928 doing = DoingNone;
929 } else {
930 status.SetError(cmStrCat("DIFFERENT given unknown argument ", args[i]));
931 return false;
932 }
933 }
934 if (!var) {
935 status.SetError("DIFFERENT not given result variable name.");
936 return false;
937 }
938 if (!file_lhs || !file_rhs) {
939 status.SetError("DIFFERENT not given FILES option with two file names.");
940 return false;
941 }
942
943 // Compare the files.
944 const char* result =
945 cmSystemTools::FilesDiffer(file_lhs, file_rhs) ? "1" : "0";
946 status.GetMakefile().AddDefinition(var, result);
947 return true;
948 }
949
HandleCopyCommand(std::vector<std::string> const & args,cmExecutionStatus & status)950 bool HandleCopyCommand(std::vector<std::string> const& args,
951 cmExecutionStatus& status)
952 {
953 cmFileCopier copier(status);
954 return copier.Run(args);
955 }
956
HandleRPathChangeCommand(std::vector<std::string> const & args,cmExecutionStatus & status)957 bool HandleRPathChangeCommand(std::vector<std::string> const& args,
958 cmExecutionStatus& status)
959 {
960 // Evaluate arguments.
961 std::string file;
962 std::string oldRPath;
963 std::string newRPath;
964 bool removeEnvironmentRPath = false;
965 cmArgumentParser<void> parser;
966 std::vector<std::string> unknownArgs;
967 std::vector<std::string> missingArgs;
968 std::vector<std::string> parsedArgs;
969 parser.Bind("FILE"_s, file)
970 .Bind("OLD_RPATH"_s, oldRPath)
971 .Bind("NEW_RPATH"_s, newRPath)
972 .Bind("INSTALL_REMOVE_ENVIRONMENT_RPATH"_s, removeEnvironmentRPath);
973 parser.Parse(cmMakeRange(args).advance(1), &unknownArgs, &missingArgs,
974 &parsedArgs);
975 if (!unknownArgs.empty()) {
976 status.SetError(
977 cmStrCat("RPATH_CHANGE given unknown argument ", unknownArgs.front()));
978 return false;
979 }
980 if (!missingArgs.empty()) {
981 status.SetError(cmStrCat("RPATH_CHANGE \"", missingArgs.front(),
982 "\" argument not given value."));
983 return false;
984 }
985 if (file.empty()) {
986 status.SetError("RPATH_CHANGE not given FILE option.");
987 return false;
988 }
989 if (oldRPath.empty() &&
990 std::find(parsedArgs.begin(), parsedArgs.end(), "OLD_RPATH") ==
991 parsedArgs.end()) {
992 status.SetError("RPATH_CHANGE not given OLD_RPATH option.");
993 return false;
994 }
995 if (newRPath.empty() &&
996 std::find(parsedArgs.begin(), parsedArgs.end(), "NEW_RPATH") ==
997 parsedArgs.end()) {
998 status.SetError("RPATH_CHANGE not given NEW_RPATH option.");
999 return false;
1000 }
1001 if (!cmSystemTools::FileExists(file, true)) {
1002 status.SetError(
1003 cmStrCat("RPATH_CHANGE given FILE \"", file, "\" that does not exist."));
1004 return false;
1005 }
1006 bool success = true;
1007 cmFileTimes const ft(file);
1008 std::string emsg;
1009 bool changed;
1010
1011 if (!cmSystemTools::ChangeRPath(file, oldRPath, newRPath,
1012 removeEnvironmentRPath, &emsg, &changed)) {
1013 status.SetError(cmStrCat("RPATH_CHANGE could not write new RPATH:\n ",
1014 newRPath, "\nto the file:\n ", file, "\n",
1015 emsg));
1016 success = false;
1017 }
1018 if (success) {
1019 if (changed) {
1020 std::string message =
1021 cmStrCat("Set runtime path of \"", file, "\" to \"", newRPath, '"');
1022 status.GetMakefile().DisplayStatus(message, -1);
1023 }
1024 ft.Store(file);
1025 }
1026 return success;
1027 }
1028
HandleRPathSetCommand(std::vector<std::string> const & args,cmExecutionStatus & status)1029 bool HandleRPathSetCommand(std::vector<std::string> const& args,
1030 cmExecutionStatus& status)
1031 {
1032 // Evaluate arguments.
1033 std::string file;
1034 std::string newRPath;
1035 cmArgumentParser<void> parser;
1036 std::vector<std::string> unknownArgs;
1037 std::vector<std::string> missingArgs;
1038 std::vector<std::string> parsedArgs;
1039 parser.Bind("FILE"_s, file).Bind("NEW_RPATH"_s, newRPath);
1040 parser.Parse(cmMakeRange(args).advance(1), &unknownArgs, &missingArgs,
1041 &parsedArgs);
1042 if (!unknownArgs.empty()) {
1043 status.SetError(cmStrCat("RPATH_SET given unrecognized argument \"",
1044 unknownArgs.front(), "\"."));
1045 return false;
1046 }
1047 if (!missingArgs.empty()) {
1048 status.SetError(cmStrCat("RPATH_SET \"", missingArgs.front(),
1049 "\" argument not given value."));
1050 return false;
1051 }
1052 if (file.empty()) {
1053 status.SetError("RPATH_SET not given FILE option.");
1054 return false;
1055 }
1056 if (newRPath.empty() &&
1057 std::find(parsedArgs.begin(), parsedArgs.end(), "NEW_RPATH") ==
1058 parsedArgs.end()) {
1059 status.SetError("RPATH_SET not given NEW_RPATH option.");
1060 return false;
1061 }
1062 if (!cmSystemTools::FileExists(file, true)) {
1063 status.SetError(
1064 cmStrCat("RPATH_SET given FILE \"", file, "\" that does not exist."));
1065 return false;
1066 }
1067 bool success = true;
1068 cmFileTimes const ft(file);
1069 std::string emsg;
1070 bool changed;
1071
1072 if (!cmSystemTools::SetRPath(file, newRPath, &emsg, &changed)) {
1073 status.SetError(cmStrCat("RPATH_SET could not write new RPATH:\n ",
1074 newRPath, "\nto the file:\n ", file, "\n",
1075 emsg));
1076 success = false;
1077 }
1078 if (success) {
1079 if (changed) {
1080 std::string message =
1081 cmStrCat("Set runtime path of \"", file, "\" to \"", newRPath, '"');
1082 status.GetMakefile().DisplayStatus(message, -1);
1083 }
1084 ft.Store(file);
1085 }
1086 return success;
1087 }
1088
HandleRPathRemoveCommand(std::vector<std::string> const & args,cmExecutionStatus & status)1089 bool HandleRPathRemoveCommand(std::vector<std::string> const& args,
1090 cmExecutionStatus& status)
1091 {
1092 // Evaluate arguments.
1093 std::string file;
1094 cmArgumentParser<void> parser;
1095 std::vector<std::string> unknownArgs;
1096 std::vector<std::string> missingArgs;
1097 parser.Bind("FILE"_s, file);
1098 parser.Parse(cmMakeRange(args).advance(1), &unknownArgs, &missingArgs);
1099 if (!unknownArgs.empty()) {
1100 status.SetError(
1101 cmStrCat("RPATH_REMOVE given unknown argument ", unknownArgs.front()));
1102 return false;
1103 }
1104 if (!missingArgs.empty()) {
1105 status.SetError(cmStrCat("RPATH_REMOVE \"", missingArgs.front(),
1106 "\" argument not given value."));
1107 return false;
1108 }
1109 if (file.empty()) {
1110 status.SetError("RPATH_REMOVE not given FILE option.");
1111 return false;
1112 }
1113 if (!cmSystemTools::FileExists(file, true)) {
1114 status.SetError(
1115 cmStrCat("RPATH_REMOVE given FILE \"", file, "\" that does not exist."));
1116 return false;
1117 }
1118 bool success = true;
1119 cmFileTimes const ft(file);
1120 std::string emsg;
1121 bool removed;
1122 if (!cmSystemTools::RemoveRPath(file, &emsg, &removed)) {
1123 status.SetError(
1124 cmStrCat("RPATH_REMOVE could not remove RPATH from file: \n ", file,
1125 "\n", emsg));
1126 success = false;
1127 }
1128 if (success) {
1129 if (removed) {
1130 std::string message =
1131 cmStrCat("Removed runtime path from \"", file, '"');
1132 status.GetMakefile().DisplayStatus(message, -1);
1133 }
1134 ft.Store(file);
1135 }
1136 return success;
1137 }
1138
HandleRPathCheckCommand(std::vector<std::string> const & args,cmExecutionStatus & status)1139 bool HandleRPathCheckCommand(std::vector<std::string> const& args,
1140 cmExecutionStatus& status)
1141 {
1142 // Evaluate arguments.
1143 std::string file;
1144 std::string rpath;
1145 cmArgumentParser<void> parser;
1146 std::vector<std::string> unknownArgs;
1147 std::vector<std::string> missingArgs;
1148 std::vector<std::string> parsedArgs;
1149 parser.Bind("FILE"_s, file).Bind("RPATH"_s, rpath);
1150 parser.Parse(cmMakeRange(args).advance(1), &unknownArgs, &missingArgs,
1151 &parsedArgs);
1152 if (!unknownArgs.empty()) {
1153 status.SetError(
1154 cmStrCat("RPATH_CHECK given unknown argument ", unknownArgs.front()));
1155 return false;
1156 }
1157 if (!missingArgs.empty()) {
1158 status.SetError(cmStrCat("RPATH_CHECK \"", missingArgs.front(),
1159 "\" argument not given value."));
1160 return false;
1161 }
1162 if (file.empty()) {
1163 status.SetError("RPATH_CHECK not given FILE option.");
1164 return false;
1165 }
1166 if (rpath.empty() &&
1167 std::find(parsedArgs.begin(), parsedArgs.end(), "RPATH") ==
1168 parsedArgs.end()) {
1169 status.SetError("RPATH_CHECK not given RPATH option.");
1170 return false;
1171 }
1172
1173 // If the file exists but does not have the desired RPath then
1174 // delete it. This is used during installation to re-install a file
1175 // if its RPath will change.
1176 if (cmSystemTools::FileExists(file, true) &&
1177 !cmSystemTools::CheckRPath(file, rpath)) {
1178 cmSystemTools::RemoveFile(file);
1179 }
1180
1181 return true;
1182 }
1183
HandleReadElfCommand(std::vector<std::string> const & args,cmExecutionStatus & status)1184 bool HandleReadElfCommand(std::vector<std::string> const& args,
1185 cmExecutionStatus& status)
1186 {
1187 if (args.size() < 4) {
1188 status.SetError("READ_ELF must be called with at least three additional "
1189 "arguments.");
1190 return false;
1191 }
1192
1193 std::string const& fileNameArg = args[1];
1194
1195 struct Arguments
1196 {
1197 std::string RPath;
1198 std::string RunPath;
1199 std::string Error;
1200 };
1201
1202 static auto const parser = cmArgumentParser<Arguments>{}
1203 .Bind("RPATH"_s, &Arguments::RPath)
1204 .Bind("RUNPATH"_s, &Arguments::RunPath)
1205 .Bind("CAPTURE_ERROR"_s, &Arguments::Error);
1206 Arguments const arguments = parser.Parse(cmMakeRange(args).advance(2));
1207
1208 if (!cmSystemTools::FileExists(fileNameArg, true)) {
1209 status.SetError(cmStrCat("READ_ELF given FILE \"", fileNameArg,
1210 "\" that does not exist."));
1211 return false;
1212 }
1213
1214 cmELF elf(fileNameArg.c_str());
1215 if (!elf) {
1216 status.SetError(cmStrCat("READ_ELF given FILE \"", fileNameArg,
1217 "\" that is not a valid ELF file."));
1218 return false;
1219 }
1220
1221 if (!arguments.RPath.empty()) {
1222 if (cmELF::StringEntry const* se_rpath = elf.GetRPath()) {
1223 std::string rpath(se_rpath->Value);
1224 std::replace(rpath.begin(), rpath.end(), ':', ';');
1225 status.GetMakefile().AddDefinition(arguments.RPath, rpath);
1226 }
1227 }
1228 if (!arguments.RunPath.empty()) {
1229 if (cmELF::StringEntry const* se_runpath = elf.GetRunPath()) {
1230 std::string runpath(se_runpath->Value);
1231 std::replace(runpath.begin(), runpath.end(), ':', ';');
1232 status.GetMakefile().AddDefinition(arguments.RunPath, runpath);
1233 }
1234 }
1235
1236 return true;
1237 }
1238
HandleInstallCommand(std::vector<std::string> const & args,cmExecutionStatus & status)1239 bool HandleInstallCommand(std::vector<std::string> const& args,
1240 cmExecutionStatus& status)
1241 {
1242 cmFileInstaller installer(status);
1243 return installer.Run(args);
1244 }
1245
HandleRealPathCommand(std::vector<std::string> const & args,cmExecutionStatus & status)1246 bool HandleRealPathCommand(std::vector<std::string> const& args,
1247 cmExecutionStatus& status)
1248 {
1249 if (args.size() < 3) {
1250 status.SetError("REAL_PATH requires a path and an output variable");
1251 return false;
1252 }
1253
1254 struct Arguments
1255 {
1256 std::string BaseDirectory;
1257 bool ExpandTilde = false;
1258 };
1259 static auto const parser =
1260 cmArgumentParser<Arguments>{}
1261 .Bind("BASE_DIRECTORY"_s, &Arguments::BaseDirectory)
1262 .Bind("EXPAND_TILDE"_s, &Arguments::ExpandTilde);
1263
1264 std::vector<std::string> unparsedArguments;
1265 std::vector<std::string> keywordsMissingValue;
1266 std::vector<std::string> parsedKeywords;
1267 auto arguments =
1268 parser.Parse(cmMakeRange(args).advance(3), &unparsedArguments,
1269 &keywordsMissingValue, &parsedKeywords);
1270
1271 if (!unparsedArguments.empty()) {
1272 status.SetError("REAL_PATH called with unexpected arguments");
1273 return false;
1274 }
1275 if (!keywordsMissingValue.empty()) {
1276 status.SetError("BASE_DIRECTORY requires a value");
1277 return false;
1278 }
1279
1280 if (parsedKeywords.empty()) {
1281 arguments.BaseDirectory = status.GetMakefile().GetCurrentSourceDirectory();
1282 }
1283
1284 auto input = args[1];
1285 if (arguments.ExpandTilde && !input.empty()) {
1286 if (input[0] == '~' && (input.length() == 1 || input[1] == '/')) {
1287 std::string home;
1288 if (
1289 #if defined(_WIN32) && !defined(__CYGWIN__)
1290 cmSystemTools::GetEnv("USERPROFILE", home) ||
1291 #endif
1292 cmSystemTools::GetEnv("HOME", home)) {
1293 input.replace(0, 1, home);
1294 }
1295 }
1296 }
1297
1298 cmCMakePath path(input, cmCMakePath::auto_format);
1299 path = path.Absolute(arguments.BaseDirectory).Normal();
1300 auto realPath = cmSystemTools::GetRealPath(path.GenericString());
1301
1302 status.GetMakefile().AddDefinition(args[2], realPath);
1303
1304 return true;
1305 }
1306
HandleRelativePathCommand(std::vector<std::string> const & args,cmExecutionStatus & status)1307 bool HandleRelativePathCommand(std::vector<std::string> const& args,
1308 cmExecutionStatus& status)
1309 {
1310 if (args.size() != 4) {
1311 status.SetError("RELATIVE_PATH called with incorrect number of arguments");
1312 return false;
1313 }
1314
1315 const std::string& outVar = args[1];
1316 const std::string& directoryName = args[2];
1317 const std::string& fileName = args[3];
1318
1319 if (!cmSystemTools::FileIsFullPath(directoryName)) {
1320 std::string errstring =
1321 "RELATIVE_PATH must be passed a full path to the directory: " +
1322 directoryName;
1323 status.SetError(errstring);
1324 return false;
1325 }
1326 if (!cmSystemTools::FileIsFullPath(fileName)) {
1327 std::string errstring =
1328 "RELATIVE_PATH must be passed a full path to the file: " + fileName;
1329 status.SetError(errstring);
1330 return false;
1331 }
1332
1333 std::string res = cmSystemTools::RelativePath(directoryName, fileName);
1334 status.GetMakefile().AddDefinition(outVar, res);
1335 return true;
1336 }
1337
HandleRename(std::vector<std::string> const & args,cmExecutionStatus & status)1338 bool HandleRename(std::vector<std::string> const& args,
1339 cmExecutionStatus& status)
1340 {
1341 if (args.size() < 3) {
1342 status.SetError("RENAME must be called with at least two additional "
1343 "arguments");
1344 return false;
1345 }
1346
1347 // Compute full path for old and new names.
1348 std::string oldname = args[1];
1349 if (!cmsys::SystemTools::FileIsFullPath(oldname)) {
1350 oldname =
1351 cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[1]);
1352 }
1353 std::string newname = args[2];
1354 if (!cmsys::SystemTools::FileIsFullPath(newname)) {
1355 newname =
1356 cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[2]);
1357 }
1358
1359 struct Arguments
1360 {
1361 bool NoReplace = false;
1362 std::string Result;
1363 };
1364
1365 static auto const parser = cmArgumentParser<Arguments>{}
1366 .Bind("NO_REPLACE"_s, &Arguments::NoReplace)
1367 .Bind("RESULT"_s, &Arguments::Result);
1368
1369 std::vector<std::string> unconsumedArgs;
1370 Arguments const arguments =
1371 parser.Parse(cmMakeRange(args).advance(3), &unconsumedArgs);
1372 if (!unconsumedArgs.empty()) {
1373 status.SetError("RENAME unknown argument:\n " + unconsumedArgs.front());
1374 return false;
1375 }
1376
1377 std::string err;
1378 switch (cmSystemTools::RenameFile(oldname, newname,
1379 arguments.NoReplace
1380 ? cmSystemTools::Replace::No
1381 : cmSystemTools::Replace::Yes,
1382 &err)) {
1383 case cmSystemTools::RenameResult::Success:
1384 if (!arguments.Result.empty()) {
1385 status.GetMakefile().AddDefinition(arguments.Result, "0");
1386 }
1387 return true;
1388 case cmSystemTools::RenameResult::NoReplace:
1389 if (!arguments.Result.empty()) {
1390 err = "NO_REPLACE";
1391 } else {
1392 err = "path not replaced";
1393 }
1394 CM_FALLTHROUGH;
1395 case cmSystemTools::RenameResult::Failure:
1396 if (!arguments.Result.empty()) {
1397 status.GetMakefile().AddDefinition(arguments.Result, err);
1398 return true;
1399 }
1400 break;
1401 }
1402 status.SetError(cmStrCat("RENAME failed to rename\n ", oldname, "\nto\n ",
1403 newname, "\nbecause: ", err, "\n"));
1404 return false;
1405 }
1406
HandleCopyFile(std::vector<std::string> const & args,cmExecutionStatus & status)1407 bool HandleCopyFile(std::vector<std::string> const& args,
1408 cmExecutionStatus& status)
1409 {
1410 if (args.size() < 3) {
1411 status.SetError("COPY_FILE must be called with at least two additional "
1412 "arguments");
1413 return false;
1414 }
1415
1416 // Compute full path for old and new names.
1417 std::string oldname = args[1];
1418 if (!cmsys::SystemTools::FileIsFullPath(oldname)) {
1419 oldname =
1420 cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[1]);
1421 }
1422 std::string newname = args[2];
1423 if (!cmsys::SystemTools::FileIsFullPath(newname)) {
1424 newname =
1425 cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[2]);
1426 }
1427
1428 struct Arguments
1429 {
1430 bool OnlyIfDifferent = false;
1431 std::string Result;
1432 };
1433
1434 static auto const parser =
1435 cmArgumentParser<Arguments>{}
1436 .Bind("ONLY_IF_DIFFERENT"_s, &Arguments::OnlyIfDifferent)
1437 .Bind("RESULT"_s, &Arguments::Result);
1438
1439 std::vector<std::string> unconsumedArgs;
1440 Arguments const arguments =
1441 parser.Parse(cmMakeRange(args).advance(3), &unconsumedArgs);
1442 if (!unconsumedArgs.empty()) {
1443 status.SetError("COPY_FILE unknown argument:\n " +
1444 unconsumedArgs.front());
1445 return false;
1446 }
1447
1448 bool result = true;
1449 if (cmsys::SystemTools::FileIsDirectory(oldname)) {
1450 if (!arguments.Result.empty()) {
1451 status.GetMakefile().AddDefinition(arguments.Result,
1452 "cannot copy a directory");
1453 } else {
1454 status.SetError(
1455 cmStrCat("COPY_FILE cannot copy a directory\n ", oldname));
1456 result = false;
1457 }
1458 return result;
1459 }
1460 if (cmsys::SystemTools::FileIsDirectory(newname)) {
1461 if (!arguments.Result.empty()) {
1462 status.GetMakefile().AddDefinition(arguments.Result,
1463 "cannot copy to a directory");
1464 } else {
1465 status.SetError(
1466 cmStrCat("COPY_FILE cannot copy to a directory\n ", newname));
1467 result = false;
1468 }
1469 return result;
1470 }
1471
1472 cmSystemTools::CopyWhen when;
1473 if (arguments.OnlyIfDifferent) {
1474 when = cmSystemTools::CopyWhen::OnlyIfDifferent;
1475 } else {
1476 when = cmSystemTools::CopyWhen::Always;
1477 }
1478
1479 std::string err;
1480 if (cmSystemTools::CopySingleFile(oldname, newname, when, &err) ==
1481 cmSystemTools::CopyResult::Success) {
1482 if (!arguments.Result.empty()) {
1483 status.GetMakefile().AddDefinition(arguments.Result, "0");
1484 }
1485 } else {
1486 if (!arguments.Result.empty()) {
1487 status.GetMakefile().AddDefinition(arguments.Result, err);
1488 } else {
1489 status.SetError(cmStrCat("COPY_FILE failed to copy\n ", oldname,
1490 "\nto\n ", newname, "\nbecause: ", err, "\n"));
1491 result = false;
1492 }
1493 }
1494
1495 return result;
1496 }
1497
HandleRemoveImpl(std::vector<std::string> const & args,bool recurse,cmExecutionStatus & status)1498 bool HandleRemoveImpl(std::vector<std::string> const& args, bool recurse,
1499 cmExecutionStatus& status)
1500 {
1501 for (std::string const& arg :
1502 cmMakeRange(args).advance(1)) // Get rid of subcommand
1503 {
1504 std::string fileName = arg;
1505 if (fileName.empty()) {
1506 std::string const r = recurse ? "REMOVE_RECURSE" : "REMOVE";
1507 status.GetMakefile().IssueMessage(
1508 MessageType::AUTHOR_WARNING, "Ignoring empty file name in " + r + ".");
1509 continue;
1510 }
1511 if (!cmsys::SystemTools::FileIsFullPath(fileName)) {
1512 fileName =
1513 cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', arg);
1514 }
1515
1516 if (cmSystemTools::FileIsDirectory(fileName) &&
1517 !cmSystemTools::FileIsSymlink(fileName) && recurse) {
1518 cmSystemTools::RepeatedRemoveDirectory(fileName);
1519 } else {
1520 cmSystemTools::RemoveFile(fileName);
1521 }
1522 }
1523 return true;
1524 }
1525
HandleRemove(std::vector<std::string> const & args,cmExecutionStatus & status)1526 bool HandleRemove(std::vector<std::string> const& args,
1527 cmExecutionStatus& status)
1528 {
1529 return HandleRemoveImpl(args, false, status);
1530 }
1531
HandleRemoveRecurse(std::vector<std::string> const & args,cmExecutionStatus & status)1532 bool HandleRemoveRecurse(std::vector<std::string> const& args,
1533 cmExecutionStatus& status)
1534 {
1535 return HandleRemoveImpl(args, true, status);
1536 }
1537
ToNativePath(const std::string & path)1538 std::string ToNativePath(const std::string& path)
1539 {
1540 const auto& outPath = cmSystemTools::ConvertToOutputPath(path);
1541 if (outPath.size() > 1 && outPath.front() == '\"' &&
1542 outPath.back() == '\"') {
1543 return outPath.substr(1, outPath.size() - 2);
1544 }
1545 return outPath;
1546 }
1547
ToCMakePath(const std::string & path)1548 std::string ToCMakePath(const std::string& path)
1549 {
1550 auto temp = path;
1551 cmSystemTools::ConvertToUnixSlashes(temp);
1552 return temp;
1553 }
1554
HandlePathCommand(std::vector<std::string> const & args,std::string (* convert)(std::string const &),cmExecutionStatus & status)1555 bool HandlePathCommand(std::vector<std::string> const& args,
1556 std::string (*convert)(std::string const&),
1557 cmExecutionStatus& status)
1558 {
1559 if (args.size() != 3) {
1560 status.SetError("FILE([TO_CMAKE_PATH|TO_NATIVE_PATH] path result) must be "
1561 "called with exactly three arguments.");
1562 return false;
1563 }
1564 #if defined(_WIN32) && !defined(__CYGWIN__)
1565 char pathSep = ';';
1566 #else
1567 char pathSep = ':';
1568 #endif
1569 std::vector<std::string> path = cmSystemTools::SplitString(args[1], pathSep);
1570
1571 std::string value = cmJoin(cmMakeRange(path).transform(convert), ";");
1572 status.GetMakefile().AddDefinition(args[2], value);
1573 return true;
1574 }
1575
HandleCMakePathCommand(std::vector<std::string> const & args,cmExecutionStatus & status)1576 bool HandleCMakePathCommand(std::vector<std::string> const& args,
1577 cmExecutionStatus& status)
1578 {
1579 return HandlePathCommand(args, ToCMakePath, status);
1580 }
1581
HandleNativePathCommand(std::vector<std::string> const & args,cmExecutionStatus & status)1582 bool HandleNativePathCommand(std::vector<std::string> const& args,
1583 cmExecutionStatus& status)
1584 {
1585 return HandlePathCommand(args, ToNativePath, status);
1586 }
1587
1588 #if !defined(CMAKE_BOOTSTRAP)
1589
1590 // Stuff for curl download/upload
1591 using cmFileCommandVectorOfChar = std::vector<char>;
1592
cmWriteToFileCallback(void * ptr,size_t size,size_t nmemb,void * data)1593 size_t cmWriteToFileCallback(void* ptr, size_t size, size_t nmemb, void* data)
1594 {
1595 int realsize = static_cast<int>(size * nmemb);
1596 cmsys::ofstream* fout = static_cast<cmsys::ofstream*>(data);
1597 if (fout) {
1598 const char* chPtr = static_cast<char*>(ptr);
1599 fout->write(chPtr, realsize);
1600 }
1601 return realsize;
1602 }
1603
cmWriteToMemoryCallback(void * ptr,size_t size,size_t nmemb,void * data)1604 size_t cmWriteToMemoryCallback(void* ptr, size_t size, size_t nmemb,
1605 void* data)
1606 {
1607 int realsize = static_cast<int>(size * nmemb);
1608 const char* chPtr = static_cast<char*>(ptr);
1609 cm::append(*static_cast<cmFileCommandVectorOfChar*>(data), chPtr,
1610 chPtr + realsize);
1611 return realsize;
1612 }
1613
cmFileCommandCurlDebugCallback(CURL *,curl_infotype type,char * chPtr,size_t size,void * data)1614 size_t cmFileCommandCurlDebugCallback(CURL*, curl_infotype type, char* chPtr,
1615 size_t size, void* data)
1616 {
1617 cmFileCommandVectorOfChar& vec =
1618 *static_cast<cmFileCommandVectorOfChar*>(data);
1619 switch (type) {
1620 case CURLINFO_TEXT:
1621 case CURLINFO_HEADER_IN:
1622 case CURLINFO_HEADER_OUT:
1623 cm::append(vec, chPtr, chPtr + size);
1624 break;
1625 case CURLINFO_DATA_IN:
1626 case CURLINFO_DATA_OUT:
1627 case CURLINFO_SSL_DATA_IN:
1628 case CURLINFO_SSL_DATA_OUT: {
1629 char buf[128];
1630 int n = sprintf(buf, "[%" KWIML_INT_PRIu64 " bytes data]\n",
1631 static_cast<KWIML_INT_uint64_t>(size));
1632 if (n > 0) {
1633 cm::append(vec, buf, buf + n);
1634 }
1635 } break;
1636 default:
1637 break;
1638 }
1639 return 0;
1640 }
1641
1642 class cURLProgressHelper
1643 {
1644 public:
cURLProgressHelper(cmMakefile * mf,const char * text)1645 cURLProgressHelper(cmMakefile* mf, const char* text)
1646 : Makefile(mf)
1647 , Text(text)
1648 {
1649 }
1650
UpdatePercentage(double value,double total,std::string & status)1651 bool UpdatePercentage(double value, double total, std::string& status)
1652 {
1653 long OldPercentage = this->CurrentPercentage;
1654
1655 if (total > 0.0) {
1656 this->CurrentPercentage = std::lround(value / total * 100.0);
1657 if (this->CurrentPercentage > 100) {
1658 // Avoid extra progress reports for unexpected data beyond total.
1659 this->CurrentPercentage = 100;
1660 }
1661 }
1662
1663 bool updated = (OldPercentage != this->CurrentPercentage);
1664
1665 if (updated) {
1666 status =
1667 cmStrCat("[", this->Text, " ", this->CurrentPercentage, "% complete]");
1668 }
1669
1670 return updated;
1671 }
1672
GetMakefile()1673 cmMakefile* GetMakefile() { return this->Makefile; }
1674
1675 private:
1676 long CurrentPercentage = -1;
1677 cmMakefile* Makefile;
1678 std::string Text;
1679 };
1680
cmFileDownloadProgressCallback(void * clientp,double dltotal,double dlnow,double ultotal,double ulnow)1681 int cmFileDownloadProgressCallback(void* clientp, double dltotal, double dlnow,
1682 double ultotal, double ulnow)
1683 {
1684 cURLProgressHelper* helper = reinterpret_cast<cURLProgressHelper*>(clientp);
1685
1686 static_cast<void>(ultotal);
1687 static_cast<void>(ulnow);
1688
1689 std::string status;
1690 if (helper->UpdatePercentage(dlnow, dltotal, status)) {
1691 cmMakefile* mf = helper->GetMakefile();
1692 mf->DisplayStatus(status, -1);
1693 }
1694
1695 return 0;
1696 }
1697
cmFileUploadProgressCallback(void * clientp,double dltotal,double dlnow,double ultotal,double ulnow)1698 int cmFileUploadProgressCallback(void* clientp, double dltotal, double dlnow,
1699 double ultotal, double ulnow)
1700 {
1701 cURLProgressHelper* helper = reinterpret_cast<cURLProgressHelper*>(clientp);
1702
1703 static_cast<void>(dltotal);
1704 static_cast<void>(dlnow);
1705
1706 std::string status;
1707 if (helper->UpdatePercentage(ulnow, ultotal, status)) {
1708 cmMakefile* mf = helper->GetMakefile();
1709 mf->DisplayStatus(status, -1);
1710 }
1711
1712 return 0;
1713 }
1714
1715 class cURLEasyGuard
1716 {
1717 public:
cURLEasyGuard(CURL * easy)1718 cURLEasyGuard(CURL* easy)
1719 : Easy(easy)
1720 {
1721 }
1722
~cURLEasyGuard()1723 ~cURLEasyGuard()
1724 {
1725 if (this->Easy) {
1726 ::curl_easy_cleanup(this->Easy);
1727 }
1728 }
1729
1730 cURLEasyGuard(const cURLEasyGuard&) = delete;
1731 cURLEasyGuard& operator=(const cURLEasyGuard&) = delete;
1732
release()1733 void release() { this->Easy = nullptr; }
1734
1735 private:
1736 ::CURL* Easy;
1737 };
1738
1739 #endif
1740
1741 #define check_curl_result(result, errstr) \
1742 do { \
1743 if (result != CURLE_OK) { \
1744 std::string e(errstr); \
1745 e += ::curl_easy_strerror(result); \
1746 status.SetError(e); \
1747 return false; \
1748 } \
1749 } while (false)
1750
HandleDownloadCommand(std::vector<std::string> const & args,cmExecutionStatus & status)1751 bool HandleDownloadCommand(std::vector<std::string> const& args,
1752 cmExecutionStatus& status)
1753 {
1754 #if !defined(CMAKE_BOOTSTRAP)
1755 auto i = args.begin();
1756 if (args.size() < 2) {
1757 status.SetError("DOWNLOAD must be called with at least two arguments.");
1758 return false;
1759 }
1760 ++i; // Get rid of subcommand
1761 std::string url = *i;
1762 ++i;
1763 std::string file;
1764
1765 long timeout = 0;
1766 long inactivity_timeout = 0;
1767 std::string logVar;
1768 std::string statusVar;
1769 bool tls_verify = status.GetMakefile().IsOn("CMAKE_TLS_VERIFY");
1770 cmValue cainfo = status.GetMakefile().GetDefinition("CMAKE_TLS_CAINFO");
1771 std::string netrc_level =
1772 status.GetMakefile().GetSafeDefinition("CMAKE_NETRC");
1773 std::string netrc_file =
1774 status.GetMakefile().GetSafeDefinition("CMAKE_NETRC_FILE");
1775 std::string expectedHash;
1776 std::string hashMatchMSG;
1777 std::unique_ptr<cmCryptoHash> hash;
1778 bool showProgress = false;
1779 std::string userpwd;
1780
1781 std::vector<std::string> curl_headers;
1782
1783 while (i != args.end()) {
1784 if (*i == "TIMEOUT") {
1785 ++i;
1786 if (i != args.end()) {
1787 timeout = atol(i->c_str());
1788 } else {
1789 status.SetError("DOWNLOAD missing time for TIMEOUT.");
1790 return false;
1791 }
1792 } else if (*i == "INACTIVITY_TIMEOUT") {
1793 ++i;
1794 if (i != args.end()) {
1795 inactivity_timeout = atol(i->c_str());
1796 } else {
1797 status.SetError("DOWNLOAD missing time for INACTIVITY_TIMEOUT.");
1798 return false;
1799 }
1800 } else if (*i == "LOG") {
1801 ++i;
1802 if (i == args.end()) {
1803 status.SetError("DOWNLOAD missing VAR for LOG.");
1804 return false;
1805 }
1806 logVar = *i;
1807 } else if (*i == "STATUS") {
1808 ++i;
1809 if (i == args.end()) {
1810 status.SetError("DOWNLOAD missing VAR for STATUS.");
1811 return false;
1812 }
1813 statusVar = *i;
1814 } else if (*i == "TLS_VERIFY") {
1815 ++i;
1816 if (i != args.end()) {
1817 tls_verify = cmIsOn(*i);
1818 } else {
1819 status.SetError("DOWNLOAD missing bool value for TLS_VERIFY.");
1820 return false;
1821 }
1822 } else if (*i == "TLS_CAINFO") {
1823 ++i;
1824 if (i != args.end()) {
1825 cainfo = cmValue(*i);
1826 } else {
1827 status.SetError("DOWNLOAD missing file value for TLS_CAINFO.");
1828 return false;
1829 }
1830 } else if (*i == "NETRC_FILE") {
1831 ++i;
1832 if (i != args.end()) {
1833 netrc_file = *i;
1834 } else {
1835 status.SetError("DOWNLOAD missing file value for NETRC_FILE.");
1836 return false;
1837 }
1838 } else if (*i == "NETRC") {
1839 ++i;
1840 if (i != args.end()) {
1841 netrc_level = *i;
1842 } else {
1843 status.SetError("DOWNLOAD missing level value for NETRC.");
1844 return false;
1845 }
1846 } else if (*i == "EXPECTED_MD5") {
1847 ++i;
1848 if (i == args.end()) {
1849 status.SetError("DOWNLOAD missing sum value for EXPECTED_MD5.");
1850 return false;
1851 }
1852 hash = cm::make_unique<cmCryptoHash>(cmCryptoHash::AlgoMD5);
1853 hashMatchMSG = "MD5 sum";
1854 expectedHash = cmSystemTools::LowerCase(*i);
1855 } else if (*i == "SHOW_PROGRESS") {
1856 showProgress = true;
1857 } else if (*i == "EXPECTED_HASH") {
1858 ++i;
1859 if (i == args.end()) {
1860 status.SetError("DOWNLOAD missing ALGO=value for EXPECTED_HASH.");
1861 return false;
1862 }
1863 std::string::size_type pos = i->find("=");
1864 if (pos == std::string::npos) {
1865 std::string err =
1866 cmStrCat("DOWNLOAD EXPECTED_HASH expects ALGO=value but got: ", *i);
1867 status.SetError(err);
1868 return false;
1869 }
1870 std::string algo = i->substr(0, pos);
1871 expectedHash = cmSystemTools::LowerCase(i->substr(pos + 1));
1872 hash = std::unique_ptr<cmCryptoHash>(cmCryptoHash::New(algo));
1873 if (!hash) {
1874 std::string err =
1875 cmStrCat("DOWNLOAD EXPECTED_HASH given unknown ALGO: ", algo);
1876 status.SetError(err);
1877 return false;
1878 }
1879 hashMatchMSG = algo + " hash";
1880 } else if (*i == "USERPWD") {
1881 ++i;
1882 if (i == args.end()) {
1883 status.SetError("DOWNLOAD missing string for USERPWD.");
1884 return false;
1885 }
1886 userpwd = *i;
1887 } else if (*i == "HTTPHEADER") {
1888 ++i;
1889 if (i == args.end()) {
1890 status.SetError("DOWNLOAD missing string for HTTPHEADER.");
1891 return false;
1892 }
1893 curl_headers.push_back(*i);
1894 } else if (file.empty()) {
1895 file = *i;
1896 } else {
1897 // Do not return error for compatibility reason.
1898 std::string err = cmStrCat("Unexpected argument: ", *i);
1899 status.GetMakefile().IssueMessage(MessageType::AUTHOR_WARNING, err);
1900 }
1901 ++i;
1902 }
1903 // Can't calculate hash if we don't save the file.
1904 // TODO Incrementally calculate hash in the write callback as the file is
1905 // being downloaded so this check can be relaxed.
1906 if (file.empty() && hash) {
1907 status.SetError("DOWNLOAD cannot calculate hash if file is not saved.");
1908 return false;
1909 }
1910 // If file exists already, and caller specified an expected md5 or sha,
1911 // and the existing file already has the expected hash, then simply
1912 // return.
1913 //
1914 if (!file.empty() && cmSystemTools::FileExists(file) && hash.get()) {
1915 std::string msg;
1916 std::string actualHash = hash->HashFile(file);
1917 if (actualHash == expectedHash) {
1918 msg = cmStrCat("returning early; file already exists with expected ",
1919 hashMatchMSG, '"');
1920 if (!statusVar.empty()) {
1921 status.GetMakefile().AddDefinition(statusVar, cmStrCat(0, ";\"", msg));
1922 }
1923 return true;
1924 }
1925 }
1926 // Make sure parent directory exists so we can write to the file
1927 // as we receive downloaded bits from curl...
1928 //
1929 if (!file.empty()) {
1930 std::string dir = cmSystemTools::GetFilenamePath(file);
1931 if (!dir.empty() && !cmSystemTools::FileExists(dir) &&
1932 !cmSystemTools::MakeDirectory(dir)) {
1933 std::string errstring = "DOWNLOAD error: cannot create directory '" +
1934 dir +
1935 "' - Specify file by full path name and verify that you "
1936 "have directory creation and file write privileges.";
1937 status.SetError(errstring);
1938 return false;
1939 }
1940 }
1941
1942 cmsys::ofstream fout;
1943 if (!file.empty()) {
1944 fout.open(file.c_str(), std::ios::binary);
1945 if (!fout) {
1946 status.SetError("DOWNLOAD cannot open file for write.");
1947 return false;
1948 }
1949 }
1950
1951 url = cmCurlFixFileURL(url);
1952
1953 ::CURL* curl;
1954 ::curl_global_init(CURL_GLOBAL_DEFAULT);
1955 curl = ::curl_easy_init();
1956 if (!curl) {
1957 status.SetError("DOWNLOAD error initializing curl.");
1958 return false;
1959 }
1960
1961 cURLEasyGuard g_curl(curl);
1962 ::CURLcode res = ::curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
1963 check_curl_result(res, "DOWNLOAD cannot set url: ");
1964
1965 // enable HTTP ERROR parsing
1966 res = ::curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
1967 check_curl_result(res, "DOWNLOAD cannot set http failure option: ");
1968
1969 res = ::curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/" LIBCURL_VERSION);
1970 check_curl_result(res, "DOWNLOAD cannot set user agent option: ");
1971
1972 res = ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cmWriteToFileCallback);
1973 check_curl_result(res, "DOWNLOAD cannot set write function: ");
1974
1975 res = ::curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION,
1976 cmFileCommandCurlDebugCallback);
1977 check_curl_result(res, "DOWNLOAD cannot set debug function: ");
1978
1979 // check to see if TLS verification is requested
1980 if (tls_verify) {
1981 res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1);
1982 check_curl_result(res, "DOWNLOAD cannot set TLS/SSL Verify on: ");
1983 } else {
1984 res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
1985 check_curl_result(res, "DOWNLOAD cannot set TLS/SSL Verify off: ");
1986 }
1987
1988 // check to see if a CAINFO file has been specified
1989 // command arg comes first
1990 std::string const& cainfo_err = cmCurlSetCAInfo(curl, cainfo);
1991 if (!cainfo_err.empty()) {
1992 status.SetError(cainfo_err);
1993 return false;
1994 }
1995
1996 // check to see if netrc parameters have been specified
1997 // local command args takes precedence over CMAKE_NETRC*
1998 netrc_level = cmSystemTools::UpperCase(netrc_level);
1999 std::string const& netrc_option_err =
2000 cmCurlSetNETRCOption(curl, netrc_level, netrc_file);
2001 if (!netrc_option_err.empty()) {
2002 status.SetError(netrc_option_err);
2003 return false;
2004 }
2005
2006 cmFileCommandVectorOfChar chunkDebug;
2007
2008 res = ::curl_easy_setopt(curl, CURLOPT_WRITEDATA,
2009 file.empty() ? nullptr : &fout);
2010 check_curl_result(res, "DOWNLOAD cannot set write data: ");
2011
2012 res = ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &chunkDebug);
2013 check_curl_result(res, "DOWNLOAD cannot set debug data: ");
2014
2015 res = ::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
2016 check_curl_result(res, "DOWNLOAD cannot set follow-redirect option: ");
2017
2018 if (!logVar.empty()) {
2019 res = ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
2020 check_curl_result(res, "DOWNLOAD cannot set verbose: ");
2021 }
2022
2023 if (timeout > 0) {
2024 res = ::curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
2025 check_curl_result(res, "DOWNLOAD cannot set timeout: ");
2026 }
2027
2028 if (inactivity_timeout > 0) {
2029 // Give up if there is no progress for a long time.
2030 ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1);
2031 ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, inactivity_timeout);
2032 }
2033
2034 // Need the progress helper's scope to last through the duration of
2035 // the curl_easy_perform call... so this object is declared at function
2036 // scope intentionally, rather than inside the "if(showProgress)"
2037 // block...
2038 //
2039 cURLProgressHelper helper(&status.GetMakefile(), "download");
2040
2041 if (showProgress) {
2042 res = ::curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
2043 check_curl_result(res, "DOWNLOAD cannot set noprogress value: ");
2044
2045 res = ::curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION,
2046 cmFileDownloadProgressCallback);
2047 check_curl_result(res, "DOWNLOAD cannot set progress function: ");
2048
2049 res = ::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA,
2050 reinterpret_cast<void*>(&helper));
2051 check_curl_result(res, "DOWNLOAD cannot set progress data: ");
2052 }
2053
2054 if (!userpwd.empty()) {
2055 res = ::curl_easy_setopt(curl, CURLOPT_USERPWD, userpwd.c_str());
2056 check_curl_result(res, "DOWNLOAD cannot set user password: ");
2057 }
2058
2059 struct curl_slist* headers = nullptr;
2060 for (std::string const& h : curl_headers) {
2061 headers = ::curl_slist_append(headers, h.c_str());
2062 }
2063 ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
2064
2065 res = ::curl_easy_perform(curl);
2066
2067 ::curl_slist_free_all(headers);
2068
2069 /* always cleanup */
2070 g_curl.release();
2071 ::curl_easy_cleanup(curl);
2072
2073 if (!statusVar.empty()) {
2074 status.GetMakefile().AddDefinition(
2075 statusVar,
2076 cmStrCat(static_cast<int>(res), ";\"", ::curl_easy_strerror(res), "\""));
2077 }
2078
2079 ::curl_global_cleanup();
2080
2081 // Explicitly flush/close so we can measure the md5 accurately.
2082 //
2083 if (!file.empty()) {
2084 fout.flush();
2085 fout.close();
2086 }
2087
2088 // Verify MD5 sum if requested:
2089 //
2090 if (hash) {
2091 std::string actualHash = hash->HashFile(file);
2092 if (actualHash.empty()) {
2093 status.SetError("DOWNLOAD cannot compute hash on downloaded file");
2094 return false;
2095 }
2096
2097 if (expectedHash != actualHash) {
2098 if (!statusVar.empty() && res == 0) {
2099 status.GetMakefile().AddDefinition(statusVar,
2100 "1;HASH mismatch: "
2101 "expected: " +
2102 expectedHash +
2103 " actual: " + actualHash);
2104 }
2105
2106 status.SetError(cmStrCat("DOWNLOAD HASH mismatch\n"
2107 " for file: [",
2108 file,
2109 "]\n"
2110 " expected hash: [",
2111 expectedHash,
2112 "]\n"
2113 " actual hash: [",
2114 actualHash,
2115 "]\n"
2116 " status: [",
2117 static_cast<int>(res), ";\"",
2118 ::curl_easy_strerror(res), "\"]\n"));
2119 return false;
2120 }
2121 }
2122
2123 if (!logVar.empty()) {
2124 chunkDebug.push_back(0);
2125 status.GetMakefile().AddDefinition(logVar, chunkDebug.data());
2126 }
2127
2128 return true;
2129 #else
2130 status.SetError("DOWNLOAD not supported by bootstrap cmake.");
2131 return false;
2132 #endif
2133 }
2134
HandleUploadCommand(std::vector<std::string> const & args,cmExecutionStatus & status)2135 bool HandleUploadCommand(std::vector<std::string> const& args,
2136 cmExecutionStatus& status)
2137 {
2138 #if !defined(CMAKE_BOOTSTRAP)
2139 if (args.size() < 3) {
2140 status.SetError("UPLOAD must be called with at least three arguments.");
2141 return false;
2142 }
2143 auto i = args.begin();
2144 ++i;
2145 std::string filename = *i;
2146 ++i;
2147 std::string url = *i;
2148 ++i;
2149
2150 long timeout = 0;
2151 long inactivity_timeout = 0;
2152 std::string logVar;
2153 std::string statusVar;
2154 bool showProgress = false;
2155 bool tls_verify = status.GetMakefile().IsOn("CMAKE_TLS_VERIFY");
2156 cmValue cainfo = status.GetMakefile().GetDefinition("CMAKE_TLS_CAINFO");
2157 std::string userpwd;
2158 std::string netrc_level =
2159 status.GetMakefile().GetSafeDefinition("CMAKE_NETRC");
2160 std::string netrc_file =
2161 status.GetMakefile().GetSafeDefinition("CMAKE_NETRC_FILE");
2162
2163 std::vector<std::string> curl_headers;
2164
2165 while (i != args.end()) {
2166 if (*i == "TIMEOUT") {
2167 ++i;
2168 if (i != args.end()) {
2169 timeout = atol(i->c_str());
2170 } else {
2171 status.SetError("UPLOAD missing time for TIMEOUT.");
2172 return false;
2173 }
2174 } else if (*i == "INACTIVITY_TIMEOUT") {
2175 ++i;
2176 if (i != args.end()) {
2177 inactivity_timeout = atol(i->c_str());
2178 } else {
2179 status.SetError("UPLOAD missing time for INACTIVITY_TIMEOUT.");
2180 return false;
2181 }
2182 } else if (*i == "LOG") {
2183 ++i;
2184 if (i == args.end()) {
2185 status.SetError("UPLOAD missing VAR for LOG.");
2186 return false;
2187 }
2188 logVar = *i;
2189 } else if (*i == "STATUS") {
2190 ++i;
2191 if (i == args.end()) {
2192 status.SetError("UPLOAD missing VAR for STATUS.");
2193 return false;
2194 }
2195 statusVar = *i;
2196 } else if (*i == "SHOW_PROGRESS") {
2197 showProgress = true;
2198 } else if (*i == "TLS_VERIFY") {
2199 ++i;
2200 if (i != args.end()) {
2201 tls_verify = cmIsOn(*i);
2202 } else {
2203 status.SetError("UPLOAD missing bool value for TLS_VERIFY.");
2204 return false;
2205 }
2206 } else if (*i == "TLS_CAINFO") {
2207 ++i;
2208 if (i != args.end()) {
2209 cainfo = cmValue(*i);
2210 } else {
2211 status.SetError("UPLOAD missing file value for TLS_CAINFO.");
2212 return false;
2213 }
2214 } else if (*i == "NETRC_FILE") {
2215 ++i;
2216 if (i != args.end()) {
2217 netrc_file = *i;
2218 } else {
2219 status.SetError("UPLOAD missing file value for NETRC_FILE.");
2220 return false;
2221 }
2222 } else if (*i == "NETRC") {
2223 ++i;
2224 if (i != args.end()) {
2225 netrc_level = *i;
2226 } else {
2227 status.SetError("UPLOAD missing level value for NETRC.");
2228 return false;
2229 }
2230 } else if (*i == "USERPWD") {
2231 ++i;
2232 if (i == args.end()) {
2233 status.SetError("UPLOAD missing string for USERPWD.");
2234 return false;
2235 }
2236 userpwd = *i;
2237 } else if (*i == "HTTPHEADER") {
2238 ++i;
2239 if (i == args.end()) {
2240 status.SetError("UPLOAD missing string for HTTPHEADER.");
2241 return false;
2242 }
2243 curl_headers.push_back(*i);
2244 } else {
2245 // Do not return error for compatibility reason.
2246 std::string err = cmStrCat("Unexpected argument: ", *i);
2247 status.GetMakefile().IssueMessage(MessageType::AUTHOR_WARNING, err);
2248 }
2249
2250 ++i;
2251 }
2252
2253 // Open file for reading:
2254 //
2255 FILE* fin = cmsys::SystemTools::Fopen(filename, "rb");
2256 if (!fin) {
2257 std::string errStr =
2258 cmStrCat("UPLOAD cannot open file '", filename, "' for reading.");
2259 status.SetError(errStr);
2260 return false;
2261 }
2262
2263 unsigned long file_size = cmsys::SystemTools::FileLength(filename);
2264
2265 url = cmCurlFixFileURL(url);
2266
2267 ::CURL* curl;
2268 ::curl_global_init(CURL_GLOBAL_DEFAULT);
2269 curl = ::curl_easy_init();
2270 if (!curl) {
2271 status.SetError("UPLOAD error initializing curl.");
2272 fclose(fin);
2273 return false;
2274 }
2275
2276 cURLEasyGuard g_curl(curl);
2277
2278 // enable HTTP ERROR parsing
2279 ::CURLcode res = ::curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
2280 check_curl_result(res, "UPLOAD cannot set fail on error flag: ");
2281
2282 // enable uploading
2283 res = ::curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
2284 check_curl_result(res, "UPLOAD cannot set upload flag: ");
2285
2286 res = ::curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
2287 check_curl_result(res, "UPLOAD cannot set url: ");
2288
2289 res =
2290 ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cmWriteToMemoryCallback);
2291 check_curl_result(res, "UPLOAD cannot set write function: ");
2292
2293 res = ::curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION,
2294 cmFileCommandCurlDebugCallback);
2295 check_curl_result(res, "UPLOAD cannot set debug function: ");
2296
2297 // check to see if TLS verification is requested
2298 if (tls_verify) {
2299 res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1);
2300 check_curl_result(res, "UPLOAD cannot set TLS/SSL Verify on: ");
2301 } else {
2302 res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
2303 check_curl_result(res, "UPLOAD cannot set TLS/SSL Verify off: ");
2304 }
2305
2306 // check to see if a CAINFO file has been specified
2307 // command arg comes first
2308 std::string const& cainfo_err = cmCurlSetCAInfo(curl, cainfo);
2309 if (!cainfo_err.empty()) {
2310 status.SetError(cainfo_err);
2311 return false;
2312 }
2313
2314 cmFileCommandVectorOfChar chunkResponse;
2315 cmFileCommandVectorOfChar chunkDebug;
2316
2317 res = ::curl_easy_setopt(curl, CURLOPT_WRITEDATA, &chunkResponse);
2318 check_curl_result(res, "UPLOAD cannot set write data: ");
2319
2320 res = ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &chunkDebug);
2321 check_curl_result(res, "UPLOAD cannot set debug data: ");
2322
2323 res = ::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
2324 check_curl_result(res, "UPLOAD cannot set follow-redirect option: ");
2325
2326 if (!logVar.empty()) {
2327 res = ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
2328 check_curl_result(res, "UPLOAD cannot set verbose: ");
2329 }
2330
2331 if (timeout > 0) {
2332 res = ::curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
2333 check_curl_result(res, "UPLOAD cannot set timeout: ");
2334 }
2335
2336 if (inactivity_timeout > 0) {
2337 // Give up if there is no progress for a long time.
2338 ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1);
2339 ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, inactivity_timeout);
2340 }
2341
2342 // Need the progress helper's scope to last through the duration of
2343 // the curl_easy_perform call... so this object is declared at function
2344 // scope intentionally, rather than inside the "if(showProgress)"
2345 // block...
2346 //
2347 cURLProgressHelper helper(&status.GetMakefile(), "upload");
2348
2349 if (showProgress) {
2350 res = ::curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
2351 check_curl_result(res, "UPLOAD cannot set noprogress value: ");
2352
2353 res = ::curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION,
2354 cmFileUploadProgressCallback);
2355 check_curl_result(res, "UPLOAD cannot set progress function: ");
2356
2357 res = ::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA,
2358 reinterpret_cast<void*>(&helper));
2359 check_curl_result(res, "UPLOAD cannot set progress data: ");
2360 }
2361
2362 // now specify which file to upload
2363 res = ::curl_easy_setopt(curl, CURLOPT_INFILE, fin);
2364 check_curl_result(res, "UPLOAD cannot set input file: ");
2365
2366 // and give the size of the upload (optional)
2367 res =
2368 ::curl_easy_setopt(curl, CURLOPT_INFILESIZE, static_cast<long>(file_size));
2369 check_curl_result(res, "UPLOAD cannot set input file size: ");
2370
2371 if (!userpwd.empty()) {
2372 res = ::curl_easy_setopt(curl, CURLOPT_USERPWD, userpwd.c_str());
2373 check_curl_result(res, "UPLOAD cannot set user password: ");
2374 }
2375
2376 // check to see if netrc parameters have been specified
2377 // local command args takes precedence over CMAKE_NETRC*
2378 netrc_level = cmSystemTools::UpperCase(netrc_level);
2379 std::string const& netrc_option_err =
2380 cmCurlSetNETRCOption(curl, netrc_level, netrc_file);
2381 if (!netrc_option_err.empty()) {
2382 status.SetError(netrc_option_err);
2383 return false;
2384 }
2385
2386 struct curl_slist* headers = nullptr;
2387 for (std::string const& h : curl_headers) {
2388 headers = ::curl_slist_append(headers, h.c_str());
2389 }
2390 ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
2391
2392 res = ::curl_easy_perform(curl);
2393
2394 ::curl_slist_free_all(headers);
2395
2396 /* always cleanup */
2397 g_curl.release();
2398 ::curl_easy_cleanup(curl);
2399
2400 if (!statusVar.empty()) {
2401 status.GetMakefile().AddDefinition(
2402 statusVar,
2403 cmStrCat(static_cast<int>(res), ";\"", ::curl_easy_strerror(res), "\""));
2404 }
2405
2406 ::curl_global_cleanup();
2407
2408 fclose(fin);
2409 fin = nullptr;
2410
2411 if (!logVar.empty()) {
2412 std::string log;
2413
2414 if (!chunkResponse.empty()) {
2415 chunkResponse.push_back(0);
2416 log += "Response:\n";
2417 log += chunkResponse.data();
2418 log += "\n";
2419 }
2420
2421 if (!chunkDebug.empty()) {
2422 chunkDebug.push_back(0);
2423 log += "Debug:\n";
2424 log += chunkDebug.data();
2425 log += "\n";
2426 }
2427
2428 status.GetMakefile().AddDefinition(logVar, log);
2429 }
2430
2431 return true;
2432 #else
2433 status.SetError("UPLOAD not supported by bootstrap cmake.");
2434 return false;
2435 #endif
2436 }
2437
AddEvaluationFile(const std::string & inputName,const std::string & targetName,const std::string & outputExpr,const std::string & condition,bool inputIsContent,const std::string & newLineCharacter,mode_t permissions,cmExecutionStatus & status)2438 void AddEvaluationFile(const std::string& inputName,
2439 const std::string& targetName,
2440 const std::string& outputExpr,
2441 const std::string& condition, bool inputIsContent,
2442 const std::string& newLineCharacter, mode_t permissions,
2443 cmExecutionStatus& status)
2444 {
2445 cmListFileBacktrace lfbt = status.GetMakefile().GetBacktrace();
2446
2447 cmGeneratorExpression outputGe(lfbt);
2448 std::unique_ptr<cmCompiledGeneratorExpression> outputCge =
2449 outputGe.Parse(outputExpr);
2450
2451 cmGeneratorExpression conditionGe(lfbt);
2452 std::unique_ptr<cmCompiledGeneratorExpression> conditionCge =
2453 conditionGe.Parse(condition);
2454
2455 status.GetMakefile().AddEvaluationFile(
2456 inputName, targetName, std::move(outputCge), std::move(conditionCge),
2457 newLineCharacter, permissions, inputIsContent);
2458 }
2459
HandleGenerateCommand(std::vector<std::string> const & args,cmExecutionStatus & status)2460 bool HandleGenerateCommand(std::vector<std::string> const& args,
2461 cmExecutionStatus& status)
2462 {
2463 if (args.size() < 5) {
2464 status.SetError("Incorrect arguments to GENERATE subcommand.");
2465 return false;
2466 }
2467
2468 struct Arguments
2469 {
2470 std::string Output;
2471 std::string Input;
2472 std::string Content;
2473 std::string Condition;
2474 std::string Target;
2475 std::string NewLineStyle;
2476 bool NoSourcePermissions = false;
2477 bool UseSourcePermissions = false;
2478 std::vector<std::string> FilePermissions;
2479 };
2480
2481 static auto const parser =
2482 cmArgumentParser<Arguments>{}
2483 .Bind("OUTPUT"_s, &Arguments::Output)
2484 .Bind("INPUT"_s, &Arguments::Input)
2485 .Bind("CONTENT"_s, &Arguments::Content)
2486 .Bind("CONDITION"_s, &Arguments::Condition)
2487 .Bind("TARGET"_s, &Arguments::Target)
2488 .Bind("NO_SOURCE_PERMISSIONS"_s, &Arguments::NoSourcePermissions)
2489 .Bind("USE_SOURCE_PERMISSIONS"_s, &Arguments::UseSourcePermissions)
2490 .Bind("FILE_PERMISSIONS"_s, &Arguments::FilePermissions)
2491 .Bind("NEWLINE_STYLE"_s, &Arguments::NewLineStyle);
2492
2493 std::vector<std::string> unparsedArguments;
2494 std::vector<std::string> keywordsMissingValues;
2495 std::vector<std::string> parsedKeywords;
2496 Arguments const arguments =
2497 parser.Parse(cmMakeRange(args).advance(1), &unparsedArguments,
2498 &keywordsMissingValues, &parsedKeywords);
2499
2500 if (!keywordsMissingValues.empty()) {
2501 status.SetError("Incorrect arguments to GENERATE subcommand.");
2502 return false;
2503 }
2504
2505 if (!unparsedArguments.empty()) {
2506 status.SetError("Unknown argument to GENERATE subcommand.");
2507 return false;
2508 }
2509
2510 bool mandatoryOptionsSpecified = false;
2511 if (parsedKeywords.size() > 1) {
2512 const bool outputOprionSpecified = parsedKeywords[0] == "OUTPUT"_s;
2513 const bool inputOrContentSpecified =
2514 parsedKeywords[1] == "INPUT"_s || parsedKeywords[1] == "CONTENT"_s;
2515 if (outputOprionSpecified && inputOrContentSpecified) {
2516 mandatoryOptionsSpecified = true;
2517 }
2518 }
2519 if (!mandatoryOptionsSpecified) {
2520 status.SetError("Incorrect arguments to GENERATE subcommand.");
2521 return false;
2522 }
2523
2524 const bool conditionOptionSpecified =
2525 std::find(parsedKeywords.begin(), parsedKeywords.end(), "CONDITION"_s) !=
2526 parsedKeywords.end();
2527 if (conditionOptionSpecified && arguments.Condition.empty()) {
2528 status.SetError("CONDITION of sub-command GENERATE must not be empty "
2529 "if specified.");
2530 return false;
2531 }
2532
2533 const bool targetOptionSpecified =
2534 std::find(parsedKeywords.begin(), parsedKeywords.end(), "TARGET"_s) !=
2535 parsedKeywords.end();
2536 if (targetOptionSpecified && arguments.Target.empty()) {
2537 status.SetError("TARGET of sub-command GENERATE must not be empty "
2538 "if specified.");
2539 return false;
2540 }
2541
2542 const bool outputOptionSpecified =
2543 std::find(parsedKeywords.begin(), parsedKeywords.end(), "OUTPUT"_s) !=
2544 parsedKeywords.end();
2545 if (outputOptionSpecified && parsedKeywords[0] != "OUTPUT"_s) {
2546 status.SetError("Incorrect arguments to GENERATE subcommand.");
2547 return false;
2548 }
2549
2550 const bool inputIsContent = parsedKeywords[1] != "INPUT"_s;
2551 if (inputIsContent && parsedKeywords[1] != "CONTENT") {
2552 status.SetError("Unknown argument to GENERATE subcommand.");
2553 }
2554
2555 const bool newLineStyleSpecified =
2556 std::find(parsedKeywords.begin(), parsedKeywords.end(),
2557 "NEWLINE_STYLE"_s) != parsedKeywords.end();
2558 cmNewLineStyle newLineStyle;
2559 if (newLineStyleSpecified) {
2560 std::string errorMessage;
2561 if (!newLineStyle.ReadFromArguments(args, errorMessage)) {
2562 status.SetError(cmStrCat("GENERATE ", errorMessage));
2563 return false;
2564 }
2565 }
2566
2567 std::string input = arguments.Input;
2568 if (inputIsContent) {
2569 input = arguments.Content;
2570 }
2571
2572 if (arguments.NoSourcePermissions && arguments.UseSourcePermissions) {
2573 status.SetError("given both NO_SOURCE_PERMISSIONS and "
2574 "USE_SOURCE_PERMISSIONS. Only one option allowed.");
2575 return false;
2576 }
2577
2578 if (!arguments.FilePermissions.empty()) {
2579 if (arguments.NoSourcePermissions) {
2580 status.SetError("given both NO_SOURCE_PERMISSIONS and "
2581 "FILE_PERMISSIONS. Only one option allowed.");
2582 return false;
2583 }
2584 if (arguments.UseSourcePermissions) {
2585 status.SetError("given both USE_SOURCE_PERMISSIONS and "
2586 "FILE_PERMISSIONS. Only one option allowed.");
2587 return false;
2588 }
2589 }
2590
2591 if (arguments.UseSourcePermissions) {
2592 if (inputIsContent) {
2593 status.SetError("given USE_SOURCE_PERMISSIONS without a file INPUT.");
2594 return false;
2595 }
2596 }
2597
2598 mode_t permissions = 0;
2599 if (arguments.NoSourcePermissions) {
2600 permissions |= cmFSPermissions::mode_owner_read;
2601 permissions |= cmFSPermissions::mode_owner_write;
2602 permissions |= cmFSPermissions::mode_group_read;
2603 permissions |= cmFSPermissions::mode_world_read;
2604 }
2605
2606 if (!arguments.FilePermissions.empty()) {
2607 std::vector<std::string> invalidOptions;
2608 for (auto const& e : arguments.FilePermissions) {
2609 if (!cmFSPermissions::stringToModeT(e, permissions)) {
2610 invalidOptions.push_back(e);
2611 }
2612 }
2613 if (!invalidOptions.empty()) {
2614 std::ostringstream oss;
2615 oss << "given invalid permission ";
2616 for (auto i = 0u; i < invalidOptions.size(); i++) {
2617 if (i == 0u) {
2618 oss << "\"" << invalidOptions[i] << "\"";
2619 } else {
2620 oss << ",\"" << invalidOptions[i] << "\"";
2621 }
2622 }
2623 oss << ".";
2624 status.SetError(oss.str());
2625 return false;
2626 }
2627 }
2628
2629 AddEvaluationFile(input, arguments.Target, arguments.Output,
2630 arguments.Condition, inputIsContent,
2631 newLineStyle.GetCharacters(), permissions, status);
2632 return true;
2633 }
2634
HandleLockCommand(std::vector<std::string> const & args,cmExecutionStatus & status)2635 bool HandleLockCommand(std::vector<std::string> const& args,
2636 cmExecutionStatus& status)
2637 {
2638 #if !defined(CMAKE_BOOTSTRAP)
2639 // Default values
2640 bool directory = false;
2641 bool release = false;
2642 enum Guard
2643 {
2644 GUARD_FUNCTION,
2645 GUARD_FILE,
2646 GUARD_PROCESS
2647 };
2648 Guard guard = GUARD_PROCESS;
2649 std::string resultVariable;
2650 unsigned long timeout = static_cast<unsigned long>(-1);
2651
2652 // Parse arguments
2653 if (args.size() < 2) {
2654 status.GetMakefile().IssueMessage(
2655 MessageType::FATAL_ERROR,
2656 "sub-command LOCK requires at least two arguments.");
2657 return false;
2658 }
2659
2660 std::string path = args[1];
2661 for (unsigned i = 2; i < args.size(); ++i) {
2662 if (args[i] == "DIRECTORY") {
2663 directory = true;
2664 } else if (args[i] == "RELEASE") {
2665 release = true;
2666 } else if (args[i] == "GUARD") {
2667 ++i;
2668 const char* merr = "expected FUNCTION, FILE or PROCESS after GUARD";
2669 if (i >= args.size()) {
2670 status.GetMakefile().IssueMessage(MessageType::FATAL_ERROR, merr);
2671 return false;
2672 }
2673 if (args[i] == "FUNCTION") {
2674 guard = GUARD_FUNCTION;
2675 } else if (args[i] == "FILE") {
2676 guard = GUARD_FILE;
2677 } else if (args[i] == "PROCESS") {
2678 guard = GUARD_PROCESS;
2679 } else {
2680 status.GetMakefile().IssueMessage(
2681 MessageType::FATAL_ERROR,
2682 cmStrCat(merr, ", but got:\n \"", args[i], "\"."));
2683 return false;
2684 }
2685
2686 } else if (args[i] == "RESULT_VARIABLE") {
2687 ++i;
2688 if (i >= args.size()) {
2689 status.GetMakefile().IssueMessage(
2690 MessageType::FATAL_ERROR,
2691 "expected variable name after RESULT_VARIABLE");
2692 return false;
2693 }
2694 resultVariable = args[i];
2695 } else if (args[i] == "TIMEOUT") {
2696 ++i;
2697 if (i >= args.size()) {
2698 status.GetMakefile().IssueMessage(
2699 MessageType::FATAL_ERROR, "expected timeout value after TIMEOUT");
2700 return false;
2701 }
2702 long scanned;
2703 if (!cmStrToLong(args[i], &scanned) || scanned < 0) {
2704 status.GetMakefile().IssueMessage(
2705 MessageType::FATAL_ERROR,
2706 cmStrCat("TIMEOUT value \"", args[i],
2707 "\" is not an unsigned integer."));
2708 return false;
2709 }
2710 timeout = static_cast<unsigned long>(scanned);
2711 } else {
2712 status.GetMakefile().IssueMessage(
2713 MessageType::FATAL_ERROR,
2714 cmStrCat("expected DIRECTORY, RELEASE, GUARD, RESULT_VARIABLE or ",
2715 "TIMEOUT\nbut got: \"", args[i], "\"."));
2716 return false;
2717 }
2718 }
2719
2720 if (directory) {
2721 path += "/cmake.lock";
2722 }
2723
2724 // Unify path (remove '//', '/../', ...)
2725 path = cmSystemTools::CollapseFullPath(
2726 path, status.GetMakefile().GetCurrentSourceDirectory());
2727
2728 // Create file and directories if needed
2729 std::string parentDir = cmSystemTools::GetParentDirectory(path);
2730 if (!cmSystemTools::MakeDirectory(parentDir)) {
2731 status.GetMakefile().IssueMessage(
2732 MessageType::FATAL_ERROR,
2733 cmStrCat("directory\n \"", parentDir,
2734 "\"\ncreation failed (check permissions)."));
2735 cmSystemTools::SetFatalErrorOccured();
2736 return false;
2737 }
2738 FILE* file = cmsys::SystemTools::Fopen(path, "w");
2739 if (!file) {
2740 status.GetMakefile().IssueMessage(
2741 MessageType::FATAL_ERROR,
2742 cmStrCat("file\n \"", path,
2743 "\"\ncreation failed (check permissions)."));
2744 cmSystemTools::SetFatalErrorOccured();
2745 return false;
2746 }
2747 fclose(file);
2748
2749 // Actual lock/unlock
2750 cmFileLockPool& lockPool =
2751 status.GetMakefile().GetGlobalGenerator()->GetFileLockPool();
2752
2753 cmFileLockResult fileLockResult(cmFileLockResult::MakeOk());
2754 if (release) {
2755 fileLockResult = lockPool.Release(path);
2756 } else {
2757 switch (guard) {
2758 case GUARD_FUNCTION:
2759 fileLockResult = lockPool.LockFunctionScope(path, timeout);
2760 break;
2761 case GUARD_FILE:
2762 fileLockResult = lockPool.LockFileScope(path, timeout);
2763 break;
2764 case GUARD_PROCESS:
2765 fileLockResult = lockPool.LockProcessScope(path, timeout);
2766 break;
2767 default:
2768 cmSystemTools::SetFatalErrorOccured();
2769 return false;
2770 }
2771 }
2772
2773 const std::string result = fileLockResult.GetOutputMessage();
2774
2775 if (resultVariable.empty() && !fileLockResult.IsOk()) {
2776 status.GetMakefile().IssueMessage(
2777 MessageType::FATAL_ERROR,
2778 cmStrCat("error locking file\n \"", path, "\"\n", result, "."));
2779 cmSystemTools::SetFatalErrorOccured();
2780 return false;
2781 }
2782
2783 if (!resultVariable.empty()) {
2784 status.GetMakefile().AddDefinition(resultVariable, result);
2785 }
2786
2787 return true;
2788 #else
2789 static_cast<void>(args);
2790 status.SetError("sub-command LOCK not implemented in bootstrap cmake");
2791 return false;
2792 #endif
2793 }
2794
HandleTimestampCommand(std::vector<std::string> const & args,cmExecutionStatus & status)2795 bool HandleTimestampCommand(std::vector<std::string> const& args,
2796 cmExecutionStatus& status)
2797 {
2798 if (args.size() < 3) {
2799 status.SetError("sub-command TIMESTAMP requires at least two arguments.");
2800 return false;
2801 }
2802 if (args.size() > 5) {
2803 status.SetError("sub-command TIMESTAMP takes at most four arguments.");
2804 return false;
2805 }
2806
2807 unsigned int argsIndex = 1;
2808
2809 const std::string& filename = args[argsIndex++];
2810
2811 const std::string& outputVariable = args[argsIndex++];
2812
2813 std::string formatString;
2814 if (args.size() > argsIndex && args[argsIndex] != "UTC") {
2815 formatString = args[argsIndex++];
2816 }
2817
2818 bool utcFlag = false;
2819 if (args.size() > argsIndex) {
2820 if (args[argsIndex] == "UTC") {
2821 utcFlag = true;
2822 } else {
2823 std::string e = " TIMESTAMP sub-command does not recognize option " +
2824 args[argsIndex] + ".";
2825 status.SetError(e);
2826 return false;
2827 }
2828 }
2829
2830 cmTimestamp timestamp;
2831 std::string result =
2832 timestamp.FileModificationTime(filename.c_str(), formatString, utcFlag);
2833 status.GetMakefile().AddDefinition(outputVariable, result);
2834
2835 return true;
2836 }
2837
HandleSizeCommand(std::vector<std::string> const & args,cmExecutionStatus & status)2838 bool HandleSizeCommand(std::vector<std::string> const& args,
2839 cmExecutionStatus& status)
2840 {
2841 if (args.size() != 3) {
2842 status.SetError(
2843 cmStrCat(args[0], " requires a file name and output variable"));
2844 return false;
2845 }
2846
2847 unsigned int argsIndex = 1;
2848
2849 const std::string& filename = args[argsIndex++];
2850
2851 const std::string& outputVariable = args[argsIndex++];
2852
2853 if (!cmSystemTools::FileExists(filename, true)) {
2854 status.SetError(
2855 cmStrCat("SIZE requested of path that is not readable:\n ", filename));
2856 return false;
2857 }
2858
2859 status.GetMakefile().AddDefinition(
2860 outputVariable, std::to_string(cmSystemTools::FileLength(filename)));
2861
2862 return true;
2863 }
2864
HandleReadSymlinkCommand(std::vector<std::string> const & args,cmExecutionStatus & status)2865 bool HandleReadSymlinkCommand(std::vector<std::string> const& args,
2866 cmExecutionStatus& status)
2867 {
2868 if (args.size() != 3) {
2869 status.SetError(
2870 cmStrCat(args[0], " requires a file name and output variable"));
2871 return false;
2872 }
2873
2874 const std::string& filename = args[1];
2875 const std::string& outputVariable = args[2];
2876
2877 std::string result;
2878 if (!cmSystemTools::ReadSymlink(filename, result)) {
2879 status.SetError(cmStrCat(
2880 "READ_SYMLINK requested of path that is not a symlink:\n ", filename));
2881 return false;
2882 }
2883
2884 status.GetMakefile().AddDefinition(outputVariable, result);
2885
2886 return true;
2887 }
2888
HandleCreateLinkCommand(std::vector<std::string> const & args,cmExecutionStatus & status)2889 bool HandleCreateLinkCommand(std::vector<std::string> const& args,
2890 cmExecutionStatus& status)
2891 {
2892 if (args.size() < 3) {
2893 status.SetError("CREATE_LINK must be called with at least two additional "
2894 "arguments");
2895 return false;
2896 }
2897
2898 std::string const& fileName = args[1];
2899 std::string const& newFileName = args[2];
2900
2901 struct Arguments
2902 {
2903 std::string Result;
2904 bool CopyOnError = false;
2905 bool Symbolic = false;
2906 };
2907
2908 static auto const parser =
2909 cmArgumentParser<Arguments>{}
2910 .Bind("RESULT"_s, &Arguments::Result)
2911 .Bind("COPY_ON_ERROR"_s, &Arguments::CopyOnError)
2912 .Bind("SYMBOLIC"_s, &Arguments::Symbolic);
2913
2914 std::vector<std::string> unconsumedArgs;
2915 Arguments const arguments =
2916 parser.Parse(cmMakeRange(args).advance(3), &unconsumedArgs);
2917
2918 if (!unconsumedArgs.empty()) {
2919 status.SetError("unknown argument: \"" + unconsumedArgs.front() + '\"');
2920 return false;
2921 }
2922
2923 // The system error message generated in the operation.
2924 std::string result;
2925
2926 // Check if the paths are distinct.
2927 if (fileName == newFileName) {
2928 result = "CREATE_LINK cannot use same file and newfile";
2929 if (!arguments.Result.empty()) {
2930 status.GetMakefile().AddDefinition(arguments.Result, result);
2931 return true;
2932 }
2933 status.SetError(result);
2934 return false;
2935 }
2936
2937 // Hard link requires original file to exist.
2938 if (!arguments.Symbolic && !cmSystemTools::FileExists(fileName)) {
2939 result = "Cannot hard link \'" + fileName + "\' as it does not exist.";
2940 if (!arguments.Result.empty()) {
2941 status.GetMakefile().AddDefinition(arguments.Result, result);
2942 return true;
2943 }
2944 status.SetError(result);
2945 return false;
2946 }
2947
2948 // Check if the new file already exists and remove it.
2949 if ((cmSystemTools::FileExists(newFileName) ||
2950 cmSystemTools::FileIsSymlink(newFileName)) &&
2951 !cmSystemTools::RemoveFile(newFileName)) {
2952 std::ostringstream e;
2953 e << "Failed to create link '" << newFileName
2954 << "' because existing path cannot be removed: "
2955 << cmSystemTools::GetLastSystemError() << "\n";
2956
2957 if (!arguments.Result.empty()) {
2958 status.GetMakefile().AddDefinition(arguments.Result, e.str());
2959 return true;
2960 }
2961 status.SetError(e.str());
2962 return false;
2963 }
2964
2965 // Whether the operation completed successfully.
2966 bool completed = false;
2967
2968 // Check if the command requires a symbolic link.
2969 if (arguments.Symbolic) {
2970 completed = static_cast<bool>(
2971 cmSystemTools::CreateSymlink(fileName, newFileName, &result));
2972 } else {
2973 completed = static_cast<bool>(
2974 cmSystemTools::CreateLink(fileName, newFileName, &result));
2975 }
2976
2977 // Check if copy-on-error is enabled in the arguments.
2978 if (!completed && arguments.CopyOnError) {
2979 cmsys::Status copied =
2980 cmsys::SystemTools::CopyFileAlways(fileName, newFileName);
2981 if (copied) {
2982 completed = true;
2983 } else {
2984 result = "Copy failed: " + copied.GetString();
2985 }
2986 }
2987
2988 // Check if the operation was successful.
2989 if (completed) {
2990 result = "0";
2991 } else if (arguments.Result.empty()) {
2992 // The operation failed and the result is not reported in a variable.
2993 status.SetError(result);
2994 return false;
2995 }
2996
2997 if (!arguments.Result.empty()) {
2998 status.GetMakefile().AddDefinition(arguments.Result, result);
2999 }
3000
3001 return true;
3002 }
3003
HandleGetRuntimeDependenciesCommand(std::vector<std::string> const & args,cmExecutionStatus & status)3004 bool HandleGetRuntimeDependenciesCommand(std::vector<std::string> const& args,
3005 cmExecutionStatus& status)
3006 {
3007 std::string platform =
3008 status.GetMakefile().GetSafeDefinition("CMAKE_HOST_SYSTEM_NAME");
3009 if (!cmRuntimeDependencyArchive::PlatformSupportsRuntimeDependencies(
3010 platform)) {
3011 status.SetError(
3012 cmStrCat("GET_RUNTIME_DEPENDENCIES is not supported on system \"",
3013 platform, "\""));
3014 cmSystemTools::SetFatalErrorOccured();
3015 return false;
3016 }
3017
3018 if (status.GetMakefile().GetState()->GetMode() == cmState::Project) {
3019 status.GetMakefile().IssueMessage(
3020 MessageType::AUTHOR_WARNING,
3021 "You have used file(GET_RUNTIME_DEPENDENCIES)"
3022 " in project mode. This is probably not what "
3023 "you intended to do. Instead, please consider"
3024 " using it in an install(CODE) or "
3025 "install(SCRIPT) command. For example:"
3026 "\n install(CODE [["
3027 "\n file(GET_RUNTIME_DEPENDENCIES"
3028 "\n # ..."
3029 "\n )"
3030 "\n ]])");
3031 }
3032
3033 struct Arguments
3034 {
3035 std::string ResolvedDependenciesVar;
3036 std::string UnresolvedDependenciesVar;
3037 std::string ConflictingDependenciesPrefix;
3038 std::string RPathPrefix;
3039 std::string BundleExecutable;
3040 std::vector<std::string> Executables;
3041 std::vector<std::string> Libraries;
3042 std::vector<std::string> Directories;
3043 std::vector<std::string> Modules;
3044 std::vector<std::string> PreIncludeRegexes;
3045 std::vector<std::string> PreExcludeRegexes;
3046 std::vector<std::string> PostIncludeRegexes;
3047 std::vector<std::string> PostExcludeRegexes;
3048 std::vector<std::string> PostIncludeFiles;
3049 std::vector<std::string> PostExcludeFiles;
3050 std::vector<std::string> PostExcludeFilesStrict;
3051 };
3052
3053 static auto const parser =
3054 cmArgumentParser<Arguments>{}
3055 .Bind("RESOLVED_DEPENDENCIES_VAR"_s, &Arguments::ResolvedDependenciesVar)
3056 .Bind("UNRESOLVED_DEPENDENCIES_VAR"_s,
3057 &Arguments::UnresolvedDependenciesVar)
3058 .Bind("CONFLICTING_DEPENDENCIES_PREFIX"_s,
3059 &Arguments::ConflictingDependenciesPrefix)
3060 .Bind("RPATH_PREFIX"_s, &Arguments::RPathPrefix)
3061 .Bind("BUNDLE_EXECUTABLE"_s, &Arguments::BundleExecutable)
3062 .Bind("EXECUTABLES"_s, &Arguments::Executables)
3063 .Bind("LIBRARIES"_s, &Arguments::Libraries)
3064 .Bind("MODULES"_s, &Arguments::Modules)
3065 .Bind("DIRECTORIES"_s, &Arguments::Directories)
3066 .Bind("PRE_INCLUDE_REGEXES"_s, &Arguments::PreIncludeRegexes)
3067 .Bind("PRE_EXCLUDE_REGEXES"_s, &Arguments::PreExcludeRegexes)
3068 .Bind("POST_INCLUDE_REGEXES"_s, &Arguments::PostIncludeRegexes)
3069 .Bind("POST_EXCLUDE_REGEXES"_s, &Arguments::PostExcludeRegexes)
3070 .Bind("POST_INCLUDE_FILES"_s, &Arguments::PostIncludeFiles)
3071 .Bind("POST_EXCLUDE_FILES"_s, &Arguments::PostExcludeFiles)
3072 .Bind("POST_EXCLUDE_FILES_STRICT"_s, &Arguments::PostExcludeFilesStrict);
3073
3074 std::vector<std::string> unrecognizedArguments;
3075 std::vector<std::string> keywordsMissingValues;
3076 auto parsedArgs =
3077 parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments,
3078 &keywordsMissingValues);
3079 auto argIt = unrecognizedArguments.begin();
3080 if (argIt != unrecognizedArguments.end()) {
3081 status.SetError(cmStrCat("Unrecognized argument: \"", *argIt, "\""));
3082 cmSystemTools::SetFatalErrorOccured();
3083 return false;
3084 }
3085
3086 const std::vector<std::string> LIST_ARGS = {
3087 "DIRECTORIES",
3088 "EXECUTABLES",
3089 "LIBRARIES",
3090 "MODULES",
3091 "POST_EXCLUDE_FILES",
3092 "POST_EXCLUDE_FILES_STRICT",
3093 "POST_EXCLUDE_REGEXES",
3094 "POST_INCLUDE_FILES",
3095 "POST_INCLUDE_REGEXES",
3096 "PRE_EXCLUDE_REGEXES",
3097 "PRE_INCLUDE_REGEXES",
3098 };
3099 auto kwbegin = keywordsMissingValues.cbegin();
3100 auto kwend = cmRemoveMatching(keywordsMissingValues, LIST_ARGS);
3101 if (kwend != kwbegin) {
3102 status.SetError(cmStrCat("Keywords missing values:\n ",
3103 cmJoin(cmMakeRange(kwbegin, kwend), "\n ")));
3104 cmSystemTools::SetFatalErrorOccured();
3105 return false;
3106 }
3107
3108 cmRuntimeDependencyArchive archive(
3109 status, parsedArgs.Directories, parsedArgs.BundleExecutable,
3110 parsedArgs.PreIncludeRegexes, parsedArgs.PreExcludeRegexes,
3111 parsedArgs.PostIncludeRegexes, parsedArgs.PostExcludeRegexes,
3112 std::move(parsedArgs.PostIncludeFiles),
3113 std::move(parsedArgs.PostExcludeFiles),
3114 std::move(parsedArgs.PostExcludeFilesStrict));
3115 if (!archive.Prepare()) {
3116 cmSystemTools::SetFatalErrorOccured();
3117 return false;
3118 }
3119
3120 if (!archive.GetRuntimeDependencies(
3121 parsedArgs.Executables, parsedArgs.Libraries, parsedArgs.Modules)) {
3122 cmSystemTools::SetFatalErrorOccured();
3123 return false;
3124 }
3125
3126 std::vector<std::string> deps;
3127 std::vector<std::string> unresolvedDeps;
3128 std::vector<std::string> conflictingDeps;
3129 for (auto const& val : archive.GetResolvedPaths()) {
3130 bool unique = true;
3131 auto it = val.second.begin();
3132 assert(it != val.second.end());
3133 auto const& firstPath = *it;
3134 while (++it != val.second.end()) {
3135 if (!cmSystemTools::SameFile(firstPath, *it)) {
3136 unique = false;
3137 break;
3138 }
3139 }
3140
3141 if (unique) {
3142 deps.push_back(firstPath);
3143 if (!parsedArgs.RPathPrefix.empty()) {
3144 status.GetMakefile().AddDefinition(
3145 parsedArgs.RPathPrefix + "_" + firstPath,
3146 cmJoin(archive.GetRPaths().at(firstPath), ";"));
3147 }
3148 } else if (!parsedArgs.ConflictingDependenciesPrefix.empty()) {
3149 conflictingDeps.push_back(val.first);
3150 std::vector<std::string> paths;
3151 paths.insert(paths.begin(), val.second.begin(), val.second.end());
3152 std::string varName =
3153 parsedArgs.ConflictingDependenciesPrefix + "_" + val.first;
3154 std::string pathsStr = cmJoin(paths, ";");
3155 status.GetMakefile().AddDefinition(varName, pathsStr);
3156 } else {
3157 std::ostringstream e;
3158 e << "Multiple conflicting paths found for " << val.first << ":";
3159 for (auto const& path : val.second) {
3160 e << "\n " << path;
3161 }
3162 status.SetError(e.str());
3163 cmSystemTools::SetFatalErrorOccured();
3164 return false;
3165 }
3166 }
3167 if (!archive.GetUnresolvedPaths().empty()) {
3168 if (!parsedArgs.UnresolvedDependenciesVar.empty()) {
3169 unresolvedDeps.insert(unresolvedDeps.begin(),
3170 archive.GetUnresolvedPaths().begin(),
3171 archive.GetUnresolvedPaths().end());
3172 } else {
3173 std::ostringstream e;
3174 e << "Could not resolve runtime dependencies:";
3175 for (auto const& path : archive.GetUnresolvedPaths()) {
3176 e << "\n " << path;
3177 }
3178 status.SetError(e.str());
3179 cmSystemTools::SetFatalErrorOccured();
3180 return false;
3181 }
3182 }
3183
3184 if (!parsedArgs.ResolvedDependenciesVar.empty()) {
3185 std::string val = cmJoin(deps, ";");
3186 status.GetMakefile().AddDefinition(parsedArgs.ResolvedDependenciesVar,
3187 val);
3188 }
3189 if (!parsedArgs.UnresolvedDependenciesVar.empty()) {
3190 std::string val = cmJoin(unresolvedDeps, ";");
3191 status.GetMakefile().AddDefinition(parsedArgs.UnresolvedDependenciesVar,
3192 val);
3193 }
3194 if (!parsedArgs.ConflictingDependenciesPrefix.empty()) {
3195 std::string val = cmJoin(conflictingDeps, ";");
3196 status.GetMakefile().AddDefinition(
3197 parsedArgs.ConflictingDependenciesPrefix + "_FILENAMES", val);
3198 }
3199 return true;
3200 }
3201
HandleConfigureCommand(std::vector<std::string> const & args,cmExecutionStatus & status)3202 bool HandleConfigureCommand(std::vector<std::string> const& args,
3203 cmExecutionStatus& status)
3204 {
3205 struct Arguments
3206 {
3207 std::string Output;
3208 std::string Content;
3209 bool EscapeQuotes = false;
3210 bool AtOnly = false;
3211 std::string NewlineStyle;
3212 };
3213
3214 static auto const parser =
3215 cmArgumentParser<Arguments>{}
3216 .Bind("OUTPUT"_s, &Arguments::Output)
3217 .Bind("CONTENT"_s, &Arguments::Content)
3218 .Bind("ESCAPE_QUOTES"_s, &Arguments::EscapeQuotes)
3219 .Bind("@ONLY"_s, &Arguments::AtOnly)
3220 .Bind("NEWLINE_STYLE"_s, &Arguments::NewlineStyle);
3221
3222 std::vector<std::string> unrecognizedArguments;
3223 std::vector<std::string> keywordsMissingArguments;
3224 std::vector<std::string> parsedKeywords;
3225 auto parsedArgs =
3226 parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments,
3227 &keywordsMissingArguments, &parsedKeywords);
3228
3229 auto argIt = unrecognizedArguments.begin();
3230 if (argIt != unrecognizedArguments.end()) {
3231 status.SetError(
3232 cmStrCat("CONFIGURE Unrecognized argument: \"", *argIt, "\""));
3233 cmSystemTools::SetFatalErrorOccured();
3234 return false;
3235 }
3236
3237 std::vector<std::string> mandatoryOptions{ "OUTPUT", "CONTENT" };
3238 for (auto const& e : mandatoryOptions) {
3239 const bool optionHasNoValue =
3240 std::find(keywordsMissingArguments.begin(),
3241 keywordsMissingArguments.end(),
3242 e) != keywordsMissingArguments.end();
3243 if (optionHasNoValue) {
3244 status.SetError(cmStrCat("CONFIGURE ", e, " option needs a value."));
3245 cmSystemTools::SetFatalErrorOccured();
3246 return false;
3247 }
3248 }
3249
3250 for (auto const& e : mandatoryOptions) {
3251 const bool optionGiven =
3252 std::find(parsedKeywords.begin(), parsedKeywords.end(), e) !=
3253 parsedKeywords.end();
3254 if (!optionGiven) {
3255 status.SetError(cmStrCat("CONFIGURE ", e, " option is mandatory."));
3256 cmSystemTools::SetFatalErrorOccured();
3257 return false;
3258 }
3259 }
3260
3261 std::string errorMessage;
3262 cmNewLineStyle newLineStyle;
3263 if (!newLineStyle.ReadFromArguments(args, errorMessage)) {
3264 status.SetError(cmStrCat("CONFIGURE ", errorMessage));
3265 return false;
3266 }
3267
3268 // Check for generator expressions
3269 std::string outputFile = cmSystemTools::CollapseFullPath(
3270 parsedArgs.Output, status.GetMakefile().GetCurrentBinaryDirectory());
3271
3272 std::string::size_type pos = outputFile.find_first_of("<>");
3273 if (pos != std::string::npos) {
3274 status.SetError(cmStrCat("CONFIGURE called with OUTPUT containing a \"",
3275 outputFile[pos],
3276 "\". This character is not allowed."));
3277 return false;
3278 }
3279
3280 cmMakefile& makeFile = status.GetMakefile();
3281 if (!makeFile.CanIWriteThisFile(outputFile)) {
3282 cmSystemTools::Error("Attempt to write file: " + outputFile +
3283 " into a source directory.");
3284 return false;
3285 }
3286
3287 cmSystemTools::ConvertToUnixSlashes(outputFile);
3288
3289 // Re-generate if non-temporary outputs are missing.
3290 // when we finalize the configuration we will remove all
3291 // output files that now don't exist.
3292 makeFile.AddCMakeOutputFile(outputFile);
3293
3294 // Create output directory
3295 const std::string::size_type slashPos = outputFile.rfind('/');
3296 if (slashPos != std::string::npos) {
3297 const std::string path = outputFile.substr(0, slashPos);
3298 cmSystemTools::MakeDirectory(path);
3299 }
3300
3301 std::string newLineCharacters = "\n";
3302 bool open_with_binary_flag = false;
3303 if (newLineStyle.IsValid()) {
3304 newLineCharacters = newLineStyle.GetCharacters();
3305 open_with_binary_flag = true;
3306 }
3307
3308 cmGeneratedFileStream fout;
3309 fout.Open(outputFile, false, open_with_binary_flag);
3310 if (!fout) {
3311 cmSystemTools::Error("Could not open file for write in copy operation " +
3312 outputFile);
3313 cmSystemTools::ReportLastSystemError("");
3314 return false;
3315 }
3316 fout.SetCopyIfDifferent(true);
3317
3318 // copy input to output and expand variables from input at the same time
3319 std::stringstream sin(parsedArgs.Content, std::ios::in);
3320 std::string inLine;
3321 std::string outLine;
3322 bool hasNewLine = false;
3323 while (cmSystemTools::GetLineFromStream(sin, inLine, &hasNewLine)) {
3324 outLine.clear();
3325 makeFile.ConfigureString(inLine, outLine, parsedArgs.AtOnly,
3326 parsedArgs.EscapeQuotes);
3327 fout << outLine;
3328 if (hasNewLine || newLineStyle.IsValid()) {
3329 fout << newLineCharacters;
3330 }
3331 }
3332
3333 // close file before attempting to copy
3334 fout.close();
3335
3336 return true;
3337 }
3338
HandleArchiveCreateCommand(std::vector<std::string> const & args,cmExecutionStatus & status)3339 bool HandleArchiveCreateCommand(std::vector<std::string> const& args,
3340 cmExecutionStatus& status)
3341 {
3342 struct Arguments
3343 {
3344 std::string Output;
3345 std::string Format;
3346 std::string Compression;
3347 std::string CompressionLevel;
3348 std::string MTime;
3349 bool Verbose = false;
3350 std::vector<std::string> Paths;
3351 };
3352
3353 static auto const parser =
3354 cmArgumentParser<Arguments>{}
3355 .Bind("OUTPUT"_s, &Arguments::Output)
3356 .Bind("FORMAT"_s, &Arguments::Format)
3357 .Bind("COMPRESSION"_s, &Arguments::Compression)
3358 .Bind("COMPRESSION_LEVEL"_s, &Arguments::CompressionLevel)
3359 .Bind("MTIME"_s, &Arguments::MTime)
3360 .Bind("VERBOSE"_s, &Arguments::Verbose)
3361 .Bind("PATHS"_s, &Arguments::Paths);
3362
3363 std::vector<std::string> unrecognizedArguments;
3364 std::vector<std::string> keywordsMissingValues;
3365 auto parsedArgs =
3366 parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments,
3367 &keywordsMissingValues);
3368 auto argIt = unrecognizedArguments.begin();
3369 if (argIt != unrecognizedArguments.end()) {
3370 status.SetError(cmStrCat("Unrecognized argument: \"", *argIt, "\""));
3371 cmSystemTools::SetFatalErrorOccured();
3372 return false;
3373 }
3374
3375 const std::vector<std::string> LIST_ARGS = {
3376 "OUTPUT", "FORMAT", "COMPRESSION", "COMPRESSION_LEVEL", "MTIME", "PATHS"
3377 };
3378 auto kwbegin = keywordsMissingValues.cbegin();
3379 auto kwend = cmRemoveMatching(keywordsMissingValues, LIST_ARGS);
3380 if (kwend != kwbegin) {
3381 status.SetError(cmStrCat("Keywords missing values:\n ",
3382 cmJoin(cmMakeRange(kwbegin, kwend), "\n ")));
3383 cmSystemTools::SetFatalErrorOccured();
3384 return false;
3385 }
3386
3387 const char* knownFormats[] = {
3388 "7zip", "gnutar", "pax", "paxr", "raw", "zip"
3389 };
3390
3391 if (!parsedArgs.Format.empty() &&
3392 !cm::contains(knownFormats, parsedArgs.Format)) {
3393 status.SetError(
3394 cmStrCat("archive format ", parsedArgs.Format, " not supported"));
3395 cmSystemTools::SetFatalErrorOccured();
3396 return false;
3397 }
3398
3399 const char* zipFileFormats[] = { "7zip", "zip" };
3400 if (!parsedArgs.Compression.empty() &&
3401 cm::contains(zipFileFormats, parsedArgs.Format)) {
3402 status.SetError(cmStrCat("archive format ", parsedArgs.Format,
3403 " does not support COMPRESSION arguments"));
3404 cmSystemTools::SetFatalErrorOccured();
3405 return false;
3406 }
3407
3408 static std::map<std::string, cmSystemTools::cmTarCompression>
3409 compressionTypeMap = { { "None", cmSystemTools::TarCompressNone },
3410 { "BZip2", cmSystemTools::TarCompressBZip2 },
3411 { "GZip", cmSystemTools::TarCompressGZip },
3412 { "XZ", cmSystemTools::TarCompressXZ },
3413 { "Zstd", cmSystemTools::TarCompressZstd } };
3414
3415 cmSystemTools::cmTarCompression compress = cmSystemTools::TarCompressNone;
3416 auto typeIt = compressionTypeMap.find(parsedArgs.Compression);
3417 if (typeIt != compressionTypeMap.end()) {
3418 compress = typeIt->second;
3419 } else if (!parsedArgs.Compression.empty()) {
3420 status.SetError(cmStrCat("compression type ", parsedArgs.Compression,
3421 " is not supported"));
3422 cmSystemTools::SetFatalErrorOccured();
3423 return false;
3424 }
3425
3426 int compressionLevel = 0;
3427 if (!parsedArgs.CompressionLevel.empty()) {
3428 if (parsedArgs.CompressionLevel.size() != 1 &&
3429 !std::isdigit(parsedArgs.CompressionLevel[0])) {
3430 status.SetError(cmStrCat("compression level ",
3431 parsedArgs.CompressionLevel,
3432 " should be in range 0 to 9"));
3433 cmSystemTools::SetFatalErrorOccured();
3434 return false;
3435 }
3436 compressionLevel = std::stoi(parsedArgs.CompressionLevel);
3437 if (compressionLevel < 0 || compressionLevel > 9) {
3438 status.SetError(cmStrCat("compression level ",
3439 parsedArgs.CompressionLevel,
3440 " should be in range 0 to 9"));
3441 cmSystemTools::SetFatalErrorOccured();
3442 return false;
3443 }
3444 if (compress == cmSystemTools::TarCompressNone) {
3445 status.SetError(cmStrCat("compression level is not supported for "
3446 "compression \"None\"",
3447 parsedArgs.Compression));
3448 cmSystemTools::SetFatalErrorOccured();
3449 return false;
3450 }
3451 }
3452
3453 if (parsedArgs.Paths.empty()) {
3454 status.SetError("ARCHIVE_CREATE requires a non-empty list of PATHS");
3455 cmSystemTools::SetFatalErrorOccured();
3456 return false;
3457 }
3458
3459 if (!cmSystemTools::CreateTar(parsedArgs.Output, parsedArgs.Paths, compress,
3460 parsedArgs.Verbose, parsedArgs.MTime,
3461 parsedArgs.Format, compressionLevel)) {
3462 status.SetError(cmStrCat("failed to compress: ", parsedArgs.Output));
3463 cmSystemTools::SetFatalErrorOccured();
3464 return false;
3465 }
3466
3467 return true;
3468 }
3469
HandleArchiveExtractCommand(std::vector<std::string> const & args,cmExecutionStatus & status)3470 bool HandleArchiveExtractCommand(std::vector<std::string> const& args,
3471 cmExecutionStatus& status)
3472 {
3473 struct Arguments
3474 {
3475 std::string Input;
3476 bool Verbose = false;
3477 bool ListOnly = false;
3478 std::string Destination;
3479 std::vector<std::string> Patterns;
3480 };
3481
3482 static auto const parser = cmArgumentParser<Arguments>{}
3483 .Bind("INPUT"_s, &Arguments::Input)
3484 .Bind("VERBOSE"_s, &Arguments::Verbose)
3485 .Bind("LIST_ONLY"_s, &Arguments::ListOnly)
3486 .Bind("DESTINATION"_s, &Arguments::Destination)
3487 .Bind("PATTERNS"_s, &Arguments::Patterns);
3488
3489 std::vector<std::string> unrecognizedArguments;
3490 std::vector<std::string> keywordsMissingValues;
3491 auto parsedArgs =
3492 parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments,
3493 &keywordsMissingValues);
3494 auto argIt = unrecognizedArguments.begin();
3495 if (argIt != unrecognizedArguments.end()) {
3496 status.SetError(cmStrCat("Unrecognized argument: \"", *argIt, "\""));
3497 cmSystemTools::SetFatalErrorOccured();
3498 return false;
3499 }
3500
3501 const std::vector<std::string> LIST_ARGS = { "INPUT", "DESTINATION",
3502 "PATTERNS" };
3503 auto kwbegin = keywordsMissingValues.cbegin();
3504 auto kwend = cmRemoveMatching(keywordsMissingValues, LIST_ARGS);
3505 if (kwend != kwbegin) {
3506 status.SetError(cmStrCat("Keywords missing values:\n ",
3507 cmJoin(cmMakeRange(kwbegin, kwend), "\n ")));
3508 cmSystemTools::SetFatalErrorOccured();
3509 return false;
3510 }
3511
3512 std::string inFile = parsedArgs.Input;
3513
3514 if (parsedArgs.ListOnly) {
3515 if (!cmSystemTools::ListTar(inFile, parsedArgs.Patterns,
3516 parsedArgs.Verbose)) {
3517 status.SetError(cmStrCat("failed to list: ", inFile));
3518 cmSystemTools::SetFatalErrorOccured();
3519 return false;
3520 }
3521 } else {
3522 std::string destDir = status.GetMakefile().GetCurrentBinaryDirectory();
3523 if (!parsedArgs.Destination.empty()) {
3524 if (cmSystemTools::FileIsFullPath(parsedArgs.Destination)) {
3525 destDir = parsedArgs.Destination;
3526 } else {
3527 destDir = cmStrCat(destDir, "/", parsedArgs.Destination);
3528 }
3529
3530 if (!cmSystemTools::MakeDirectory(destDir)) {
3531 status.SetError(cmStrCat("failed to create directory: ", destDir));
3532 cmSystemTools::SetFatalErrorOccured();
3533 return false;
3534 }
3535
3536 if (!cmSystemTools::FileIsFullPath(inFile)) {
3537 inFile =
3538 cmStrCat(cmSystemTools::GetCurrentWorkingDirectory(), "/", inFile);
3539 }
3540 }
3541
3542 cmWorkingDirectory workdir(destDir);
3543 if (workdir.Failed()) {
3544 status.SetError(
3545 cmStrCat("failed to change working directory to: ", destDir));
3546 cmSystemTools::SetFatalErrorOccured();
3547 return false;
3548 }
3549
3550 if (!cmSystemTools::ExtractTar(inFile, parsedArgs.Patterns,
3551 parsedArgs.Verbose)) {
3552 status.SetError(cmStrCat("failed to extract: ", inFile));
3553 cmSystemTools::SetFatalErrorOccured();
3554 return false;
3555 }
3556 }
3557
3558 return true;
3559 }
3560
ValidateAndConvertPermissions(const std::vector<std::string> & permissions,mode_t & perms,cmExecutionStatus & status)3561 bool ValidateAndConvertPermissions(const std::vector<std::string>& permissions,
3562 mode_t& perms, cmExecutionStatus& status)
3563 {
3564 for (const auto& i : permissions) {
3565 if (!cmFSPermissions::stringToModeT(i, perms)) {
3566 status.SetError(i + " is an invalid permission specifier");
3567 cmSystemTools::SetFatalErrorOccured();
3568 return false;
3569 }
3570 }
3571 return true;
3572 }
3573
SetPermissions(const std::string & filename,const mode_t & perms,cmExecutionStatus & status)3574 bool SetPermissions(const std::string& filename, const mode_t& perms,
3575 cmExecutionStatus& status)
3576 {
3577 if (!cmSystemTools::SetPermissions(filename, perms)) {
3578 status.SetError("Failed to set permissions for " + filename);
3579 cmSystemTools::SetFatalErrorOccured();
3580 return false;
3581 }
3582 return true;
3583 }
3584
HandleChmodCommandImpl(std::vector<std::string> const & args,bool recurse,cmExecutionStatus & status)3585 bool HandleChmodCommandImpl(std::vector<std::string> const& args, bool recurse,
3586 cmExecutionStatus& status)
3587 {
3588 mode_t perms = 0;
3589 mode_t fperms = 0;
3590 mode_t dperms = 0;
3591 cmsys::Glob globber;
3592
3593 globber.SetRecurse(recurse);
3594 globber.SetRecurseListDirs(recurse);
3595
3596 struct Arguments
3597 {
3598 std::vector<std::string> Permissions;
3599 std::vector<std::string> FilePermissions;
3600 std::vector<std::string> DirectoryPermissions;
3601 };
3602
3603 static auto const parser =
3604 cmArgumentParser<Arguments>{}
3605 .Bind("PERMISSIONS"_s, &Arguments::Permissions)
3606 .Bind("FILE_PERMISSIONS"_s, &Arguments::FilePermissions)
3607 .Bind("DIRECTORY_PERMISSIONS"_s, &Arguments::DirectoryPermissions);
3608
3609 std::vector<std::string> pathEntries;
3610 std::vector<std::string> keywordsMissingValues;
3611 Arguments parsedArgs = parser.Parse(cmMakeRange(args).advance(1),
3612 &pathEntries, &keywordsMissingValues);
3613
3614 // check validity of arguments
3615 if (parsedArgs.Permissions.empty() && parsedArgs.FilePermissions.empty() &&
3616 parsedArgs.DirectoryPermissions.empty()) // no permissions given
3617 {
3618 status.SetError("No permissions given");
3619 cmSystemTools::SetFatalErrorOccured();
3620 return false;
3621 }
3622
3623 if (!parsedArgs.Permissions.empty() && !parsedArgs.FilePermissions.empty() &&
3624 !parsedArgs.DirectoryPermissions.empty()) // all keywords are used
3625 {
3626 status.SetError("Remove either PERMISSIONS or FILE_PERMISSIONS or "
3627 "DIRECTORY_PERMISSIONS from the invocation");
3628 cmSystemTools::SetFatalErrorOccured();
3629 return false;
3630 }
3631
3632 if (!keywordsMissingValues.empty()) {
3633 for (const auto& i : keywordsMissingValues) {
3634 status.SetError(i + " is not given any arguments");
3635 cmSystemTools::SetFatalErrorOccured();
3636 }
3637 return false;
3638 }
3639
3640 // validate permissions
3641 bool validatePermissions =
3642 ValidateAndConvertPermissions(parsedArgs.Permissions, perms, status) &&
3643 ValidateAndConvertPermissions(parsedArgs.FilePermissions, fperms,
3644 status) &&
3645 ValidateAndConvertPermissions(parsedArgs.DirectoryPermissions, dperms,
3646 status);
3647 if (!validatePermissions) {
3648 return false;
3649 }
3650
3651 std::vector<std::string> allPathEntries;
3652
3653 if (recurse) {
3654 std::vector<std::string> tempPathEntries;
3655 for (const auto& i : pathEntries) {
3656 if (cmSystemTools::FileIsDirectory(i)) {
3657 globber.FindFiles(i + "/*");
3658 tempPathEntries = globber.GetFiles();
3659 allPathEntries.insert(allPathEntries.end(), tempPathEntries.begin(),
3660 tempPathEntries.end());
3661 allPathEntries.emplace_back(i);
3662 } else {
3663 allPathEntries.emplace_back(i); // We validate path entries below
3664 }
3665 }
3666 } else {
3667 allPathEntries = std::move(pathEntries);
3668 }
3669
3670 // chmod
3671 for (const auto& i : allPathEntries) {
3672 if (!(cmSystemTools::FileExists(i) || cmSystemTools::FileIsDirectory(i))) {
3673 status.SetError(cmStrCat("does not exist:\n ", i));
3674 cmSystemTools::SetFatalErrorOccured();
3675 return false;
3676 }
3677
3678 if (cmSystemTools::FileExists(i, true)) {
3679 bool success = true;
3680 const mode_t& filePermissions =
3681 parsedArgs.FilePermissions.empty() ? perms : fperms;
3682 if (filePermissions) {
3683 success = SetPermissions(i, filePermissions, status);
3684 }
3685 if (!success) {
3686 return false;
3687 }
3688 }
3689
3690 else if (cmSystemTools::FileIsDirectory(i)) {
3691 bool success = true;
3692 const mode_t& directoryPermissions =
3693 parsedArgs.DirectoryPermissions.empty() ? perms : dperms;
3694 if (directoryPermissions) {
3695 success = SetPermissions(i, directoryPermissions, status);
3696 }
3697 if (!success) {
3698 return false;
3699 }
3700 }
3701 }
3702
3703 return true;
3704 }
3705
HandleChmodCommand(std::vector<std::string> const & args,cmExecutionStatus & status)3706 bool HandleChmodCommand(std::vector<std::string> const& args,
3707 cmExecutionStatus& status)
3708 {
3709 return HandleChmodCommandImpl(args, false, status);
3710 }
3711
HandleChmodRecurseCommand(std::vector<std::string> const & args,cmExecutionStatus & status)3712 bool HandleChmodRecurseCommand(std::vector<std::string> const& args,
3713 cmExecutionStatus& status)
3714 {
3715 return HandleChmodCommandImpl(args, true, status);
3716 }
3717
3718 } // namespace
3719
cmFileCommand(std::vector<std::string> const & args,cmExecutionStatus & status)3720 bool cmFileCommand(std::vector<std::string> const& args,
3721 cmExecutionStatus& status)
3722 {
3723 if (args.size() < 2) {
3724 status.SetError("must be called with at least two arguments.");
3725 return false;
3726 }
3727
3728 static cmSubcommandTable const subcommand{
3729 { "WRITE"_s, HandleWriteCommand },
3730 { "APPEND"_s, HandleAppendCommand },
3731 { "DOWNLOAD"_s, HandleDownloadCommand },
3732 { "UPLOAD"_s, HandleUploadCommand },
3733 { "READ"_s, HandleReadCommand },
3734 { "MD5"_s, HandleHashCommand },
3735 { "SHA1"_s, HandleHashCommand },
3736 { "SHA224"_s, HandleHashCommand },
3737 { "SHA256"_s, HandleHashCommand },
3738 { "SHA384"_s, HandleHashCommand },
3739 { "SHA512"_s, HandleHashCommand },
3740 { "SHA3_224"_s, HandleHashCommand },
3741 { "SHA3_256"_s, HandleHashCommand },
3742 { "SHA3_384"_s, HandleHashCommand },
3743 { "SHA3_512"_s, HandleHashCommand },
3744 { "STRINGS"_s, HandleStringsCommand },
3745 { "GLOB"_s, HandleGlobCommand },
3746 { "GLOB_RECURSE"_s, HandleGlobRecurseCommand },
3747 { "MAKE_DIRECTORY"_s, HandleMakeDirectoryCommand },
3748 { "RENAME"_s, HandleRename },
3749 { "COPY_FILE"_s, HandleCopyFile },
3750 { "REMOVE"_s, HandleRemove },
3751 { "REMOVE_RECURSE"_s, HandleRemoveRecurse },
3752 { "COPY"_s, HandleCopyCommand },
3753 { "INSTALL"_s, HandleInstallCommand },
3754 { "DIFFERENT"_s, HandleDifferentCommand },
3755 { "RPATH_CHANGE"_s, HandleRPathChangeCommand },
3756 { "CHRPATH"_s, HandleRPathChangeCommand },
3757 { "RPATH_SET"_s, HandleRPathSetCommand },
3758 { "RPATH_CHECK"_s, HandleRPathCheckCommand },
3759 { "RPATH_REMOVE"_s, HandleRPathRemoveCommand },
3760 { "READ_ELF"_s, HandleReadElfCommand },
3761 { "REAL_PATH"_s, HandleRealPathCommand },
3762 { "RELATIVE_PATH"_s, HandleRelativePathCommand },
3763 { "TO_CMAKE_PATH"_s, HandleCMakePathCommand },
3764 { "TO_NATIVE_PATH"_s, HandleNativePathCommand },
3765 { "TOUCH"_s, HandleTouchCommand },
3766 { "TOUCH_NOCREATE"_s, HandleTouchNocreateCommand },
3767 { "TIMESTAMP"_s, HandleTimestampCommand },
3768 { "GENERATE"_s, HandleGenerateCommand },
3769 { "LOCK"_s, HandleLockCommand },
3770 { "SIZE"_s, HandleSizeCommand },
3771 { "READ_SYMLINK"_s, HandleReadSymlinkCommand },
3772 { "CREATE_LINK"_s, HandleCreateLinkCommand },
3773 { "GET_RUNTIME_DEPENDENCIES"_s, HandleGetRuntimeDependenciesCommand },
3774 { "CONFIGURE"_s, HandleConfigureCommand },
3775 { "ARCHIVE_CREATE"_s, HandleArchiveCreateCommand },
3776 { "ARCHIVE_EXTRACT"_s, HandleArchiveExtractCommand },
3777 { "CHMOD"_s, HandleChmodCommand },
3778 { "CHMOD_RECURSE"_s, HandleChmodRecurseCommand },
3779 };
3780
3781 return subcommand(args[0], args, status);
3782 }
3783