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 "cmAlgorithms.h"
32 #include "cmArgumentParser.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));
177 std::string fileName = fileNameArg;
178 if (!cmsys::SystemTools::FileIsFullPath(fileName)) {
179 fileName = cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/',
183 // Open the specified file.
184 #if defined(_WIN32) || defined(__CYGWIN__)
185 cmsys::ifstream file(fileName.c_str(),
186 arguments.Hex ? (std::ios::binary | std::ios::in)
189 cmsys::ifstream file(fileName.c_str());
194 cmStrCat("failed to open for reading (",
195 cmSystemTools::GetLastSystemError(), "):\n ", fileName);
196 status.SetError(error);
201 std::string::size_type sizeLimit = std::string::npos;
202 if (!arguments.Limit.empty()) {
203 unsigned long long limit;
204 if (cmStrToULongLong(arguments.Limit, &limit)) {
205 sizeLimit = static_cast<std::string::size_type>(limit);
209 // is there an offset?
210 cmsys::ifstream::off_type offset = 0;
211 if (!arguments.Offset.empty()) {
213 if (cmStrToLongLong(arguments.Offset, &off)) {
214 offset = static_cast<cmsys::ifstream::off_type>(off);
218 file.seekg(offset, std::ios::beg); // explicit ios::beg for IBM VisualAge 6
223 // Convert part of the file into hex code
225 while ((sizeLimit > 0) && (file.get(c))) {
227 snprintf(hex, sizeof(hex), "%.2x", c & 0xff);
233 bool has_newline = false;
236 cmSystemTools::GetLineFromStream(file, line, &has_newline, sizeLimit)) {
237 sizeLimit = sizeLimit - line.size();
238 if (has_newline && sizeLimit > 0) {
247 status.GetMakefile().AddDefinition(variable, output);
251 bool HandleHashCommand(std::vector<std::string> const& args,
252 cmExecutionStatus& status)
254 #if !defined(CMAKE_BOOTSTRAP)
255 if (args.size() != 3) {
257 cmStrCat(args[0], " requires a file name and output variable"));
261 std::unique_ptr<cmCryptoHash> hash(cmCryptoHash::New(args[0]));
263 std::string out = hash->HashFile(args[1]);
265 status.GetMakefile().AddDefinition(args[2], out);
268 status.SetError(cmStrCat(args[0], " failed to read file \"", args[1],
269 "\": ", cmSystemTools::GetLastSystemError()));
273 status.SetError(cmStrCat(args[0], " not available during bootstrap"));
278 bool HandleStringsCommand(std::vector<std::string> const& args,
279 cmExecutionStatus& status)
281 if (args.size() < 3) {
282 status.SetError("STRINGS requires a file name and output variable");
286 // Get the file to read.
287 std::string fileName = args[1];
288 if (!cmsys::SystemTools::FileIsFullPath(fileName)) {
290 cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[1]);
293 // Get the variable in which to store the results.
294 std::string const& outVar = args[2];
296 // Parse the options.
309 unsigned int minlen = 0;
310 unsigned int maxlen = 0;
311 int limit_input = -1;
312 int limit_output = -1;
313 unsigned int limit_count = 0;
314 cmsys::RegularExpression regex;
315 bool have_regex = false;
316 bool newline_consume = false;
317 bool hex_conversion_enabled = true;
320 encoding_none = cmsys::FStream::BOM_None,
321 encoding_utf8 = cmsys::FStream::BOM_UTF8,
322 encoding_utf16le = cmsys::FStream::BOM_UTF16LE,
323 encoding_utf16be = cmsys::FStream::BOM_UTF16BE,
324 encoding_utf32le = cmsys::FStream::BOM_UTF32LE,
325 encoding_utf32be = cmsys::FStream::BOM_UTF32BE
327 int encoding = encoding_none;
328 int arg_mode = arg_none;
329 for (unsigned int i = 3; i < args.size(); ++i) {
330 if (args[i] == "LIMIT_INPUT") {
331 arg_mode = arg_limit_input;
332 } else if (args[i] == "LIMIT_OUTPUT") {
333 arg_mode = arg_limit_output;
334 } else if (args[i] == "LIMIT_COUNT") {
335 arg_mode = arg_limit_count;
336 } else if (args[i] == "LENGTH_MINIMUM") {
337 arg_mode = arg_length_minimum;
338 } else if (args[i] == "LENGTH_MAXIMUM") {
339 arg_mode = arg_length_maximum;
340 } else if (args[i] == "REGEX") {
341 arg_mode = arg_regex;
342 } else if (args[i] == "NEWLINE_CONSUME") {
343 newline_consume = true;
345 } else if (args[i] == "NO_HEX_CONVERSION") {
346 hex_conversion_enabled = false;
348 } else if (args[i] == "ENCODING") {
349 arg_mode = arg_encoding;
350 } else if (arg_mode == arg_limit_input) {
351 if (sscanf(args[i].c_str(), "%d", &limit_input) != 1 ||
353 status.SetError(cmStrCat("STRINGS option LIMIT_INPUT value \"",
354 args[i], "\" is not an unsigned integer."));
358 } else if (arg_mode == arg_limit_output) {
359 if (sscanf(args[i].c_str(), "%d", &limit_output) != 1 ||
361 status.SetError(cmStrCat("STRINGS option LIMIT_OUTPUT value \"",
362 args[i], "\" is not an unsigned integer."));
366 } else if (arg_mode == arg_limit_count) {
368 if (sscanf(args[i].c_str(), "%d", &count) != 1 || count < 0) {
369 status.SetError(cmStrCat("STRINGS option LIMIT_COUNT value \"",
370 args[i], "\" is not an unsigned integer."));
375 } else if (arg_mode == arg_length_minimum) {
377 if (sscanf(args[i].c_str(), "%d", &len) != 1 || len < 0) {
378 status.SetError(cmStrCat("STRINGS option LENGTH_MINIMUM value \"",
379 args[i], "\" is not an unsigned integer."));
384 } else if (arg_mode == arg_length_maximum) {
386 if (sscanf(args[i].c_str(), "%d", &len) != 1 || len < 0) {
387 status.SetError(cmStrCat("STRINGS option LENGTH_MAXIMUM value \"",
388 args[i], "\" is not an unsigned integer."));
393 } else if (arg_mode == arg_regex) {
394 if (!regex.compile(args[i])) {
395 status.SetError(cmStrCat("STRINGS option REGEX value \"", args[i],
396 "\" could not be compiled."));
401 } else if (arg_mode == arg_encoding) {
402 if (args[i] == "UTF-8") {
403 encoding = encoding_utf8;
404 } else if (args[i] == "UTF-16LE") {
405 encoding = encoding_utf16le;
406 } else if (args[i] == "UTF-16BE") {
407 encoding = encoding_utf16be;
408 } else if (args[i] == "UTF-32LE") {
409 encoding = encoding_utf32le;
410 } else if (args[i] == "UTF-32BE") {
411 encoding = encoding_utf32be;
413 status.SetError(cmStrCat("STRINGS option ENCODING \"", args[i],
414 "\" not recognized."));
420 cmStrCat("STRINGS given unknown argument \"", args[i], "\""));
425 if (hex_conversion_enabled) {
426 // TODO: should work without temp file, but just on a memory buffer
427 std::string binaryFileName =
428 cmStrCat(status.GetMakefile().GetCurrentBinaryDirectory(),
429 "/CMakeFiles/FileCommandStringsBinaryFile");
430 if (cmHexFileConverter::TryConvert(fileName, binaryFileName)) {
431 fileName = binaryFileName;
435 // Open the specified file.
436 #if defined(_WIN32) || defined(__CYGWIN__)
437 cmsys::ifstream fin(fileName.c_str(), std::ios::in | std::ios::binary);
439 cmsys::ifstream fin(fileName.c_str());
443 cmStrCat("STRINGS file \"", fileName, "\" cannot be read."));
447 // If BOM is found and encoding was not specified, use the BOM
448 int bom_found = cmsys::FStream::ReadBOM(fin);
449 if (encoding == encoding_none && bom_found != cmsys::FStream::BOM_None) {
450 encoding = bom_found;
453 unsigned int bytes_rem = 0;
454 if (encoding == encoding_utf16le || encoding == encoding_utf16be) {
457 if (encoding == encoding_utf32le || encoding == encoding_utf32be) {
461 // Parse strings out of the file.
463 std::vector<std::string> strings;
465 while ((!limit_count || strings.size() < limit_count) &&
466 (limit_input < 0 || static_cast<int>(fin.tellg()) < limit_input) &&
468 std::string current_str;
471 for (unsigned int i = 0; i < bytes_rem; ++i) {
474 fin.putback(static_cast<char>(c1));
479 if (encoding == encoding_utf16le) {
480 c = ((c & 0xFF) << 8) | ((c & 0xFF00) >> 8);
481 } else if (encoding == encoding_utf32le) {
482 c = (((c & 0xFF) << 24) | ((c & 0xFF00) << 8) | ((c & 0xFF0000) >> 8) |
483 ((c & 0xFF000000) >> 24));
487 // Ignore CR character to make output always have UNIX newlines.
491 if (c >= 0 && c <= 0xFF &&
492 (isprint(c) || c == '\t' || (c == '\n' && newline_consume))) {
493 // This is an ASCII character that may be part of a string.
494 // Cast added to avoid compiler warning. Cast is ok because
495 // c is guaranteed to fit in char by the above if...
496 current_str += static_cast<char>(c);
497 } else if (encoding == encoding_utf8) {
498 // Check for UTF-8 encoded string (up to 4 octets)
499 static const unsigned char utf8_check_table[3][2] = {
505 // how many octets are there?
506 unsigned int num_utf8_bytes = 0;
507 for (unsigned int j = 0; num_utf8_bytes == 0 && j < 3; j++) {
508 if ((c & utf8_check_table[j][0]) == utf8_check_table[j][1]) {
509 num_utf8_bytes = j + 2;
513 // get subsequent octets and check that they are valid
514 for (unsigned int j = 0; j < num_utf8_bytes; j++) {
517 if (!fin || (c & 0xC0) != 0x80) {
518 fin.putback(static_cast<char>(c));
522 current_str += static_cast<char>(c);
525 // if this was an invalid utf8 sequence, discard the data, and put
526 // back subsequent characters
527 if ((current_str.length() != num_utf8_bytes)) {
528 for (unsigned int j = 0; j < current_str.size() - 1; j++) {
529 fin.putback(current_str[current_str.size() - 1 - j]);
535 if (c == '\n' && !newline_consume) {
536 // The current line has been terminated. Check if the current
537 // string matches the requirements. The length may now be as
538 // low as zero since blank lines are allowed.
539 if (s.length() >= minlen && (!have_regex || regex.find(s))) {
540 output_size += static_cast<int>(s.size()) + 1;
541 if (limit_output >= 0 && output_size >= limit_output) {
545 strings.push_back(s);
548 // Reset the string to empty.
550 } else if (current_str.empty()) {
551 // A non-string character has been found. Check if the current
552 // string matches the requirements. We require that the length
553 // be at least one no matter what the user specified.
554 if (s.length() >= minlen && !s.empty() &&
555 (!have_regex || regex.find(s))) {
556 output_size += static_cast<int>(s.size()) + 1;
557 if (limit_output >= 0 && output_size >= limit_output) {
561 strings.push_back(s);
564 // Reset the string to empty.
570 if (maxlen > 0 && s.size() == maxlen) {
571 // Terminate a string if the maximum length is reached.
572 if (s.length() >= minlen && (!have_regex || regex.find(s))) {
573 output_size += static_cast<int>(s.size()) + 1;
574 if (limit_output >= 0 && output_size >= limit_output) {
578 strings.push_back(s);
584 // If there is a non-empty current string we have hit the end of the
585 // input file or the input size limit. Check if the current string
586 // matches the requirements.
587 if ((!limit_count || strings.size() < limit_count) && !s.empty() &&
588 s.length() >= minlen && (!have_regex || regex.find(s))) {
589 output_size += static_cast<int>(s.size()) + 1;
590 if (limit_output < 0 || output_size < limit_output) {
591 strings.push_back(s);
595 // Encode the result in a CMake list.
596 const char* sep = "";
598 for (std::string const& sr : strings) {
599 // Separate the strings in the output to make it a list.
603 // Store the string in the output, but escape semicolons to
604 // make sure it is a list.
613 // Save the output in a makefile variable.
614 status.GetMakefile().AddDefinition(outVar, output);
618 bool HandleGlobImpl(std::vector<std::string> const& args, bool recurse,
619 cmExecutionStatus& status)
621 // File commands has at least one argument
622 assert(args.size() > 1);
624 auto i = args.begin();
626 i++; // Get rid of subcommand
628 std::string variable = *i;
631 g.SetRecurse(recurse);
633 bool explicitFollowSymlinks = false;
634 cmPolicies::PolicyStatus policyStatus =
635 status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0009);
637 switch (policyStatus) {
638 case cmPolicies::REQUIRED_IF_USED:
639 case cmPolicies::REQUIRED_ALWAYS:
640 case cmPolicies::NEW:
641 g.RecurseThroughSymlinksOff();
643 case cmPolicies::WARN:
645 case cmPolicies::OLD:
646 g.RecurseThroughSymlinksOn();
651 cmake* cm = status.GetMakefile().GetCMakeInstance();
652 std::vector<std::string> files;
653 bool configureDepends = false;
654 bool warnConfigureLate = false;
655 bool warnFollowedSymlinks = false;
656 const cmake::WorkingMode workingMode = cm->GetWorkingMode();
657 while (i != args.end()) {
658 if (*i == "LIST_DIRECTORIES") {
659 ++i; // skip LIST_DIRECTORIES
660 if (i != args.end()) {
663 g.SetRecurseListDirs(true);
664 } else if (cmIsOff(*i)) {
665 g.SetListDirs(false);
666 g.SetRecurseListDirs(false);
668 status.SetError("LIST_DIRECTORIES missing bool value.");
673 status.SetError("LIST_DIRECTORIES missing bool value.");
676 } else if (*i == "FOLLOW_SYMLINKS") {
677 ++i; // skip FOLLOW_SYMLINKS
679 explicitFollowSymlinks = true;
680 g.RecurseThroughSymlinksOn();
681 if (i == args.end()) {
683 "GLOB_RECURSE requires a glob expression after FOLLOW_SYMLINKS.");
687 } else if (*i == "RELATIVE") {
688 ++i; // skip RELATIVE
689 if (i == args.end()) {
690 status.SetError("GLOB requires a directory after the RELATIVE tag.");
693 g.SetRelative(i->c_str());
695 if (i == args.end()) {
697 "GLOB requires a glob expression after the directory.");
700 } else if (*i == "CONFIGURE_DEPENDS") {
701 // Generated build system depends on glob results
702 if (!configureDepends && warnConfigureLate) {
703 status.GetMakefile().IssueMessage(
704 MessageType::AUTHOR_WARNING,
705 "CONFIGURE_DEPENDS flag was given after a glob expression was "
706 "already evaluated.");
708 if (workingMode != cmake::NORMAL_MODE) {
709 status.GetMakefile().IssueMessage(
710 MessageType::FATAL_ERROR,
711 "CONFIGURE_DEPENDS is invalid for script and find package modes.");
714 configureDepends = true;
716 if (i == args.end()) {
718 "GLOB requires a glob expression after CONFIGURE_DEPENDS.");
722 std::string expr = *i;
723 if (!cmsys::SystemTools::FileIsFullPath(*i)) {
724 expr = status.GetMakefile().GetCurrentSourceDirectory();
725 // Handle script mode
733 cmsys::Glob::GlobMessages globMessages;
734 g.FindFiles(expr, &globMessages);
736 if (!globMessages.empty()) {
737 bool shouldExit = false;
738 for (cmsys::Glob::Message const& globMessage : globMessages) {
739 if (globMessage.type == cmsys::Glob::cyclicRecursion) {
740 status.GetMakefile().IssueMessage(
741 MessageType::AUTHOR_WARNING,
742 "Cyclic recursion detected while globbing for '" + *i + "':\n" +
743 globMessage.content);
744 } else if (globMessage.type == cmsys::Glob::error) {
745 status.GetMakefile().IssueMessage(
746 MessageType::FATAL_ERROR,
747 "Error has occurred while globbing for '" + *i + "' - " +
748 globMessage.content);
750 } else if (cm->GetDebugOutput() || cm->GetTrace()) {
751 status.GetMakefile().IssueMessage(
753 cmStrCat("Globbing for\n ", *i, "\nEncountered an error:\n ",
754 globMessage.content));
762 if (recurse && !explicitFollowSymlinks &&
763 g.GetFollowedSymlinkCount() != 0) {
764 warnFollowedSymlinks = true;
767 std::vector<std::string>& foundFiles = g.GetFiles();
768 cm::append(files, foundFiles);
770 if (configureDepends) {
771 std::sort(foundFiles.begin(), foundFiles.end());
772 foundFiles.erase(std::unique(foundFiles.begin(), foundFiles.end()),
774 cm->AddGlobCacheEntry(
775 recurse, (recurse ? g.GetRecurseListDirs() : g.GetListDirs()),
776 (recurse ? g.GetRecurseThroughSymlinks() : false),
777 (g.GetRelative() ? g.GetRelative() : ""), expr, foundFiles, variable,
778 status.GetMakefile().GetBacktrace());
780 warnConfigureLate = true;
786 switch (policyStatus) {
787 case cmPolicies::REQUIRED_IF_USED:
788 case cmPolicies::REQUIRED_ALWAYS:
789 case cmPolicies::NEW:
790 // Correct behavior, yay!
792 case cmPolicies::OLD:
793 // Probably not really the expected behavior, but the author explicitly
794 // asked for the old behavior... no warning.
795 case cmPolicies::WARN:
796 // Possibly unexpected old behavior *and* we actually traversed
797 // symlinks without being explicitly asked to: warn the author.
798 if (warnFollowedSymlinks) {
799 status.GetMakefile().IssueMessage(
800 MessageType::AUTHOR_WARNING,
801 cmPolicies::GetPolicyWarning(cmPolicies::CMP0009));
806 std::sort(files.begin(), files.end());
807 files.erase(std::unique(files.begin(), files.end()), files.end());
808 status.GetMakefile().AddDefinition(variable, cmJoin(files, ";"));
812 bool HandleGlobCommand(std::vector<std::string> const& args,
813 cmExecutionStatus& status)
815 return HandleGlobImpl(args, false, status);
818 bool HandleGlobRecurseCommand(std::vector<std::string> const& args,
819 cmExecutionStatus& status)
821 return HandleGlobImpl(args, true, status);
824 bool HandleMakeDirectoryCommand(std::vector<std::string> const& args,
825 cmExecutionStatus& status)
827 // File command has at least one argument
828 assert(args.size() > 1);
831 for (std::string const& arg :
832 cmMakeRange(args).advance(1)) // Get rid of subcommand
834 const std::string* cdir = &arg;
835 if (!cmsys::SystemTools::FileIsFullPath(arg)) {
837 cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', arg);
840 if (!status.GetMakefile().CanIWriteThisFile(*cdir)) {
841 std::string e = "attempted to create a directory: " + *cdir +
842 " into a source directory.";
844 cmSystemTools::SetFatalErrorOccurred();
847 if (!cmSystemTools::MakeDirectory(*cdir)) {
848 std::string error = "problem creating directory: " + *cdir;
849 status.SetError(error);
856 bool HandleTouchImpl(std::vector<std::string> const& args, bool create,
857 cmExecutionStatus& status)
859 // File command has at least one argument
860 assert(args.size() > 1);
862 for (std::string const& arg :
863 cmMakeRange(args).advance(1)) // Get rid of subcommand
865 std::string tfile = arg;
866 if (!cmsys::SystemTools::FileIsFullPath(tfile)) {
868 cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', arg);
870 if (!status.GetMakefile().CanIWriteThisFile(tfile)) {
872 "attempted to touch a file: " + tfile + " in a source directory.";
874 cmSystemTools::SetFatalErrorOccurred();
877 if (!cmSystemTools::Touch(tfile, create)) {
878 std::string error = "problem touching file: " + tfile;
879 status.SetError(error);
886 bool HandleTouchCommand(std::vector<std::string> const& args,
887 cmExecutionStatus& status)
889 return HandleTouchImpl(args, true, status);
892 bool HandleTouchNocreateCommand(std::vector<std::string> const& args,
893 cmExecutionStatus& status)
895 return HandleTouchImpl(args, false, status);
898 bool HandleDifferentCommand(std::vector<std::string> const& args,
899 cmExecutionStatus& status)
902 FILE(DIFFERENT <variable> FILES <lhs> <rhs>)
905 // Evaluate arguments.
906 const char* file_lhs = nullptr;
907 const char* file_rhs = nullptr;
908 const char* var = nullptr;
916 Doing doing = DoingVar;
917 for (unsigned int i = 1; i < args.size(); ++i) {
918 if (args[i] == "FILES") {
919 doing = DoingFileLHS;
920 } else if (doing == DoingVar) {
921 var = args[i].c_str();
923 } else if (doing == DoingFileLHS) {
924 file_lhs = args[i].c_str();
925 doing = DoingFileRHS;
926 } else if (doing == DoingFileRHS) {
927 file_rhs = args[i].c_str();
930 status.SetError(cmStrCat("DIFFERENT given unknown argument ", args[i]));
935 status.SetError("DIFFERENT not given result variable name.");
938 if (!file_lhs || !file_rhs) {
939 status.SetError("DIFFERENT not given FILES option with two file names.");
943 // Compare the files.
945 cmSystemTools::FilesDiffer(file_lhs, file_rhs) ? "1" : "0";
946 status.GetMakefile().AddDefinition(var, result);
950 bool HandleCopyCommand(std::vector<std::string> const& args,
951 cmExecutionStatus& status)
953 cmFileCopier copier(status);
954 return copier.Run(args);
957 bool HandleRPathChangeCommand(std::vector<std::string> const& args,
958 cmExecutionStatus& status)
960 // Evaluate arguments.
962 std::string oldRPath;
963 std::string newRPath;
964 bool removeEnvironmentRPath = false;
965 cmArgumentParser<void> parser;
966 std::vector<std::string> unknownArgs;
967 std::vector<std::string> missingArgs;
968 std::vector<std::string> parsedArgs;
969 parser.Bind("FILE"_s, file)
970 .Bind("OLD_RPATH"_s, oldRPath)
971 .Bind("NEW_RPATH"_s, newRPath)
972 .Bind("INSTALL_REMOVE_ENVIRONMENT_RPATH"_s, removeEnvironmentRPath);
973 parser.Parse(cmMakeRange(args).advance(1), &unknownArgs, &missingArgs,
975 if (!unknownArgs.empty()) {
977 cmStrCat("RPATH_CHANGE given unknown argument ", unknownArgs.front()));
980 if (!missingArgs.empty()) {
981 status.SetError(cmStrCat("RPATH_CHANGE \"", missingArgs.front(),
982 "\" argument not given value."));
986 status.SetError("RPATH_CHANGE not given FILE option.");
989 if (oldRPath.empty() &&
990 std::find(parsedArgs.begin(), parsedArgs.end(), "OLD_RPATH") ==
992 status.SetError("RPATH_CHANGE not given OLD_RPATH option.");
995 if (newRPath.empty() &&
996 std::find(parsedArgs.begin(), parsedArgs.end(), "NEW_RPATH") ==
998 status.SetError("RPATH_CHANGE not given NEW_RPATH option.");
1001 if (!cmSystemTools::FileExists(file, true)) {
1003 cmStrCat("RPATH_CHANGE given FILE \"", file, "\" that does not exist."));
1006 bool success = true;
1007 cmFileTimes const ft(file);
1011 if (!cmSystemTools::ChangeRPath(file, oldRPath, newRPath,
1012 removeEnvironmentRPath, &emsg, &changed)) {
1013 status.SetError(cmStrCat("RPATH_CHANGE could not write new RPATH:\n ",
1014 newRPath, "\nto the file:\n ", file, "\n",
1020 std::string message =
1021 cmStrCat("Set runtime path of \"", file, "\" to \"", newRPath, '"');
1022 status.GetMakefile().DisplayStatus(message, -1);
1029 bool HandleRPathSetCommand(std::vector<std::string> const& args,
1030 cmExecutionStatus& status)
1032 // Evaluate arguments.
1034 std::string newRPath;
1035 cmArgumentParser<void> parser;
1036 std::vector<std::string> unknownArgs;
1037 std::vector<std::string> missingArgs;
1038 std::vector<std::string> parsedArgs;
1039 parser.Bind("FILE"_s, file).Bind("NEW_RPATH"_s, newRPath);
1040 parser.Parse(cmMakeRange(args).advance(1), &unknownArgs, &missingArgs,
1042 if (!unknownArgs.empty()) {
1043 status.SetError(cmStrCat("RPATH_SET given unrecognized argument \"",
1044 unknownArgs.front(), "\"."));
1047 if (!missingArgs.empty()) {
1048 status.SetError(cmStrCat("RPATH_SET \"", missingArgs.front(),
1049 "\" argument not given value."));
1053 status.SetError("RPATH_SET not given FILE option.");
1056 if (newRPath.empty() &&
1057 std::find(parsedArgs.begin(), parsedArgs.end(), "NEW_RPATH") ==
1059 status.SetError("RPATH_SET not given NEW_RPATH option.");
1062 if (!cmSystemTools::FileExists(file, true)) {
1064 cmStrCat("RPATH_SET given FILE \"", file, "\" that does not exist."));
1067 bool success = true;
1068 cmFileTimes const ft(file);
1072 if (!cmSystemTools::SetRPath(file, newRPath, &emsg, &changed)) {
1073 status.SetError(cmStrCat("RPATH_SET could not write new RPATH:\n ",
1074 newRPath, "\nto the file:\n ", file, "\n",
1080 std::string message =
1081 cmStrCat("Set runtime path of \"", file, "\" to \"", newRPath, '"');
1082 status.GetMakefile().DisplayStatus(message, -1);
1089 bool HandleRPathRemoveCommand(std::vector<std::string> const& args,
1090 cmExecutionStatus& status)
1092 // Evaluate arguments.
1094 cmArgumentParser<void> parser;
1095 std::vector<std::string> unknownArgs;
1096 std::vector<std::string> missingArgs;
1097 parser.Bind("FILE"_s, file);
1098 parser.Parse(cmMakeRange(args).advance(1), &unknownArgs, &missingArgs);
1099 if (!unknownArgs.empty()) {
1101 cmStrCat("RPATH_REMOVE given unknown argument ", unknownArgs.front()));
1104 if (!missingArgs.empty()) {
1105 status.SetError(cmStrCat("RPATH_REMOVE \"", missingArgs.front(),
1106 "\" argument not given value."));
1110 status.SetError("RPATH_REMOVE not given FILE option.");
1113 if (!cmSystemTools::FileExists(file, true)) {
1115 cmStrCat("RPATH_REMOVE given FILE \"", file, "\" that does not exist."));
1118 bool success = true;
1119 cmFileTimes const ft(file);
1122 if (!cmSystemTools::RemoveRPath(file, &emsg, &removed)) {
1124 cmStrCat("RPATH_REMOVE could not remove RPATH from file: \n ", file,
1130 std::string message =
1131 cmStrCat("Removed runtime path from \"", file, '"');
1132 status.GetMakefile().DisplayStatus(message, -1);
1139 bool HandleRPathCheckCommand(std::vector<std::string> const& args,
1140 cmExecutionStatus& status)
1142 // Evaluate arguments.
1145 cmArgumentParser<void> parser;
1146 std::vector<std::string> unknownArgs;
1147 std::vector<std::string> missingArgs;
1148 std::vector<std::string> parsedArgs;
1149 parser.Bind("FILE"_s, file).Bind("RPATH"_s, rpath);
1150 parser.Parse(cmMakeRange(args).advance(1), &unknownArgs, &missingArgs,
1152 if (!unknownArgs.empty()) {
1154 cmStrCat("RPATH_CHECK given unknown argument ", unknownArgs.front()));
1157 if (!missingArgs.empty()) {
1158 status.SetError(cmStrCat("RPATH_CHECK \"", missingArgs.front(),
1159 "\" argument not given value."));
1163 status.SetError("RPATH_CHECK not given FILE option.");
1166 if (rpath.empty() &&
1167 std::find(parsedArgs.begin(), parsedArgs.end(), "RPATH") ==
1169 status.SetError("RPATH_CHECK not given RPATH option.");
1173 // If the file exists but does not have the desired RPath then
1174 // delete it. This is used during installation to re-install a file
1175 // if its RPath will change.
1176 if (cmSystemTools::FileExists(file, true) &&
1177 !cmSystemTools::CheckRPath(file, rpath)) {
1178 cmSystemTools::RemoveFile(file);
1184 bool HandleReadElfCommand(std::vector<std::string> const& args,
1185 cmExecutionStatus& status)
1187 if (args.size() < 4) {
1188 status.SetError("READ_ELF must be called with at least three additional "
1193 std::string const& fileNameArg = args[1];
1198 std::string RunPath;
1202 static auto const parser = cmArgumentParser<Arguments>{}
1203 .Bind("RPATH"_s, &Arguments::RPath)
1204 .Bind("RUNPATH"_s, &Arguments::RunPath)
1205 .Bind("CAPTURE_ERROR"_s, &Arguments::Error);
1206 Arguments const arguments = parser.Parse(cmMakeRange(args).advance(2));
1208 if (!cmSystemTools::FileExists(fileNameArg, true)) {
1209 status.SetError(cmStrCat("READ_ELF given FILE \"", fileNameArg,
1210 "\" that does not exist."));
1214 cmELF elf(fileNameArg.c_str());
1216 if (arguments.Error.empty()) {
1217 status.SetError(cmStrCat("READ_ELF given FILE:\n ", fileNameArg,
1218 "\nthat is not a valid ELF file."));
1221 status.GetMakefile().AddDefinition(arguments.Error,
1222 "not a valid ELF file");
1226 if (!arguments.RPath.empty()) {
1227 if (cmELF::StringEntry const* se_rpath = elf.GetRPath()) {
1228 std::string rpath(se_rpath->Value);
1229 std::replace(rpath.begin(), rpath.end(), ':', ';');
1230 status.GetMakefile().AddDefinition(arguments.RPath, rpath);
1233 if (!arguments.RunPath.empty()) {
1234 if (cmELF::StringEntry const* se_runpath = elf.GetRunPath()) {
1235 std::string runpath(se_runpath->Value);
1236 std::replace(runpath.begin(), runpath.end(), ':', ';');
1237 status.GetMakefile().AddDefinition(arguments.RunPath, runpath);
1244 bool HandleInstallCommand(std::vector<std::string> const& args,
1245 cmExecutionStatus& status)
1247 cmFileInstaller installer(status);
1248 return installer.Run(args);
1251 bool HandleRealPathCommand(std::vector<std::string> const& args,
1252 cmExecutionStatus& status)
1254 if (args.size() < 3) {
1255 status.SetError("REAL_PATH requires a path and an output variable");
1261 std::string BaseDirectory;
1262 bool ExpandTilde = false;
1264 static auto const parser =
1265 cmArgumentParser<Arguments>{}
1266 .Bind("BASE_DIRECTORY"_s, &Arguments::BaseDirectory)
1267 .Bind("EXPAND_TILDE"_s, &Arguments::ExpandTilde);
1269 std::vector<std::string> unparsedArguments;
1270 std::vector<std::string> keywordsMissingValue;
1271 std::vector<std::string> parsedKeywords;
1273 parser.Parse(cmMakeRange(args).advance(3), &unparsedArguments,
1274 &keywordsMissingValue, &parsedKeywords);
1276 if (!unparsedArguments.empty()) {
1277 status.SetError("REAL_PATH called with unexpected arguments");
1280 if (!keywordsMissingValue.empty()) {
1281 status.SetError("BASE_DIRECTORY requires a value");
1285 if (parsedKeywords.empty()) {
1286 arguments.BaseDirectory = status.GetMakefile().GetCurrentSourceDirectory();
1289 auto input = args[1];
1290 if (arguments.ExpandTilde && !input.empty()) {
1291 if (input[0] == '~' && (input.length() == 1 || input[1] == '/')) {
1294 #if defined(_WIN32) && !defined(__CYGWIN__)
1295 cmSystemTools::GetEnv("USERPROFILE", home) ||
1297 cmSystemTools::GetEnv("HOME", home)) {
1298 input.replace(0, 1, home);
1303 cmCMakePath path(input, cmCMakePath::auto_format);
1304 path = path.Absolute(arguments.BaseDirectory).Normal();
1305 auto realPath = cmSystemTools::GetRealPath(path.GenericString());
1307 status.GetMakefile().AddDefinition(args[2], realPath);
1312 bool HandleRelativePathCommand(std::vector<std::string> const& args,
1313 cmExecutionStatus& status)
1315 if (args.size() != 4) {
1316 status.SetError("RELATIVE_PATH called with incorrect number of arguments");
1320 const std::string& outVar = args[1];
1321 const std::string& directoryName = args[2];
1322 const std::string& fileName = args[3];
1324 if (!cmSystemTools::FileIsFullPath(directoryName)) {
1325 std::string errstring =
1326 "RELATIVE_PATH must be passed a full path to the directory: " +
1328 status.SetError(errstring);
1331 if (!cmSystemTools::FileIsFullPath(fileName)) {
1332 std::string errstring =
1333 "RELATIVE_PATH must be passed a full path to the file: " + fileName;
1334 status.SetError(errstring);
1338 std::string res = cmSystemTools::RelativePath(directoryName, fileName);
1339 status.GetMakefile().AddDefinition(outVar, res);
1343 bool HandleRename(std::vector<std::string> const& args,
1344 cmExecutionStatus& status)
1346 if (args.size() < 3) {
1347 status.SetError("RENAME must be called with at least two additional "
1352 // Compute full path for old and new names.
1353 std::string oldname = args[1];
1354 if (!cmsys::SystemTools::FileIsFullPath(oldname)) {
1356 cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[1]);
1358 std::string newname = args[2];
1359 if (!cmsys::SystemTools::FileIsFullPath(newname)) {
1361 cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[2]);
1366 bool NoReplace = false;
1370 static auto const parser = cmArgumentParser<Arguments>{}
1371 .Bind("NO_REPLACE"_s, &Arguments::NoReplace)
1372 .Bind("RESULT"_s, &Arguments::Result);
1374 std::vector<std::string> unconsumedArgs;
1375 Arguments const arguments =
1376 parser.Parse(cmMakeRange(args).advance(3), &unconsumedArgs);
1377 if (!unconsumedArgs.empty()) {
1378 status.SetError("RENAME unknown argument:\n " + unconsumedArgs.front());
1383 switch (cmSystemTools::RenameFile(oldname, newname,
1385 ? cmSystemTools::Replace::No
1386 : cmSystemTools::Replace::Yes,
1388 case cmSystemTools::RenameResult::Success:
1389 if (!arguments.Result.empty()) {
1390 status.GetMakefile().AddDefinition(arguments.Result, "0");
1393 case cmSystemTools::RenameResult::NoReplace:
1394 if (!arguments.Result.empty()) {
1397 err = "path not replaced";
1400 case cmSystemTools::RenameResult::Failure:
1401 if (!arguments.Result.empty()) {
1402 status.GetMakefile().AddDefinition(arguments.Result, err);
1407 status.SetError(cmStrCat("RENAME failed to rename\n ", oldname, "\nto\n ",
1408 newname, "\nbecause: ", err, "\n"));
1412 bool HandleCopyFile(std::vector<std::string> const& args,
1413 cmExecutionStatus& status)
1415 if (args.size() < 3) {
1416 status.SetError("COPY_FILE must be called with at least two additional "
1421 // Compute full path for old and new names.
1422 std::string oldname = args[1];
1423 if (!cmsys::SystemTools::FileIsFullPath(oldname)) {
1425 cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[1]);
1427 std::string newname = args[2];
1428 if (!cmsys::SystemTools::FileIsFullPath(newname)) {
1430 cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[2]);
1435 bool OnlyIfDifferent = false;
1439 static auto const parser =
1440 cmArgumentParser<Arguments>{}
1441 .Bind("ONLY_IF_DIFFERENT"_s, &Arguments::OnlyIfDifferent)
1442 .Bind("RESULT"_s, &Arguments::Result);
1444 std::vector<std::string> unconsumedArgs;
1445 Arguments const arguments =
1446 parser.Parse(cmMakeRange(args).advance(3), &unconsumedArgs);
1447 if (!unconsumedArgs.empty()) {
1448 status.SetError("COPY_FILE unknown argument:\n " +
1449 unconsumedArgs.front());
1454 if (cmsys::SystemTools::FileIsDirectory(oldname)) {
1455 if (!arguments.Result.empty()) {
1456 status.GetMakefile().AddDefinition(arguments.Result,
1457 "cannot copy a directory");
1460 cmStrCat("COPY_FILE cannot copy a directory\n ", oldname));
1465 if (cmsys::SystemTools::FileIsDirectory(newname)) {
1466 if (!arguments.Result.empty()) {
1467 status.GetMakefile().AddDefinition(arguments.Result,
1468 "cannot copy to a directory");
1471 cmStrCat("COPY_FILE cannot copy to a directory\n ", newname));
1477 cmSystemTools::CopyWhen when;
1478 if (arguments.OnlyIfDifferent) {
1479 when = cmSystemTools::CopyWhen::OnlyIfDifferent;
1481 when = cmSystemTools::CopyWhen::Always;
1485 if (cmSystemTools::CopySingleFile(oldname, newname, when, &err) ==
1486 cmSystemTools::CopyResult::Success) {
1487 if (!arguments.Result.empty()) {
1488 status.GetMakefile().AddDefinition(arguments.Result, "0");
1491 if (!arguments.Result.empty()) {
1492 status.GetMakefile().AddDefinition(arguments.Result, err);
1494 status.SetError(cmStrCat("COPY_FILE failed to copy\n ", oldname,
1495 "\nto\n ", newname, "\nbecause: ", err, "\n"));
1503 bool HandleRemoveImpl(std::vector<std::string> const& args, bool recurse,
1504 cmExecutionStatus& status)
1506 for (std::string const& arg :
1507 cmMakeRange(args).advance(1)) // Get rid of subcommand
1509 std::string fileName = arg;
1510 if (fileName.empty()) {
1511 std::string const r = recurse ? "REMOVE_RECURSE" : "REMOVE";
1512 status.GetMakefile().IssueMessage(
1513 MessageType::AUTHOR_WARNING, "Ignoring empty file name in " + r + ".");
1516 if (!cmsys::SystemTools::FileIsFullPath(fileName)) {
1518 cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', arg);
1521 if (cmSystemTools::FileIsDirectory(fileName) &&
1522 !cmSystemTools::FileIsSymlink(fileName) && recurse) {
1523 cmSystemTools::RepeatedRemoveDirectory(fileName);
1525 cmSystemTools::RemoveFile(fileName);
1531 bool HandleRemove(std::vector<std::string> const& args,
1532 cmExecutionStatus& status)
1534 return HandleRemoveImpl(args, false, status);
1537 bool HandleRemoveRecurse(std::vector<std::string> const& args,
1538 cmExecutionStatus& status)
1540 return HandleRemoveImpl(args, true, status);
1543 std::string ToNativePath(const std::string& path)
1545 const auto& outPath = cmSystemTools::ConvertToOutputPath(path);
1546 if (outPath.size() > 1 && outPath.front() == '\"' &&
1547 outPath.back() == '\"') {
1548 return outPath.substr(1, outPath.size() - 2);
1553 std::string ToCMakePath(const std::string& path)
1556 cmSystemTools::ConvertToUnixSlashes(temp);
1560 bool HandlePathCommand(std::vector<std::string> const& args,
1561 std::string (*convert)(std::string const&),
1562 cmExecutionStatus& status)
1564 if (args.size() != 3) {
1565 status.SetError("FILE([TO_CMAKE_PATH|TO_NATIVE_PATH] path result) must be "
1566 "called with exactly three arguments.");
1569 #if defined(_WIN32) && !defined(__CYGWIN__)
1574 std::vector<std::string> path = cmSystemTools::SplitString(args[1], pathSep);
1576 std::string value = cmJoin(cmMakeRange(path).transform(convert), ";");
1577 status.GetMakefile().AddDefinition(args[2], value);
1581 bool HandleCMakePathCommand(std::vector<std::string> const& args,
1582 cmExecutionStatus& status)
1584 return HandlePathCommand(args, ToCMakePath, status);
1587 bool HandleNativePathCommand(std::vector<std::string> const& args,
1588 cmExecutionStatus& status)
1590 return HandlePathCommand(args, ToNativePath, status);
1593 #if !defined(CMAKE_BOOTSTRAP)
1595 // Stuff for curl download/upload
1596 using cmFileCommandVectorOfChar = std::vector<char>;
1598 size_t cmWriteToFileCallback(void* ptr, size_t size, size_t nmemb, void* data)
1600 int realsize = static_cast<int>(size * nmemb);
1601 cmsys::ofstream* fout = static_cast<cmsys::ofstream*>(data);
1603 const char* chPtr = static_cast<char*>(ptr);
1604 fout->write(chPtr, realsize);
1609 size_t cmWriteToMemoryCallback(void* ptr, size_t size, size_t nmemb,
1612 int realsize = static_cast<int>(size * nmemb);
1613 const char* chPtr = static_cast<char*>(ptr);
1614 cm::append(*static_cast<cmFileCommandVectorOfChar*>(data), chPtr,
1619 int cmFileCommandCurlDebugCallback(CURL*, curl_infotype type, char* chPtr,
1620 size_t size, void* data)
1622 cmFileCommandVectorOfChar& vec =
1623 *static_cast<cmFileCommandVectorOfChar*>(data);
1626 case CURLINFO_HEADER_IN:
1627 case CURLINFO_HEADER_OUT:
1628 cm::append(vec, chPtr, chPtr + size);
1630 case CURLINFO_DATA_IN:
1631 case CURLINFO_DATA_OUT:
1632 case CURLINFO_SSL_DATA_IN:
1633 case CURLINFO_SSL_DATA_OUT: {
1636 snprintf(buf, sizeof(buf), "[%" KWIML_INT_PRIu64 " bytes data]\n",
1637 static_cast<KWIML_INT_uint64_t>(size));
1639 cm::append(vec, buf, buf + n);
1648 class cURLProgressHelper
1651 cURLProgressHelper(cmMakefile* mf, const char* text)
1657 bool UpdatePercentage(double value, double total, std::string& status)
1659 long OldPercentage = this->CurrentPercentage;
1662 this->CurrentPercentage = std::lround(value / total * 100.0);
1663 if (this->CurrentPercentage > 100) {
1664 // Avoid extra progress reports for unexpected data beyond total.
1665 this->CurrentPercentage = 100;
1669 bool updated = (OldPercentage != this->CurrentPercentage);
1673 cmStrCat("[", this->Text, " ", this->CurrentPercentage, "% complete]");
1679 cmMakefile* GetMakefile() { return this->Makefile; }
1682 long CurrentPercentage = -1;
1683 cmMakefile* Makefile;
1687 int cmFileDownloadProgressCallback(void* clientp, double dltotal, double dlnow,
1688 double ultotal, double ulnow)
1690 cURLProgressHelper* helper = reinterpret_cast<cURLProgressHelper*>(clientp);
1692 static_cast<void>(ultotal);
1693 static_cast<void>(ulnow);
1696 if (helper->UpdatePercentage(dlnow, dltotal, status)) {
1697 cmMakefile* mf = helper->GetMakefile();
1698 mf->DisplayStatus(status, -1);
1704 int cmFileUploadProgressCallback(void* clientp, double dltotal, double dlnow,
1705 double ultotal, double ulnow)
1707 cURLProgressHelper* helper = reinterpret_cast<cURLProgressHelper*>(clientp);
1709 static_cast<void>(dltotal);
1710 static_cast<void>(dlnow);
1713 if (helper->UpdatePercentage(ulnow, ultotal, status)) {
1714 cmMakefile* mf = helper->GetMakefile();
1715 mf->DisplayStatus(status, -1);
1724 cURLEasyGuard(CURL* easy)
1732 ::curl_easy_cleanup(this->Easy);
1736 cURLEasyGuard(const cURLEasyGuard&) = delete;
1737 cURLEasyGuard& operator=(const cURLEasyGuard&) = delete;
1739 void release() { this->Easy = nullptr; }
1747 #define check_curl_result(result, errstr) \
1749 if (result != CURLE_OK) { \
1750 std::string e(errstr); \
1751 e += ::curl_easy_strerror(result); \
1752 status.SetError(e); \
1757 bool HandleDownloadCommand(std::vector<std::string> const& args,
1758 cmExecutionStatus& status)
1760 #if !defined(CMAKE_BOOTSTRAP)
1761 auto i = args.begin();
1762 if (args.size() < 2) {
1763 status.SetError("DOWNLOAD must be called with at least two arguments.");
1766 ++i; // Get rid of subcommand
1767 std::string url = *i;
1772 long inactivity_timeout = 0;
1774 std::string statusVar;
1775 bool tls_verify = status.GetMakefile().IsOn("CMAKE_TLS_VERIFY");
1776 cmValue cainfo = status.GetMakefile().GetDefinition("CMAKE_TLS_CAINFO");
1777 std::string netrc_level =
1778 status.GetMakefile().GetSafeDefinition("CMAKE_NETRC");
1779 std::string netrc_file =
1780 status.GetMakefile().GetSafeDefinition("CMAKE_NETRC_FILE");
1781 std::string expectedHash;
1782 std::string hashMatchMSG;
1783 std::unique_ptr<cmCryptoHash> hash;
1784 bool showProgress = false;
1785 std::string userpwd;
1787 std::vector<std::string> curl_headers;
1788 std::vector<std::pair<std::string, cm::optional<std::string>>> curl_ranges;
1790 while (i != args.end()) {
1791 if (*i == "TIMEOUT") {
1793 if (i != args.end()) {
1794 timeout = atol(i->c_str());
1796 status.SetError("DOWNLOAD missing time for TIMEOUT.");
1799 } else if (*i == "INACTIVITY_TIMEOUT") {
1801 if (i != args.end()) {
1802 inactivity_timeout = atol(i->c_str());
1804 status.SetError("DOWNLOAD missing time for INACTIVITY_TIMEOUT.");
1807 } else if (*i == "LOG") {
1809 if (i == args.end()) {
1810 status.SetError("DOWNLOAD missing VAR for LOG.");
1814 } else if (*i == "STATUS") {
1816 if (i == args.end()) {
1817 status.SetError("DOWNLOAD missing VAR for STATUS.");
1821 } else if (*i == "TLS_VERIFY") {
1823 if (i != args.end()) {
1824 tls_verify = cmIsOn(*i);
1826 status.SetError("DOWNLOAD missing bool value for TLS_VERIFY.");
1829 } else if (*i == "TLS_CAINFO") {
1831 if (i != args.end()) {
1832 cainfo = cmValue(*i);
1834 status.SetError("DOWNLOAD missing file value for TLS_CAINFO.");
1837 } else if (*i == "NETRC_FILE") {
1839 if (i != args.end()) {
1842 status.SetError("DOWNLOAD missing file value for NETRC_FILE.");
1845 } else if (*i == "NETRC") {
1847 if (i != args.end()) {
1850 status.SetError("DOWNLOAD missing level value for NETRC.");
1853 } else if (*i == "EXPECTED_MD5") {
1855 if (i == args.end()) {
1856 status.SetError("DOWNLOAD missing sum value for EXPECTED_MD5.");
1859 hash = cm::make_unique<cmCryptoHash>(cmCryptoHash::AlgoMD5);
1860 hashMatchMSG = "MD5 sum";
1861 expectedHash = cmSystemTools::LowerCase(*i);
1862 } else if (*i == "SHOW_PROGRESS") {
1863 showProgress = true;
1864 } else if (*i == "EXPECTED_HASH") {
1866 if (i == args.end()) {
1867 status.SetError("DOWNLOAD missing ALGO=value for EXPECTED_HASH.");
1870 std::string::size_type pos = i->find("=");
1871 if (pos == std::string::npos) {
1873 cmStrCat("DOWNLOAD EXPECTED_HASH expects ALGO=value but got: ", *i);
1874 status.SetError(err);
1877 std::string algo = i->substr(0, pos);
1878 expectedHash = cmSystemTools::LowerCase(i->substr(pos + 1));
1879 hash = cmCryptoHash::New(algo);
1882 cmStrCat("DOWNLOAD EXPECTED_HASH given unknown ALGO: ", algo);
1883 status.SetError(err);
1886 hashMatchMSG = algo + " hash";
1887 } else if (*i == "USERPWD") {
1889 if (i == args.end()) {
1890 status.SetError("DOWNLOAD missing string for USERPWD.");
1894 } else if (*i == "HTTPHEADER") {
1896 if (i == args.end()) {
1897 status.SetError("DOWNLOAD missing string for HTTPHEADER.");
1900 curl_headers.push_back(*i);
1901 } else if (*i == "RANGE_START") {
1903 if (i == args.end()) {
1904 status.SetError("DOWNLOAD missing value for RANGE_START.");
1907 curl_ranges.emplace_back(*i, cm::nullopt);
1908 } else if (*i == "RANGE_END") {
1910 if (curl_ranges.empty()) {
1911 curl_ranges.emplace_back("0", *i);
1913 auto& last_range = curl_ranges.back();
1914 if (!last_range.second.has_value()) {
1915 last_range.second = *i;
1917 status.SetError("Multiple RANGE_END values is provided without "
1918 "the corresponding RANGE_START.");
1922 } else if (file.empty()) {
1925 // Do not return error for compatibility reason.
1926 std::string err = cmStrCat("Unexpected argument: ", *i);
1927 status.GetMakefile().IssueMessage(MessageType::AUTHOR_WARNING, err);
1932 // Can't calculate hash if we don't save the file.
1933 // TODO Incrementally calculate hash in the write callback as the file is
1934 // being downloaded so this check can be relaxed.
1935 if (file.empty() && hash) {
1936 status.SetError("DOWNLOAD cannot calculate hash if file is not saved.");
1939 // If file exists already, and caller specified an expected md5 or sha,
1940 // and the existing file already has the expected hash, then simply
1943 if (!file.empty() && cmSystemTools::FileExists(file) && hash.get()) {
1945 std::string actualHash = hash->HashFile(file);
1946 if (actualHash == expectedHash) {
1947 msg = cmStrCat("returning early; file already exists with expected ",
1949 if (!statusVar.empty()) {
1950 status.GetMakefile().AddDefinition(statusVar, cmStrCat(0, ";\"", msg));
1955 // Make sure parent directory exists so we can write to the file
1956 // as we receive downloaded bits from curl...
1958 if (!file.empty()) {
1959 std::string dir = cmSystemTools::GetFilenamePath(file);
1960 if (!dir.empty() && !cmSystemTools::FileExists(dir) &&
1961 !cmSystemTools::MakeDirectory(dir)) {
1962 std::string errstring = "DOWNLOAD error: cannot create directory '" +
1964 "' - Specify file by full path name and verify that you "
1965 "have directory creation and file write privileges.";
1966 status.SetError(errstring);
1971 cmsys::ofstream fout;
1972 if (!file.empty()) {
1973 fout.open(file.c_str(), std::ios::binary);
1975 status.SetError("DOWNLOAD cannot open file for write.");
1980 url = cmCurlFixFileURL(url);
1983 ::curl_global_init(CURL_GLOBAL_DEFAULT);
1984 curl = ::curl_easy_init();
1986 status.SetError("DOWNLOAD error initializing curl.");
1990 cURLEasyGuard g_curl(curl);
1991 ::CURLcode res = ::curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
1992 check_curl_result(res, "DOWNLOAD cannot set url: ");
1994 // enable HTTP ERROR parsing
1995 res = ::curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
1996 check_curl_result(res, "DOWNLOAD cannot set http failure option: ");
1998 res = ::curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/" LIBCURL_VERSION);
1999 check_curl_result(res, "DOWNLOAD cannot set user agent option: ");
2001 res = ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cmWriteToFileCallback);
2002 check_curl_result(res, "DOWNLOAD cannot set write function: ");
2004 res = ::curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION,
2005 cmFileCommandCurlDebugCallback);
2006 check_curl_result(res, "DOWNLOAD cannot set debug function: ");
2008 // check to see if TLS verification is requested
2010 res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1);
2011 check_curl_result(res, "DOWNLOAD cannot set TLS/SSL Verify on: ");
2013 res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
2014 check_curl_result(res, "DOWNLOAD cannot set TLS/SSL Verify off: ");
2017 for (const auto& range : curl_ranges) {
2018 std::string curl_range = range.first + '-' +
2019 (range.second.has_value() ? range.second.value() : "");
2020 res = ::curl_easy_setopt(curl, CURLOPT_RANGE, curl_range.c_str());
2021 check_curl_result(res, "DOWNLOAD cannot set range: ");
2024 // check to see if a CAINFO file has been specified
2025 // command arg comes first
2026 std::string const& cainfo_err = cmCurlSetCAInfo(curl, cainfo);
2027 if (!cainfo_err.empty()) {
2028 status.SetError(cainfo_err);
2032 // check to see if netrc parameters have been specified
2033 // local command args takes precedence over CMAKE_NETRC*
2034 netrc_level = cmSystemTools::UpperCase(netrc_level);
2035 std::string const& netrc_option_err =
2036 cmCurlSetNETRCOption(curl, netrc_level, netrc_file);
2037 if (!netrc_option_err.empty()) {
2038 status.SetError(netrc_option_err);
2042 cmFileCommandVectorOfChar chunkDebug;
2044 res = ::curl_easy_setopt(curl, CURLOPT_WRITEDATA,
2045 file.empty() ? nullptr : &fout);
2046 check_curl_result(res, "DOWNLOAD cannot set write data: ");
2048 res = ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &chunkDebug);
2049 check_curl_result(res, "DOWNLOAD cannot set debug data: ");
2051 res = ::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
2052 check_curl_result(res, "DOWNLOAD cannot set follow-redirect option: ");
2054 if (!logVar.empty()) {
2055 res = ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
2056 check_curl_result(res, "DOWNLOAD cannot set verbose: ");
2060 res = ::curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
2061 check_curl_result(res, "DOWNLOAD cannot set timeout: ");
2064 if (inactivity_timeout > 0) {
2065 // Give up if there is no progress for a long time.
2066 ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1);
2067 ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, inactivity_timeout);
2070 // Need the progress helper's scope to last through the duration of
2071 // the curl_easy_perform call... so this object is declared at function
2072 // scope intentionally, rather than inside the "if(showProgress)"
2075 cURLProgressHelper helper(&status.GetMakefile(), "download");
2078 res = ::curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
2079 check_curl_result(res, "DOWNLOAD cannot set noprogress value: ");
2081 res = ::curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION,
2082 cmFileDownloadProgressCallback);
2083 check_curl_result(res, "DOWNLOAD cannot set progress function: ");
2085 res = ::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA,
2086 reinterpret_cast<void*>(&helper));
2087 check_curl_result(res, "DOWNLOAD cannot set progress data: ");
2090 if (!userpwd.empty()) {
2091 res = ::curl_easy_setopt(curl, CURLOPT_USERPWD, userpwd.c_str());
2092 check_curl_result(res, "DOWNLOAD cannot set user password: ");
2095 struct curl_slist* headers = nullptr;
2096 for (std::string const& h : curl_headers) {
2097 headers = ::curl_slist_append(headers, h.c_str());
2099 ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
2101 res = ::curl_easy_perform(curl);
2103 ::curl_slist_free_all(headers);
2105 /* always cleanup */
2107 ::curl_easy_cleanup(curl);
2109 if (!statusVar.empty()) {
2110 status.GetMakefile().AddDefinition(
2112 cmStrCat(static_cast<int>(res), ";\"", ::curl_easy_strerror(res), "\""));
2115 ::curl_global_cleanup();
2117 // Explicitly flush/close so we can measure the md5 accurately.
2119 if (!file.empty()) {
2124 // Verify MD5 sum if requested:
2127 std::string actualHash = hash->HashFile(file);
2128 if (actualHash.empty()) {
2129 status.SetError("DOWNLOAD cannot compute hash on downloaded file");
2133 if (expectedHash != actualHash) {
2134 if (!statusVar.empty() && res == 0) {
2135 status.GetMakefile().AddDefinition(statusVar,
2139 " actual: " + actualHash);
2142 status.SetError(cmStrCat("DOWNLOAD HASH mismatch\n"
2146 " expected hash: [",
2153 static_cast<int>(res), ";\"",
2154 ::curl_easy_strerror(res), "\"]\n"));
2159 if (!logVar.empty()) {
2160 chunkDebug.push_back(0);
2161 status.GetMakefile().AddDefinition(logVar, chunkDebug.data());
2166 status.SetError("DOWNLOAD not supported by bootstrap cmake.");
2171 bool HandleUploadCommand(std::vector<std::string> const& args,
2172 cmExecutionStatus& status)
2174 #if !defined(CMAKE_BOOTSTRAP)
2175 if (args.size() < 3) {
2176 status.SetError("UPLOAD must be called with at least three arguments.");
2179 auto i = args.begin();
2181 std::string filename = *i;
2183 std::string url = *i;
2187 long inactivity_timeout = 0;
2189 std::string statusVar;
2190 bool showProgress = false;
2191 bool tls_verify = status.GetMakefile().IsOn("CMAKE_TLS_VERIFY");
2192 cmValue cainfo = status.GetMakefile().GetDefinition("CMAKE_TLS_CAINFO");
2193 std::string userpwd;
2194 std::string netrc_level =
2195 status.GetMakefile().GetSafeDefinition("CMAKE_NETRC");
2196 std::string netrc_file =
2197 status.GetMakefile().GetSafeDefinition("CMAKE_NETRC_FILE");
2199 std::vector<std::string> curl_headers;
2201 while (i != args.end()) {
2202 if (*i == "TIMEOUT") {
2204 if (i != args.end()) {
2205 timeout = atol(i->c_str());
2207 status.SetError("UPLOAD missing time for TIMEOUT.");
2210 } else if (*i == "INACTIVITY_TIMEOUT") {
2212 if (i != args.end()) {
2213 inactivity_timeout = atol(i->c_str());
2215 status.SetError("UPLOAD missing time for INACTIVITY_TIMEOUT.");
2218 } else if (*i == "LOG") {
2220 if (i == args.end()) {
2221 status.SetError("UPLOAD missing VAR for LOG.");
2225 } else if (*i == "STATUS") {
2227 if (i == args.end()) {
2228 status.SetError("UPLOAD missing VAR for STATUS.");
2232 } else if (*i == "SHOW_PROGRESS") {
2233 showProgress = true;
2234 } else if (*i == "TLS_VERIFY") {
2236 if (i != args.end()) {
2237 tls_verify = cmIsOn(*i);
2239 status.SetError("UPLOAD missing bool value for TLS_VERIFY.");
2242 } else if (*i == "TLS_CAINFO") {
2244 if (i != args.end()) {
2245 cainfo = cmValue(*i);
2247 status.SetError("UPLOAD missing file value for TLS_CAINFO.");
2250 } else if (*i == "NETRC_FILE") {
2252 if (i != args.end()) {
2255 status.SetError("UPLOAD missing file value for NETRC_FILE.");
2258 } else if (*i == "NETRC") {
2260 if (i != args.end()) {
2263 status.SetError("UPLOAD missing level value for NETRC.");
2266 } else if (*i == "USERPWD") {
2268 if (i == args.end()) {
2269 status.SetError("UPLOAD missing string for USERPWD.");
2273 } else if (*i == "HTTPHEADER") {
2275 if (i == args.end()) {
2276 status.SetError("UPLOAD missing string for HTTPHEADER.");
2279 curl_headers.push_back(*i);
2281 // Do not return error for compatibility reason.
2282 std::string err = cmStrCat("Unexpected argument: ", *i);
2283 status.GetMakefile().IssueMessage(MessageType::AUTHOR_WARNING, err);
2289 // Open file for reading:
2291 FILE* fin = cmsys::SystemTools::Fopen(filename, "rb");
2293 std::string errStr =
2294 cmStrCat("UPLOAD cannot open file '", filename, "' for reading.");
2295 status.SetError(errStr);
2299 unsigned long file_size = cmsys::SystemTools::FileLength(filename);
2301 url = cmCurlFixFileURL(url);
2304 ::curl_global_init(CURL_GLOBAL_DEFAULT);
2305 curl = ::curl_easy_init();
2307 status.SetError("UPLOAD error initializing curl.");
2312 cURLEasyGuard g_curl(curl);
2314 // enable HTTP ERROR parsing
2315 ::CURLcode res = ::curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
2316 check_curl_result(res, "UPLOAD cannot set fail on error flag: ");
2319 res = ::curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
2320 check_curl_result(res, "UPLOAD cannot set upload flag: ");
2322 res = ::curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
2323 check_curl_result(res, "UPLOAD cannot set url: ");
2326 ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cmWriteToMemoryCallback);
2327 check_curl_result(res, "UPLOAD cannot set write function: ");
2329 res = ::curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION,
2330 cmFileCommandCurlDebugCallback);
2331 check_curl_result(res, "UPLOAD cannot set debug function: ");
2333 // check to see if TLS verification is requested
2335 res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1);
2336 check_curl_result(res, "UPLOAD cannot set TLS/SSL Verify on: ");
2338 res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
2339 check_curl_result(res, "UPLOAD cannot set TLS/SSL Verify off: ");
2342 // check to see if a CAINFO file has been specified
2343 // command arg comes first
2344 std::string const& cainfo_err = cmCurlSetCAInfo(curl, cainfo);
2345 if (!cainfo_err.empty()) {
2346 status.SetError(cainfo_err);
2350 cmFileCommandVectorOfChar chunkResponse;
2351 cmFileCommandVectorOfChar chunkDebug;
2353 res = ::curl_easy_setopt(curl, CURLOPT_WRITEDATA, &chunkResponse);
2354 check_curl_result(res, "UPLOAD cannot set write data: ");
2356 res = ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &chunkDebug);
2357 check_curl_result(res, "UPLOAD cannot set debug data: ");
2359 res = ::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
2360 check_curl_result(res, "UPLOAD cannot set follow-redirect option: ");
2362 if (!logVar.empty()) {
2363 res = ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
2364 check_curl_result(res, "UPLOAD cannot set verbose: ");
2368 res = ::curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
2369 check_curl_result(res, "UPLOAD cannot set timeout: ");
2372 if (inactivity_timeout > 0) {
2373 // Give up if there is no progress for a long time.
2374 ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1);
2375 ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, inactivity_timeout);
2378 // Need the progress helper's scope to last through the duration of
2379 // the curl_easy_perform call... so this object is declared at function
2380 // scope intentionally, rather than inside the "if(showProgress)"
2383 cURLProgressHelper helper(&status.GetMakefile(), "upload");
2386 res = ::curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
2387 check_curl_result(res, "UPLOAD cannot set noprogress value: ");
2389 res = ::curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION,
2390 cmFileUploadProgressCallback);
2391 check_curl_result(res, "UPLOAD cannot set progress function: ");
2393 res = ::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA,
2394 reinterpret_cast<void*>(&helper));
2395 check_curl_result(res, "UPLOAD cannot set progress data: ");
2398 // now specify which file to upload
2399 res = ::curl_easy_setopt(curl, CURLOPT_INFILE, fin);
2400 check_curl_result(res, "UPLOAD cannot set input file: ");
2402 // and give the size of the upload (optional)
2404 ::curl_easy_setopt(curl, CURLOPT_INFILESIZE, static_cast<long>(file_size));
2405 check_curl_result(res, "UPLOAD cannot set input file size: ");
2407 if (!userpwd.empty()) {
2408 res = ::curl_easy_setopt(curl, CURLOPT_USERPWD, userpwd.c_str());
2409 check_curl_result(res, "UPLOAD cannot set user password: ");
2412 // check to see if netrc parameters have been specified
2413 // local command args takes precedence over CMAKE_NETRC*
2414 netrc_level = cmSystemTools::UpperCase(netrc_level);
2415 std::string const& netrc_option_err =
2416 cmCurlSetNETRCOption(curl, netrc_level, netrc_file);
2417 if (!netrc_option_err.empty()) {
2418 status.SetError(netrc_option_err);
2422 struct curl_slist* headers = nullptr;
2423 for (std::string const& h : curl_headers) {
2424 headers = ::curl_slist_append(headers, h.c_str());
2426 ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
2428 res = ::curl_easy_perform(curl);
2430 ::curl_slist_free_all(headers);
2432 /* always cleanup */
2434 ::curl_easy_cleanup(curl);
2436 if (!statusVar.empty()) {
2437 status.GetMakefile().AddDefinition(
2439 cmStrCat(static_cast<int>(res), ";\"", ::curl_easy_strerror(res), "\""));
2442 ::curl_global_cleanup();
2447 if (!logVar.empty()) {
2450 if (!chunkResponse.empty()) {
2451 chunkResponse.push_back(0);
2452 log += "Response:\n";
2453 log += chunkResponse.data();
2457 if (!chunkDebug.empty()) {
2458 chunkDebug.push_back(0);
2460 log += chunkDebug.data();
2464 status.GetMakefile().AddDefinition(logVar, log);
2469 status.SetError("UPLOAD not supported by bootstrap cmake.");
2474 void AddEvaluationFile(const std::string& inputName,
2475 const std::string& targetName,
2476 const std::string& outputExpr,
2477 const std::string& condition, bool inputIsContent,
2478 const std::string& newLineCharacter, mode_t permissions,
2479 cmExecutionStatus& status)
2481 cmListFileBacktrace lfbt = status.GetMakefile().GetBacktrace();
2483 cmGeneratorExpression outputGe(lfbt);
2484 std::unique_ptr<cmCompiledGeneratorExpression> outputCge =
2485 outputGe.Parse(outputExpr);
2487 cmGeneratorExpression conditionGe(lfbt);
2488 std::unique_ptr<cmCompiledGeneratorExpression> conditionCge =
2489 conditionGe.Parse(condition);
2491 status.GetMakefile().AddEvaluationFile(
2492 inputName, targetName, std::move(outputCge), std::move(conditionCge),
2493 newLineCharacter, permissions, inputIsContent);
2496 bool HandleGenerateCommand(std::vector<std::string> const& args,
2497 cmExecutionStatus& status)
2499 if (args.size() < 5) {
2500 status.SetError("Incorrect arguments to GENERATE subcommand.");
2508 std::string Content;
2509 std::string Condition;
2511 std::string NewLineStyle;
2512 bool NoSourcePermissions = false;
2513 bool UseSourcePermissions = false;
2514 std::vector<std::string> FilePermissions;
2517 static auto const parser =
2518 cmArgumentParser<Arguments>{}
2519 .Bind("OUTPUT"_s, &Arguments::Output)
2520 .Bind("INPUT"_s, &Arguments::Input)
2521 .Bind("CONTENT"_s, &Arguments::Content)
2522 .Bind("CONDITION"_s, &Arguments::Condition)
2523 .Bind("TARGET"_s, &Arguments::Target)
2524 .Bind("NO_SOURCE_PERMISSIONS"_s, &Arguments::NoSourcePermissions)
2525 .Bind("USE_SOURCE_PERMISSIONS"_s, &Arguments::UseSourcePermissions)
2526 .Bind("FILE_PERMISSIONS"_s, &Arguments::FilePermissions)
2527 .Bind("NEWLINE_STYLE"_s, &Arguments::NewLineStyle);
2529 std::vector<std::string> unparsedArguments;
2530 std::vector<std::string> keywordsMissingValues;
2531 std::vector<std::string> parsedKeywords;
2532 Arguments const arguments =
2533 parser.Parse(cmMakeRange(args).advance(1), &unparsedArguments,
2534 &keywordsMissingValues, &parsedKeywords);
2536 if (!keywordsMissingValues.empty()) {
2537 status.SetError("Incorrect arguments to GENERATE subcommand.");
2541 if (!unparsedArguments.empty()) {
2542 status.SetError("Unknown argument to GENERATE subcommand.");
2546 bool mandatoryOptionsSpecified = false;
2547 if (parsedKeywords.size() > 1) {
2548 const bool outputOprionSpecified = parsedKeywords[0] == "OUTPUT"_s;
2549 const bool inputOrContentSpecified =
2550 parsedKeywords[1] == "INPUT"_s || parsedKeywords[1] == "CONTENT"_s;
2551 if (outputOprionSpecified && inputOrContentSpecified) {
2552 mandatoryOptionsSpecified = true;
2555 if (!mandatoryOptionsSpecified) {
2556 status.SetError("Incorrect arguments to GENERATE subcommand.");
2560 const bool conditionOptionSpecified =
2561 std::find(parsedKeywords.begin(), parsedKeywords.end(), "CONDITION"_s) !=
2562 parsedKeywords.end();
2563 if (conditionOptionSpecified && arguments.Condition.empty()) {
2564 status.SetError("CONDITION of sub-command GENERATE must not be empty "
2569 const bool targetOptionSpecified =
2570 std::find(parsedKeywords.begin(), parsedKeywords.end(), "TARGET"_s) !=
2571 parsedKeywords.end();
2572 if (targetOptionSpecified && arguments.Target.empty()) {
2573 status.SetError("TARGET of sub-command GENERATE must not be empty "
2578 const bool outputOptionSpecified =
2579 std::find(parsedKeywords.begin(), parsedKeywords.end(), "OUTPUT"_s) !=
2580 parsedKeywords.end();
2581 if (outputOptionSpecified && parsedKeywords[0] != "OUTPUT"_s) {
2582 status.SetError("Incorrect arguments to GENERATE subcommand.");
2586 const bool inputIsContent = parsedKeywords[1] != "INPUT"_s;
2587 if (inputIsContent && parsedKeywords[1] != "CONTENT") {
2588 status.SetError("Unknown argument to GENERATE subcommand.");
2591 const bool newLineStyleSpecified =
2592 std::find(parsedKeywords.begin(), parsedKeywords.end(),
2593 "NEWLINE_STYLE"_s) != parsedKeywords.end();
2594 cmNewLineStyle newLineStyle;
2595 if (newLineStyleSpecified) {
2596 std::string errorMessage;
2597 if (!newLineStyle.ReadFromArguments(args, errorMessage)) {
2598 status.SetError(cmStrCat("GENERATE ", errorMessage));
2603 std::string input = arguments.Input;
2604 if (inputIsContent) {
2605 input = arguments.Content;
2608 if (arguments.NoSourcePermissions && arguments.UseSourcePermissions) {
2609 status.SetError("given both NO_SOURCE_PERMISSIONS and "
2610 "USE_SOURCE_PERMISSIONS. Only one option allowed.");
2614 if (!arguments.FilePermissions.empty()) {
2615 if (arguments.NoSourcePermissions) {
2616 status.SetError("given both NO_SOURCE_PERMISSIONS and "
2617 "FILE_PERMISSIONS. Only one option allowed.");
2620 if (arguments.UseSourcePermissions) {
2621 status.SetError("given both USE_SOURCE_PERMISSIONS and "
2622 "FILE_PERMISSIONS. Only one option allowed.");
2627 if (arguments.UseSourcePermissions) {
2628 if (inputIsContent) {
2629 status.SetError("given USE_SOURCE_PERMISSIONS without a file INPUT.");
2634 mode_t permissions = 0;
2635 if (arguments.NoSourcePermissions) {
2636 permissions |= cmFSPermissions::mode_owner_read;
2637 permissions |= cmFSPermissions::mode_owner_write;
2638 permissions |= cmFSPermissions::mode_group_read;
2639 permissions |= cmFSPermissions::mode_world_read;
2642 if (!arguments.FilePermissions.empty()) {
2643 std::vector<std::string> invalidOptions;
2644 for (auto const& e : arguments.FilePermissions) {
2645 if (!cmFSPermissions::stringToModeT(e, permissions)) {
2646 invalidOptions.push_back(e);
2649 if (!invalidOptions.empty()) {
2650 std::ostringstream oss;
2651 oss << "given invalid permission ";
2652 for (auto i = 0u; i < invalidOptions.size(); i++) {
2654 oss << "\"" << invalidOptions[i] << "\"";
2656 oss << ",\"" << invalidOptions[i] << "\"";
2660 status.SetError(oss.str());
2665 AddEvaluationFile(input, arguments.Target, arguments.Output,
2666 arguments.Condition, inputIsContent,
2667 newLineStyle.GetCharacters(), permissions, status);
2671 bool HandleLockCommand(std::vector<std::string> const& args,
2672 cmExecutionStatus& status)
2674 #if !defined(CMAKE_BOOTSTRAP)
2676 bool directory = false;
2677 bool release = false;
2684 Guard guard = GUARD_PROCESS;
2685 std::string resultVariable;
2686 unsigned long timeout = static_cast<unsigned long>(-1);
2689 if (args.size() < 2) {
2690 status.GetMakefile().IssueMessage(
2691 MessageType::FATAL_ERROR,
2692 "sub-command LOCK requires at least two arguments.");
2696 std::string path = args[1];
2697 for (unsigned i = 2; i < args.size(); ++i) {
2698 if (args[i] == "DIRECTORY") {
2700 } else if (args[i] == "RELEASE") {
2702 } else if (args[i] == "GUARD") {
2704 const char* merr = "expected FUNCTION, FILE or PROCESS after GUARD";
2705 if (i >= args.size()) {
2706 status.GetMakefile().IssueMessage(MessageType::FATAL_ERROR, merr);
2709 if (args[i] == "FUNCTION") {
2710 guard = GUARD_FUNCTION;
2711 } else if (args[i] == "FILE") {
2713 } else if (args[i] == "PROCESS") {
2714 guard = GUARD_PROCESS;
2716 status.GetMakefile().IssueMessage(
2717 MessageType::FATAL_ERROR,
2718 cmStrCat(merr, ", but got:\n \"", args[i], "\"."));
2722 } else if (args[i] == "RESULT_VARIABLE") {
2724 if (i >= args.size()) {
2725 status.GetMakefile().IssueMessage(
2726 MessageType::FATAL_ERROR,
2727 "expected variable name after RESULT_VARIABLE");
2730 resultVariable = args[i];
2731 } else if (args[i] == "TIMEOUT") {
2733 if (i >= args.size()) {
2734 status.GetMakefile().IssueMessage(
2735 MessageType::FATAL_ERROR, "expected timeout value after TIMEOUT");
2739 if (!cmStrToLong(args[i], &scanned) || scanned < 0) {
2740 status.GetMakefile().IssueMessage(
2741 MessageType::FATAL_ERROR,
2742 cmStrCat("TIMEOUT value \"", args[i],
2743 "\" is not an unsigned integer."));
2746 timeout = static_cast<unsigned long>(scanned);
2748 status.GetMakefile().IssueMessage(
2749 MessageType::FATAL_ERROR,
2750 cmStrCat("expected DIRECTORY, RELEASE, GUARD, RESULT_VARIABLE or ",
2751 "TIMEOUT\nbut got: \"", args[i], "\"."));
2757 path += "/cmake.lock";
2760 // Unify path (remove '//', '/../', ...)
2761 path = cmSystemTools::CollapseFullPath(
2762 path, status.GetMakefile().GetCurrentSourceDirectory());
2764 // Create file and directories if needed
2765 std::string parentDir = cmSystemTools::GetParentDirectory(path);
2766 if (!cmSystemTools::MakeDirectory(parentDir)) {
2767 status.GetMakefile().IssueMessage(
2768 MessageType::FATAL_ERROR,
2769 cmStrCat("directory\n \"", parentDir,
2770 "\"\ncreation failed (check permissions)."));
2771 cmSystemTools::SetFatalErrorOccurred();
2774 FILE* file = cmsys::SystemTools::Fopen(path, "w");
2776 status.GetMakefile().IssueMessage(
2777 MessageType::FATAL_ERROR,
2778 cmStrCat("file\n \"", path,
2779 "\"\ncreation failed (check permissions)."));
2780 cmSystemTools::SetFatalErrorOccurred();
2785 // Actual lock/unlock
2786 cmFileLockPool& lockPool =
2787 status.GetMakefile().GetGlobalGenerator()->GetFileLockPool();
2789 cmFileLockResult fileLockResult(cmFileLockResult::MakeOk());
2791 fileLockResult = lockPool.Release(path);
2794 case GUARD_FUNCTION:
2795 fileLockResult = lockPool.LockFunctionScope(path, timeout);
2798 fileLockResult = lockPool.LockFileScope(path, timeout);
2801 fileLockResult = lockPool.LockProcessScope(path, timeout);
2804 cmSystemTools::SetFatalErrorOccurred();
2809 const std::string result = fileLockResult.GetOutputMessage();
2811 if (resultVariable.empty() && !fileLockResult.IsOk()) {
2812 status.GetMakefile().IssueMessage(
2813 MessageType::FATAL_ERROR,
2814 cmStrCat("error locking file\n \"", path, "\"\n", result, "."));
2815 cmSystemTools::SetFatalErrorOccurred();
2819 if (!resultVariable.empty()) {
2820 status.GetMakefile().AddDefinition(resultVariable, result);
2825 static_cast<void>(args);
2826 status.SetError("sub-command LOCK not implemented in bootstrap cmake");
2831 bool HandleTimestampCommand(std::vector<std::string> const& args,
2832 cmExecutionStatus& status)
2834 if (args.size() < 3) {
2835 status.SetError("sub-command TIMESTAMP requires at least two arguments.");
2838 if (args.size() > 5) {
2839 status.SetError("sub-command TIMESTAMP takes at most four arguments.");
2843 unsigned int argsIndex = 1;
2845 const std::string& filename = args[argsIndex++];
2847 const std::string& outputVariable = args[argsIndex++];
2849 std::string formatString;
2850 if (args.size() > argsIndex && args[argsIndex] != "UTC") {
2851 formatString = args[argsIndex++];
2854 bool utcFlag = false;
2855 if (args.size() > argsIndex) {
2856 if (args[argsIndex] == "UTC") {
2859 std::string e = " TIMESTAMP sub-command does not recognize option " +
2860 args[argsIndex] + ".";
2866 cmTimestamp timestamp;
2867 std::string result =
2868 timestamp.FileModificationTime(filename.c_str(), formatString, utcFlag);
2869 status.GetMakefile().AddDefinition(outputVariable, result);
2874 bool HandleSizeCommand(std::vector<std::string> const& args,
2875 cmExecutionStatus& status)
2877 if (args.size() != 3) {
2879 cmStrCat(args[0], " requires a file name and output variable"));
2883 unsigned int argsIndex = 1;
2885 const std::string& filename = args[argsIndex++];
2887 const std::string& outputVariable = args[argsIndex++];
2889 if (!cmSystemTools::FileExists(filename, true)) {
2891 cmStrCat("SIZE requested of path that is not readable:\n ", filename));
2895 status.GetMakefile().AddDefinition(
2896 outputVariable, std::to_string(cmSystemTools::FileLength(filename)));
2901 bool HandleReadSymlinkCommand(std::vector<std::string> const& args,
2902 cmExecutionStatus& status)
2904 if (args.size() != 3) {
2906 cmStrCat(args[0], " requires a file name and output variable"));
2910 const std::string& filename = args[1];
2911 const std::string& outputVariable = args[2];
2914 if (!cmSystemTools::ReadSymlink(filename, result)) {
2915 status.SetError(cmStrCat(
2916 "READ_SYMLINK requested of path that is not a symlink:\n ", filename));
2920 status.GetMakefile().AddDefinition(outputVariable, result);
2925 bool HandleCreateLinkCommand(std::vector<std::string> const& args,
2926 cmExecutionStatus& status)
2928 if (args.size() < 3) {
2929 status.SetError("CREATE_LINK must be called with at least two additional "
2934 std::string const& fileName = args[1];
2935 std::string const& newFileName = args[2];
2940 bool CopyOnError = false;
2941 bool Symbolic = false;
2944 static auto const parser =
2945 cmArgumentParser<Arguments>{}
2946 .Bind("RESULT"_s, &Arguments::Result)
2947 .Bind("COPY_ON_ERROR"_s, &Arguments::CopyOnError)
2948 .Bind("SYMBOLIC"_s, &Arguments::Symbolic);
2950 std::vector<std::string> unconsumedArgs;
2951 Arguments const arguments =
2952 parser.Parse(cmMakeRange(args).advance(3), &unconsumedArgs);
2954 if (!unconsumedArgs.empty()) {
2955 status.SetError("unknown argument: \"" + unconsumedArgs.front() + '\"');
2959 // The system error message generated in the operation.
2962 // Check if the paths are distinct.
2963 if (fileName == newFileName) {
2964 result = "CREATE_LINK cannot use same file and newfile";
2965 if (!arguments.Result.empty()) {
2966 status.GetMakefile().AddDefinition(arguments.Result, result);
2969 status.SetError(result);
2973 // Hard link requires original file to exist.
2974 if (!arguments.Symbolic && !cmSystemTools::FileExists(fileName)) {
2975 result = "Cannot hard link \'" + fileName + "\' as it does not exist.";
2976 if (!arguments.Result.empty()) {
2977 status.GetMakefile().AddDefinition(arguments.Result, result);
2980 status.SetError(result);
2984 // Check if the new file already exists and remove it.
2985 if ((cmSystemTools::FileExists(newFileName) ||
2986 cmSystemTools::FileIsSymlink(newFileName)) &&
2987 !cmSystemTools::RemoveFile(newFileName)) {
2988 std::ostringstream e;
2989 e << "Failed to create link '" << newFileName
2990 << "' because existing path cannot be removed: "
2991 << cmSystemTools::GetLastSystemError() << "\n";
2993 if (!arguments.Result.empty()) {
2994 status.GetMakefile().AddDefinition(arguments.Result, e.str());
2997 status.SetError(e.str());
3001 // Whether the operation completed successfully.
3002 bool completed = false;
3004 // Check if the command requires a symbolic link.
3005 if (arguments.Symbolic) {
3006 completed = static_cast<bool>(
3007 cmSystemTools::CreateSymlink(fileName, newFileName, &result));
3009 completed = static_cast<bool>(
3010 cmSystemTools::CreateLink(fileName, newFileName, &result));
3013 // Check if copy-on-error is enabled in the arguments.
3014 if (!completed && arguments.CopyOnError) {
3015 cmsys::Status copied =
3016 cmsys::SystemTools::CopyFileAlways(fileName, newFileName);
3020 result = "Copy failed: " + copied.GetString();
3024 // Check if the operation was successful.
3027 } else if (arguments.Result.empty()) {
3028 // The operation failed and the result is not reported in a variable.
3029 status.SetError(result);
3033 if (!arguments.Result.empty()) {
3034 status.GetMakefile().AddDefinition(arguments.Result, result);
3040 bool HandleGetRuntimeDependenciesCommand(std::vector<std::string> const& args,
3041 cmExecutionStatus& status)
3043 std::string platform =
3044 status.GetMakefile().GetSafeDefinition("CMAKE_HOST_SYSTEM_NAME");
3045 if (!cmRuntimeDependencyArchive::PlatformSupportsRuntimeDependencies(
3048 cmStrCat("GET_RUNTIME_DEPENDENCIES is not supported on system \"",
3050 cmSystemTools::SetFatalErrorOccurred();
3054 if (status.GetMakefile().GetState()->GetMode() == cmState::Project) {
3055 status.GetMakefile().IssueMessage(
3056 MessageType::AUTHOR_WARNING,
3057 "You have used file(GET_RUNTIME_DEPENDENCIES)"
3058 " in project mode. This is probably not what "
3059 "you intended to do. Instead, please consider"
3060 " using it in an install(CODE) or "
3061 "install(SCRIPT) command. For example:"
3062 "\n install(CODE [["
3063 "\n file(GET_RUNTIME_DEPENDENCIES"
3071 std::string ResolvedDependenciesVar;
3072 std::string UnresolvedDependenciesVar;
3073 std::string ConflictingDependenciesPrefix;
3074 std::string RPathPrefix;
3075 std::string BundleExecutable;
3076 std::vector<std::string> Executables;
3077 std::vector<std::string> Libraries;
3078 std::vector<std::string> Directories;
3079 std::vector<std::string> Modules;
3080 std::vector<std::string> PreIncludeRegexes;
3081 std::vector<std::string> PreExcludeRegexes;
3082 std::vector<std::string> PostIncludeRegexes;
3083 std::vector<std::string> PostExcludeRegexes;
3084 std::vector<std::string> PostIncludeFiles;
3085 std::vector<std::string> PostExcludeFiles;
3086 std::vector<std::string> PostExcludeFilesStrict;
3089 static auto const parser =
3090 cmArgumentParser<Arguments>{}
3091 .Bind("RESOLVED_DEPENDENCIES_VAR"_s, &Arguments::ResolvedDependenciesVar)
3092 .Bind("UNRESOLVED_DEPENDENCIES_VAR"_s,
3093 &Arguments::UnresolvedDependenciesVar)
3094 .Bind("CONFLICTING_DEPENDENCIES_PREFIX"_s,
3095 &Arguments::ConflictingDependenciesPrefix)
3096 .Bind("RPATH_PREFIX"_s, &Arguments::RPathPrefix)
3097 .Bind("BUNDLE_EXECUTABLE"_s, &Arguments::BundleExecutable)
3098 .Bind("EXECUTABLES"_s, &Arguments::Executables)
3099 .Bind("LIBRARIES"_s, &Arguments::Libraries)
3100 .Bind("MODULES"_s, &Arguments::Modules)
3101 .Bind("DIRECTORIES"_s, &Arguments::Directories)
3102 .Bind("PRE_INCLUDE_REGEXES"_s, &Arguments::PreIncludeRegexes)
3103 .Bind("PRE_EXCLUDE_REGEXES"_s, &Arguments::PreExcludeRegexes)
3104 .Bind("POST_INCLUDE_REGEXES"_s, &Arguments::PostIncludeRegexes)
3105 .Bind("POST_EXCLUDE_REGEXES"_s, &Arguments::PostExcludeRegexes)
3106 .Bind("POST_INCLUDE_FILES"_s, &Arguments::PostIncludeFiles)
3107 .Bind("POST_EXCLUDE_FILES"_s, &Arguments::PostExcludeFiles)
3108 .Bind("POST_EXCLUDE_FILES_STRICT"_s, &Arguments::PostExcludeFilesStrict);
3110 std::vector<std::string> unrecognizedArguments;
3111 std::vector<std::string> keywordsMissingValues;
3113 parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments,
3114 &keywordsMissingValues);
3115 auto argIt = unrecognizedArguments.begin();
3116 if (argIt != unrecognizedArguments.end()) {
3117 status.SetError(cmStrCat("Unrecognized argument: \"", *argIt, "\""));
3118 cmSystemTools::SetFatalErrorOccurred();
3122 const std::vector<std::string> LIST_ARGS = {
3127 "POST_EXCLUDE_FILES",
3128 "POST_EXCLUDE_FILES_STRICT",
3129 "POST_EXCLUDE_REGEXES",
3130 "POST_INCLUDE_FILES",
3131 "POST_INCLUDE_REGEXES",
3132 "PRE_EXCLUDE_REGEXES",
3133 "PRE_INCLUDE_REGEXES",
3135 auto kwbegin = keywordsMissingValues.cbegin();
3136 auto kwend = cmRemoveMatching(keywordsMissingValues, LIST_ARGS);
3137 if (kwend != kwbegin) {
3138 status.SetError(cmStrCat("Keywords missing values:\n ",
3139 cmJoin(cmMakeRange(kwbegin, kwend), "\n ")));
3140 cmSystemTools::SetFatalErrorOccurred();
3144 cmRuntimeDependencyArchive archive(
3145 status, parsedArgs.Directories, parsedArgs.BundleExecutable,
3146 parsedArgs.PreIncludeRegexes, parsedArgs.PreExcludeRegexes,
3147 parsedArgs.PostIncludeRegexes, parsedArgs.PostExcludeRegexes,
3148 std::move(parsedArgs.PostIncludeFiles),
3149 std::move(parsedArgs.PostExcludeFiles),
3150 std::move(parsedArgs.PostExcludeFilesStrict));
3151 if (!archive.Prepare()) {
3152 cmSystemTools::SetFatalErrorOccurred();
3156 if (!archive.GetRuntimeDependencies(
3157 parsedArgs.Executables, parsedArgs.Libraries, parsedArgs.Modules)) {
3158 cmSystemTools::SetFatalErrorOccurred();
3162 std::vector<std::string> deps;
3163 std::vector<std::string> unresolvedDeps;
3164 std::vector<std::string> conflictingDeps;
3165 for (auto const& val : archive.GetResolvedPaths()) {
3167 auto it = val.second.begin();
3168 assert(it != val.second.end());
3169 auto const& firstPath = *it;
3170 while (++it != val.second.end()) {
3171 if (!cmSystemTools::SameFile(firstPath, *it)) {
3178 deps.push_back(firstPath);
3179 if (!parsedArgs.RPathPrefix.empty()) {
3180 status.GetMakefile().AddDefinition(
3181 parsedArgs.RPathPrefix + "_" + firstPath,
3182 cmJoin(archive.GetRPaths().at(firstPath), ";"));
3184 } else if (!parsedArgs.ConflictingDependenciesPrefix.empty()) {
3185 conflictingDeps.push_back(val.first);
3186 std::vector<std::string> paths;
3187 paths.insert(paths.begin(), val.second.begin(), val.second.end());
3188 std::string varName =
3189 parsedArgs.ConflictingDependenciesPrefix + "_" + val.first;
3190 std::string pathsStr = cmJoin(paths, ";");
3191 status.GetMakefile().AddDefinition(varName, pathsStr);
3193 std::ostringstream e;
3194 e << "Multiple conflicting paths found for " << val.first << ":";
3195 for (auto const& path : val.second) {
3198 status.SetError(e.str());
3199 cmSystemTools::SetFatalErrorOccurred();
3203 if (!archive.GetUnresolvedPaths().empty()) {
3204 if (!parsedArgs.UnresolvedDependenciesVar.empty()) {
3205 unresolvedDeps.insert(unresolvedDeps.begin(),
3206 archive.GetUnresolvedPaths().begin(),
3207 archive.GetUnresolvedPaths().end());
3209 std::ostringstream e;
3210 e << "Could not resolve runtime dependencies:";
3211 for (auto const& path : archive.GetUnresolvedPaths()) {
3214 status.SetError(e.str());
3215 cmSystemTools::SetFatalErrorOccurred();
3220 if (!parsedArgs.ResolvedDependenciesVar.empty()) {
3221 std::string val = cmJoin(deps, ";");
3222 status.GetMakefile().AddDefinition(parsedArgs.ResolvedDependenciesVar,
3225 if (!parsedArgs.UnresolvedDependenciesVar.empty()) {
3226 std::string val = cmJoin(unresolvedDeps, ";");
3227 status.GetMakefile().AddDefinition(parsedArgs.UnresolvedDependenciesVar,
3230 if (!parsedArgs.ConflictingDependenciesPrefix.empty()) {
3231 std::string val = cmJoin(conflictingDeps, ";");
3232 status.GetMakefile().AddDefinition(
3233 parsedArgs.ConflictingDependenciesPrefix + "_FILENAMES", val);
3238 bool HandleConfigureCommand(std::vector<std::string> const& args,
3239 cmExecutionStatus& status)
3244 std::string Content;
3245 bool EscapeQuotes = false;
3246 bool AtOnly = false;
3247 std::string NewlineStyle;
3250 static auto const parser =
3251 cmArgumentParser<Arguments>{}
3252 .Bind("OUTPUT"_s, &Arguments::Output)
3253 .Bind("CONTENT"_s, &Arguments::Content)
3254 .Bind("ESCAPE_QUOTES"_s, &Arguments::EscapeQuotes)
3255 .Bind("@ONLY"_s, &Arguments::AtOnly)
3256 .Bind("NEWLINE_STYLE"_s, &Arguments::NewlineStyle);
3258 std::vector<std::string> unrecognizedArguments;
3259 std::vector<std::string> keywordsMissingArguments;
3260 std::vector<std::string> parsedKeywords;
3262 parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments,
3263 &keywordsMissingArguments, &parsedKeywords);
3265 auto argIt = unrecognizedArguments.begin();
3266 if (argIt != unrecognizedArguments.end()) {
3268 cmStrCat("CONFIGURE Unrecognized argument: \"", *argIt, "\""));
3269 cmSystemTools::SetFatalErrorOccurred();
3273 std::vector<std::string> mandatoryOptions{ "OUTPUT", "CONTENT" };
3274 for (auto const& e : mandatoryOptions) {
3275 const bool optionHasNoValue =
3276 std::find(keywordsMissingArguments.begin(),
3277 keywordsMissingArguments.end(),
3278 e) != keywordsMissingArguments.end();
3279 if (optionHasNoValue) {
3280 status.SetError(cmStrCat("CONFIGURE ", e, " option needs a value."));
3281 cmSystemTools::SetFatalErrorOccurred();
3286 for (auto const& e : mandatoryOptions) {
3287 const bool optionGiven =
3288 std::find(parsedKeywords.begin(), parsedKeywords.end(), e) !=
3289 parsedKeywords.end();
3291 status.SetError(cmStrCat("CONFIGURE ", e, " option is mandatory."));
3292 cmSystemTools::SetFatalErrorOccurred();
3297 std::string errorMessage;
3298 cmNewLineStyle newLineStyle;
3299 if (!newLineStyle.ReadFromArguments(args, errorMessage)) {
3300 status.SetError(cmStrCat("CONFIGURE ", errorMessage));
3304 // Check for generator expressions
3305 std::string outputFile = cmSystemTools::CollapseFullPath(
3306 parsedArgs.Output, status.GetMakefile().GetCurrentBinaryDirectory());
3308 std::string::size_type pos = outputFile.find_first_of("<>");
3309 if (pos != std::string::npos) {
3310 status.SetError(cmStrCat("CONFIGURE called with OUTPUT containing a \"",
3312 "\". This character is not allowed."));
3316 cmMakefile& makeFile = status.GetMakefile();
3317 if (!makeFile.CanIWriteThisFile(outputFile)) {
3318 cmSystemTools::Error("Attempt to write file: " + outputFile +
3319 " into a source directory.");
3323 cmSystemTools::ConvertToUnixSlashes(outputFile);
3325 // Re-generate if non-temporary outputs are missing.
3326 // when we finalize the configuration we will remove all
3327 // output files that now don't exist.
3328 makeFile.AddCMakeOutputFile(outputFile);
3330 // Create output directory
3331 const std::string::size_type slashPos = outputFile.rfind('/');
3332 if (slashPos != std::string::npos) {
3333 const std::string path = outputFile.substr(0, slashPos);
3334 cmSystemTools::MakeDirectory(path);
3337 std::string newLineCharacters = "\n";
3338 bool open_with_binary_flag = false;
3339 if (newLineStyle.IsValid()) {
3340 newLineCharacters = newLineStyle.GetCharacters();
3341 open_with_binary_flag = true;
3344 cmGeneratedFileStream fout;
3345 fout.Open(outputFile, false, open_with_binary_flag);
3347 cmSystemTools::Error("Could not open file for write in copy operation " +
3349 cmSystemTools::ReportLastSystemError("");
3352 fout.SetCopyIfDifferent(true);
3354 // copy input to output and expand variables from input at the same time
3355 std::stringstream sin(parsedArgs.Content, std::ios::in);
3357 std::string outLine;
3358 bool hasNewLine = false;
3359 while (cmSystemTools::GetLineFromStream(sin, inLine, &hasNewLine)) {
3361 makeFile.ConfigureString(inLine, outLine, parsedArgs.AtOnly,
3362 parsedArgs.EscapeQuotes);
3364 if (hasNewLine || newLineStyle.IsValid()) {
3365 fout << newLineCharacters;
3369 // close file before attempting to copy
3375 bool HandleArchiveCreateCommand(std::vector<std::string> const& args,
3376 cmExecutionStatus& status)
3382 std::string Compression;
3383 std::string CompressionLevel;
3385 bool Verbose = false;
3386 std::vector<std::string> Paths;
3389 static auto const parser =
3390 cmArgumentParser<Arguments>{}
3391 .Bind("OUTPUT"_s, &Arguments::Output)
3392 .Bind("FORMAT"_s, &Arguments::Format)
3393 .Bind("COMPRESSION"_s, &Arguments::Compression)
3394 .Bind("COMPRESSION_LEVEL"_s, &Arguments::CompressionLevel)
3395 .Bind("MTIME"_s, &Arguments::MTime)
3396 .Bind("VERBOSE"_s, &Arguments::Verbose)
3397 .Bind("PATHS"_s, &Arguments::Paths);
3399 std::vector<std::string> unrecognizedArguments;
3400 std::vector<std::string> keywordsMissingValues;
3402 parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments,
3403 &keywordsMissingValues);
3404 auto argIt = unrecognizedArguments.begin();
3405 if (argIt != unrecognizedArguments.end()) {
3406 status.SetError(cmStrCat("Unrecognized argument: \"", *argIt, "\""));
3407 cmSystemTools::SetFatalErrorOccurred();
3411 const std::vector<std::string> LIST_ARGS = {
3412 "OUTPUT", "FORMAT", "COMPRESSION", "COMPRESSION_LEVEL", "MTIME", "PATHS"
3414 auto kwbegin = keywordsMissingValues.cbegin();
3415 auto kwend = cmRemoveMatching(keywordsMissingValues, LIST_ARGS);
3416 if (kwend != kwbegin) {
3417 status.SetError(cmStrCat("Keywords missing values:\n ",
3418 cmJoin(cmMakeRange(kwbegin, kwend), "\n ")));
3419 cmSystemTools::SetFatalErrorOccurred();
3423 const char* knownFormats[] = {
3424 "7zip", "gnutar", "pax", "paxr", "raw", "zip"
3427 if (!parsedArgs.Format.empty() &&
3428 !cm::contains(knownFormats, parsedArgs.Format)) {
3430 cmStrCat("archive format ", parsedArgs.Format, " not supported"));
3431 cmSystemTools::SetFatalErrorOccurred();
3435 const char* zipFileFormats[] = { "7zip", "zip" };
3436 if (!parsedArgs.Compression.empty() &&
3437 cm::contains(zipFileFormats, parsedArgs.Format)) {
3438 status.SetError(cmStrCat("archive format ", parsedArgs.Format,
3439 " does not support COMPRESSION arguments"));
3440 cmSystemTools::SetFatalErrorOccurred();
3444 static std::map<std::string, cmSystemTools::cmTarCompression>
3445 compressionTypeMap = { { "None", cmSystemTools::TarCompressNone },
3446 { "BZip2", cmSystemTools::TarCompressBZip2 },
3447 { "GZip", cmSystemTools::TarCompressGZip },
3448 { "XZ", cmSystemTools::TarCompressXZ },
3449 { "Zstd", cmSystemTools::TarCompressZstd } };
3451 cmSystemTools::cmTarCompression compress = cmSystemTools::TarCompressNone;
3452 auto typeIt = compressionTypeMap.find(parsedArgs.Compression);
3453 if (typeIt != compressionTypeMap.end()) {
3454 compress = typeIt->second;
3455 } else if (!parsedArgs.Compression.empty()) {
3456 status.SetError(cmStrCat("compression type ", parsedArgs.Compression,
3457 " is not supported"));
3458 cmSystemTools::SetFatalErrorOccurred();
3462 int compressionLevel = 0;
3463 if (!parsedArgs.CompressionLevel.empty()) {
3464 if (parsedArgs.CompressionLevel.size() != 1 &&
3465 !std::isdigit(parsedArgs.CompressionLevel[0])) {
3466 status.SetError(cmStrCat("compression level ",
3467 parsedArgs.CompressionLevel,
3468 " should be in range 0 to 9"));
3469 cmSystemTools::SetFatalErrorOccurred();
3472 compressionLevel = std::stoi(parsedArgs.CompressionLevel);
3473 if (compressionLevel < 0 || compressionLevel > 9) {
3474 status.SetError(cmStrCat("compression level ",
3475 parsedArgs.CompressionLevel,
3476 " should be in range 0 to 9"));
3477 cmSystemTools::SetFatalErrorOccurred();
3480 if (compress == cmSystemTools::TarCompressNone) {
3481 status.SetError(cmStrCat("compression level is not supported for "
3482 "compression \"None\"",
3483 parsedArgs.Compression));
3484 cmSystemTools::SetFatalErrorOccurred();
3489 if (parsedArgs.Paths.empty()) {
3490 status.SetError("ARCHIVE_CREATE requires a non-empty list of PATHS");
3491 cmSystemTools::SetFatalErrorOccurred();
3495 if (!cmSystemTools::CreateTar(parsedArgs.Output, parsedArgs.Paths, compress,
3496 parsedArgs.Verbose, parsedArgs.MTime,
3497 parsedArgs.Format, compressionLevel)) {
3498 status.SetError(cmStrCat("failed to compress: ", parsedArgs.Output));
3499 cmSystemTools::SetFatalErrorOccurred();
3506 bool HandleArchiveExtractCommand(std::vector<std::string> const& args,
3507 cmExecutionStatus& status)
3512 bool Verbose = false;
3513 bool ListOnly = false;
3514 std::string Destination;
3515 std::vector<std::string> Patterns;
3519 static auto const parser = cmArgumentParser<Arguments>{}
3520 .Bind("INPUT"_s, &Arguments::Input)
3521 .Bind("VERBOSE"_s, &Arguments::Verbose)
3522 .Bind("LIST_ONLY"_s, &Arguments::ListOnly)
3523 .Bind("DESTINATION"_s, &Arguments::Destination)
3524 .Bind("PATTERNS"_s, &Arguments::Patterns)
3525 .Bind("TOUCH"_s, &Arguments::Touch);
3527 std::vector<std::string> unrecognizedArguments;
3528 std::vector<std::string> keywordsMissingValues;
3530 parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments,
3531 &keywordsMissingValues);
3532 auto argIt = unrecognizedArguments.begin();
3533 if (argIt != unrecognizedArguments.end()) {
3534 status.SetError(cmStrCat("Unrecognized argument: \"", *argIt, "\""));
3535 cmSystemTools::SetFatalErrorOccurred();
3539 const std::vector<std::string> LIST_ARGS = { "INPUT", "DESTINATION",
3541 auto kwbegin = keywordsMissingValues.cbegin();
3542 auto kwend = cmRemoveMatching(keywordsMissingValues, LIST_ARGS);
3543 if (kwend != kwbegin) {
3544 status.SetError(cmStrCat("Keywords missing values:\n ",
3545 cmJoin(cmMakeRange(kwbegin, kwend), "\n ")));
3546 cmSystemTools::SetFatalErrorOccurred();
3550 std::string inFile = parsedArgs.Input;
3552 if (parsedArgs.ListOnly) {
3553 if (!cmSystemTools::ListTar(inFile, parsedArgs.Patterns,
3554 parsedArgs.Verbose)) {
3555 status.SetError(cmStrCat("failed to list: ", inFile));
3556 cmSystemTools::SetFatalErrorOccurred();
3560 std::string destDir = status.GetMakefile().GetCurrentBinaryDirectory();
3561 if (!parsedArgs.Destination.empty()) {
3562 if (cmSystemTools::FileIsFullPath(parsedArgs.Destination)) {
3563 destDir = parsedArgs.Destination;
3565 destDir = cmStrCat(destDir, "/", parsedArgs.Destination);
3568 if (!cmSystemTools::MakeDirectory(destDir)) {
3569 status.SetError(cmStrCat("failed to create directory: ", destDir));
3570 cmSystemTools::SetFatalErrorOccurred();
3574 if (!cmSystemTools::FileIsFullPath(inFile)) {
3576 cmStrCat(cmSystemTools::GetCurrentWorkingDirectory(), "/", inFile);
3580 cmWorkingDirectory workdir(destDir);
3581 if (workdir.Failed()) {
3583 cmStrCat("failed to change working directory to: ", destDir));
3584 cmSystemTools::SetFatalErrorOccurred();
3588 if (!cmSystemTools::ExtractTar(
3589 inFile, parsedArgs.Patterns,
3590 parsedArgs.Touch ? cmSystemTools::cmTarExtractTimestamps::No
3591 : cmSystemTools::cmTarExtractTimestamps::Yes,
3592 parsedArgs.Verbose)) {
3593 status.SetError(cmStrCat("failed to extract: ", inFile));
3594 cmSystemTools::SetFatalErrorOccurred();
3602 bool ValidateAndConvertPermissions(const std::vector<std::string>& permissions,
3603 mode_t& perms, cmExecutionStatus& status)
3605 for (const auto& i : permissions) {
3606 if (!cmFSPermissions::stringToModeT(i, perms)) {
3607 status.SetError(i + " is an invalid permission specifier");
3608 cmSystemTools::SetFatalErrorOccurred();
3615 bool SetPermissions(const std::string& filename, const mode_t& perms,
3616 cmExecutionStatus& status)
3618 if (!cmSystemTools::SetPermissions(filename, perms)) {
3619 status.SetError("Failed to set permissions for " + filename);
3620 cmSystemTools::SetFatalErrorOccurred();
3626 bool HandleChmodCommandImpl(std::vector<std::string> const& args, bool recurse,
3627 cmExecutionStatus& status)
3632 cmsys::Glob globber;
3634 globber.SetRecurse(recurse);
3635 globber.SetRecurseListDirs(recurse);
3639 std::vector<std::string> Permissions;
3640 std::vector<std::string> FilePermissions;
3641 std::vector<std::string> DirectoryPermissions;
3644 static auto const parser =
3645 cmArgumentParser<Arguments>{}
3646 .Bind("PERMISSIONS"_s, &Arguments::Permissions)
3647 .Bind("FILE_PERMISSIONS"_s, &Arguments::FilePermissions)
3648 .Bind("DIRECTORY_PERMISSIONS"_s, &Arguments::DirectoryPermissions);
3650 std::vector<std::string> pathEntries;
3651 std::vector<std::string> keywordsMissingValues;
3652 Arguments parsedArgs = parser.Parse(cmMakeRange(args).advance(1),
3653 &pathEntries, &keywordsMissingValues);
3655 // check validity of arguments
3656 if (parsedArgs.Permissions.empty() && parsedArgs.FilePermissions.empty() &&
3657 parsedArgs.DirectoryPermissions.empty()) // no permissions given
3659 status.SetError("No permissions given");
3660 cmSystemTools::SetFatalErrorOccurred();
3664 if (!parsedArgs.Permissions.empty() && !parsedArgs.FilePermissions.empty() &&
3665 !parsedArgs.DirectoryPermissions.empty()) // all keywords are used
3667 status.SetError("Remove either PERMISSIONS or FILE_PERMISSIONS or "
3668 "DIRECTORY_PERMISSIONS from the invocation");
3669 cmSystemTools::SetFatalErrorOccurred();
3673 if (!keywordsMissingValues.empty()) {
3674 for (const auto& i : keywordsMissingValues) {
3675 status.SetError(i + " is not given any arguments");
3676 cmSystemTools::SetFatalErrorOccurred();
3681 // validate permissions
3682 bool validatePermissions =
3683 ValidateAndConvertPermissions(parsedArgs.Permissions, perms, status) &&
3684 ValidateAndConvertPermissions(parsedArgs.FilePermissions, fperms,
3686 ValidateAndConvertPermissions(parsedArgs.DirectoryPermissions, dperms,
3688 if (!validatePermissions) {
3692 std::vector<std::string> allPathEntries;
3695 std::vector<std::string> tempPathEntries;
3696 for (const auto& i : pathEntries) {
3697 if (cmSystemTools::FileIsDirectory(i)) {
3698 globber.FindFiles(i + "/*");
3699 tempPathEntries = globber.GetFiles();
3700 allPathEntries.insert(allPathEntries.end(), tempPathEntries.begin(),
3701 tempPathEntries.end());
3702 allPathEntries.emplace_back(i);
3704 allPathEntries.emplace_back(i); // We validate path entries below
3708 allPathEntries = std::move(pathEntries);
3712 for (const auto& i : allPathEntries) {
3713 if (!(cmSystemTools::FileExists(i) || cmSystemTools::FileIsDirectory(i))) {
3714 status.SetError(cmStrCat("does not exist:\n ", i));
3715 cmSystemTools::SetFatalErrorOccurred();
3719 if (cmSystemTools::FileExists(i, true)) {
3720 bool success = true;
3721 const mode_t& filePermissions =
3722 parsedArgs.FilePermissions.empty() ? perms : fperms;
3723 if (filePermissions) {
3724 success = SetPermissions(i, filePermissions, status);
3731 else if (cmSystemTools::FileIsDirectory(i)) {
3732 bool success = true;
3733 const mode_t& directoryPermissions =
3734 parsedArgs.DirectoryPermissions.empty() ? perms : dperms;
3735 if (directoryPermissions) {
3736 success = SetPermissions(i, directoryPermissions, status);
3747 bool HandleChmodCommand(std::vector<std::string> const& args,
3748 cmExecutionStatus& status)
3750 return HandleChmodCommandImpl(args, false, status);
3753 bool HandleChmodRecurseCommand(std::vector<std::string> const& args,
3754 cmExecutionStatus& status)
3756 return HandleChmodCommandImpl(args, true, status);
3761 bool cmFileCommand(std::vector<std::string> const& args,
3762 cmExecutionStatus& status)
3764 if (args.size() < 2) {
3765 status.SetError("must be called with at least two arguments.");
3769 static cmSubcommandTable const subcommand{
3770 { "WRITE"_s, HandleWriteCommand },
3771 { "APPEND"_s, HandleAppendCommand },
3772 { "DOWNLOAD"_s, HandleDownloadCommand },
3773 { "UPLOAD"_s, HandleUploadCommand },
3774 { "READ"_s, HandleReadCommand },
3775 { "MD5"_s, HandleHashCommand },
3776 { "SHA1"_s, HandleHashCommand },
3777 { "SHA224"_s, HandleHashCommand },
3778 { "SHA256"_s, HandleHashCommand },
3779 { "SHA384"_s, HandleHashCommand },
3780 { "SHA512"_s, HandleHashCommand },
3781 { "SHA3_224"_s, HandleHashCommand },
3782 { "SHA3_256"_s, HandleHashCommand },
3783 { "SHA3_384"_s, HandleHashCommand },
3784 { "SHA3_512"_s, HandleHashCommand },
3785 { "STRINGS"_s, HandleStringsCommand },
3786 { "GLOB"_s, HandleGlobCommand },
3787 { "GLOB_RECURSE"_s, HandleGlobRecurseCommand },
3788 { "MAKE_DIRECTORY"_s, HandleMakeDirectoryCommand },
3789 { "RENAME"_s, HandleRename },
3790 { "COPY_FILE"_s, HandleCopyFile },
3791 { "REMOVE"_s, HandleRemove },
3792 { "REMOVE_RECURSE"_s, HandleRemoveRecurse },
3793 { "COPY"_s, HandleCopyCommand },
3794 { "INSTALL"_s, HandleInstallCommand },
3795 { "DIFFERENT"_s, HandleDifferentCommand },
3796 { "RPATH_CHANGE"_s, HandleRPathChangeCommand },
3797 { "CHRPATH"_s, HandleRPathChangeCommand },
3798 { "RPATH_SET"_s, HandleRPathSetCommand },
3799 { "RPATH_CHECK"_s, HandleRPathCheckCommand },
3800 { "RPATH_REMOVE"_s, HandleRPathRemoveCommand },
3801 { "READ_ELF"_s, HandleReadElfCommand },
3802 { "REAL_PATH"_s, HandleRealPathCommand },
3803 { "RELATIVE_PATH"_s, HandleRelativePathCommand },
3804 { "TO_CMAKE_PATH"_s, HandleCMakePathCommand },
3805 { "TO_NATIVE_PATH"_s, HandleNativePathCommand },
3806 { "TOUCH"_s, HandleTouchCommand },
3807 { "TOUCH_NOCREATE"_s, HandleTouchNocreateCommand },
3808 { "TIMESTAMP"_s, HandleTimestampCommand },
3809 { "GENERATE"_s, HandleGenerateCommand },
3810 { "LOCK"_s, HandleLockCommand },
3811 { "SIZE"_s, HandleSizeCommand },
3812 { "READ_SYMLINK"_s, HandleReadSymlinkCommand },
3813 { "CREATE_LINK"_s, HandleCreateLinkCommand },
3814 { "GET_RUNTIME_DEPENDENCIES"_s, HandleGetRuntimeDependenciesCommand },
3815 { "CONFIGURE"_s, HandleConfigureCommand },
3816 { "ARCHIVE_CREATE"_s, HandleArchiveCreateCommand },
3817 { "ARCHIVE_EXTRACT"_s, HandleArchiveExtractCommand },
3818 { "CHMOD"_s, HandleChmodCommand },
3819 { "CHMOD_RECURSE"_s, HandleChmodRecurseCommand },
3822 return subcommand(args[0], args, status);