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"
18 #include <cm/optional>
19 #include <cm/string_view>
20 #include <cmext/algorithm>
21 #include <cmext/string_view>
23 #include <cm3p/kwiml/int.h>
25 #include "cmsys/FStream.hxx"
26 #include "cmsys/Glob.hxx"
27 #include "cmsys/RegularExpression.hxx"
29 #include "cm_sys_stat.h"
31 #include "cmArgumentParser.h"
32 #include "cmArgumentParserTypes.h"
33 #include "cmCMakePath.h"
34 #include "cmCryptoHash.h"
36 #include "cmExecutionStatus.h"
37 #include "cmFSPermissions.h"
38 #include "cmFileCopier.h"
39 #include "cmFileInstaller.h"
40 #include "cmFileLockPool.h"
41 #include "cmFileTimes.h"
42 #include "cmGeneratedFileStream.h"
43 #include "cmGeneratorExpression.h"
44 #include "cmGlobalGenerator.h"
45 #include "cmHexFileConverter.h"
46 #include "cmListFileCache.h"
47 #include "cmMakefile.h"
48 #include "cmMessageType.h"
49 #include "cmNewLineStyle.h"
50 #include "cmPolicies.h"
52 #include "cmRuntimeDependencyArchive.h"
54 #include "cmStringAlgorithms.h"
55 #include "cmSubcommandTable.h"
56 #include "cmSystemTools.h"
57 #include "cmTimestamp.h"
59 #include "cmWorkingDirectory.h"
62 #if !defined(CMAKE_BOOTSTRAP)
63 # include <cm3p/curl/curl.h>
66 # include "cmFileLockResult.h"
71 bool HandleWriteImpl(std::vector<std::string> const& args, bool append,
72 cmExecutionStatus& status)
74 auto i = args.begin();
76 i++; // Get rid of subcommand
78 std::string fileName = *i;
79 if (!cmsys::SystemTools::FileIsFullPath(*i)) {
81 cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', *i);
86 if (!status.GetMakefile().CanIWriteThisFile(fileName)) {
88 "attempted to write a file: " + fileName + " into a source directory.";
90 cmSystemTools::SetFatalErrorOccurred();
93 std::string dir = cmSystemTools::GetFilenamePath(fileName);
94 cmSystemTools::MakeDirectory(dir);
97 bool writable = false;
99 // Set permissions to writable
100 if (cmSystemTools::GetPermissions(fileName, mode)) {
101 #if defined(_MSC_VER) || defined(__MINGW32__)
102 writable = (mode & S_IWRITE) != 0;
103 mode_t newMode = mode | S_IWRITE;
105 writable = mode & S_IWUSR;
106 mode_t newMode = mode | S_IWUSR | S_IWGRP;
109 cmSystemTools::SetPermissions(fileName, newMode);
112 // If GetPermissions fails, pretend like it is ok. File open will fail if
113 // the file is not writable
114 cmsys::ofstream file(fileName.c_str(),
115 append ? std::ios::app : std::ios::out);
118 cmStrCat("failed to open for writing (",
119 cmSystemTools::GetLastSystemError(), "):\n ", fileName);
120 status.SetError(error);
123 std::string message = cmJoin(cmMakeRange(i, args.end()), std::string());
127 cmStrCat("write failed (", cmSystemTools::GetLastSystemError(), "):\n ",
129 status.SetError(error);
133 if (mode && !writable) {
134 cmSystemTools::SetPermissions(fileName, mode);
139 bool HandleWriteCommand(std::vector<std::string> const& args,
140 cmExecutionStatus& status)
142 return HandleWriteImpl(args, false, status);
145 bool HandleAppendCommand(std::vector<std::string> const& args,
146 cmExecutionStatus& status)
148 return HandleWriteImpl(args, true, status);
151 bool HandleReadCommand(std::vector<std::string> const& args,
152 cmExecutionStatus& status)
154 if (args.size() < 3) {
155 status.SetError("READ must be called with at least two additional "
160 std::string const& fileNameArg = args[1];
161 std::string const& variable = args[2];
170 static auto const parser = cmArgumentParser<Arguments>{}
171 .Bind("OFFSET"_s, &Arguments::Offset)
172 .Bind("LIMIT"_s, &Arguments::Limit)
173 .Bind("HEX"_s, &Arguments::Hex);
175 Arguments const arguments = parser.Parse(cmMakeRange(args).advance(3),
176 /*unparsedArguments=*/nullptr);
178 std::string fileName = fileNameArg;
179 if (!cmsys::SystemTools::FileIsFullPath(fileName)) {
180 fileName = cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/',
184 // Open the specified file.
185 #if defined(_WIN32) || defined(__CYGWIN__)
186 cmsys::ifstream file(fileName.c_str(),
187 arguments.Hex ? (std::ios::binary | std::ios::in)
190 cmsys::ifstream file(fileName.c_str());
195 cmStrCat("failed to open for reading (",
196 cmSystemTools::GetLastSystemError(), "):\n ", fileName);
197 status.SetError(error);
202 std::string::size_type sizeLimit = std::string::npos;
203 if (!arguments.Limit.empty()) {
204 unsigned long long limit;
205 if (cmStrToULongLong(arguments.Limit, &limit)) {
206 sizeLimit = static_cast<std::string::size_type>(limit);
210 // is there an offset?
211 cmsys::ifstream::off_type offset = 0;
212 if (!arguments.Offset.empty()) {
214 if (cmStrToLongLong(arguments.Offset, &off)) {
215 offset = static_cast<cmsys::ifstream::off_type>(off);
219 file.seekg(offset, std::ios::beg); // explicit ios::beg for IBM VisualAge 6
224 // Convert part of the file into hex code
226 while ((sizeLimit > 0) && (file.get(c))) {
228 snprintf(hex, sizeof(hex), "%.2x", c & 0xff);
234 bool has_newline = false;
237 cmSystemTools::GetLineFromStream(file, line, &has_newline, sizeLimit)) {
238 sizeLimit = sizeLimit - line.size();
239 if (has_newline && sizeLimit > 0) {
248 status.GetMakefile().AddDefinition(variable, output);
252 bool HandleHashCommand(std::vector<std::string> const& args,
253 cmExecutionStatus& status)
255 #if !defined(CMAKE_BOOTSTRAP)
256 if (args.size() != 3) {
258 cmStrCat(args[0], " requires a file name and output variable"));
262 std::unique_ptr<cmCryptoHash> hash(cmCryptoHash::New(args[0]));
264 std::string out = hash->HashFile(args[1]);
266 status.GetMakefile().AddDefinition(args[2], out);
269 status.SetError(cmStrCat(args[0], " failed to read file \"", args[1],
270 "\": ", cmSystemTools::GetLastSystemError()));
274 status.SetError(cmStrCat(args[0], " not available during bootstrap"));
279 bool HandleStringsCommand(std::vector<std::string> const& args,
280 cmExecutionStatus& status)
282 if (args.size() < 3) {
283 status.SetError("STRINGS requires a file name and output variable");
287 // Get the file to read.
288 std::string fileName = args[1];
289 if (!cmsys::SystemTools::FileIsFullPath(fileName)) {
291 cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[1]);
294 // Get the variable in which to store the results.
295 std::string const& outVar = args[2];
297 // Parse the options.
310 unsigned int minlen = 0;
311 unsigned int maxlen = 0;
312 int limit_input = -1;
313 int limit_output = -1;
314 unsigned int limit_count = 0;
315 cmsys::RegularExpression regex;
316 bool have_regex = false;
317 bool newline_consume = false;
318 bool hex_conversion_enabled = true;
321 encoding_none = cmsys::FStream::BOM_None,
322 encoding_utf8 = cmsys::FStream::BOM_UTF8,
323 encoding_utf16le = cmsys::FStream::BOM_UTF16LE,
324 encoding_utf16be = cmsys::FStream::BOM_UTF16BE,
325 encoding_utf32le = cmsys::FStream::BOM_UTF32LE,
326 encoding_utf32be = cmsys::FStream::BOM_UTF32BE
328 int encoding = encoding_none;
329 int arg_mode = arg_none;
330 for (unsigned int i = 3; i < args.size(); ++i) {
331 if (args[i] == "LIMIT_INPUT") {
332 arg_mode = arg_limit_input;
333 } else if (args[i] == "LIMIT_OUTPUT") {
334 arg_mode = arg_limit_output;
335 } else if (args[i] == "LIMIT_COUNT") {
336 arg_mode = arg_limit_count;
337 } else if (args[i] == "LENGTH_MINIMUM") {
338 arg_mode = arg_length_minimum;
339 } else if (args[i] == "LENGTH_MAXIMUM") {
340 arg_mode = arg_length_maximum;
341 } else if (args[i] == "REGEX") {
342 arg_mode = arg_regex;
343 } else if (args[i] == "NEWLINE_CONSUME") {
344 newline_consume = true;
346 } else if (args[i] == "NO_HEX_CONVERSION") {
347 hex_conversion_enabled = false;
349 } else if (args[i] == "ENCODING") {
350 arg_mode = arg_encoding;
351 } else if (arg_mode == arg_limit_input) {
352 if (sscanf(args[i].c_str(), "%d", &limit_input) != 1 ||
354 status.SetError(cmStrCat("STRINGS option LIMIT_INPUT value \"",
355 args[i], "\" is not an unsigned integer."));
359 } else if (arg_mode == arg_limit_output) {
360 if (sscanf(args[i].c_str(), "%d", &limit_output) != 1 ||
362 status.SetError(cmStrCat("STRINGS option LIMIT_OUTPUT value \"",
363 args[i], "\" is not an unsigned integer."));
367 } else if (arg_mode == arg_limit_count) {
369 if (sscanf(args[i].c_str(), "%d", &count) != 1 || count < 0) {
370 status.SetError(cmStrCat("STRINGS option LIMIT_COUNT value \"",
371 args[i], "\" is not an unsigned integer."));
376 } else if (arg_mode == arg_length_minimum) {
378 if (sscanf(args[i].c_str(), "%d", &len) != 1 || len < 0) {
379 status.SetError(cmStrCat("STRINGS option LENGTH_MINIMUM value \"",
380 args[i], "\" is not an unsigned integer."));
385 } else if (arg_mode == arg_length_maximum) {
387 if (sscanf(args[i].c_str(), "%d", &len) != 1 || len < 0) {
388 status.SetError(cmStrCat("STRINGS option LENGTH_MAXIMUM value \"",
389 args[i], "\" is not an unsigned integer."));
394 } else if (arg_mode == arg_regex) {
395 if (!regex.compile(args[i])) {
396 status.SetError(cmStrCat("STRINGS option REGEX value \"", args[i],
397 "\" could not be compiled."));
402 } else if (arg_mode == arg_encoding) {
403 if (args[i] == "UTF-8") {
404 encoding = encoding_utf8;
405 } else if (args[i] == "UTF-16LE") {
406 encoding = encoding_utf16le;
407 } else if (args[i] == "UTF-16BE") {
408 encoding = encoding_utf16be;
409 } else if (args[i] == "UTF-32LE") {
410 encoding = encoding_utf32le;
411 } else if (args[i] == "UTF-32BE") {
412 encoding = encoding_utf32be;
414 status.SetError(cmStrCat("STRINGS option ENCODING \"", args[i],
415 "\" not recognized."));
421 cmStrCat("STRINGS given unknown argument \"", args[i], "\""));
426 if (hex_conversion_enabled) {
427 // TODO: should work without temp file, but just on a memory buffer
428 std::string binaryFileName =
429 cmStrCat(status.GetMakefile().GetCurrentBinaryDirectory(),
430 "/CMakeFiles/FileCommandStringsBinaryFile");
431 if (cmHexFileConverter::TryConvert(fileName, binaryFileName)) {
432 fileName = binaryFileName;
436 // Open the specified file.
437 #if defined(_WIN32) || defined(__CYGWIN__)
438 cmsys::ifstream fin(fileName.c_str(), std::ios::in | std::ios::binary);
440 cmsys::ifstream fin(fileName.c_str());
444 cmStrCat("STRINGS file \"", fileName, "\" cannot be read."));
448 // If BOM is found and encoding was not specified, use the BOM
449 int bom_found = cmsys::FStream::ReadBOM(fin);
450 if (encoding == encoding_none && bom_found != cmsys::FStream::BOM_None) {
451 encoding = bom_found;
454 unsigned int bytes_rem = 0;
455 if (encoding == encoding_utf16le || encoding == encoding_utf16be) {
458 if (encoding == encoding_utf32le || encoding == encoding_utf32be) {
462 // Parse strings out of the file.
464 std::vector<std::string> strings;
466 while ((!limit_count || strings.size() < limit_count) &&
467 (limit_input < 0 || static_cast<int>(fin.tellg()) < limit_input) &&
469 std::string current_str;
472 for (unsigned int i = 0; i < bytes_rem; ++i) {
475 fin.putback(static_cast<char>(c1));
480 if (encoding == encoding_utf16le) {
481 c = ((c & 0xFF) << 8) | ((c & 0xFF00) >> 8);
482 } else if (encoding == encoding_utf32le) {
483 c = (((c & 0xFF) << 24) | ((c & 0xFF00) << 8) | ((c & 0xFF0000) >> 8) |
484 ((c & 0xFF000000) >> 24));
488 // Ignore CR character to make output always have UNIX newlines.
492 if (c >= 0 && c <= 0xFF &&
493 (isprint(c) || c == '\t' || (c == '\n' && newline_consume))) {
494 // This is an ASCII character that may be part of a string.
495 // Cast added to avoid compiler warning. Cast is ok because
496 // c is guaranteed to fit in char by the above if...
497 current_str += static_cast<char>(c);
498 } else if (encoding == encoding_utf8) {
499 // Check for UTF-8 encoded string (up to 4 octets)
500 static const unsigned char utf8_check_table[3][2] = {
506 // how many octets are there?
507 unsigned int num_utf8_bytes = 0;
508 for (unsigned int j = 0; num_utf8_bytes == 0 && j < 3; j++) {
509 if ((c & utf8_check_table[j][0]) == utf8_check_table[j][1]) {
510 num_utf8_bytes = j + 2;
514 // get subsequent octets and check that they are valid
515 for (unsigned int j = 0; j < num_utf8_bytes; j++) {
518 if (!fin || (c & 0xC0) != 0x80) {
519 fin.putback(static_cast<char>(c));
523 current_str += static_cast<char>(c);
526 // if this was an invalid utf8 sequence, discard the data, and put
527 // back subsequent characters
528 if ((current_str.length() != num_utf8_bytes)) {
529 for (unsigned int j = 0; j < current_str.size() - 1; j++) {
530 fin.putback(current_str[current_str.size() - 1 - j]);
536 if (c == '\n' && !newline_consume) {
537 // The current line has been terminated. Check if the current
538 // string matches the requirements. The length may now be as
539 // low as zero since blank lines are allowed.
540 if (s.length() >= minlen && (!have_regex || regex.find(s))) {
541 output_size += static_cast<int>(s.size()) + 1;
542 if (limit_output >= 0 && output_size >= limit_output) {
546 strings.push_back(s);
549 // Reset the string to empty.
551 } else if (current_str.empty()) {
552 // A non-string character has been found. Check if the current
553 // string matches the requirements. We require that the length
554 // be at least one no matter what the user specified.
555 if (s.length() >= minlen && !s.empty() &&
556 (!have_regex || regex.find(s))) {
557 output_size += static_cast<int>(s.size()) + 1;
558 if (limit_output >= 0 && output_size >= limit_output) {
562 strings.push_back(s);
565 // Reset the string to empty.
571 if (maxlen > 0 && s.size() == maxlen) {
572 // Terminate a string if the maximum length is reached.
573 if (s.length() >= minlen && (!have_regex || regex.find(s))) {
574 output_size += static_cast<int>(s.size()) + 1;
575 if (limit_output >= 0 && output_size >= limit_output) {
579 strings.push_back(s);
585 // If there is a non-empty current string we have hit the end of the
586 // input file or the input size limit. Check if the current string
587 // matches the requirements.
588 if ((!limit_count || strings.size() < limit_count) && !s.empty() &&
589 s.length() >= minlen && (!have_regex || regex.find(s))) {
590 output_size += static_cast<int>(s.size()) + 1;
591 if (limit_output < 0 || output_size < limit_output) {
592 strings.push_back(s);
596 // Encode the result in a CMake list.
597 const char* sep = "";
599 for (std::string const& sr : strings) {
600 // Separate the strings in the output to make it a list.
604 // Store the string in the output, but escape semicolons to
605 // make sure it is a list.
614 // Save the output in a makefile variable.
615 status.GetMakefile().AddDefinition(outVar, output);
619 bool HandleGlobImpl(std::vector<std::string> const& args, bool recurse,
620 cmExecutionStatus& status)
622 // File commands has at least one argument
623 assert(args.size() > 1);
625 auto i = args.begin();
627 i++; // Get rid of subcommand
629 std::string variable = *i;
632 g.SetRecurse(recurse);
634 bool explicitFollowSymlinks = false;
635 cmPolicies::PolicyStatus policyStatus =
636 status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0009);
638 switch (policyStatus) {
639 case cmPolicies::REQUIRED_IF_USED:
640 case cmPolicies::REQUIRED_ALWAYS:
641 case cmPolicies::NEW:
642 g.RecurseThroughSymlinksOff();
644 case cmPolicies::WARN:
646 case cmPolicies::OLD:
647 g.RecurseThroughSymlinksOn();
652 cmake* cm = status.GetMakefile().GetCMakeInstance();
653 std::vector<std::string> files;
654 bool configureDepends = false;
655 bool warnConfigureLate = false;
656 bool warnFollowedSymlinks = false;
657 const cmake::WorkingMode workingMode = cm->GetWorkingMode();
658 while (i != args.end()) {
659 if (*i == "LIST_DIRECTORIES") {
660 ++i; // skip LIST_DIRECTORIES
661 if (i != args.end()) {
664 g.SetRecurseListDirs(true);
665 } else if (cmIsOff(*i)) {
666 g.SetListDirs(false);
667 g.SetRecurseListDirs(false);
669 status.SetError("LIST_DIRECTORIES missing bool value.");
674 status.SetError("LIST_DIRECTORIES missing bool value.");
677 } else if (*i == "FOLLOW_SYMLINKS") {
678 ++i; // skip FOLLOW_SYMLINKS
680 explicitFollowSymlinks = true;
681 g.RecurseThroughSymlinksOn();
682 if (i == args.end()) {
684 "GLOB_RECURSE requires a glob expression after FOLLOW_SYMLINKS.");
688 } else if (*i == "RELATIVE") {
689 ++i; // skip RELATIVE
690 if (i == args.end()) {
691 status.SetError("GLOB requires a directory after the RELATIVE tag.");
694 g.SetRelative(i->c_str());
696 if (i == args.end()) {
698 "GLOB requires a glob expression after the directory.");
701 } else if (*i == "CONFIGURE_DEPENDS") {
702 // Generated build system depends on glob results
703 if (!configureDepends && warnConfigureLate) {
704 status.GetMakefile().IssueMessage(
705 MessageType::AUTHOR_WARNING,
706 "CONFIGURE_DEPENDS flag was given after a glob expression was "
707 "already evaluated.");
709 if (workingMode != cmake::NORMAL_MODE) {
710 status.GetMakefile().IssueMessage(
711 MessageType::FATAL_ERROR,
712 "CONFIGURE_DEPENDS is invalid for script and find package modes.");
715 configureDepends = true;
717 if (i == args.end()) {
719 "GLOB requires a glob expression after CONFIGURE_DEPENDS.");
723 std::string expr = *i;
724 if (!cmsys::SystemTools::FileIsFullPath(*i)) {
725 expr = status.GetMakefile().GetCurrentSourceDirectory();
726 // Handle script mode
734 cmsys::Glob::GlobMessages globMessages;
735 g.FindFiles(expr, &globMessages);
737 if (!globMessages.empty()) {
738 bool shouldExit = false;
739 for (cmsys::Glob::Message const& globMessage : globMessages) {
740 if (globMessage.type == cmsys::Glob::cyclicRecursion) {
741 status.GetMakefile().IssueMessage(
742 MessageType::AUTHOR_WARNING,
743 "Cyclic recursion detected while globbing for '" + *i + "':\n" +
744 globMessage.content);
745 } else if (globMessage.type == cmsys::Glob::error) {
746 status.GetMakefile().IssueMessage(
747 MessageType::FATAL_ERROR,
748 "Error has occurred while globbing for '" + *i + "' - " +
749 globMessage.content);
751 } else if (cm->GetDebugOutput() || cm->GetTrace()) {
752 status.GetMakefile().IssueMessage(
754 cmStrCat("Globbing for\n ", *i, "\nEncountered an error:\n ",
755 globMessage.content));
763 if (recurse && !explicitFollowSymlinks &&
764 g.GetFollowedSymlinkCount() != 0) {
765 warnFollowedSymlinks = true;
768 std::vector<std::string>& foundFiles = g.GetFiles();
769 cm::append(files, foundFiles);
771 if (configureDepends) {
772 std::sort(foundFiles.begin(), foundFiles.end());
773 foundFiles.erase(std::unique(foundFiles.begin(), foundFiles.end()),
775 cm->AddGlobCacheEntry(
776 recurse, (recurse ? g.GetRecurseListDirs() : g.GetListDirs()),
777 (recurse ? g.GetRecurseThroughSymlinks() : false),
778 (g.GetRelative() ? g.GetRelative() : ""), expr, foundFiles, variable,
779 status.GetMakefile().GetBacktrace());
781 warnConfigureLate = true;
787 switch (policyStatus) {
788 case cmPolicies::REQUIRED_IF_USED:
789 case cmPolicies::REQUIRED_ALWAYS:
790 case cmPolicies::NEW:
791 // Correct behavior, yay!
793 case cmPolicies::OLD:
794 // Probably not really the expected behavior, but the author explicitly
795 // asked for the old behavior... no warning.
796 case cmPolicies::WARN:
797 // Possibly unexpected old behavior *and* we actually traversed
798 // symlinks without being explicitly asked to: warn the author.
799 if (warnFollowedSymlinks) {
800 status.GetMakefile().IssueMessage(
801 MessageType::AUTHOR_WARNING,
802 cmPolicies::GetPolicyWarning(cmPolicies::CMP0009));
807 std::sort(files.begin(), files.end());
808 files.erase(std::unique(files.begin(), files.end()), files.end());
809 status.GetMakefile().AddDefinition(variable, cmJoin(files, ";"));
813 bool HandleGlobCommand(std::vector<std::string> const& args,
814 cmExecutionStatus& status)
816 return HandleGlobImpl(args, false, status);
819 bool HandleGlobRecurseCommand(std::vector<std::string> const& args,
820 cmExecutionStatus& status)
822 return HandleGlobImpl(args, true, status);
825 bool HandleMakeDirectoryCommand(std::vector<std::string> const& args,
826 cmExecutionStatus& status)
828 // File command has at least one argument
829 assert(args.size() > 1);
832 for (std::string const& arg :
833 cmMakeRange(args).advance(1)) // Get rid of subcommand
835 const std::string* cdir = &arg;
836 if (!cmsys::SystemTools::FileIsFullPath(arg)) {
838 cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', arg);
841 if (!status.GetMakefile().CanIWriteThisFile(*cdir)) {
842 std::string e = "attempted to create a directory: " + *cdir +
843 " into a source directory.";
845 cmSystemTools::SetFatalErrorOccurred();
848 if (!cmSystemTools::MakeDirectory(*cdir)) {
849 std::string error = "problem creating directory: " + *cdir;
850 status.SetError(error);
857 bool HandleTouchImpl(std::vector<std::string> const& args, bool create,
858 cmExecutionStatus& status)
860 // File command has at least one argument
861 assert(args.size() > 1);
863 for (std::string const& arg :
864 cmMakeRange(args).advance(1)) // Get rid of subcommand
866 std::string tfile = arg;
867 if (!cmsys::SystemTools::FileIsFullPath(tfile)) {
869 cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', arg);
871 if (!status.GetMakefile().CanIWriteThisFile(tfile)) {
873 "attempted to touch a file: " + tfile + " in a source directory.";
875 cmSystemTools::SetFatalErrorOccurred();
878 if (!cmSystemTools::Touch(tfile, create)) {
879 std::string error = "problem touching file: " + tfile;
880 status.SetError(error);
887 bool HandleTouchCommand(std::vector<std::string> const& args,
888 cmExecutionStatus& status)
890 return HandleTouchImpl(args, true, status);
893 bool HandleTouchNocreateCommand(std::vector<std::string> const& args,
894 cmExecutionStatus& status)
896 return HandleTouchImpl(args, false, status);
899 bool HandleDifferentCommand(std::vector<std::string> const& args,
900 cmExecutionStatus& status)
903 FILE(DIFFERENT <variable> FILES <lhs> <rhs>)
906 // Evaluate arguments.
907 const char* file_lhs = nullptr;
908 const char* file_rhs = nullptr;
909 const char* var = nullptr;
917 Doing doing = DoingVar;
918 for (unsigned int i = 1; i < args.size(); ++i) {
919 if (args[i] == "FILES") {
920 doing = DoingFileLHS;
921 } else if (doing == DoingVar) {
922 var = args[i].c_str();
924 } else if (doing == DoingFileLHS) {
925 file_lhs = args[i].c_str();
926 doing = DoingFileRHS;
927 } else if (doing == DoingFileRHS) {
928 file_rhs = args[i].c_str();
931 status.SetError(cmStrCat("DIFFERENT given unknown argument ", args[i]));
936 status.SetError("DIFFERENT not given result variable name.");
939 if (!file_lhs || !file_rhs) {
940 status.SetError("DIFFERENT not given FILES option with two file names.");
944 // Compare the files.
946 cmSystemTools::FilesDiffer(file_lhs, file_rhs) ? "1" : "0";
947 status.GetMakefile().AddDefinition(var, result);
951 bool HandleCopyCommand(std::vector<std::string> const& args,
952 cmExecutionStatus& status)
954 cmFileCopier copier(status);
955 return copier.Run(args);
958 bool HandleRPathChangeCommand(std::vector<std::string> const& args,
959 cmExecutionStatus& status)
961 // Evaluate arguments.
963 cm::optional<std::string> oldRPath;
964 cm::optional<std::string> newRPath;
965 bool removeEnvironmentRPath = false;
966 cmArgumentParser<void> parser;
967 std::vector<std::string> unknownArgs;
968 parser.Bind("FILE"_s, file)
969 .Bind("OLD_RPATH"_s, oldRPath)
970 .Bind("NEW_RPATH"_s, newRPath)
971 .Bind("INSTALL_REMOVE_ENVIRONMENT_RPATH"_s, removeEnvironmentRPath);
972 ArgumentParser::ParseResult parseResult =
973 parser.Parse(cmMakeRange(args).advance(1), &unknownArgs);
974 if (!unknownArgs.empty()) {
976 cmStrCat("RPATH_CHANGE given unknown argument ", unknownArgs.front()));
979 if (parseResult.MaybeReportError(status.GetMakefile())) {
983 status.SetError("RPATH_CHANGE not given FILE option.");
987 status.SetError("RPATH_CHANGE not given OLD_RPATH option.");
991 status.SetError("RPATH_CHANGE not given NEW_RPATH option.");
994 if (!cmSystemTools::FileExists(file, true)) {
996 cmStrCat("RPATH_CHANGE given FILE \"", file, "\" that does not exist."));
1000 cmFileTimes const ft(file);
1004 if (!cmSystemTools::ChangeRPath(file, *oldRPath, *newRPath,
1005 removeEnvironmentRPath, &emsg, &changed)) {
1006 status.SetError(cmStrCat("RPATH_CHANGE could not write new RPATH:\n ",
1007 *newRPath, "\nto the file:\n ", file, "\n",
1013 std::string message =
1014 cmStrCat("Set runtime path of \"", file, "\" to \"", *newRPath, '"');
1015 status.GetMakefile().DisplayStatus(message, -1);
1022 bool HandleRPathSetCommand(std::vector<std::string> const& args,
1023 cmExecutionStatus& status)
1025 // Evaluate arguments.
1027 cm::optional<std::string> newRPath;
1028 cmArgumentParser<void> parser;
1029 std::vector<std::string> unknownArgs;
1030 parser.Bind("FILE"_s, file).Bind("NEW_RPATH"_s, newRPath);
1031 ArgumentParser::ParseResult parseResult =
1032 parser.Parse(cmMakeRange(args).advance(1), &unknownArgs);
1033 if (!unknownArgs.empty()) {
1034 status.SetError(cmStrCat("RPATH_SET given unrecognized argument \"",
1035 unknownArgs.front(), "\"."));
1038 if (parseResult.MaybeReportError(status.GetMakefile())) {
1042 status.SetError("RPATH_SET not given FILE option.");
1046 status.SetError("RPATH_SET not given NEW_RPATH option.");
1049 if (!cmSystemTools::FileExists(file, true)) {
1051 cmStrCat("RPATH_SET given FILE \"", file, "\" that does not exist."));
1054 bool success = true;
1055 cmFileTimes const ft(file);
1059 if (!cmSystemTools::SetRPath(file, *newRPath, &emsg, &changed)) {
1060 status.SetError(cmStrCat("RPATH_SET could not write new RPATH:\n ",
1061 *newRPath, "\nto the file:\n ", file, "\n",
1067 std::string message =
1068 cmStrCat("Set runtime path of \"", file, "\" to \"", *newRPath, '"');
1069 status.GetMakefile().DisplayStatus(message, -1);
1076 bool HandleRPathRemoveCommand(std::vector<std::string> const& args,
1077 cmExecutionStatus& status)
1079 // Evaluate arguments.
1081 cmArgumentParser<void> parser;
1082 std::vector<std::string> unknownArgs;
1083 parser.Bind("FILE"_s, file);
1084 ArgumentParser::ParseResult parseResult =
1085 parser.Parse(cmMakeRange(args).advance(1), &unknownArgs);
1086 if (!unknownArgs.empty()) {
1088 cmStrCat("RPATH_REMOVE given unknown argument ", unknownArgs.front()));
1091 if (parseResult.MaybeReportError(status.GetMakefile())) {
1095 status.SetError("RPATH_REMOVE not given FILE option.");
1098 if (!cmSystemTools::FileExists(file, true)) {
1100 cmStrCat("RPATH_REMOVE given FILE \"", file, "\" that does not exist."));
1103 bool success = true;
1104 cmFileTimes const ft(file);
1107 if (!cmSystemTools::RemoveRPath(file, &emsg, &removed)) {
1109 cmStrCat("RPATH_REMOVE could not remove RPATH from file: \n ", file,
1115 std::string message =
1116 cmStrCat("Removed runtime path from \"", file, '"');
1117 status.GetMakefile().DisplayStatus(message, -1);
1124 bool HandleRPathCheckCommand(std::vector<std::string> const& args,
1125 cmExecutionStatus& status)
1127 // Evaluate arguments.
1129 cm::optional<std::string> rpath;
1130 cmArgumentParser<void> parser;
1131 std::vector<std::string> unknownArgs;
1132 parser.Bind("FILE"_s, file).Bind("RPATH"_s, rpath);
1133 ArgumentParser::ParseResult parseResult =
1134 parser.Parse(cmMakeRange(args).advance(1), &unknownArgs);
1135 if (!unknownArgs.empty()) {
1137 cmStrCat("RPATH_CHECK given unknown argument ", unknownArgs.front()));
1140 if (parseResult.MaybeReportError(status.GetMakefile())) {
1144 status.SetError("RPATH_CHECK not given FILE option.");
1148 status.SetError("RPATH_CHECK not given RPATH option.");
1152 // If the file exists but does not have the desired RPath then
1153 // delete it. This is used during installation to re-install a file
1154 // if its RPath will change.
1155 if (cmSystemTools::FileExists(file, true) &&
1156 !cmSystemTools::CheckRPath(file, *rpath)) {
1157 cmSystemTools::RemoveFile(file);
1163 bool HandleReadElfCommand(std::vector<std::string> const& args,
1164 cmExecutionStatus& status)
1166 if (args.size() < 4) {
1167 status.SetError("READ_ELF must be called with at least three additional "
1172 std::string const& fileNameArg = args[1];
1177 std::string RunPath;
1181 static auto const parser = cmArgumentParser<Arguments>{}
1182 .Bind("RPATH"_s, &Arguments::RPath)
1183 .Bind("RUNPATH"_s, &Arguments::RunPath)
1184 .Bind("CAPTURE_ERROR"_s, &Arguments::Error);
1185 Arguments const arguments = parser.Parse(cmMakeRange(args).advance(2),
1186 /*unparsedArguments=*/nullptr);
1188 if (!cmSystemTools::FileExists(fileNameArg, true)) {
1189 status.SetError(cmStrCat("READ_ELF given FILE \"", fileNameArg,
1190 "\" that does not exist."));
1194 cmELF elf(fileNameArg.c_str());
1196 if (arguments.Error.empty()) {
1197 status.SetError(cmStrCat("READ_ELF given FILE:\n ", fileNameArg,
1198 "\nthat is not a valid ELF file."));
1201 status.GetMakefile().AddDefinition(arguments.Error,
1202 "not a valid ELF file");
1206 if (!arguments.RPath.empty()) {
1207 if (cmELF::StringEntry const* se_rpath = elf.GetRPath()) {
1208 std::string rpath(se_rpath->Value);
1209 std::replace(rpath.begin(), rpath.end(), ':', ';');
1210 status.GetMakefile().AddDefinition(arguments.RPath, rpath);
1213 if (!arguments.RunPath.empty()) {
1214 if (cmELF::StringEntry const* se_runpath = elf.GetRunPath()) {
1215 std::string runpath(se_runpath->Value);
1216 std::replace(runpath.begin(), runpath.end(), ':', ';');
1217 status.GetMakefile().AddDefinition(arguments.RunPath, runpath);
1224 bool HandleInstallCommand(std::vector<std::string> const& args,
1225 cmExecutionStatus& status)
1227 cmFileInstaller installer(status);
1228 return installer.Run(args);
1231 bool HandleRealPathCommand(std::vector<std::string> const& args,
1232 cmExecutionStatus& status)
1234 if (args.size() < 3) {
1235 status.SetError("REAL_PATH requires a path and an output variable");
1239 struct Arguments : public ArgumentParser::ParseResult
1241 cm::optional<std::string> BaseDirectory;
1242 bool ExpandTilde = false;
1244 static auto const parser =
1245 cmArgumentParser<Arguments>{}
1246 .Bind("BASE_DIRECTORY"_s, &Arguments::BaseDirectory)
1247 .Bind("EXPAND_TILDE"_s, &Arguments::ExpandTilde);
1249 std::vector<std::string> unparsedArguments;
1251 parser.Parse(cmMakeRange(args).advance(3), &unparsedArguments);
1253 if (!unparsedArguments.empty()) {
1254 status.SetError("REAL_PATH called with unexpected arguments");
1257 if (arguments.MaybeReportError(status.GetMakefile())) {
1261 if (!arguments.BaseDirectory) {
1262 arguments.BaseDirectory = status.GetMakefile().GetCurrentSourceDirectory();
1265 auto input = args[1];
1266 if (arguments.ExpandTilde && !input.empty()) {
1267 if (input[0] == '~' && (input.length() == 1 || input[1] == '/')) {
1270 #if defined(_WIN32) && !defined(__CYGWIN__)
1271 cmSystemTools::GetEnv("USERPROFILE", home) ||
1273 cmSystemTools::GetEnv("HOME", home)) {
1274 input.replace(0, 1, home);
1279 cmCMakePath path(input, cmCMakePath::auto_format);
1280 path = path.Absolute(*arguments.BaseDirectory).Normal();
1281 auto realPath = cmSystemTools::GetRealPath(path.GenericString());
1283 status.GetMakefile().AddDefinition(args[2], realPath);
1288 bool HandleRelativePathCommand(std::vector<std::string> const& args,
1289 cmExecutionStatus& status)
1291 if (args.size() != 4) {
1292 status.SetError("RELATIVE_PATH called with incorrect number of arguments");
1296 const std::string& outVar = args[1];
1297 const std::string& directoryName = args[2];
1298 const std::string& fileName = args[3];
1300 if (!cmSystemTools::FileIsFullPath(directoryName)) {
1301 std::string errstring =
1302 "RELATIVE_PATH must be passed a full path to the directory: " +
1304 status.SetError(errstring);
1307 if (!cmSystemTools::FileIsFullPath(fileName)) {
1308 std::string errstring =
1309 "RELATIVE_PATH must be passed a full path to the file: " + fileName;
1310 status.SetError(errstring);
1314 std::string res = cmSystemTools::RelativePath(directoryName, fileName);
1315 status.GetMakefile().AddDefinition(outVar, res);
1319 bool HandleRename(std::vector<std::string> const& args,
1320 cmExecutionStatus& status)
1322 if (args.size() < 3) {
1323 status.SetError("RENAME must be called with at least two additional "
1328 // Compute full path for old and new names.
1329 std::string oldname = args[1];
1330 if (!cmsys::SystemTools::FileIsFullPath(oldname)) {
1332 cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[1]);
1334 std::string newname = args[2];
1335 if (!cmsys::SystemTools::FileIsFullPath(newname)) {
1337 cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[2]);
1342 bool NoReplace = false;
1346 static auto const parser = cmArgumentParser<Arguments>{}
1347 .Bind("NO_REPLACE"_s, &Arguments::NoReplace)
1348 .Bind("RESULT"_s, &Arguments::Result);
1350 std::vector<std::string> unconsumedArgs;
1351 Arguments const arguments =
1352 parser.Parse(cmMakeRange(args).advance(3), &unconsumedArgs);
1353 if (!unconsumedArgs.empty()) {
1354 status.SetError("RENAME unknown argument:\n " + unconsumedArgs.front());
1359 switch (cmSystemTools::RenameFile(oldname, newname,
1361 ? cmSystemTools::Replace::No
1362 : cmSystemTools::Replace::Yes,
1364 case cmSystemTools::RenameResult::Success:
1365 if (!arguments.Result.empty()) {
1366 status.GetMakefile().AddDefinition(arguments.Result, "0");
1369 case cmSystemTools::RenameResult::NoReplace:
1370 if (!arguments.Result.empty()) {
1373 err = "path not replaced";
1376 case cmSystemTools::RenameResult::Failure:
1377 if (!arguments.Result.empty()) {
1378 status.GetMakefile().AddDefinition(arguments.Result, err);
1383 status.SetError(cmStrCat("RENAME failed to rename\n ", oldname, "\nto\n ",
1384 newname, "\nbecause: ", err, "\n"));
1388 bool HandleCopyFile(std::vector<std::string> const& args,
1389 cmExecutionStatus& status)
1391 if (args.size() < 3) {
1392 status.SetError("COPY_FILE must be called with at least two additional "
1397 // Compute full path for old and new names.
1398 std::string oldname = args[1];
1399 if (!cmsys::SystemTools::FileIsFullPath(oldname)) {
1401 cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[1]);
1403 std::string newname = args[2];
1404 if (!cmsys::SystemTools::FileIsFullPath(newname)) {
1406 cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[2]);
1411 bool OnlyIfDifferent = false;
1415 static auto const parser =
1416 cmArgumentParser<Arguments>{}
1417 .Bind("ONLY_IF_DIFFERENT"_s, &Arguments::OnlyIfDifferent)
1418 .Bind("RESULT"_s, &Arguments::Result);
1420 std::vector<std::string> unconsumedArgs;
1421 Arguments const arguments =
1422 parser.Parse(cmMakeRange(args).advance(3), &unconsumedArgs);
1423 if (!unconsumedArgs.empty()) {
1424 status.SetError("COPY_FILE unknown argument:\n " +
1425 unconsumedArgs.front());
1430 if (cmsys::SystemTools::FileIsDirectory(oldname)) {
1431 if (!arguments.Result.empty()) {
1432 status.GetMakefile().AddDefinition(arguments.Result,
1433 "cannot copy a directory");
1436 cmStrCat("COPY_FILE cannot copy a directory\n ", oldname));
1441 if (cmsys::SystemTools::FileIsDirectory(newname)) {
1442 if (!arguments.Result.empty()) {
1443 status.GetMakefile().AddDefinition(arguments.Result,
1444 "cannot copy to a directory");
1447 cmStrCat("COPY_FILE cannot copy to a directory\n ", newname));
1453 cmSystemTools::CopyWhen when;
1454 if (arguments.OnlyIfDifferent) {
1455 when = cmSystemTools::CopyWhen::OnlyIfDifferent;
1457 when = cmSystemTools::CopyWhen::Always;
1461 if (cmSystemTools::CopySingleFile(oldname, newname, when, &err) ==
1462 cmSystemTools::CopyResult::Success) {
1463 if (!arguments.Result.empty()) {
1464 status.GetMakefile().AddDefinition(arguments.Result, "0");
1467 if (!arguments.Result.empty()) {
1468 status.GetMakefile().AddDefinition(arguments.Result, err);
1470 status.SetError(cmStrCat("COPY_FILE failed to copy\n ", oldname,
1471 "\nto\n ", newname, "\nbecause: ", err, "\n"));
1479 bool HandleRemoveImpl(std::vector<std::string> const& args, bool recurse,
1480 cmExecutionStatus& status)
1482 for (std::string const& arg :
1483 cmMakeRange(args).advance(1)) // Get rid of subcommand
1485 std::string fileName = arg;
1486 if (fileName.empty()) {
1487 std::string const r = recurse ? "REMOVE_RECURSE" : "REMOVE";
1488 status.GetMakefile().IssueMessage(
1489 MessageType::AUTHOR_WARNING, "Ignoring empty file name in " + r + ".");
1492 if (!cmsys::SystemTools::FileIsFullPath(fileName)) {
1494 cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', arg);
1497 if (cmSystemTools::FileIsDirectory(fileName) &&
1498 !cmSystemTools::FileIsSymlink(fileName) && recurse) {
1499 cmSystemTools::RepeatedRemoveDirectory(fileName);
1501 cmSystemTools::RemoveFile(fileName);
1507 bool HandleRemove(std::vector<std::string> const& args,
1508 cmExecutionStatus& status)
1510 return HandleRemoveImpl(args, false, status);
1513 bool HandleRemoveRecurse(std::vector<std::string> const& args,
1514 cmExecutionStatus& status)
1516 return HandleRemoveImpl(args, true, status);
1519 std::string ToNativePath(const std::string& path)
1521 const auto& outPath = cmSystemTools::ConvertToOutputPath(path);
1522 if (outPath.size() > 1 && outPath.front() == '\"' &&
1523 outPath.back() == '\"') {
1524 return outPath.substr(1, outPath.size() - 2);
1529 std::string ToCMakePath(const std::string& path)
1532 cmSystemTools::ConvertToUnixSlashes(temp);
1536 bool HandlePathCommand(std::vector<std::string> const& args,
1537 std::string (*convert)(std::string const&),
1538 cmExecutionStatus& status)
1540 if (args.size() != 3) {
1541 status.SetError("FILE([TO_CMAKE_PATH|TO_NATIVE_PATH] path result) must be "
1542 "called with exactly three arguments.");
1545 #if defined(_WIN32) && !defined(__CYGWIN__)
1550 std::vector<std::string> path = cmSystemTools::SplitString(args[1], pathSep);
1552 std::string value = cmJoin(cmMakeRange(path).transform(convert), ";");
1553 status.GetMakefile().AddDefinition(args[2], value);
1557 bool HandleCMakePathCommand(std::vector<std::string> const& args,
1558 cmExecutionStatus& status)
1560 return HandlePathCommand(args, ToCMakePath, status);
1563 bool HandleNativePathCommand(std::vector<std::string> const& args,
1564 cmExecutionStatus& status)
1566 return HandlePathCommand(args, ToNativePath, status);
1569 #if !defined(CMAKE_BOOTSTRAP)
1571 // Stuff for curl download/upload
1572 using cmFileCommandVectorOfChar = std::vector<char>;
1574 size_t cmWriteToFileCallback(void* ptr, size_t size, size_t nmemb, void* data)
1576 int realsize = static_cast<int>(size * nmemb);
1577 cmsys::ofstream* fout = static_cast<cmsys::ofstream*>(data);
1579 const char* chPtr = static_cast<char*>(ptr);
1580 fout->write(chPtr, realsize);
1585 size_t cmWriteToMemoryCallback(void* ptr, size_t size, size_t nmemb,
1588 int realsize = static_cast<int>(size * nmemb);
1589 const char* chPtr = static_cast<char*>(ptr);
1590 cm::append(*static_cast<cmFileCommandVectorOfChar*>(data), chPtr,
1595 int cmFileCommandCurlDebugCallback(CURL*, curl_infotype type, char* chPtr,
1596 size_t size, void* data)
1598 cmFileCommandVectorOfChar& vec =
1599 *static_cast<cmFileCommandVectorOfChar*>(data);
1602 case CURLINFO_HEADER_IN:
1603 case CURLINFO_HEADER_OUT:
1604 cm::append(vec, chPtr, chPtr + size);
1606 case CURLINFO_DATA_IN:
1607 case CURLINFO_DATA_OUT:
1608 case CURLINFO_SSL_DATA_IN:
1609 case CURLINFO_SSL_DATA_OUT: {
1612 snprintf(buf, sizeof(buf), "[%" KWIML_INT_PRIu64 " bytes data]\n",
1613 static_cast<KWIML_INT_uint64_t>(size));
1615 cm::append(vec, buf, buf + n);
1624 class cURLProgressHelper
1627 cURLProgressHelper(cmMakefile* mf, const char* text)
1633 bool UpdatePercentage(double value, double total, std::string& status)
1635 long OldPercentage = this->CurrentPercentage;
1638 this->CurrentPercentage = std::lround(value / total * 100.0);
1639 if (this->CurrentPercentage > 100) {
1640 // Avoid extra progress reports for unexpected data beyond total.
1641 this->CurrentPercentage = 100;
1645 bool updated = (OldPercentage != this->CurrentPercentage);
1649 cmStrCat("[", this->Text, " ", this->CurrentPercentage, "% complete]");
1655 cmMakefile* GetMakefile() { return this->Makefile; }
1658 long CurrentPercentage = -1;
1659 cmMakefile* Makefile;
1663 int cmFileDownloadProgressCallback(void* clientp, double dltotal, double dlnow,
1664 double ultotal, double ulnow)
1666 cURLProgressHelper* helper = reinterpret_cast<cURLProgressHelper*>(clientp);
1668 static_cast<void>(ultotal);
1669 static_cast<void>(ulnow);
1672 if (helper->UpdatePercentage(dlnow, dltotal, status)) {
1673 cmMakefile* mf = helper->GetMakefile();
1674 mf->DisplayStatus(status, -1);
1680 int cmFileUploadProgressCallback(void* clientp, double dltotal, double dlnow,
1681 double ultotal, double ulnow)
1683 cURLProgressHelper* helper = reinterpret_cast<cURLProgressHelper*>(clientp);
1685 static_cast<void>(dltotal);
1686 static_cast<void>(dlnow);
1689 if (helper->UpdatePercentage(ulnow, ultotal, status)) {
1690 cmMakefile* mf = helper->GetMakefile();
1691 mf->DisplayStatus(status, -1);
1700 cURLEasyGuard(CURL* easy)
1708 ::curl_easy_cleanup(this->Easy);
1712 cURLEasyGuard(const cURLEasyGuard&) = delete;
1713 cURLEasyGuard& operator=(const cURLEasyGuard&) = delete;
1715 void release() { this->Easy = nullptr; }
1723 #define check_curl_result(result, errstr) \
1725 if (result != CURLE_OK) { \
1726 std::string e(errstr); \
1727 e += ::curl_easy_strerror(result); \
1728 status.SetError(e); \
1733 bool HandleDownloadCommand(std::vector<std::string> const& args,
1734 cmExecutionStatus& status)
1736 #if !defined(CMAKE_BOOTSTRAP)
1737 auto i = args.begin();
1738 if (args.size() < 2) {
1739 status.SetError("DOWNLOAD must be called with at least two arguments.");
1742 ++i; // Get rid of subcommand
1743 std::string url = *i;
1748 long inactivity_timeout = 0;
1750 std::string statusVar;
1751 bool tls_verify = status.GetMakefile().IsOn("CMAKE_TLS_VERIFY");
1752 cmValue cainfo = status.GetMakefile().GetDefinition("CMAKE_TLS_CAINFO");
1753 std::string netrc_level =
1754 status.GetMakefile().GetSafeDefinition("CMAKE_NETRC");
1755 std::string netrc_file =
1756 status.GetMakefile().GetSafeDefinition("CMAKE_NETRC_FILE");
1757 std::string expectedHash;
1758 std::string hashMatchMSG;
1759 std::unique_ptr<cmCryptoHash> hash;
1760 bool showProgress = false;
1761 std::string userpwd;
1763 std::vector<std::string> curl_headers;
1764 std::vector<std::pair<std::string, cm::optional<std::string>>> curl_ranges;
1766 while (i != args.end()) {
1767 if (*i == "TIMEOUT") {
1769 if (i != args.end()) {
1770 timeout = atol(i->c_str());
1772 status.SetError("DOWNLOAD missing time for TIMEOUT.");
1775 } else if (*i == "INACTIVITY_TIMEOUT") {
1777 if (i != args.end()) {
1778 inactivity_timeout = atol(i->c_str());
1780 status.SetError("DOWNLOAD missing time for INACTIVITY_TIMEOUT.");
1783 } else if (*i == "LOG") {
1785 if (i == args.end()) {
1786 status.SetError("DOWNLOAD missing VAR for LOG.");
1790 } else if (*i == "STATUS") {
1792 if (i == args.end()) {
1793 status.SetError("DOWNLOAD missing VAR for STATUS.");
1797 } else if (*i == "TLS_VERIFY") {
1799 if (i != args.end()) {
1800 tls_verify = cmIsOn(*i);
1802 status.SetError("DOWNLOAD missing bool value for TLS_VERIFY.");
1805 } else if (*i == "TLS_CAINFO") {
1807 if (i != args.end()) {
1808 cainfo = cmValue(*i);
1810 status.SetError("DOWNLOAD missing file value for TLS_CAINFO.");
1813 } else if (*i == "NETRC_FILE") {
1815 if (i != args.end()) {
1818 status.SetError("DOWNLOAD missing file value for NETRC_FILE.");
1821 } else if (*i == "NETRC") {
1823 if (i != args.end()) {
1826 status.SetError("DOWNLOAD missing level value for NETRC.");
1829 } else if (*i == "EXPECTED_MD5") {
1831 if (i == args.end()) {
1832 status.SetError("DOWNLOAD missing sum value for EXPECTED_MD5.");
1835 hash = cm::make_unique<cmCryptoHash>(cmCryptoHash::AlgoMD5);
1836 hashMatchMSG = "MD5 sum";
1837 expectedHash = cmSystemTools::LowerCase(*i);
1838 } else if (*i == "SHOW_PROGRESS") {
1839 showProgress = true;
1840 } else if (*i == "EXPECTED_HASH") {
1842 if (i == args.end()) {
1843 status.SetError("DOWNLOAD missing ALGO=value for EXPECTED_HASH.");
1846 std::string::size_type pos = i->find("=");
1847 if (pos == std::string::npos) {
1849 cmStrCat("DOWNLOAD EXPECTED_HASH expects ALGO=value but got: ", *i);
1850 status.SetError(err);
1853 std::string algo = i->substr(0, pos);
1854 expectedHash = cmSystemTools::LowerCase(i->substr(pos + 1));
1855 hash = cmCryptoHash::New(algo);
1858 cmStrCat("DOWNLOAD EXPECTED_HASH given unknown ALGO: ", algo);
1859 status.SetError(err);
1862 hashMatchMSG = algo + " hash";
1863 } else if (*i == "USERPWD") {
1865 if (i == args.end()) {
1866 status.SetError("DOWNLOAD missing string for USERPWD.");
1870 } else if (*i == "HTTPHEADER") {
1872 if (i == args.end()) {
1873 status.SetError("DOWNLOAD missing string for HTTPHEADER.");
1876 curl_headers.push_back(*i);
1877 } else if (*i == "RANGE_START") {
1879 if (i == args.end()) {
1880 status.SetError("DOWNLOAD missing value for RANGE_START.");
1883 curl_ranges.emplace_back(*i, cm::nullopt);
1884 } else if (*i == "RANGE_END") {
1886 if (curl_ranges.empty()) {
1887 curl_ranges.emplace_back("0", *i);
1889 auto& last_range = curl_ranges.back();
1890 if (!last_range.second.has_value()) {
1891 last_range.second = *i;
1893 status.SetError("Multiple RANGE_END values is provided without "
1894 "the corresponding RANGE_START.");
1898 } else if (file.empty()) {
1901 // Do not return error for compatibility reason.
1902 std::string err = cmStrCat("Unexpected argument: ", *i);
1903 status.GetMakefile().IssueMessage(MessageType::AUTHOR_WARNING, err);
1908 // Can't calculate hash if we don't save the file.
1909 // TODO Incrementally calculate hash in the write callback as the file is
1910 // being downloaded so this check can be relaxed.
1911 if (file.empty() && hash) {
1912 status.SetError("DOWNLOAD cannot calculate hash if file is not saved.");
1915 // If file exists already, and caller specified an expected md5 or sha,
1916 // and the existing file already has the expected hash, then simply
1919 if (!file.empty() && cmSystemTools::FileExists(file) && hash.get()) {
1921 std::string actualHash = hash->HashFile(file);
1922 if (actualHash == expectedHash) {
1923 msg = cmStrCat("skipping download as file already exists with expected ",
1925 if (!statusVar.empty()) {
1926 status.GetMakefile().AddDefinition(statusVar, cmStrCat(0, ";\"", msg));
1931 // Make sure parent directory exists so we can write to the file
1932 // as we receive downloaded bits from curl...
1934 if (!file.empty()) {
1935 std::string dir = cmSystemTools::GetFilenamePath(file);
1936 if (!dir.empty() && !cmSystemTools::FileExists(dir) &&
1937 !cmSystemTools::MakeDirectory(dir)) {
1938 std::string errstring = "DOWNLOAD error: cannot create directory '" +
1940 "' - Specify file by full path name and verify that you "
1941 "have directory creation and file write privileges.";
1942 status.SetError(errstring);
1947 cmsys::ofstream fout;
1948 if (!file.empty()) {
1949 fout.open(file.c_str(), std::ios::binary);
1951 status.SetError("DOWNLOAD cannot open file for write.");
1956 url = cmCurlFixFileURL(url);
1959 ::curl_global_init(CURL_GLOBAL_DEFAULT);
1960 curl = ::curl_easy_init();
1962 status.SetError("DOWNLOAD error initializing curl.");
1966 cURLEasyGuard g_curl(curl);
1967 ::CURLcode res = ::curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
1968 check_curl_result(res, "DOWNLOAD cannot set url: ");
1970 // enable HTTP ERROR parsing
1971 res = ::curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
1972 check_curl_result(res, "DOWNLOAD cannot set http failure option: ");
1974 res = ::curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/" LIBCURL_VERSION);
1975 check_curl_result(res, "DOWNLOAD cannot set user agent option: ");
1977 res = ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cmWriteToFileCallback);
1978 check_curl_result(res, "DOWNLOAD cannot set write function: ");
1980 res = ::curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION,
1981 cmFileCommandCurlDebugCallback);
1982 check_curl_result(res, "DOWNLOAD cannot set debug function: ");
1984 // check to see if TLS verification is requested
1986 res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1);
1987 check_curl_result(res, "DOWNLOAD cannot set TLS/SSL Verify on: ");
1989 res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
1990 check_curl_result(res, "DOWNLOAD cannot set TLS/SSL Verify off: ");
1993 for (const auto& range : curl_ranges) {
1994 std::string curl_range = range.first + '-' +
1995 (range.second.has_value() ? range.second.value() : "");
1996 res = ::curl_easy_setopt(curl, CURLOPT_RANGE, curl_range.c_str());
1997 check_curl_result(res, "DOWNLOAD cannot set range: ");
2000 // check to see if a CAINFO file has been specified
2001 // command arg comes first
2002 std::string const& cainfo_err = cmCurlSetCAInfo(curl, cainfo);
2003 if (!cainfo_err.empty()) {
2004 status.SetError(cainfo_err);
2008 // check to see if netrc parameters have been specified
2009 // local command args takes precedence over CMAKE_NETRC*
2010 netrc_level = cmSystemTools::UpperCase(netrc_level);
2011 std::string const& netrc_option_err =
2012 cmCurlSetNETRCOption(curl, netrc_level, netrc_file);
2013 if (!netrc_option_err.empty()) {
2014 status.SetError(netrc_option_err);
2018 cmFileCommandVectorOfChar chunkDebug;
2020 res = ::curl_easy_setopt(curl, CURLOPT_WRITEDATA,
2021 file.empty() ? nullptr : &fout);
2022 check_curl_result(res, "DOWNLOAD cannot set write data: ");
2024 res = ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &chunkDebug);
2025 check_curl_result(res, "DOWNLOAD cannot set debug data: ");
2027 res = ::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
2028 check_curl_result(res, "DOWNLOAD cannot set follow-redirect option: ");
2030 if (!logVar.empty()) {
2031 res = ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
2032 check_curl_result(res, "DOWNLOAD cannot set verbose: ");
2036 res = ::curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
2037 check_curl_result(res, "DOWNLOAD cannot set timeout: ");
2040 if (inactivity_timeout > 0) {
2041 // Give up if there is no progress for a long time.
2042 ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1);
2043 ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, inactivity_timeout);
2046 // Need the progress helper's scope to last through the duration of
2047 // the curl_easy_perform call... so this object is declared at function
2048 // scope intentionally, rather than inside the "if(showProgress)"
2051 cURLProgressHelper helper(&status.GetMakefile(), "download");
2054 res = ::curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
2055 check_curl_result(res, "DOWNLOAD cannot set noprogress value: ");
2057 res = ::curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION,
2058 cmFileDownloadProgressCallback);
2059 check_curl_result(res, "DOWNLOAD cannot set progress function: ");
2061 res = ::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA,
2062 reinterpret_cast<void*>(&helper));
2063 check_curl_result(res, "DOWNLOAD cannot set progress data: ");
2066 if (!userpwd.empty()) {
2067 res = ::curl_easy_setopt(curl, CURLOPT_USERPWD, userpwd.c_str());
2068 check_curl_result(res, "DOWNLOAD cannot set user password: ");
2071 struct curl_slist* headers = nullptr;
2072 for (std::string const& h : curl_headers) {
2073 headers = ::curl_slist_append(headers, h.c_str());
2075 ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
2077 res = ::curl_easy_perform(curl);
2079 ::curl_slist_free_all(headers);
2081 /* always cleanup */
2083 ::curl_easy_cleanup(curl);
2085 if (!statusVar.empty()) {
2086 status.GetMakefile().AddDefinition(
2088 cmStrCat(static_cast<int>(res), ";\"", ::curl_easy_strerror(res), "\""));
2091 ::curl_global_cleanup();
2093 // Ensure requested curl logs are returned (especially in case of failure)
2095 if (!logVar.empty()) {
2096 chunkDebug.push_back(0);
2097 status.GetMakefile().AddDefinition(logVar, chunkDebug.data());
2100 // Explicitly flush/close so we can measure the md5 accurately.
2102 if (!file.empty()) {
2107 // Verify MD5 sum if requested:
2110 std::string actualHash = hash->HashFile(file);
2111 if (actualHash.empty()) {
2112 status.SetError("DOWNLOAD cannot compute hash on downloaded file");
2116 if (expectedHash != actualHash) {
2117 if (!statusVar.empty() && res == 0) {
2118 status.GetMakefile().AddDefinition(statusVar,
2122 " actual: " + actualHash);
2125 status.SetError(cmStrCat("DOWNLOAD HASH mismatch\n"
2129 " expected hash: [",
2136 static_cast<int>(res), ";\"",
2137 ::curl_easy_strerror(res), "\"]\n"));
2144 status.SetError("DOWNLOAD not supported by bootstrap cmake.");
2149 bool HandleUploadCommand(std::vector<std::string> const& args,
2150 cmExecutionStatus& status)
2152 #if !defined(CMAKE_BOOTSTRAP)
2153 if (args.size() < 3) {
2154 status.SetError("UPLOAD must be called with at least three arguments.");
2157 auto i = args.begin();
2159 std::string filename = *i;
2161 std::string url = *i;
2165 long inactivity_timeout = 0;
2167 std::string statusVar;
2168 bool showProgress = false;
2169 bool tls_verify = status.GetMakefile().IsOn("CMAKE_TLS_VERIFY");
2170 cmValue cainfo = status.GetMakefile().GetDefinition("CMAKE_TLS_CAINFO");
2171 std::string userpwd;
2172 std::string netrc_level =
2173 status.GetMakefile().GetSafeDefinition("CMAKE_NETRC");
2174 std::string netrc_file =
2175 status.GetMakefile().GetSafeDefinition("CMAKE_NETRC_FILE");
2177 std::vector<std::string> curl_headers;
2179 while (i != args.end()) {
2180 if (*i == "TIMEOUT") {
2182 if (i != args.end()) {
2183 timeout = atol(i->c_str());
2185 status.SetError("UPLOAD missing time for TIMEOUT.");
2188 } else if (*i == "INACTIVITY_TIMEOUT") {
2190 if (i != args.end()) {
2191 inactivity_timeout = atol(i->c_str());
2193 status.SetError("UPLOAD missing time for INACTIVITY_TIMEOUT.");
2196 } else if (*i == "LOG") {
2198 if (i == args.end()) {
2199 status.SetError("UPLOAD missing VAR for LOG.");
2203 } else if (*i == "STATUS") {
2205 if (i == args.end()) {
2206 status.SetError("UPLOAD missing VAR for STATUS.");
2210 } else if (*i == "SHOW_PROGRESS") {
2211 showProgress = true;
2212 } else if (*i == "TLS_VERIFY") {
2214 if (i != args.end()) {
2215 tls_verify = cmIsOn(*i);
2217 status.SetError("UPLOAD missing bool value for TLS_VERIFY.");
2220 } else if (*i == "TLS_CAINFO") {
2222 if (i != args.end()) {
2223 cainfo = cmValue(*i);
2225 status.SetError("UPLOAD missing file value for TLS_CAINFO.");
2228 } else if (*i == "NETRC_FILE") {
2230 if (i != args.end()) {
2233 status.SetError("UPLOAD missing file value for NETRC_FILE.");
2236 } else if (*i == "NETRC") {
2238 if (i != args.end()) {
2241 status.SetError("UPLOAD missing level value for NETRC.");
2244 } else if (*i == "USERPWD") {
2246 if (i == args.end()) {
2247 status.SetError("UPLOAD missing string for USERPWD.");
2251 } else if (*i == "HTTPHEADER") {
2253 if (i == args.end()) {
2254 status.SetError("UPLOAD missing string for HTTPHEADER.");
2257 curl_headers.push_back(*i);
2259 // Do not return error for compatibility reason.
2260 std::string err = cmStrCat("Unexpected argument: ", *i);
2261 status.GetMakefile().IssueMessage(MessageType::AUTHOR_WARNING, err);
2267 // Open file for reading:
2269 FILE* fin = cmsys::SystemTools::Fopen(filename, "rb");
2271 std::string errStr =
2272 cmStrCat("UPLOAD cannot open file '", filename, "' for reading.");
2273 status.SetError(errStr);
2277 unsigned long file_size = cmsys::SystemTools::FileLength(filename);
2279 url = cmCurlFixFileURL(url);
2282 ::curl_global_init(CURL_GLOBAL_DEFAULT);
2283 curl = ::curl_easy_init();
2285 status.SetError("UPLOAD error initializing curl.");
2290 cURLEasyGuard g_curl(curl);
2292 // enable HTTP ERROR parsing
2293 ::CURLcode res = ::curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
2294 check_curl_result(res, "UPLOAD cannot set fail on error flag: ");
2297 res = ::curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
2298 check_curl_result(res, "UPLOAD cannot set upload flag: ");
2300 res = ::curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
2301 check_curl_result(res, "UPLOAD cannot set url: ");
2304 ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cmWriteToMemoryCallback);
2305 check_curl_result(res, "UPLOAD cannot set write function: ");
2307 res = ::curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION,
2308 cmFileCommandCurlDebugCallback);
2309 check_curl_result(res, "UPLOAD cannot set debug function: ");
2311 // check to see if TLS verification is requested
2313 res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1);
2314 check_curl_result(res, "UPLOAD cannot set TLS/SSL Verify on: ");
2316 res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
2317 check_curl_result(res, "UPLOAD cannot set TLS/SSL Verify off: ");
2320 // check to see if a CAINFO file has been specified
2321 // command arg comes first
2322 std::string const& cainfo_err = cmCurlSetCAInfo(curl, cainfo);
2323 if (!cainfo_err.empty()) {
2324 status.SetError(cainfo_err);
2328 cmFileCommandVectorOfChar chunkResponse;
2329 cmFileCommandVectorOfChar chunkDebug;
2331 res = ::curl_easy_setopt(curl, CURLOPT_WRITEDATA, &chunkResponse);
2332 check_curl_result(res, "UPLOAD cannot set write data: ");
2334 res = ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &chunkDebug);
2335 check_curl_result(res, "UPLOAD cannot set debug data: ");
2337 res = ::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
2338 check_curl_result(res, "UPLOAD cannot set follow-redirect option: ");
2340 if (!logVar.empty()) {
2341 res = ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
2342 check_curl_result(res, "UPLOAD cannot set verbose: ");
2346 res = ::curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
2347 check_curl_result(res, "UPLOAD cannot set timeout: ");
2350 if (inactivity_timeout > 0) {
2351 // Give up if there is no progress for a long time.
2352 ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1);
2353 ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, inactivity_timeout);
2356 // Need the progress helper's scope to last through the duration of
2357 // the curl_easy_perform call... so this object is declared at function
2358 // scope intentionally, rather than inside the "if(showProgress)"
2361 cURLProgressHelper helper(&status.GetMakefile(), "upload");
2364 res = ::curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
2365 check_curl_result(res, "UPLOAD cannot set noprogress value: ");
2367 res = ::curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION,
2368 cmFileUploadProgressCallback);
2369 check_curl_result(res, "UPLOAD cannot set progress function: ");
2371 res = ::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA,
2372 reinterpret_cast<void*>(&helper));
2373 check_curl_result(res, "UPLOAD cannot set progress data: ");
2376 // now specify which file to upload
2377 res = ::curl_easy_setopt(curl, CURLOPT_INFILE, fin);
2378 check_curl_result(res, "UPLOAD cannot set input file: ");
2380 // and give the size of the upload (optional)
2382 ::curl_easy_setopt(curl, CURLOPT_INFILESIZE, static_cast<long>(file_size));
2383 check_curl_result(res, "UPLOAD cannot set input file size: ");
2385 if (!userpwd.empty()) {
2386 res = ::curl_easy_setopt(curl, CURLOPT_USERPWD, userpwd.c_str());
2387 check_curl_result(res, "UPLOAD cannot set user password: ");
2390 // check to see if netrc parameters have been specified
2391 // local command args takes precedence over CMAKE_NETRC*
2392 netrc_level = cmSystemTools::UpperCase(netrc_level);
2393 std::string const& netrc_option_err =
2394 cmCurlSetNETRCOption(curl, netrc_level, netrc_file);
2395 if (!netrc_option_err.empty()) {
2396 status.SetError(netrc_option_err);
2400 struct curl_slist* headers = nullptr;
2401 for (std::string const& h : curl_headers) {
2402 headers = ::curl_slist_append(headers, h.c_str());
2404 ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
2406 res = ::curl_easy_perform(curl);
2408 ::curl_slist_free_all(headers);
2410 /* always cleanup */
2412 ::curl_easy_cleanup(curl);
2414 if (!statusVar.empty()) {
2415 status.GetMakefile().AddDefinition(
2417 cmStrCat(static_cast<int>(res), ";\"", ::curl_easy_strerror(res), "\""));
2420 ::curl_global_cleanup();
2425 if (!logVar.empty()) {
2428 if (!chunkResponse.empty()) {
2429 chunkResponse.push_back(0);
2430 log += "Response:\n";
2431 log += chunkResponse.data();
2435 if (!chunkDebug.empty()) {
2436 chunkDebug.push_back(0);
2438 log += chunkDebug.data();
2442 status.GetMakefile().AddDefinition(logVar, log);
2447 status.SetError("UPLOAD not supported by bootstrap cmake.");
2452 void AddEvaluationFile(const std::string& inputName,
2453 const std::string& targetName,
2454 const std::string& outputExpr,
2455 const std::string& condition, bool inputIsContent,
2456 const std::string& newLineCharacter, mode_t permissions,
2457 cmExecutionStatus& status)
2459 cmListFileBacktrace lfbt = status.GetMakefile().GetBacktrace();
2461 cmGeneratorExpression outputGe(lfbt);
2462 std::unique_ptr<cmCompiledGeneratorExpression> outputCge =
2463 outputGe.Parse(outputExpr);
2465 cmGeneratorExpression conditionGe(lfbt);
2466 std::unique_ptr<cmCompiledGeneratorExpression> conditionCge =
2467 conditionGe.Parse(condition);
2469 status.GetMakefile().AddEvaluationFile(
2470 inputName, targetName, std::move(outputCge), std::move(conditionCge),
2471 newLineCharacter, permissions, inputIsContent);
2474 bool HandleGenerateCommand(std::vector<std::string> const& args,
2475 cmExecutionStatus& status)
2477 if (args.size() < 5) {
2478 status.SetError("Incorrect arguments to GENERATE subcommand.");
2482 struct Arguments : public ArgumentParser::ParseResult
2484 cm::optional<std::string> Output;
2485 cm::optional<std::string> Input;
2486 cm::optional<std::string> Content;
2487 cm::optional<std::string> Condition;
2488 cm::optional<std::string> Target;
2489 cm::optional<std::string> NewLineStyle;
2490 bool NoSourcePermissions = false;
2491 bool UseSourcePermissions = false;
2492 ArgumentParser::NonEmpty<std::vector<std::string>> FilePermissions;
2493 std::vector<cm::string_view> ParsedKeywords;
2496 static auto const parser =
2497 cmArgumentParser<Arguments>{}
2498 .Bind("OUTPUT"_s, &Arguments::Output)
2499 .Bind("INPUT"_s, &Arguments::Input)
2500 .Bind("CONTENT"_s, &Arguments::Content)
2501 .Bind("CONDITION"_s, &Arguments::Condition)
2502 .Bind("TARGET"_s, &Arguments::Target)
2503 .Bind("NO_SOURCE_PERMISSIONS"_s, &Arguments::NoSourcePermissions)
2504 .Bind("USE_SOURCE_PERMISSIONS"_s, &Arguments::UseSourcePermissions)
2505 .Bind("FILE_PERMISSIONS"_s, &Arguments::FilePermissions)
2506 .Bind("NEWLINE_STYLE"_s, &Arguments::NewLineStyle)
2507 .BindParsedKeywords(&Arguments::ParsedKeywords);
2509 std::vector<std::string> unparsedArguments;
2510 Arguments const arguments =
2511 parser.Parse(cmMakeRange(args).advance(1), &unparsedArguments);
2513 if (arguments.MaybeReportError(status.GetMakefile())) {
2517 if (!unparsedArguments.empty()) {
2518 status.SetError("Unknown argument to GENERATE subcommand.");
2522 if (!arguments.Output || arguments.ParsedKeywords[0] != "OUTPUT"_s) {
2523 status.SetError("GENERATE requires OUTPUT as first option.");
2526 std::string const& output = *arguments.Output;
2528 if (!arguments.Input && !arguments.Content) {
2529 status.SetError("GENERATE requires INPUT or CONTENT option.");
2532 const bool inputIsContent = arguments.ParsedKeywords[1] == "CONTENT"_s;
2533 if (!inputIsContent && arguments.ParsedKeywords[1] == "INPUT") {
2534 status.SetError("Unknown argument to GENERATE subcommand.");
2536 std::string const& input =
2537 inputIsContent ? *arguments.Content : *arguments.Input;
2539 if (arguments.Condition && arguments.Condition->empty()) {
2540 status.SetError("CONDITION of sub-command GENERATE must not be empty "
2544 std::string const& condition =
2545 arguments.Condition ? *arguments.Condition : std::string();
2547 if (arguments.Target && arguments.Target->empty()) {
2548 status.SetError("TARGET of sub-command GENERATE must not be empty "
2552 std::string const& target =
2553 arguments.Target ? *arguments.Target : std::string();
2555 cmNewLineStyle newLineStyle;
2556 if (arguments.NewLineStyle) {
2557 std::string errorMessage;
2558 if (!newLineStyle.ReadFromArguments(args, errorMessage)) {
2559 status.SetError(cmStrCat("GENERATE ", errorMessage));
2564 if (arguments.NoSourcePermissions && arguments.UseSourcePermissions) {
2565 status.SetError("given both NO_SOURCE_PERMISSIONS and "
2566 "USE_SOURCE_PERMISSIONS. Only one option allowed.");
2570 if (!arguments.FilePermissions.empty()) {
2571 if (arguments.NoSourcePermissions) {
2572 status.SetError("given both NO_SOURCE_PERMISSIONS and "
2573 "FILE_PERMISSIONS. Only one option allowed.");
2576 if (arguments.UseSourcePermissions) {
2577 status.SetError("given both USE_SOURCE_PERMISSIONS and "
2578 "FILE_PERMISSIONS. Only one option allowed.");
2583 if (arguments.UseSourcePermissions) {
2584 if (inputIsContent) {
2585 status.SetError("given USE_SOURCE_PERMISSIONS without a file INPUT.");
2590 mode_t permissions = 0;
2591 if (arguments.NoSourcePermissions) {
2592 permissions |= cmFSPermissions::mode_owner_read;
2593 permissions |= cmFSPermissions::mode_owner_write;
2594 permissions |= cmFSPermissions::mode_group_read;
2595 permissions |= cmFSPermissions::mode_world_read;
2598 if (!arguments.FilePermissions.empty()) {
2599 std::vector<std::string> invalidOptions;
2600 for (auto const& e : arguments.FilePermissions) {
2601 if (!cmFSPermissions::stringToModeT(e, permissions)) {
2602 invalidOptions.push_back(e);
2605 if (!invalidOptions.empty()) {
2606 std::ostringstream oss;
2607 oss << "given invalid permission ";
2608 for (auto i = 0u; i < invalidOptions.size(); i++) {
2610 oss << "\"" << invalidOptions[i] << "\"";
2612 oss << ",\"" << invalidOptions[i] << "\"";
2616 status.SetError(oss.str());
2621 AddEvaluationFile(input, target, output, condition, inputIsContent,
2622 newLineStyle.GetCharacters(), permissions, status);
2626 bool HandleLockCommand(std::vector<std::string> const& args,
2627 cmExecutionStatus& status)
2629 #if !defined(CMAKE_BOOTSTRAP)
2631 bool directory = false;
2632 bool release = false;
2639 Guard guard = GUARD_PROCESS;
2640 std::string resultVariable;
2641 unsigned long timeout = static_cast<unsigned long>(-1);
2644 if (args.size() < 2) {
2645 status.GetMakefile().IssueMessage(
2646 MessageType::FATAL_ERROR,
2647 "sub-command LOCK requires at least two arguments.");
2651 std::string path = args[1];
2652 for (unsigned i = 2; i < args.size(); ++i) {
2653 if (args[i] == "DIRECTORY") {
2655 } else if (args[i] == "RELEASE") {
2657 } else if (args[i] == "GUARD") {
2659 const char* merr = "expected FUNCTION, FILE or PROCESS after GUARD";
2660 if (i >= args.size()) {
2661 status.GetMakefile().IssueMessage(MessageType::FATAL_ERROR, merr);
2664 if (args[i] == "FUNCTION") {
2665 guard = GUARD_FUNCTION;
2666 } else if (args[i] == "FILE") {
2668 } else if (args[i] == "PROCESS") {
2669 guard = GUARD_PROCESS;
2671 status.GetMakefile().IssueMessage(
2672 MessageType::FATAL_ERROR,
2673 cmStrCat(merr, ", but got:\n \"", args[i], "\"."));
2677 } else if (args[i] == "RESULT_VARIABLE") {
2679 if (i >= args.size()) {
2680 status.GetMakefile().IssueMessage(
2681 MessageType::FATAL_ERROR,
2682 "expected variable name after RESULT_VARIABLE");
2685 resultVariable = args[i];
2686 } else if (args[i] == "TIMEOUT") {
2688 if (i >= args.size()) {
2689 status.GetMakefile().IssueMessage(
2690 MessageType::FATAL_ERROR, "expected timeout value after TIMEOUT");
2694 if (!cmStrToLong(args[i], &scanned) || scanned < 0) {
2695 status.GetMakefile().IssueMessage(
2696 MessageType::FATAL_ERROR,
2697 cmStrCat("TIMEOUT value \"", args[i],
2698 "\" is not an unsigned integer."));
2701 timeout = static_cast<unsigned long>(scanned);
2703 status.GetMakefile().IssueMessage(
2704 MessageType::FATAL_ERROR,
2705 cmStrCat("expected DIRECTORY, RELEASE, GUARD, RESULT_VARIABLE or ",
2706 "TIMEOUT\nbut got: \"", args[i], "\"."));
2712 path += "/cmake.lock";
2715 // Unify path (remove '//', '/../', ...)
2716 path = cmSystemTools::CollapseFullPath(
2717 path, status.GetMakefile().GetCurrentSourceDirectory());
2719 // Create file and directories if needed
2720 std::string parentDir = cmSystemTools::GetParentDirectory(path);
2721 if (!cmSystemTools::MakeDirectory(parentDir)) {
2722 status.GetMakefile().IssueMessage(
2723 MessageType::FATAL_ERROR,
2724 cmStrCat("directory\n \"", parentDir,
2725 "\"\ncreation failed (check permissions)."));
2726 cmSystemTools::SetFatalErrorOccurred();
2729 FILE* file = cmsys::SystemTools::Fopen(path, "w");
2731 status.GetMakefile().IssueMessage(
2732 MessageType::FATAL_ERROR,
2733 cmStrCat("file\n \"", path,
2734 "\"\ncreation failed (check permissions)."));
2735 cmSystemTools::SetFatalErrorOccurred();
2740 // Actual lock/unlock
2741 cmFileLockPool& lockPool =
2742 status.GetMakefile().GetGlobalGenerator()->GetFileLockPool();
2744 cmFileLockResult fileLockResult(cmFileLockResult::MakeOk());
2746 fileLockResult = lockPool.Release(path);
2749 case GUARD_FUNCTION:
2750 fileLockResult = lockPool.LockFunctionScope(path, timeout);
2753 fileLockResult = lockPool.LockFileScope(path, timeout);
2756 fileLockResult = lockPool.LockProcessScope(path, timeout);
2759 cmSystemTools::SetFatalErrorOccurred();
2764 const std::string result = fileLockResult.GetOutputMessage();
2766 if (resultVariable.empty() && !fileLockResult.IsOk()) {
2767 status.GetMakefile().IssueMessage(
2768 MessageType::FATAL_ERROR,
2769 cmStrCat("error locking file\n \"", path, "\"\n", result, "."));
2770 cmSystemTools::SetFatalErrorOccurred();
2774 if (!resultVariable.empty()) {
2775 status.GetMakefile().AddDefinition(resultVariable, result);
2780 static_cast<void>(args);
2781 status.SetError("sub-command LOCK not implemented in bootstrap cmake");
2786 bool HandleTimestampCommand(std::vector<std::string> const& args,
2787 cmExecutionStatus& status)
2789 if (args.size() < 3) {
2790 status.SetError("sub-command TIMESTAMP requires at least two arguments.");
2793 if (args.size() > 5) {
2794 status.SetError("sub-command TIMESTAMP takes at most four arguments.");
2798 unsigned int argsIndex = 1;
2800 std::string filename = args[argsIndex++];
2801 if (!cmsys::SystemTools::FileIsFullPath(filename)) {
2802 filename = cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/',
2806 const std::string& outputVariable = args[argsIndex++];
2808 std::string formatString;
2809 if (args.size() > argsIndex && args[argsIndex] != "UTC") {
2810 formatString = args[argsIndex++];
2813 bool utcFlag = false;
2814 if (args.size() > argsIndex) {
2815 if (args[argsIndex] == "UTC") {
2818 std::string e = " TIMESTAMP sub-command does not recognize option " +
2819 args[argsIndex] + ".";
2825 cmTimestamp timestamp;
2826 std::string result =
2827 timestamp.FileModificationTime(filename.c_str(), formatString, utcFlag);
2828 status.GetMakefile().AddDefinition(outputVariable, result);
2833 bool HandleSizeCommand(std::vector<std::string> const& args,
2834 cmExecutionStatus& status)
2836 if (args.size() != 3) {
2838 cmStrCat(args[0], " requires a file name and output variable"));
2842 unsigned int argsIndex = 1;
2844 const std::string& filename = args[argsIndex++];
2846 const std::string& outputVariable = args[argsIndex++];
2848 if (!cmSystemTools::FileExists(filename, true)) {
2850 cmStrCat("SIZE requested of path that is not readable:\n ", filename));
2854 status.GetMakefile().AddDefinition(
2855 outputVariable, std::to_string(cmSystemTools::FileLength(filename)));
2860 bool HandleReadSymlinkCommand(std::vector<std::string> const& args,
2861 cmExecutionStatus& status)
2863 if (args.size() != 3) {
2865 cmStrCat(args[0], " requires a file name and output variable"));
2869 const std::string& filename = args[1];
2870 const std::string& outputVariable = args[2];
2873 if (!cmSystemTools::ReadSymlink(filename, result)) {
2874 status.SetError(cmStrCat(
2875 "READ_SYMLINK requested of path that is not a symlink:\n ", filename));
2879 status.GetMakefile().AddDefinition(outputVariable, result);
2884 bool HandleCreateLinkCommand(std::vector<std::string> const& args,
2885 cmExecutionStatus& status)
2887 if (args.size() < 3) {
2888 status.SetError("CREATE_LINK must be called with at least two additional "
2893 std::string const& fileName = args[1];
2894 std::string const& newFileName = args[2];
2899 bool CopyOnError = false;
2900 bool Symbolic = false;
2903 static auto const parser =
2904 cmArgumentParser<Arguments>{}
2905 .Bind("RESULT"_s, &Arguments::Result)
2906 .Bind("COPY_ON_ERROR"_s, &Arguments::CopyOnError)
2907 .Bind("SYMBOLIC"_s, &Arguments::Symbolic);
2909 std::vector<std::string> unconsumedArgs;
2910 Arguments const arguments =
2911 parser.Parse(cmMakeRange(args).advance(3), &unconsumedArgs);
2913 if (!unconsumedArgs.empty()) {
2914 status.SetError("unknown argument: \"" + unconsumedArgs.front() + '\"');
2918 // The system error message generated in the operation.
2921 // Check if the paths are distinct.
2922 if (fileName == newFileName) {
2923 result = "CREATE_LINK cannot use same file and newfile";
2924 if (!arguments.Result.empty()) {
2925 status.GetMakefile().AddDefinition(arguments.Result, result);
2928 status.SetError(result);
2932 // Hard link requires original file to exist.
2933 if (!arguments.Symbolic && !cmSystemTools::FileExists(fileName)) {
2934 result = "Cannot hard link \'" + fileName + "\' as it does not exist.";
2935 if (!arguments.Result.empty()) {
2936 status.GetMakefile().AddDefinition(arguments.Result, result);
2939 status.SetError(result);
2943 // Check if the new file already exists and remove it.
2944 if ((cmSystemTools::FileExists(newFileName) ||
2945 cmSystemTools::FileIsSymlink(newFileName)) &&
2946 !cmSystemTools::RemoveFile(newFileName)) {
2947 std::ostringstream e;
2948 e << "Failed to create link '" << newFileName
2949 << "' because existing path cannot be removed: "
2950 << cmSystemTools::GetLastSystemError() << "\n";
2952 if (!arguments.Result.empty()) {
2953 status.GetMakefile().AddDefinition(arguments.Result, e.str());
2956 status.SetError(e.str());
2960 // Whether the operation completed successfully.
2961 bool completed = false;
2963 // Check if the command requires a symbolic link.
2964 if (arguments.Symbolic) {
2965 cmsys::Status linked =
2966 cmSystemTools::CreateSymlinkQuietly(fileName, newFileName);
2970 result = cmStrCat("failed to create symbolic link '", newFileName,
2971 "': ", linked.GetString());
2974 cmsys::Status linked =
2975 cmSystemTools::CreateLinkQuietly(fileName, newFileName);
2979 result = cmStrCat("failed to create link '", newFileName,
2980 "': ", linked.GetString());
2984 // Check if copy-on-error is enabled in the arguments.
2985 if (!completed && arguments.CopyOnError) {
2986 cmsys::Status copied =
2987 cmsys::SystemTools::CopyFileAlways(fileName, newFileName);
2991 result = "Copy failed: " + copied.GetString();
2995 // Check if the operation was successful.
2998 } else if (arguments.Result.empty()) {
2999 // The operation failed and the result is not reported in a variable.
3000 status.SetError(result);
3004 if (!arguments.Result.empty()) {
3005 status.GetMakefile().AddDefinition(arguments.Result, result);
3011 bool HandleGetRuntimeDependenciesCommand(std::vector<std::string> const& args,
3012 cmExecutionStatus& status)
3014 std::string platform =
3015 status.GetMakefile().GetSafeDefinition("CMAKE_HOST_SYSTEM_NAME");
3016 if (!cmRuntimeDependencyArchive::PlatformSupportsRuntimeDependencies(
3019 cmStrCat("GET_RUNTIME_DEPENDENCIES is not supported on system \"",
3021 cmSystemTools::SetFatalErrorOccurred();
3025 if (status.GetMakefile().GetState()->GetMode() == cmState::Project) {
3026 status.GetMakefile().IssueMessage(
3027 MessageType::AUTHOR_WARNING,
3028 "You have used file(GET_RUNTIME_DEPENDENCIES)"
3029 " in project mode. This is probably not what "
3030 "you intended to do. Instead, please consider"
3031 " using it in an install(CODE) or "
3032 "install(SCRIPT) command. For example:"
3033 "\n install(CODE [["
3034 "\n file(GET_RUNTIME_DEPENDENCIES"
3040 struct Arguments : public ArgumentParser::ParseResult
3042 std::string ResolvedDependenciesVar;
3043 std::string UnresolvedDependenciesVar;
3044 std::string ConflictingDependenciesPrefix;
3045 std::string RPathPrefix;
3046 std::string BundleExecutable;
3047 ArgumentParser::MaybeEmpty<std::vector<std::string>> Executables;
3048 ArgumentParser::MaybeEmpty<std::vector<std::string>> Libraries;
3049 ArgumentParser::MaybeEmpty<std::vector<std::string>> Directories;
3050 ArgumentParser::MaybeEmpty<std::vector<std::string>> Modules;
3051 ArgumentParser::MaybeEmpty<std::vector<std::string>> PreIncludeRegexes;
3052 ArgumentParser::MaybeEmpty<std::vector<std::string>> PreExcludeRegexes;
3053 ArgumentParser::MaybeEmpty<std::vector<std::string>> PostIncludeRegexes;
3054 ArgumentParser::MaybeEmpty<std::vector<std::string>> PostExcludeRegexes;
3055 ArgumentParser::MaybeEmpty<std::vector<std::string>> PostIncludeFiles;
3056 ArgumentParser::MaybeEmpty<std::vector<std::string>> PostExcludeFiles;
3057 ArgumentParser::MaybeEmpty<std::vector<std::string>>
3058 PostExcludeFilesStrict;
3061 static auto const parser =
3062 cmArgumentParser<Arguments>{}
3063 .Bind("RESOLVED_DEPENDENCIES_VAR"_s, &Arguments::ResolvedDependenciesVar)
3064 .Bind("UNRESOLVED_DEPENDENCIES_VAR"_s,
3065 &Arguments::UnresolvedDependenciesVar)
3066 .Bind("CONFLICTING_DEPENDENCIES_PREFIX"_s,
3067 &Arguments::ConflictingDependenciesPrefix)
3068 .Bind("RPATH_PREFIX"_s, &Arguments::RPathPrefix)
3069 .Bind("BUNDLE_EXECUTABLE"_s, &Arguments::BundleExecutable)
3070 .Bind("EXECUTABLES"_s, &Arguments::Executables)
3071 .Bind("LIBRARIES"_s, &Arguments::Libraries)
3072 .Bind("MODULES"_s, &Arguments::Modules)
3073 .Bind("DIRECTORIES"_s, &Arguments::Directories)
3074 .Bind("PRE_INCLUDE_REGEXES"_s, &Arguments::PreIncludeRegexes)
3075 .Bind("PRE_EXCLUDE_REGEXES"_s, &Arguments::PreExcludeRegexes)
3076 .Bind("POST_INCLUDE_REGEXES"_s, &Arguments::PostIncludeRegexes)
3077 .Bind("POST_EXCLUDE_REGEXES"_s, &Arguments::PostExcludeRegexes)
3078 .Bind("POST_INCLUDE_FILES"_s, &Arguments::PostIncludeFiles)
3079 .Bind("POST_EXCLUDE_FILES"_s, &Arguments::PostExcludeFiles)
3080 .Bind("POST_EXCLUDE_FILES_STRICT"_s, &Arguments::PostExcludeFilesStrict);
3082 std::vector<std::string> unrecognizedArguments;
3084 parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments);
3085 auto argIt = unrecognizedArguments.begin();
3086 if (argIt != unrecognizedArguments.end()) {
3087 status.SetError(cmStrCat("Unrecognized argument: \"", *argIt, "\""));
3088 cmSystemTools::SetFatalErrorOccurred();
3092 if (parsedArgs.MaybeReportError(status.GetMakefile())) {
3093 cmSystemTools::SetFatalErrorOccurred();
3097 cmRuntimeDependencyArchive archive(
3098 status, parsedArgs.Directories, parsedArgs.BundleExecutable,
3099 parsedArgs.PreIncludeRegexes, parsedArgs.PreExcludeRegexes,
3100 parsedArgs.PostIncludeRegexes, parsedArgs.PostExcludeRegexes,
3101 std::move(parsedArgs.PostIncludeFiles),
3102 std::move(parsedArgs.PostExcludeFiles),
3103 std::move(parsedArgs.PostExcludeFilesStrict));
3104 if (!archive.Prepare()) {
3105 cmSystemTools::SetFatalErrorOccurred();
3109 if (!archive.GetRuntimeDependencies(
3110 parsedArgs.Executables, parsedArgs.Libraries, parsedArgs.Modules)) {
3111 cmSystemTools::SetFatalErrorOccurred();
3115 std::vector<std::string> deps;
3116 std::vector<std::string> unresolvedDeps;
3117 std::vector<std::string> conflictingDeps;
3118 for (auto const& val : archive.GetResolvedPaths()) {
3120 auto it = val.second.begin();
3121 assert(it != val.second.end());
3122 auto const& firstPath = *it;
3123 while (++it != val.second.end()) {
3124 if (!cmSystemTools::SameFile(firstPath, *it)) {
3131 deps.push_back(firstPath);
3132 if (!parsedArgs.RPathPrefix.empty()) {
3133 status.GetMakefile().AddDefinition(
3134 parsedArgs.RPathPrefix + "_" + firstPath,
3135 cmJoin(archive.GetRPaths().at(firstPath), ";"));
3137 } else if (!parsedArgs.ConflictingDependenciesPrefix.empty()) {
3138 conflictingDeps.push_back(val.first);
3139 std::vector<std::string> paths;
3140 paths.insert(paths.begin(), val.second.begin(), val.second.end());
3141 std::string varName =
3142 parsedArgs.ConflictingDependenciesPrefix + "_" + val.first;
3143 std::string pathsStr = cmJoin(paths, ";");
3144 status.GetMakefile().AddDefinition(varName, pathsStr);
3146 std::ostringstream e;
3147 e << "Multiple conflicting paths found for " << val.first << ":";
3148 for (auto const& path : val.second) {
3151 status.SetError(e.str());
3152 cmSystemTools::SetFatalErrorOccurred();
3156 if (!archive.GetUnresolvedPaths().empty()) {
3157 if (!parsedArgs.UnresolvedDependenciesVar.empty()) {
3158 unresolvedDeps.insert(unresolvedDeps.begin(),
3159 archive.GetUnresolvedPaths().begin(),
3160 archive.GetUnresolvedPaths().end());
3162 std::ostringstream e;
3163 e << "Could not resolve runtime dependencies:";
3164 for (auto const& path : archive.GetUnresolvedPaths()) {
3167 status.SetError(e.str());
3168 cmSystemTools::SetFatalErrorOccurred();
3173 if (!parsedArgs.ResolvedDependenciesVar.empty()) {
3174 std::string val = cmJoin(deps, ";");
3175 status.GetMakefile().AddDefinition(parsedArgs.ResolvedDependenciesVar,
3178 if (!parsedArgs.UnresolvedDependenciesVar.empty()) {
3179 std::string val = cmJoin(unresolvedDeps, ";");
3180 status.GetMakefile().AddDefinition(parsedArgs.UnresolvedDependenciesVar,
3183 if (!parsedArgs.ConflictingDependenciesPrefix.empty()) {
3184 std::string val = cmJoin(conflictingDeps, ";");
3185 status.GetMakefile().AddDefinition(
3186 parsedArgs.ConflictingDependenciesPrefix + "_FILENAMES", val);
3191 bool HandleConfigureCommand(std::vector<std::string> const& args,
3192 cmExecutionStatus& status)
3194 struct Arguments : public ArgumentParser::ParseResult
3196 cm::optional<std::string> Output;
3197 cm::optional<std::string> Content;
3198 bool EscapeQuotes = false;
3199 bool AtOnly = false;
3200 // "NEWLINE_STYLE" requires one value, but we use a custom check below.
3201 ArgumentParser::Maybe<std::string> NewlineStyle;
3204 static auto const parser =
3205 cmArgumentParser<Arguments>{}
3206 .Bind("OUTPUT"_s, &Arguments::Output)
3207 .Bind("CONTENT"_s, &Arguments::Content)
3208 .Bind("ESCAPE_QUOTES"_s, &Arguments::EscapeQuotes)
3209 .Bind("@ONLY"_s, &Arguments::AtOnly)
3210 .Bind("NEWLINE_STYLE"_s, &Arguments::NewlineStyle);
3212 std::vector<std::string> unrecognizedArguments;
3214 parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments);
3216 auto argIt = unrecognizedArguments.begin();
3217 if (argIt != unrecognizedArguments.end()) {
3219 cmStrCat("CONFIGURE Unrecognized argument: \"", *argIt, "\""));
3220 cmSystemTools::SetFatalErrorOccurred();
3224 if (parsedArgs.MaybeReportError(status.GetMakefile())) {
3225 cmSystemTools::SetFatalErrorOccurred();
3229 if (!parsedArgs.Output) {
3230 status.SetError("CONFIGURE OUTPUT option is mandatory.");
3231 cmSystemTools::SetFatalErrorOccurred();
3234 if (!parsedArgs.Content) {
3235 status.SetError("CONFIGURE CONTENT option is mandatory.");
3236 cmSystemTools::SetFatalErrorOccurred();
3240 std::string errorMessage;
3241 cmNewLineStyle newLineStyle;
3242 if (!newLineStyle.ReadFromArguments(args, errorMessage)) {
3243 status.SetError(cmStrCat("CONFIGURE ", errorMessage));
3247 // Check for generator expressions
3248 std::string outputFile = cmSystemTools::CollapseFullPath(
3249 *parsedArgs.Output, status.GetMakefile().GetCurrentBinaryDirectory());
3251 std::string::size_type pos = outputFile.find_first_of("<>");
3252 if (pos != std::string::npos) {
3253 status.SetError(cmStrCat("CONFIGURE called with OUTPUT containing a \"",
3255 "\". This character is not allowed."));
3259 cmMakefile& makeFile = status.GetMakefile();
3260 if (!makeFile.CanIWriteThisFile(outputFile)) {
3261 cmSystemTools::Error("Attempt to write file: " + outputFile +
3262 " into a source directory.");
3266 cmSystemTools::ConvertToUnixSlashes(outputFile);
3268 // Re-generate if non-temporary outputs are missing.
3269 // when we finalize the configuration we will remove all
3270 // output files that now don't exist.
3271 makeFile.AddCMakeOutputFile(outputFile);
3273 // Create output directory
3274 const std::string::size_type slashPos = outputFile.rfind('/');
3275 if (slashPos != std::string::npos) {
3276 const std::string path = outputFile.substr(0, slashPos);
3277 cmSystemTools::MakeDirectory(path);
3280 std::string newLineCharacters = "\n";
3281 bool open_with_binary_flag = false;
3282 if (newLineStyle.IsValid()) {
3283 newLineCharacters = newLineStyle.GetCharacters();
3284 open_with_binary_flag = true;
3287 cmGeneratedFileStream fout;
3288 fout.Open(outputFile, false, open_with_binary_flag);
3290 cmSystemTools::Error("Could not open file for write in copy operation " +
3292 cmSystemTools::ReportLastSystemError("");
3295 fout.SetCopyIfDifferent(true);
3297 // copy input to output and expand variables from input at the same time
3298 std::stringstream sin(*parsedArgs.Content, std::ios::in);
3300 std::string outLine;
3301 bool hasNewLine = false;
3302 while (cmSystemTools::GetLineFromStream(sin, inLine, &hasNewLine)) {
3304 makeFile.ConfigureString(inLine, outLine, parsedArgs.AtOnly,
3305 parsedArgs.EscapeQuotes);
3307 if (hasNewLine || newLineStyle.IsValid()) {
3308 fout << newLineCharacters;
3312 // close file before attempting to copy
3318 bool HandleArchiveCreateCommand(std::vector<std::string> const& args,
3319 cmExecutionStatus& status)
3321 struct Arguments : public ArgumentParser::ParseResult
3325 std::string Compression;
3326 std::string CompressionLevel;
3327 // "MTIME" should require one value, but it has long been accidentally
3328 // accepted without one and treated as if an empty value were given.
3329 // Fixing this would require a policy.
3330 ArgumentParser::Maybe<std::string> MTime;
3331 bool Verbose = false;
3332 // "PATHS" requires at least one value, but use a custom check below.
3333 ArgumentParser::MaybeEmpty<std::vector<std::string>> Paths;
3336 static auto const parser =
3337 cmArgumentParser<Arguments>{}
3338 .Bind("OUTPUT"_s, &Arguments::Output)
3339 .Bind("FORMAT"_s, &Arguments::Format)
3340 .Bind("COMPRESSION"_s, &Arguments::Compression)
3341 .Bind("COMPRESSION_LEVEL"_s, &Arguments::CompressionLevel)
3342 .Bind("MTIME"_s, &Arguments::MTime)
3343 .Bind("VERBOSE"_s, &Arguments::Verbose)
3344 .Bind("PATHS"_s, &Arguments::Paths);
3346 std::vector<std::string> unrecognizedArguments;
3348 parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments);
3349 auto argIt = unrecognizedArguments.begin();
3350 if (argIt != unrecognizedArguments.end()) {
3351 status.SetError(cmStrCat("Unrecognized argument: \"", *argIt, "\""));
3352 cmSystemTools::SetFatalErrorOccurred();
3356 if (parsedArgs.MaybeReportError(status.GetMakefile())) {
3357 cmSystemTools::SetFatalErrorOccurred();
3361 const char* knownFormats[] = {
3362 "7zip", "gnutar", "pax", "paxr", "raw", "zip"
3365 if (!parsedArgs.Format.empty() &&
3366 !cm::contains(knownFormats, parsedArgs.Format)) {
3368 cmStrCat("archive format ", parsedArgs.Format, " not supported"));
3369 cmSystemTools::SetFatalErrorOccurred();
3373 const char* zipFileFormats[] = { "7zip", "zip" };
3374 if (!parsedArgs.Compression.empty() &&
3375 cm::contains(zipFileFormats, parsedArgs.Format)) {
3376 status.SetError(cmStrCat("archive format ", parsedArgs.Format,
3377 " does not support COMPRESSION arguments"));
3378 cmSystemTools::SetFatalErrorOccurred();
3382 static std::map<std::string, cmSystemTools::cmTarCompression>
3383 compressionTypeMap = { { "None", cmSystemTools::TarCompressNone },
3384 { "BZip2", cmSystemTools::TarCompressBZip2 },
3385 { "GZip", cmSystemTools::TarCompressGZip },
3386 { "XZ", cmSystemTools::TarCompressXZ },
3387 { "Zstd", cmSystemTools::TarCompressZstd } };
3389 cmSystemTools::cmTarCompression compress = cmSystemTools::TarCompressNone;
3390 auto typeIt = compressionTypeMap.find(parsedArgs.Compression);
3391 if (typeIt != compressionTypeMap.end()) {
3392 compress = typeIt->second;
3393 } else if (!parsedArgs.Compression.empty()) {
3394 status.SetError(cmStrCat("compression type ", parsedArgs.Compression,
3395 " is not supported"));
3396 cmSystemTools::SetFatalErrorOccurred();
3400 int compressionLevel = 0;
3401 if (!parsedArgs.CompressionLevel.empty()) {
3402 if (parsedArgs.CompressionLevel.size() != 1 &&
3403 !std::isdigit(parsedArgs.CompressionLevel[0])) {
3404 status.SetError(cmStrCat("compression level ",
3405 parsedArgs.CompressionLevel,
3406 " should be in range 0 to 9"));
3407 cmSystemTools::SetFatalErrorOccurred();
3410 compressionLevel = std::stoi(parsedArgs.CompressionLevel);
3411 if (compressionLevel < 0 || compressionLevel > 9) {
3412 status.SetError(cmStrCat("compression level ",
3413 parsedArgs.CompressionLevel,
3414 " should be in range 0 to 9"));
3415 cmSystemTools::SetFatalErrorOccurred();
3418 if (compress == cmSystemTools::TarCompressNone) {
3419 status.SetError(cmStrCat("compression level is not supported for "
3420 "compression \"None\"",
3421 parsedArgs.Compression));
3422 cmSystemTools::SetFatalErrorOccurred();
3427 if (parsedArgs.Paths.empty()) {
3428 status.SetError("ARCHIVE_CREATE requires a non-empty list of PATHS");
3429 cmSystemTools::SetFatalErrorOccurred();
3433 if (!cmSystemTools::CreateTar(parsedArgs.Output, parsedArgs.Paths, compress,
3434 parsedArgs.Verbose, parsedArgs.MTime,
3435 parsedArgs.Format, compressionLevel)) {
3436 status.SetError(cmStrCat("failed to compress: ", parsedArgs.Output));
3437 cmSystemTools::SetFatalErrorOccurred();
3444 bool HandleArchiveExtractCommand(std::vector<std::string> const& args,
3445 cmExecutionStatus& status)
3447 struct Arguments : public ArgumentParser::ParseResult
3450 bool Verbose = false;
3451 bool ListOnly = false;
3452 std::string Destination;
3453 ArgumentParser::MaybeEmpty<std::vector<std::string>> Patterns;
3457 static auto const parser = cmArgumentParser<Arguments>{}
3458 .Bind("INPUT"_s, &Arguments::Input)
3459 .Bind("VERBOSE"_s, &Arguments::Verbose)
3460 .Bind("LIST_ONLY"_s, &Arguments::ListOnly)
3461 .Bind("DESTINATION"_s, &Arguments::Destination)
3462 .Bind("PATTERNS"_s, &Arguments::Patterns)
3463 .Bind("TOUCH"_s, &Arguments::Touch);
3465 std::vector<std::string> unrecognizedArguments;
3467 parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments);
3468 auto argIt = unrecognizedArguments.begin();
3469 if (argIt != unrecognizedArguments.end()) {
3470 status.SetError(cmStrCat("Unrecognized argument: \"", *argIt, "\""));
3471 cmSystemTools::SetFatalErrorOccurred();
3475 if (parsedArgs.MaybeReportError(status.GetMakefile())) {
3476 cmSystemTools::SetFatalErrorOccurred();
3480 std::string inFile = parsedArgs.Input;
3482 if (parsedArgs.ListOnly) {
3483 if (!cmSystemTools::ListTar(inFile, parsedArgs.Patterns,
3484 parsedArgs.Verbose)) {
3485 status.SetError(cmStrCat("failed to list: ", inFile));
3486 cmSystemTools::SetFatalErrorOccurred();
3490 std::string destDir = status.GetMakefile().GetCurrentBinaryDirectory();
3491 if (!parsedArgs.Destination.empty()) {
3492 if (cmSystemTools::FileIsFullPath(parsedArgs.Destination)) {
3493 destDir = parsedArgs.Destination;
3495 destDir = cmStrCat(destDir, "/", parsedArgs.Destination);
3498 if (!cmSystemTools::MakeDirectory(destDir)) {
3499 status.SetError(cmStrCat("failed to create directory: ", destDir));
3500 cmSystemTools::SetFatalErrorOccurred();
3504 if (!cmSystemTools::FileIsFullPath(inFile)) {
3506 cmStrCat(cmSystemTools::GetCurrentWorkingDirectory(), "/", inFile);
3510 cmWorkingDirectory workdir(destDir);
3511 if (workdir.Failed()) {
3513 cmStrCat("failed to change working directory to: ", destDir));
3514 cmSystemTools::SetFatalErrorOccurred();
3518 if (!cmSystemTools::ExtractTar(
3519 inFile, parsedArgs.Patterns,
3520 parsedArgs.Touch ? cmSystemTools::cmTarExtractTimestamps::No
3521 : cmSystemTools::cmTarExtractTimestamps::Yes,
3522 parsedArgs.Verbose)) {
3523 status.SetError(cmStrCat("failed to extract: ", inFile));
3524 cmSystemTools::SetFatalErrorOccurred();
3532 bool ValidateAndConvertPermissions(
3533 cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>> const&
3535 mode_t& perms, cmExecutionStatus& status)
3540 for (const auto& i : *permissions) {
3541 if (!cmFSPermissions::stringToModeT(i, perms)) {
3542 status.SetError(i + " is an invalid permission specifier");
3543 cmSystemTools::SetFatalErrorOccurred();
3550 bool SetPermissions(const std::string& filename, const mode_t& perms,
3551 cmExecutionStatus& status)
3553 if (!cmSystemTools::SetPermissions(filename, perms)) {
3554 status.SetError("Failed to set permissions for " + filename);
3555 cmSystemTools::SetFatalErrorOccurred();
3561 bool HandleChmodCommandImpl(std::vector<std::string> const& args, bool recurse,
3562 cmExecutionStatus& status)
3567 cmsys::Glob globber;
3569 globber.SetRecurse(recurse);
3570 globber.SetRecurseListDirs(recurse);
3572 struct Arguments : public ArgumentParser::ParseResult
3574 cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>>
3576 cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>>
3578 cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>>
3579 DirectoryPermissions;
3582 static auto const parser =
3583 cmArgumentParser<Arguments>{}
3584 .Bind("PERMISSIONS"_s, &Arguments::Permissions)
3585 .Bind("FILE_PERMISSIONS"_s, &Arguments::FilePermissions)
3586 .Bind("DIRECTORY_PERMISSIONS"_s, &Arguments::DirectoryPermissions);
3588 std::vector<std::string> pathEntries;
3589 Arguments parsedArgs =
3590 parser.Parse(cmMakeRange(args).advance(1), &pathEntries);
3592 // check validity of arguments
3593 if (!parsedArgs.Permissions && !parsedArgs.FilePermissions &&
3594 !parsedArgs.DirectoryPermissions) // no permissions given
3596 status.SetError("No permissions given");
3597 cmSystemTools::SetFatalErrorOccurred();
3601 if (parsedArgs.Permissions && parsedArgs.FilePermissions &&
3602 parsedArgs.DirectoryPermissions) // all keywords are used
3604 status.SetError("Remove either PERMISSIONS or FILE_PERMISSIONS or "
3605 "DIRECTORY_PERMISSIONS from the invocation");
3606 cmSystemTools::SetFatalErrorOccurred();
3610 if (parsedArgs.MaybeReportError(status.GetMakefile())) {
3611 cmSystemTools::SetFatalErrorOccurred();
3615 // validate permissions
3616 bool validatePermissions =
3617 ValidateAndConvertPermissions(parsedArgs.Permissions, perms, status) &&
3618 ValidateAndConvertPermissions(parsedArgs.FilePermissions, fperms,
3620 ValidateAndConvertPermissions(parsedArgs.DirectoryPermissions, dperms,
3622 if (!validatePermissions) {
3626 std::vector<std::string> allPathEntries;
3629 std::vector<std::string> tempPathEntries;
3630 for (const auto& i : pathEntries) {
3631 if (cmSystemTools::FileIsDirectory(i)) {
3632 globber.FindFiles(i + "/*");
3633 tempPathEntries = globber.GetFiles();
3634 allPathEntries.insert(allPathEntries.end(), tempPathEntries.begin(),
3635 tempPathEntries.end());
3636 allPathEntries.emplace_back(i);
3638 allPathEntries.emplace_back(i); // We validate path entries below
3642 allPathEntries = std::move(pathEntries);
3646 for (const auto& i : allPathEntries) {
3647 if (!(cmSystemTools::FileExists(i) || cmSystemTools::FileIsDirectory(i))) {
3648 status.SetError(cmStrCat("does not exist:\n ", i));
3649 cmSystemTools::SetFatalErrorOccurred();
3653 if (cmSystemTools::FileExists(i, true)) {
3654 bool success = true;
3655 const mode_t& filePermissions =
3656 parsedArgs.FilePermissions ? fperms : perms;
3657 if (filePermissions) {
3658 success = SetPermissions(i, filePermissions, status);
3665 else if (cmSystemTools::FileIsDirectory(i)) {
3666 bool success = true;
3667 const mode_t& directoryPermissions =
3668 parsedArgs.DirectoryPermissions ? dperms : perms;
3669 if (directoryPermissions) {
3670 success = SetPermissions(i, directoryPermissions, status);
3681 bool HandleChmodCommand(std::vector<std::string> const& args,
3682 cmExecutionStatus& status)
3684 return HandleChmodCommandImpl(args, false, status);
3687 bool HandleChmodRecurseCommand(std::vector<std::string> const& args,
3688 cmExecutionStatus& status)
3690 return HandleChmodCommandImpl(args, true, status);
3695 bool cmFileCommand(std::vector<std::string> const& args,
3696 cmExecutionStatus& status)
3698 if (args.size() < 2) {
3699 status.SetError("must be called with at least two arguments.");
3703 static cmSubcommandTable const subcommand{
3704 { "WRITE"_s, HandleWriteCommand },
3705 { "APPEND"_s, HandleAppendCommand },
3706 { "DOWNLOAD"_s, HandleDownloadCommand },
3707 { "UPLOAD"_s, HandleUploadCommand },
3708 { "READ"_s, HandleReadCommand },
3709 { "MD5"_s, HandleHashCommand },
3710 { "SHA1"_s, HandleHashCommand },
3711 { "SHA224"_s, HandleHashCommand },
3712 { "SHA256"_s, HandleHashCommand },
3713 { "SHA384"_s, HandleHashCommand },
3714 { "SHA512"_s, HandleHashCommand },
3715 { "SHA3_224"_s, HandleHashCommand },
3716 { "SHA3_256"_s, HandleHashCommand },
3717 { "SHA3_384"_s, HandleHashCommand },
3718 { "SHA3_512"_s, HandleHashCommand },
3719 { "STRINGS"_s, HandleStringsCommand },
3720 { "GLOB"_s, HandleGlobCommand },
3721 { "GLOB_RECURSE"_s, HandleGlobRecurseCommand },
3722 { "MAKE_DIRECTORY"_s, HandleMakeDirectoryCommand },
3723 { "RENAME"_s, HandleRename },
3724 { "COPY_FILE"_s, HandleCopyFile },
3725 { "REMOVE"_s, HandleRemove },
3726 { "REMOVE_RECURSE"_s, HandleRemoveRecurse },
3727 { "COPY"_s, HandleCopyCommand },
3728 { "INSTALL"_s, HandleInstallCommand },
3729 { "DIFFERENT"_s, HandleDifferentCommand },
3730 { "RPATH_CHANGE"_s, HandleRPathChangeCommand },
3731 { "CHRPATH"_s, HandleRPathChangeCommand },
3732 { "RPATH_SET"_s, HandleRPathSetCommand },
3733 { "RPATH_CHECK"_s, HandleRPathCheckCommand },
3734 { "RPATH_REMOVE"_s, HandleRPathRemoveCommand },
3735 { "READ_ELF"_s, HandleReadElfCommand },
3736 { "REAL_PATH"_s, HandleRealPathCommand },
3737 { "RELATIVE_PATH"_s, HandleRelativePathCommand },
3738 { "TO_CMAKE_PATH"_s, HandleCMakePathCommand },
3739 { "TO_NATIVE_PATH"_s, HandleNativePathCommand },
3740 { "TOUCH"_s, HandleTouchCommand },
3741 { "TOUCH_NOCREATE"_s, HandleTouchNocreateCommand },
3742 { "TIMESTAMP"_s, HandleTimestampCommand },
3743 { "GENERATE"_s, HandleGenerateCommand },
3744 { "LOCK"_s, HandleLockCommand },
3745 { "SIZE"_s, HandleSizeCommand },
3746 { "READ_SYMLINK"_s, HandleReadSymlinkCommand },
3747 { "CREATE_LINK"_s, HandleCreateLinkCommand },
3748 { "GET_RUNTIME_DEPENDENCIES"_s, HandleGetRuntimeDependenciesCommand },
3749 { "CONFIGURE"_s, HandleConfigureCommand },
3750 { "ARCHIVE_CREATE"_s, HandleArchiveCreateCommand },
3751 { "ARCHIVE_EXTRACT"_s, HandleArchiveExtractCommand },
3752 { "CHMOD"_s, HandleChmodCommand },
3753 { "CHMOD_RECURSE"_s, HandleChmodRecurseCommand },
3756 return subcommand(args[0], args, status);