| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file LICENSE.rst or https://cmake.org/licensing for details. */ | |
| #include "cmFileCommand.h" | |
| #include <algorithm> | |
| #include <cassert> | |
| #include <cmath> | |
| #include <cstdio> | |
| #include <cstdlib> | |
| #include <iterator> | |
| #include <map> | |
| #include <set> | |
| #include <sstream> | |
| #include <utility> | |
| #include <vector> | |
| #include <cm/memory> | |
| #include <cm/optional> | |
| #include <cm/string_view> | |
| #include <cmext/algorithm> | |
| #include <cmext/string_view> | |
| #include <cm3p/kwiml/int.h> | |
| #include "cmsys/FStream.hxx" | |
| #include "cmsys/Glob.hxx" | |
| #include "cmsys/RegularExpression.hxx" | |
| #include "cmsys/String.h" | |
| #include "cm_sys_stat.h" | |
| #include "cmArgumentParser.h" | |
| #include "cmArgumentParserTypes.h" | |
| #include "cmCMakePath.h" | |
| #include "cmCryptoHash.h" | |
| #include "cmDiagnostics.h" | |
| #include "cmELF.h" | |
| #include "cmExecutionStatus.h" | |
| #include "cmFSPermissions.h" | |
| #include "cmFileCommand_ReadMacho.h" | |
| #include "cmFileCopier.h" | |
| #include "cmFileInstaller.h" | |
| #include "cmFileLockPool.h" | |
| #include "cmFileTimes.h" | |
| #include "cmGeneratedFileStream.h" | |
| #include "cmGeneratorExpression.h" | |
| #include "cmGlobCacheEntry.h" | |
| #include "cmGlobalGenerator.h" | |
| #include "cmHexFileConverter.h" | |
| #include "cmList.h" | |
| #include "cmListFileCache.h" | |
| #include "cmMakefile.h" | |
| #include "cmMessageType.h" | |
| #include "cmNewLineStyle.h" | |
| #include "cmPolicies.h" | |
| #include "cmRange.h" | |
| #include "cmRuntimeDependencyArchive.h" | |
| #include "cmState.h" | |
| #include "cmStringAlgorithms.h" | |
| #include "cmSubcommandTable.h" | |
| #include "cmSystemTools.h" | |
| #include "cmTimestamp.h" | |
| #include "cmValue.h" | |
| #include "cmWorkingDirectory.h" | |
| #include "cmake.h" | |
| #if !defined(CMAKE_BOOTSTRAP) | |
| # include <cm3p/curl/curl.h> | |
| # include "cmCurl.h" | |
| # include "cmFileLockResult.h" | |
| #endif | |
| namespace { | |
| bool HandleWriteImpl(std::vector<std::string> const& args, bool append, | |
| cmExecutionStatus& status) | |
| { | |
| if (args.size() < 2) { | |
| status.SetError(cmStrCat( | |
| args[0], " must be called with at least one additional argument.")); | |
| return false; | |
| } | |
| auto i = args.begin(); | |
| i++; // Get rid of subcommand | |
| std::string fileName = *i; | |
| if (!cmsys::SystemTools::FileIsFullPath(*i)) { | |
| fileName = | |
| cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', *i); | |
| } | |
| i++; | |
| if (!status.GetMakefile().CanIWriteThisFile(fileName)) { | |
| std::string e = cmStrCat("attempted to write a file: ", fileName, | |
| " into a source directory."); | |
| status.SetError(e); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| std::string dir = cmSystemTools::GetFilenamePath(fileName); | |
| cmSystemTools::MakeDirectory(dir); | |
| mode_t mode = 0; | |
| bool writable = false; | |
| // Set permissions to writable | |
| if (cmSystemTools::GetPermissions(fileName, mode)) { | |
| #if defined(_MSC_VER) || defined(__MINGW32__) | |
| writable = (mode & S_IWRITE) != 0; | |
| mode_t newMode = mode | S_IWRITE; | |
| #else | |
| writable = mode & S_IWUSR; | |
| mode_t newMode = mode | S_IWUSR | S_IWGRP; | |
| #endif | |
| if (!writable) { | |
| cmSystemTools::SetPermissions(fileName, newMode); | |
| } | |
| } | |
| // If GetPermissions fails, pretend like it is ok. File open will fail if | |
| // the file is not writable | |
| cmsys::ofstream file(fileName.c_str(), | |
| append ? std::ios::app : std::ios::out); | |
| if (!file) { | |
| std::string error = | |
| cmStrCat("failed to open for writing (", | |
| cmSystemTools::GetLastSystemError(), "):\n ", fileName); | |
| status.SetError(error); | |
| return false; | |
| } | |
| std::string message = cmJoin(cmMakeRange(i, args.end()), std::string()); | |
| file << message; | |
| if (!file) { | |
| std::string error = | |
| cmStrCat("write failed (", cmSystemTools::GetLastSystemError(), "):\n ", | |
| fileName); | |
| status.SetError(error); | |
| return false; | |
| } | |
| file.close(); | |
| if (mode && !writable) { | |
| cmSystemTools::SetPermissions(fileName, mode); | |
| } | |
| return true; | |
| } | |
| bool HandleWriteCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| return HandleWriteImpl(args, false, status); | |
| } | |
| bool HandleAppendCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| return HandleWriteImpl(args, true, status); | |
| } | |
| bool HandleReadCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| if (args.size() < 3) { | |
| status.SetError("READ must be called with at least two additional " | |
| "arguments"); | |
| return false; | |
| } | |
| std::string const& fileNameArg = args[1]; | |
| std::string const& variable = args[2]; | |
| struct Arguments | |
| { | |
| std::string Offset; | |
| std::string Limit; | |
| bool Hex = false; | |
| }; | |
| static auto const parser = cmArgumentParser<Arguments>{} | |
| .Bind("OFFSET"_s, &Arguments::Offset) | |
| .Bind("LIMIT"_s, &Arguments::Limit) | |
| .Bind("HEX"_s, &Arguments::Hex); | |
| Arguments const arguments = parser.Parse(cmMakeRange(args).advance(3), | |
| /*unparsedArguments=*/nullptr); | |
| std::string fileName = fileNameArg; | |
| if (!cmsys::SystemTools::FileIsFullPath(fileName)) { | |
| fileName = cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', | |
| fileNameArg); | |
| } | |
| // Open the specified file. | |
| #if defined(_WIN32) || defined(__CYGWIN__) | |
| cmsys::ifstream file(fileName.c_str(), | |
| arguments.Hex ? (std::ios::binary | std::ios::in) | |
| : std::ios::in); | |
| #else | |
| cmsys::ifstream file(fileName.c_str()); | |
| #endif | |
| if (!file) { | |
| std::string error = | |
| cmStrCat("failed to open for reading (", | |
| cmSystemTools::GetLastSystemError(), "):\n ", fileName); | |
| status.SetError(error); | |
| return false; | |
| } | |
| // is there a limit? | |
| std::string::size_type sizeLimit = std::string::npos; | |
| if (!arguments.Limit.empty()) { | |
| unsigned long long limit; | |
| if (cmStrToULongLong(arguments.Limit, &limit)) { | |
| sizeLimit = static_cast<std::string::size_type>(limit); | |
| } | |
| } | |
| // is there an offset? | |
| cmsys::ifstream::off_type offset = 0; | |
| if (!arguments.Offset.empty()) { | |
| long long off; | |
| if (cmStrToLongLong(arguments.Offset, &off)) { | |
| offset = static_cast<cmsys::ifstream::off_type>(off); | |
| } | |
| } | |
| file.seekg(offset, std::ios::beg); // explicit ios::beg for IBM VisualAge 6 | |
| std::string output; | |
| if (arguments.Hex) { | |
| // Convert part of the file into hex code | |
| char c; | |
| while ((sizeLimit > 0) && (file.get(c))) { | |
| char hex[4]; | |
| snprintf(hex, sizeof(hex), "%.2x", c & 0xFFu); | |
| output += hex; | |
| sizeLimit--; | |
| } | |
| } else { | |
| std::string line; | |
| bool has_newline = false; | |
| while ( | |
| sizeLimit > 0 && | |
| cmSystemTools::GetLineFromStream(file, line, &has_newline, sizeLimit)) { | |
| sizeLimit = sizeLimit - line.size(); | |
| if (has_newline && sizeLimit > 0) { | |
| sizeLimit--; | |
| } | |
| output += line; | |
| if (has_newline) { | |
| output += "\n"; | |
| } | |
| } | |
| } | |
| status.GetMakefile().AddDefinition(variable, output); | |
| return true; | |
| } | |
| bool HandleHashCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| #if !defined(CMAKE_BOOTSTRAP) | |
| if (args.size() != 3) { | |
| status.SetError( | |
| cmStrCat(args[0], " requires a file name and output variable")); | |
| return false; | |
| } | |
| std::unique_ptr<cmCryptoHash> hash(cmCryptoHash::New(args[0])); | |
| if (hash) { | |
| std::string out = hash->HashFile(args[1]); | |
| if (!out.empty()) { | |
| status.GetMakefile().AddDefinition(args[2], out); | |
| return true; | |
| } | |
| status.SetError(cmStrCat(args[0], " failed to read file \"", args[1], | |
| "\": ", cmSystemTools::GetLastSystemError())); | |
| } | |
| return false; | |
| #else | |
| status.SetError(cmStrCat(args[0], " not available during bootstrap")); | |
| return false; | |
| #endif | |
| } | |
| bool HandleStringsCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| if (args.size() < 3) { | |
| status.SetError("STRINGS requires a file name and output variable"); | |
| return false; | |
| } | |
| // Get the file to read. | |
| std::string fileName = args[1]; | |
| if (!cmsys::SystemTools::FileIsFullPath(fileName)) { | |
| fileName = | |
| cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[1]); | |
| } | |
| // Get the variable in which to store the results. | |
| std::string const& outVar = args[2]; | |
| // Parse the options. | |
| enum | |
| { | |
| arg_none, | |
| arg_limit_input, | |
| arg_limit_output, | |
| arg_limit_count, | |
| arg_length_minimum, | |
| arg_length_maximum, | |
| arg_maximum, | |
| arg_regex, | |
| arg_encoding | |
| }; | |
| unsigned int minlen = 0; | |
| unsigned int maxlen = 0; | |
| int limit_input = -1; | |
| int limit_output = -1; | |
| unsigned int limit_count = 0; | |
| cmsys::RegularExpression regex; | |
| bool have_regex = false; | |
| bool store_regex = true; | |
| bool newline_consume = false; | |
| bool hex_conversion_enabled = true; | |
| enum | |
| { | |
| encoding_none = cmsys::FStream::BOM_None, | |
| encoding_utf8 = cmsys::FStream::BOM_UTF8, | |
| encoding_utf16le = cmsys::FStream::BOM_UTF16LE, | |
| encoding_utf16be = cmsys::FStream::BOM_UTF16BE, | |
| encoding_utf32le = cmsys::FStream::BOM_UTF32LE, | |
| encoding_utf32be = cmsys::FStream::BOM_UTF32BE | |
| }; | |
| int encoding = encoding_none; | |
| int arg_mode = arg_none; | |
| for (unsigned int i = 3; i < args.size(); ++i) { | |
| if (args[i] == "LIMIT_INPUT") { | |
| arg_mode = arg_limit_input; | |
| } else if (args[i] == "LIMIT_OUTPUT") { | |
| arg_mode = arg_limit_output; | |
| } else if (args[i] == "LIMIT_COUNT") { | |
| arg_mode = arg_limit_count; | |
| } else if (args[i] == "LENGTH_MINIMUM") { | |
| arg_mode = arg_length_minimum; | |
| } else if (args[i] == "LENGTH_MAXIMUM") { | |
| arg_mode = arg_length_maximum; | |
| } else if (args[i] == "REGEX") { | |
| arg_mode = arg_regex; | |
| } else if (args[i] == "NEWLINE_CONSUME") { | |
| newline_consume = true; | |
| arg_mode = arg_none; | |
| } else if (args[i] == "NO_HEX_CONVERSION") { | |
| hex_conversion_enabled = false; | |
| arg_mode = arg_none; | |
| } else if (args[i] == "ENCODING") { | |
| arg_mode = arg_encoding; | |
| } else if (arg_mode == arg_limit_input) { | |
| if (sscanf(args[i].c_str(), "%d", &limit_input) != 1 || | |
| limit_input < 0) { | |
| status.SetError(cmStrCat("STRINGS option LIMIT_INPUT value \"", | |
| args[i], "\" is not an unsigned integer.")); | |
| return false; | |
| } | |
| arg_mode = arg_none; | |
| } else if (arg_mode == arg_limit_output) { | |
| if (sscanf(args[i].c_str(), "%d", &limit_output) != 1 || | |
| limit_output < 0) { | |
| status.SetError(cmStrCat("STRINGS option LIMIT_OUTPUT value \"", | |
| args[i], "\" is not an unsigned integer.")); | |
| return false; | |
| } | |
| arg_mode = arg_none; | |
| } else if (arg_mode == arg_limit_count) { | |
| int count; | |
| if (sscanf(args[i].c_str(), "%d", &count) != 1 || count < 0) { | |
| status.SetError(cmStrCat("STRINGS option LIMIT_COUNT value \"", | |
| args[i], "\" is not an unsigned integer.")); | |
| return false; | |
| } | |
| limit_count = count; | |
| arg_mode = arg_none; | |
| } else if (arg_mode == arg_length_minimum) { | |
| int len; | |
| if (sscanf(args[i].c_str(), "%d", &len) != 1 || len < 0) { | |
| status.SetError(cmStrCat("STRINGS option LENGTH_MINIMUM value \"", | |
| args[i], "\" is not an unsigned integer.")); | |
| return false; | |
| } | |
| minlen = len; | |
| arg_mode = arg_none; | |
| } else if (arg_mode == arg_length_maximum) { | |
| int len; | |
| if (sscanf(args[i].c_str(), "%d", &len) != 1 || len < 0) { | |
| status.SetError(cmStrCat("STRINGS option LENGTH_MAXIMUM value \"", | |
| args[i], "\" is not an unsigned integer.")); | |
| return false; | |
| } | |
| maxlen = len; | |
| arg_mode = arg_none; | |
| } else if (arg_mode == arg_regex) { | |
| if (!regex.compile(args[i])) { | |
| status.SetError(cmStrCat("STRINGS option REGEX value \"", args[i], | |
| "\" could not be compiled.")); | |
| return false; | |
| } | |
| have_regex = true; | |
| switch (status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0159)) { | |
| case cmPolicies::NEW: | |
| // store_regex = true | |
| break; | |
| case cmPolicies::WARN: | |
| if (status.GetMakefile().PolicyOptionalWarningEnabled( | |
| "CMAKE_POLICY_WARNING_CMP0159")) { | |
| status.GetMakefile().IssuePolicyWarning( | |
| cmPolicies::CMP0159, {}, | |
| "For compatibility, CMake is leaving CMAKE_MATCH_<n> " | |
| "unchanged."_s); | |
| } | |
| CM_FALLTHROUGH; | |
| case cmPolicies::OLD: | |
| store_regex = false; | |
| break; | |
| } | |
| arg_mode = arg_none; | |
| } else if (arg_mode == arg_encoding) { | |
| if (args[i] == "UTF-8") { | |
| encoding = encoding_utf8; | |
| } else if (args[i] == "UTF-16LE") { | |
| encoding = encoding_utf16le; | |
| } else if (args[i] == "UTF-16BE") { | |
| encoding = encoding_utf16be; | |
| } else if (args[i] == "UTF-32LE") { | |
| encoding = encoding_utf32le; | |
| } else if (args[i] == "UTF-32BE") { | |
| encoding = encoding_utf32be; | |
| } else { | |
| status.SetError(cmStrCat("STRINGS option ENCODING \"", args[i], | |
| "\" not recognized.")); | |
| return false; | |
| } | |
| arg_mode = arg_none; | |
| } else { | |
| status.SetError( | |
| cmStrCat("STRINGS given unknown argument \"", args[i], '"')); | |
| return false; | |
| } | |
| } | |
| if (hex_conversion_enabled) { | |
| // TODO: should work without temp file, but just on a memory buffer | |
| std::string binaryFileName = | |
| cmStrCat(status.GetMakefile().GetCurrentBinaryDirectory(), | |
| "/CMakeFiles/FileCommandStringsBinaryFile"); | |
| if (cmHexFileConverter::TryConvert(fileName, binaryFileName)) { | |
| fileName = binaryFileName; | |
| } | |
| } | |
| // Open the specified file. | |
| #if defined(_WIN32) || defined(__CYGWIN__) | |
| cmsys::ifstream fin(fileName.c_str(), std::ios::in | std::ios::binary); | |
| #else | |
| cmsys::ifstream fin(fileName.c_str()); | |
| #endif | |
| if (!fin) { | |
| status.SetError( | |
| cmStrCat("STRINGS file \"", fileName, "\" cannot be read.")); | |
| return false; | |
| } | |
| // If BOM is found and encoding was not specified, use the BOM | |
| int bom_found = cmsys::FStream::ReadBOM(fin); | |
| if (encoding == encoding_none && bom_found != cmsys::FStream::BOM_None) { | |
| encoding = bom_found; | |
| } | |
| unsigned int bytes_rem = 0; | |
| if (encoding == encoding_utf16le || encoding == encoding_utf16be) { | |
| bytes_rem = 1; | |
| } | |
| if (encoding == encoding_utf32le || encoding == encoding_utf32be) { | |
| bytes_rem = 3; | |
| } | |
| // Parse strings out of the file. | |
| int output_size = 0; | |
| std::vector<std::string> strings; | |
| std::string s; | |
| while ((!limit_count || strings.size() < limit_count) && | |
| (limit_input < 0 || static_cast<int>(fin.tellg()) < limit_input) && | |
| fin) { | |
| std::string current_str; | |
| int c = fin.get(); | |
| for (unsigned int i = 0; i < bytes_rem; ++i) { | |
| int c1 = fin.get(); | |
| if (!fin) { | |
| fin.putback(static_cast<char>(c1)); | |
| break; | |
| } | |
| c = (c << 8) | c1; | |
| } | |
| if (encoding == encoding_utf16le) { | |
| c = ((c & 0xFF) << 8) | ((c & 0xFF00) >> 8); | |
| } else if (encoding == encoding_utf32le) { | |
| c = (((c & 0xFF) << 24) | ((c & 0xFF00) << 8) | ((c & 0xFF0000) >> 8) | | |
| ((c & 0xFF000000) >> 24)); | |
| } | |
| if (c == '\r') { | |
| // Ignore CR character to make output always have UNIX newlines. | |
| continue; | |
| } | |
| if (c >= 0 && c <= 0xFF && | |
| (cmsysString_isprint(static_cast<char>(c)) || c == '\t' || | |
| (c == '\n' && newline_consume))) { | |
| // This is an ASCII character that may be part of a string. | |
| // Cast added to avoid compiler warning. Cast is ok because | |
| // c is guaranteed to fit in char by the above if... | |
| current_str += static_cast<char>(c); | |
| } else if (encoding == encoding_utf8) { | |
| // Check for UTF-8 encoded string (up to 4 octets) | |
| static unsigned char const utf8_check_table[3][2] = { | |
| { 0xE0, 0xC0 }, | |
| { 0xF0, 0xE0 }, | |
| { 0xF8, 0xF0 }, | |
| }; | |
| // how many octets are there? | |
| unsigned int num_utf8_bytes = 0; | |
| for (unsigned int j = 0; num_utf8_bytes == 0 && j < 3; j++) { | |
| if ((c & utf8_check_table[j][0]) == utf8_check_table[j][1]) { | |
| num_utf8_bytes = j + 2; | |
| } | |
| } | |
| // get subsequent octets and check that they are valid | |
| for (unsigned int j = 0; j < num_utf8_bytes; j++) { | |
| if (j != 0) { | |
| c = fin.get(); | |
| if (!fin || (c & 0xC0) != 0x80) { | |
| fin.putback(static_cast<char>(c)); | |
| break; | |
| } | |
| } | |
| current_str += static_cast<char>(c); | |
| } | |
| // if this was an invalid utf8 sequence, discard the data, and put | |
| // back subsequent characters | |
| if ((current_str.length() != num_utf8_bytes)) { | |
| for (unsigned int j = 0; j < current_str.size() - 1; j++) { | |
| fin.putback(current_str[current_str.size() - 1 - j]); | |
| } | |
| current_str.clear(); | |
| } | |
| } | |
| if (c == '\n' && !newline_consume) { | |
| // The current line has been terminated. Check if the current | |
| // string matches the requirements. The length may now be as | |
| // low as zero since blank lines are allowed. | |
| if (s.length() >= minlen && (!have_regex || regex.find(s))) { | |
| if (store_regex) { | |
| status.GetMakefile().ClearMatches(); | |
| status.GetMakefile().StoreMatches(regex); | |
| } | |
| output_size += static_cast<int>(s.size()) + 1; | |
| if (limit_output >= 0 && output_size >= limit_output) { | |
| s.clear(); | |
| break; | |
| } | |
| strings.push_back(s); | |
| } | |
| // Reset the string to empty. | |
| s.clear(); | |
| } else if (current_str.empty()) { | |
| // A non-string character has been found. Check if the current | |
| // string matches the requirements. We require that the length | |
| // be at least one no matter what the user specified. | |
| if (s.length() >= minlen && !s.empty() && | |
| (!have_regex || regex.find(s))) { | |
| if (store_regex) { | |
| status.GetMakefile().ClearMatches(); | |
| status.GetMakefile().StoreMatches(regex); | |
| } | |
| output_size += static_cast<int>(s.size()) + 1; | |
| if (limit_output >= 0 && output_size >= limit_output) { | |
| s.clear(); | |
| break; | |
| } | |
| strings.push_back(s); | |
| } | |
| // Reset the string to empty. | |
| s.clear(); | |
| } else { | |
| s += current_str; | |
| } | |
| if (maxlen > 0 && s.size() == maxlen) { | |
| // Terminate a string if the maximum length is reached. | |
| if (s.length() >= minlen && (!have_regex || regex.find(s))) { | |
| if (store_regex) { | |
| status.GetMakefile().ClearMatches(); | |
| status.GetMakefile().StoreMatches(regex); | |
| } | |
| output_size += static_cast<int>(s.size()) + 1; | |
| if (limit_output >= 0 && output_size >= limit_output) { | |
| s.clear(); | |
| break; | |
| } | |
| strings.push_back(s); | |
| } | |
| s.clear(); | |
| } | |
| } | |
| // If there is a non-empty current string we have hit the end of the | |
| // input file or the input size limit. Check if the current string | |
| // matches the requirements. | |
| if ((!limit_count || strings.size() < limit_count) && !s.empty() && | |
| s.length() >= minlen && (!have_regex || regex.find(s))) { | |
| if (store_regex) { | |
| status.GetMakefile().ClearMatches(); | |
| status.GetMakefile().StoreMatches(regex); | |
| } | |
| output_size += static_cast<int>(s.size()) + 1; | |
| if (limit_output < 0 || output_size < limit_output) { | |
| strings.push_back(s); | |
| } | |
| } | |
| // Encode the result in a CMake list. | |
| char const* sep = ""; | |
| std::string output; | |
| for (std::string const& sr : strings) { | |
| // Separate the strings in the output to make it a list. | |
| output += sep; | |
| sep = ";"; | |
| // Store the string in the output, but escape semicolons to | |
| // make sure it is a list. | |
| for (char i : sr) { | |
| if (i == ';') { | |
| output += '\\'; | |
| } | |
| output += i; | |
| } | |
| } | |
| // Save the output in a makefile variable. | |
| status.GetMakefile().AddDefinition(outVar, output); | |
| return true; | |
| } | |
| bool HandleGlobImpl(std::vector<std::string> const& args, bool recurse, | |
| cmExecutionStatus& status) | |
| { | |
| if (args.size() < 2) { | |
| status.SetError(cmStrCat( | |
| args[0], " must be called with at least one additional argument.")); | |
| return false; | |
| } | |
| auto i = args.begin(); | |
| i++; // Get rid of subcommand | |
| std::string variable = *i; | |
| i++; | |
| cmsys::Glob g; | |
| g.SetRecurse(recurse); | |
| if (recurse) { | |
| g.RecurseThroughSymlinksOff(); | |
| } | |
| cmake* cm = status.GetMakefile().GetCMakeInstance(); | |
| std::vector<std::string> files; | |
| bool configureDepends = false; | |
| bool warnConfigureLate = false; | |
| while (i != args.end()) { | |
| if (*i == "LIST_DIRECTORIES") { | |
| ++i; // skip LIST_DIRECTORIES | |
| if (i != args.end()) { | |
| if (cmIsOn(*i)) { | |
| g.SetListDirs(true); | |
| g.SetRecurseListDirs(true); | |
| } else if (cmIsOff(*i)) { | |
| g.SetListDirs(false); | |
| g.SetRecurseListDirs(false); | |
| } else { | |
| status.SetError("LIST_DIRECTORIES missing bool value."); | |
| return false; | |
| } | |
| ++i; | |
| } else { | |
| status.SetError("LIST_DIRECTORIES missing bool value."); | |
| return false; | |
| } | |
| } else if (*i == "FOLLOW_SYMLINKS") { | |
| ++i; // skip FOLLOW_SYMLINKS | |
| if (recurse) { | |
| g.RecurseThroughSymlinksOn(); | |
| if (i == args.end()) { | |
| status.SetError( | |
| "GLOB_RECURSE requires a glob expression after FOLLOW_SYMLINKS."); | |
| return false; | |
| } | |
| } | |
| } else if (*i == "RELATIVE") { | |
| ++i; // skip RELATIVE | |
| if (i == args.end()) { | |
| status.SetError("GLOB requires a directory after the RELATIVE tag."); | |
| return false; | |
| } | |
| g.SetRelative(i->c_str()); | |
| ++i; | |
| if (i == args.end()) { | |
| status.SetError( | |
| "GLOB requires a glob expression after the directory."); | |
| return false; | |
| } | |
| } else if (*i == "CONFIGURE_DEPENDS") { | |
| // Generated build system depends on glob results | |
| if (!configureDepends && warnConfigureLate) { | |
| status.GetMakefile().IssueDiagnostic( | |
| cmDiagnostics::CMD_AUTHOR, | |
| "CONFIGURE_DEPENDS flag was given after a glob expression was " | |
| "already evaluated."); | |
| } | |
| if (cm->GetState()->GetRole() != cmState::Role::Project) { | |
| status.GetMakefile().IssueMessage( | |
| MessageType::FATAL_ERROR, | |
| "CONFIGURE_DEPENDS is invalid for script and find package modes."); | |
| return false; | |
| } | |
| configureDepends = true; | |
| ++i; | |
| if (i == args.end()) { | |
| status.SetError( | |
| "GLOB requires a glob expression after CONFIGURE_DEPENDS."); | |
| return false; | |
| } | |
| } else { | |
| std::string expr = *i; | |
| if (!cmsys::SystemTools::FileIsFullPath(*i)) { | |
| expr = status.GetMakefile().GetCurrentSourceDirectory(); | |
| // Handle script mode | |
| if (!expr.empty()) { | |
| expr = cmStrCat(std::move(expr), '/', *i); | |
| } else { | |
| expr = *i; | |
| } | |
| } | |
| cmsys::Glob::GlobMessages globMessages; | |
| g.FindFiles(expr, &globMessages); | |
| if (!globMessages.empty()) { | |
| bool shouldExit = false; | |
| for (cmsys::Glob::Message const& globMessage : globMessages) { | |
| if (globMessage.type == cmsys::Glob::cyclicRecursion) { | |
| status.GetMakefile().IssueDiagnostic( | |
| cmDiagnostics::CMD_AUTHOR, | |
| cmStrCat("Cyclic recursion detected while globbing for '", *i, | |
| "':\n", globMessage.content)); | |
| } else if (globMessage.type == cmsys::Glob::error) { | |
| status.GetMakefile().IssueMessage( | |
| MessageType::FATAL_ERROR, | |
| cmStrCat("Error has occurred while globbing for '", *i, "' - ", | |
| globMessage.content)); | |
| shouldExit = true; | |
| } else if (cm->GetDebugOutput() || cm->GetTrace()) { | |
| status.GetMakefile().IssueMessage( | |
| MessageType::LOG, | |
| cmStrCat("Globbing for\n ", *i, "\nEncountered an error:\n ", | |
| globMessage.content)); | |
| } | |
| } | |
| if (shouldExit) { | |
| return false; | |
| } | |
| } | |
| std::vector<std::string>& foundFiles = g.GetFiles(); | |
| cm::append(files, foundFiles); | |
| if (configureDepends) { | |
| std::sort(foundFiles.begin(), foundFiles.end()); | |
| foundFiles.erase(std::unique(foundFiles.begin(), foundFiles.end()), | |
| foundFiles.end()); | |
| auto entry = cmGlobCacheEntry{ | |
| recurse, | |
| (recurse ? g.GetRecurseListDirs() : g.GetListDirs()), | |
| (recurse ? g.GetRecurseThroughSymlinks() : false), | |
| (g.GetRelative() ? g.GetRelative() : ""), | |
| expr, | |
| foundFiles | |
| }; | |
| cm->AddGlobCacheEntry(entry, variable, | |
| status.GetMakefile().GetBacktrace()); | |
| } else { | |
| warnConfigureLate = true; | |
| } | |
| ++i; | |
| } | |
| } | |
| std::sort(files.begin(), files.end()); | |
| files.erase(std::unique(files.begin(), files.end()), files.end()); | |
| status.GetMakefile().AddDefinition(variable, cmList::to_string(files)); | |
| return true; | |
| } | |
| bool HandleGlobCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| return HandleGlobImpl(args, false, status); | |
| } | |
| bool HandleGlobRecurseCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| return HandleGlobImpl(args, true, status); | |
| } | |
| bool HandleMakeDirectoryCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| // Projects might pass a dynamically generated list of directories, and it | |
| // could be an empty list. We should not assume there is at least one. | |
| cmRange<std::vector<std::string>::const_iterator> argsRange = | |
| cmMakeRange(args).advance(1); // Get rid of subcommand | |
| struct Arguments : public ArgumentParser::ParseResult | |
| { | |
| std::string Result; | |
| }; | |
| Arguments arguments; | |
| auto resultPosItr = | |
| std::find(cm::begin(argsRange), cm::end(argsRange), "RESULT"); | |
| if (resultPosItr != cm::end(argsRange)) { | |
| static auto const parser = | |
| cmArgumentParser<Arguments>{}.Bind("RESULT"_s, &Arguments::Result); | |
| std::vector<std::string> unparsedArguments; | |
| auto resultDistanceFromBegin = | |
| std::distance(cm::begin(argsRange), resultPosItr); | |
| arguments = | |
| parser.Parse(cmMakeRange(argsRange).advance(resultDistanceFromBegin), | |
| &unparsedArguments); | |
| if (!unparsedArguments.empty()) { | |
| std::string unexpectedArgsStr = cmJoin( | |
| cmMakeRange(cm::begin(unparsedArguments), cm::end(unparsedArguments)), | |
| "\n"); | |
| status.SetError("MAKE_DIRECTORY called with unexpected\n" | |
| "arguments:\n " + | |
| unexpectedArgsStr); | |
| return false; | |
| } | |
| auto resultDistanceFromEnd = | |
| std::distance(cm::end(argsRange), resultPosItr); | |
| argsRange = argsRange.retreat(-resultDistanceFromEnd); | |
| } | |
| std::string expr; | |
| for (std::string const& arg : argsRange) { | |
| std::string const* cdir = &arg; | |
| if (!cmsys::SystemTools::FileIsFullPath(arg)) { | |
| expr = | |
| cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', arg); | |
| cdir = &expr; | |
| } | |
| if (!status.GetMakefile().CanIWriteThisFile(*cdir)) { | |
| std::string e = cmStrCat("attempted to create a directory: ", *cdir, | |
| " into a source directory."); | |
| if (arguments.Result.empty()) { | |
| status.SetError(e); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| status.GetMakefile().AddDefinition(arguments.Result, e); | |
| return true; | |
| } | |
| cmsys::Status mkdirStatus = cmSystemTools::MakeDirectory(*cdir); | |
| if (!mkdirStatus) { | |
| if (arguments.Result.empty()) { | |
| std::string errorOutput = | |
| cmStrCat("failed to create directory:\n ", *cdir, | |
| "\nbecause: ", mkdirStatus.GetString()); | |
| status.SetError(errorOutput); | |
| return false; | |
| } | |
| std::string errorResult = cmStrCat("Failed to create directory: ", *cdir, | |
| " Error: ", mkdirStatus.GetString()); | |
| status.GetMakefile().AddDefinition(arguments.Result, errorResult); | |
| return true; | |
| } | |
| } | |
| if (!arguments.Result.empty()) { | |
| status.GetMakefile().AddDefinition(arguments.Result, "0"); | |
| } | |
| return true; | |
| } | |
| bool HandleTouchImpl(std::vector<std::string> const& args, bool create, | |
| cmExecutionStatus& status) | |
| { | |
| // Projects might pass a dynamically generated list of files, and it | |
| // could be an empty list. We should not assume there is at least one. | |
| for (std::string const& arg : | |
| cmMakeRange(args).advance(1)) // Get rid of subcommand | |
| { | |
| std::string tfile = arg; | |
| if (!cmsys::SystemTools::FileIsFullPath(tfile)) { | |
| tfile = | |
| cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', arg); | |
| } | |
| if (!status.GetMakefile().CanIWriteThisFile(tfile)) { | |
| std::string e = cmStrCat("attempted to touch a file: ", tfile, | |
| " in a source directory."); | |
| status.SetError(e); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| if (!cmSystemTools::Touch(tfile, create)) { | |
| std::string error = "problem touching file: " + tfile; | |
| status.SetError(error); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool HandleTouchCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| return HandleTouchImpl(args, true, status); | |
| } | |
| bool HandleTouchNocreateCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| return HandleTouchImpl(args, false, status); | |
| } | |
| bool HandleDifferentCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| /* | |
| FILE(DIFFERENT <variable> FILES <lhs> <rhs>) | |
| */ | |
| // Evaluate arguments. | |
| char const* file_lhs = nullptr; | |
| char const* file_rhs = nullptr; | |
| char const* var = nullptr; | |
| enum Doing | |
| { | |
| DoingNone, | |
| DoingVar, | |
| DoingFileLHS, | |
| DoingFileRHS | |
| }; | |
| Doing doing = DoingVar; | |
| for (unsigned int i = 1; i < args.size(); ++i) { | |
| if (args[i] == "FILES") { | |
| doing = DoingFileLHS; | |
| } else if (doing == DoingVar) { | |
| var = args[i].c_str(); | |
| doing = DoingNone; | |
| } else if (doing == DoingFileLHS) { | |
| file_lhs = args[i].c_str(); | |
| doing = DoingFileRHS; | |
| } else if (doing == DoingFileRHS) { | |
| file_rhs = args[i].c_str(); | |
| doing = DoingNone; | |
| } else { | |
| status.SetError(cmStrCat("DIFFERENT given unknown argument ", args[i])); | |
| return false; | |
| } | |
| } | |
| if (!var) { | |
| status.SetError("DIFFERENT not given result variable name."); | |
| return false; | |
| } | |
| if (!file_lhs || !file_rhs) { | |
| status.SetError("DIFFERENT not given FILES option with two file names."); | |
| return false; | |
| } | |
| // Compare the files. | |
| char const* result = | |
| cmSystemTools::FilesDiffer(file_lhs, file_rhs) ? "1" : "0"; | |
| status.GetMakefile().AddDefinition(var, result); | |
| return true; | |
| } | |
| bool HandleCopyCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| cmFileCopier copier(status); | |
| return copier.Run(args); | |
| } | |
| bool HandleRPathChangeCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| // Evaluate arguments. | |
| std::string file; | |
| cm::optional<std::string> oldRPath; | |
| cm::optional<std::string> newRPath; | |
| bool removeEnvironmentRPath = false; | |
| cmArgumentParser<void> parser; | |
| std::vector<std::string> unknownArgs; | |
| parser.Bind("FILE"_s, file) | |
| .Bind("OLD_RPATH"_s, oldRPath) | |
| .Bind("NEW_RPATH"_s, newRPath) | |
| .Bind("INSTALL_REMOVE_ENVIRONMENT_RPATH"_s, removeEnvironmentRPath); | |
| ArgumentParser::ParseResult parseResult = | |
| parser.Parse(cmMakeRange(args).advance(1), &unknownArgs); | |
| if (!unknownArgs.empty()) { | |
| status.SetError( | |
| cmStrCat("RPATH_CHANGE given unknown argument ", unknownArgs.front())); | |
| return false; | |
| } | |
| if (parseResult.MaybeReportError(status.GetMakefile())) { | |
| return true; | |
| } | |
| if (file.empty()) { | |
| status.SetError("RPATH_CHANGE not given FILE option."); | |
| return false; | |
| } | |
| if (!oldRPath) { | |
| status.SetError("RPATH_CHANGE not given OLD_RPATH option."); | |
| return false; | |
| } | |
| if (!newRPath) { | |
| status.SetError("RPATH_CHANGE not given NEW_RPATH option."); | |
| return false; | |
| } | |
| if (!cmSystemTools::FileExists(file, true)) { | |
| status.SetError( | |
| cmStrCat("RPATH_CHANGE given FILE \"", file, "\" that does not exist.")); | |
| return false; | |
| } | |
| bool success = true; | |
| cmFileTimes const ft(file); | |
| std::string emsg; | |
| bool changed; | |
| if (!cmSystemTools::ChangeRPath(file, *oldRPath, *newRPath, | |
| removeEnvironmentRPath, &emsg, &changed)) { | |
| status.SetError(cmStrCat("RPATH_CHANGE could not write new RPATH:\n ", | |
| *newRPath, "\nto the file:\n ", file, '\n', | |
| emsg)); | |
| success = false; | |
| } | |
| if (success) { | |
| if (changed) { | |
| std::string message = | |
| cmStrCat("Set non-toolchain portion of runtime path of \"", file, | |
| "\" to \"", *newRPath, '"'); | |
| status.GetMakefile().DisplayStatus(message, -1); | |
| } | |
| ft.Store(file); | |
| } | |
| return success; | |
| } | |
| bool HandleRPathSetCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| // Evaluate arguments. | |
| std::string file; | |
| cm::optional<std::string> newRPath; | |
| cmArgumentParser<void> parser; | |
| std::vector<std::string> unknownArgs; | |
| parser.Bind("FILE"_s, file).Bind("NEW_RPATH"_s, newRPath); | |
| ArgumentParser::ParseResult parseResult = | |
| parser.Parse(cmMakeRange(args).advance(1), &unknownArgs); | |
| if (!unknownArgs.empty()) { | |
| status.SetError(cmStrCat("RPATH_SET given unrecognized argument \"", | |
| unknownArgs.front(), "\".")); | |
| return false; | |
| } | |
| if (parseResult.MaybeReportError(status.GetMakefile())) { | |
| return true; | |
| } | |
| if (file.empty()) { | |
| status.SetError("RPATH_SET not given FILE option."); | |
| return false; | |
| } | |
| if (!newRPath) { | |
| status.SetError("RPATH_SET not given NEW_RPATH option."); | |
| return false; | |
| } | |
| if (!cmSystemTools::FileExists(file, true)) { | |
| status.SetError( | |
| cmStrCat("RPATH_SET given FILE \"", file, "\" that does not exist.")); | |
| return false; | |
| } | |
| bool success = true; | |
| cmFileTimes const ft(file); | |
| std::string emsg; | |
| bool changed; | |
| if (!cmSystemTools::SetRPath(file, *newRPath, &emsg, &changed)) { | |
| status.SetError(cmStrCat("RPATH_SET could not write new RPATH:\n ", | |
| *newRPath, "\nto the file:\n ", file, '\n', | |
| emsg)); | |
| success = false; | |
| } | |
| if (success) { | |
| if (changed) { | |
| std::string message = | |
| cmStrCat("Set non-toolchain portion of runtime path of \"", file, | |
| "\" to \"", *newRPath, '"'); | |
| status.GetMakefile().DisplayStatus(message, -1); | |
| } | |
| ft.Store(file); | |
| } | |
| return success; | |
| } | |
| bool HandleRPathRemoveCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| // Evaluate arguments. | |
| std::string file; | |
| cmArgumentParser<void> parser; | |
| std::vector<std::string> unknownArgs; | |
| parser.Bind("FILE"_s, file); | |
| ArgumentParser::ParseResult parseResult = | |
| parser.Parse(cmMakeRange(args).advance(1), &unknownArgs); | |
| if (!unknownArgs.empty()) { | |
| status.SetError( | |
| cmStrCat("RPATH_REMOVE given unknown argument ", unknownArgs.front())); | |
| return false; | |
| } | |
| if (parseResult.MaybeReportError(status.GetMakefile())) { | |
| return true; | |
| } | |
| if (file.empty()) { | |
| status.SetError("RPATH_REMOVE not given FILE option."); | |
| return false; | |
| } | |
| if (!cmSystemTools::FileExists(file, true)) { | |
| status.SetError( | |
| cmStrCat("RPATH_REMOVE given FILE \"", file, "\" that does not exist.")); | |
| return false; | |
| } | |
| bool success = true; | |
| cmFileTimes const ft(file); | |
| std::string emsg; | |
| bool removed; | |
| if (!cmSystemTools::RemoveRPath(file, &emsg, &removed)) { | |
| status.SetError( | |
| cmStrCat("RPATH_REMOVE could not remove RPATH from file: \n ", file, | |
| '\n', emsg)); | |
| success = false; | |
| } | |
| if (success) { | |
| if (removed) { | |
| std::string message = | |
| cmStrCat("Removed runtime path from \"", file, '"'); | |
| status.GetMakefile().DisplayStatus(message, -1); | |
| } | |
| ft.Store(file); | |
| } | |
| return success; | |
| } | |
| bool HandleRPathCheckCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| // Evaluate arguments. | |
| std::string file; | |
| cm::optional<std::string> rpath; | |
| cmArgumentParser<void> parser; | |
| std::vector<std::string> unknownArgs; | |
| parser.Bind("FILE"_s, file).Bind("RPATH"_s, rpath); | |
| ArgumentParser::ParseResult parseResult = | |
| parser.Parse(cmMakeRange(args).advance(1), &unknownArgs); | |
| if (!unknownArgs.empty()) { | |
| status.SetError( | |
| cmStrCat("RPATH_CHECK given unknown argument ", unknownArgs.front())); | |
| return false; | |
| } | |
| if (parseResult.MaybeReportError(status.GetMakefile())) { | |
| return true; | |
| } | |
| if (file.empty()) { | |
| status.SetError("RPATH_CHECK not given FILE option."); | |
| return false; | |
| } | |
| if (!rpath) { | |
| status.SetError("RPATH_CHECK not given RPATH option."); | |
| return false; | |
| } | |
| // If the file exists but does not have the desired RPath then | |
| // delete it. This is used during installation to re-install a file | |
| // if its RPath will change. | |
| if (cmSystemTools::FileExists(file, true) && | |
| !cmSystemTools::CheckRPath(file, *rpath)) { | |
| cmSystemTools::RemoveFile(file); | |
| } | |
| return true; | |
| } | |
| bool HandleReadElfCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| if (args.size() < 4) { | |
| status.SetError("READ_ELF must be called with at least three additional " | |
| "arguments."); | |
| return false; | |
| } | |
| std::string const& fileNameArg = args[1]; | |
| struct Arguments | |
| { | |
| std::string RPath; | |
| std::string RunPath; | |
| std::string Error; | |
| }; | |
| static auto const parser = cmArgumentParser<Arguments>{} | |
| .Bind("RPATH"_s, &Arguments::RPath) | |
| .Bind("RUNPATH"_s, &Arguments::RunPath) | |
| .Bind("CAPTURE_ERROR"_s, &Arguments::Error); | |
| Arguments const arguments = parser.Parse(cmMakeRange(args).advance(2), | |
| /*unparsedArguments=*/nullptr); | |
| if (!cmSystemTools::FileExists(fileNameArg, true)) { | |
| status.SetError(cmStrCat("READ_ELF given FILE \"", fileNameArg, | |
| "\" that does not exist.")); | |
| return false; | |
| } | |
| cmELF elf(fileNameArg.c_str()); | |
| if (!elf) { | |
| if (arguments.Error.empty()) { | |
| status.SetError(cmStrCat("READ_ELF given FILE:\n ", fileNameArg, | |
| "\nthat is not a valid ELF file.")); | |
| return false; | |
| } | |
| status.GetMakefile().AddDefinition(arguments.Error, | |
| "not a valid ELF file"); | |
| return true; | |
| } | |
| if (!arguments.RPath.empty()) { | |
| if (cmELF::StringEntry const* se_rpath = elf.GetRPath()) { | |
| std::string rpath(se_rpath->Value); | |
| std::replace(rpath.begin(), rpath.end(), ':', ';'); | |
| status.GetMakefile().AddDefinition(arguments.RPath, rpath); | |
| } | |
| } | |
| if (!arguments.RunPath.empty()) { | |
| if (cmELF::StringEntry const* se_runpath = elf.GetRunPath()) { | |
| std::string runpath(se_runpath->Value); | |
| std::replace(runpath.begin(), runpath.end(), ':', ';'); | |
| status.GetMakefile().AddDefinition(arguments.RunPath, runpath); | |
| } | |
| } | |
| return true; | |
| } | |
| bool HandleInstallCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| cmFileInstaller installer(status); | |
| return installer.Run(args); | |
| } | |
| bool HandleRealPathCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| if (args.size() < 3) { | |
| status.SetError("REAL_PATH requires a path and an output variable"); | |
| return false; | |
| } | |
| struct Arguments : public ArgumentParser::ParseResult | |
| { | |
| cm::optional<std::string> BaseDirectory; | |
| bool ExpandTilde = false; | |
| }; | |
| static auto const parser = | |
| cmArgumentParser<Arguments>{} | |
| .Bind("BASE_DIRECTORY"_s, &Arguments::BaseDirectory) | |
| .Bind("EXPAND_TILDE"_s, &Arguments::ExpandTilde); | |
| std::vector<std::string> unparsedArguments; | |
| auto arguments = | |
| parser.Parse(cmMakeRange(args).advance(3), &unparsedArguments); | |
| if (!unparsedArguments.empty()) { | |
| status.SetError("REAL_PATH called with unexpected arguments"); | |
| return false; | |
| } | |
| if (arguments.MaybeReportError(status.GetMakefile())) { | |
| return true; | |
| } | |
| if (!arguments.BaseDirectory) { | |
| arguments.BaseDirectory = status.GetMakefile().GetCurrentSourceDirectory(); | |
| } | |
| auto input = args[1]; | |
| if (arguments.ExpandTilde && !input.empty()) { | |
| if (input[0] == '~' && (input.length() == 1 || input[1] == '/')) { | |
| std::string home; | |
| if ( | |
| #if defined(_WIN32) && !defined(__CYGWIN__) | |
| cmSystemTools::GetEnv("USERPROFILE", home) || | |
| #endif | |
| cmSystemTools::GetEnv("HOME", home)) { | |
| input.replace(0, 1, home); | |
| } | |
| } | |
| } | |
| bool warnAbout152 = false; | |
| bool use152New = true; | |
| cmPolicies::PolicyStatus policyStatus = | |
| status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0152); | |
| switch (policyStatus) { | |
| case cmPolicies::NEW: | |
| break; | |
| case cmPolicies::WARN: | |
| use152New = false; | |
| warnAbout152 = true; | |
| break; | |
| case cmPolicies::OLD: | |
| use152New = false; | |
| warnAbout152 = false; | |
| break; | |
| } | |
| auto computeNewPath = [=](std::string const& in, std::string& result) { | |
| auto path = cmCMakePath{ in }; | |
| if (path.IsRelative()) { | |
| auto basePath = cmCMakePath{ *arguments.BaseDirectory }; | |
| path = basePath.Append(path); | |
| } | |
| result = cmSystemTools::GetRealPath(path.String()); | |
| }; | |
| std::string realPath; | |
| if (use152New) { | |
| computeNewPath(input, realPath); | |
| } else { | |
| std::string oldPolicyPath = | |
| cmSystemTools::CollapseFullPath(input, *arguments.BaseDirectory); | |
| oldPolicyPath = cmSystemTools::GetRealPath(oldPolicyPath); | |
| if (warnAbout152) { | |
| computeNewPath(input, realPath); | |
| if (oldPolicyPath != realPath) { | |
| status.GetMakefile().IssuePolicyWarning( | |
| cmPolicies::CMP0152, {}, | |
| cmStrCat( | |
| "From input path:\n ", input, | |
| "\nthe policy OLD behavior produces path:\n ", oldPolicyPath, | |
| "\nbut the policy NEW behavior produces path:\n ", realPath, | |
| "\nSince the policy is not set, CMake is using the OLD " | |
| "behavior for compatibility.")); | |
| } | |
| } | |
| realPath = oldPolicyPath; | |
| } | |
| if (!cmSystemTools::FileExists(realPath)) { | |
| status.GetMakefile().IssueDiagnostic( | |
| cmDiagnostics::CMD_AUTHOR, | |
| cmStrCat("Given path:\n ", input, | |
| "\ndoes not refer to an existing path on disk.")); | |
| } | |
| status.GetMakefile().AddDefinition(args[2], realPath); | |
| return true; | |
| } | |
| bool HandleRelativePathCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| if (args.size() != 4) { | |
| status.SetError("RELATIVE_PATH called with incorrect number of arguments"); | |
| return false; | |
| } | |
| std::string const& outVar = args[1]; | |
| std::string const& directoryName = args[2]; | |
| std::string const& fileName = args[3]; | |
| if (!cmSystemTools::FileIsFullPath(directoryName)) { | |
| std::string errstring = | |
| "RELATIVE_PATH must be passed a full path to the directory: " + | |
| directoryName; | |
| status.SetError(errstring); | |
| return false; | |
| } | |
| if (!cmSystemTools::FileIsFullPath(fileName)) { | |
| std::string errstring = | |
| "RELATIVE_PATH must be passed a full path to the file: " + fileName; | |
| status.SetError(errstring); | |
| return false; | |
| } | |
| std::string res = cmSystemTools::RelativePath(directoryName, fileName); | |
| status.GetMakefile().AddDefinition(outVar, res); | |
| return true; | |
| } | |
| bool HandleRename(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| if (args.size() < 3) { | |
| status.SetError("RENAME must be called with at least two additional " | |
| "arguments"); | |
| return false; | |
| } | |
| // Compute full path for old and new names. | |
| std::string oldname = args[1]; | |
| if (!cmsys::SystemTools::FileIsFullPath(oldname)) { | |
| oldname = | |
| cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[1]); | |
| } | |
| std::string newname = args[2]; | |
| if (!cmsys::SystemTools::FileIsFullPath(newname)) { | |
| newname = | |
| cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[2]); | |
| } | |
| struct Arguments | |
| { | |
| bool NoReplace = false; | |
| std::string Result; | |
| }; | |
| static auto const parser = cmArgumentParser<Arguments>{} | |
| .Bind("NO_REPLACE"_s, &Arguments::NoReplace) | |
| .Bind("RESULT"_s, &Arguments::Result); | |
| std::vector<std::string> unconsumedArgs; | |
| Arguments const arguments = | |
| parser.Parse(cmMakeRange(args).advance(3), &unconsumedArgs); | |
| if (!unconsumedArgs.empty()) { | |
| status.SetError("RENAME unknown argument:\n " + unconsumedArgs.front()); | |
| return false; | |
| } | |
| std::string err; | |
| switch (cmSystemTools::RenameFile(oldname, newname, | |
| arguments.NoReplace | |
| ? cmSystemTools::Replace::No | |
| : cmSystemTools::Replace::Yes, | |
| &err)) { | |
| case cmSystemTools::RenameResult::Success: | |
| if (!arguments.Result.empty()) { | |
| status.GetMakefile().AddDefinition(arguments.Result, "0"); | |
| } | |
| return true; | |
| case cmSystemTools::RenameResult::NoReplace: | |
| if (!arguments.Result.empty()) { | |
| err = "NO_REPLACE"; | |
| } else { | |
| err = "path not replaced"; | |
| } | |
| CM_FALLTHROUGH; | |
| case cmSystemTools::RenameResult::Failure: | |
| if (!arguments.Result.empty()) { | |
| status.GetMakefile().AddDefinition(arguments.Result, err); | |
| return true; | |
| } | |
| break; | |
| } | |
| status.SetError(cmStrCat("RENAME failed to rename\n ", oldname, "\nto\n ", | |
| newname, "\nbecause: ", err, '\n')); | |
| return false; | |
| } | |
| bool HandleCopyFile(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| if (args.size() < 3) { | |
| status.SetError("COPY_FILE must be called with at least two additional " | |
| "arguments"); | |
| return false; | |
| } | |
| // Compute full path for old and new names. | |
| std::string oldname = args[1]; | |
| if (!cmsys::SystemTools::FileIsFullPath(oldname)) { | |
| oldname = | |
| cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[1]); | |
| } | |
| std::string newname = args[2]; | |
| if (!cmsys::SystemTools::FileIsFullPath(newname)) { | |
| newname = | |
| cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[2]); | |
| } | |
| struct Arguments | |
| { | |
| bool InputMayBeRecent = false; | |
| bool OnlyIfDifferent = false; | |
| std::string Result; | |
| }; | |
| static auto const parser = | |
| cmArgumentParser<Arguments>{} | |
| .Bind("INPUT_MAY_BE_RECENT"_s, &Arguments::InputMayBeRecent) | |
| .Bind("ONLY_IF_DIFFERENT"_s, &Arguments::OnlyIfDifferent) | |
| .Bind("RESULT"_s, &Arguments::Result); | |
| std::vector<std::string> unconsumedArgs; | |
| Arguments const arguments = | |
| parser.Parse(cmMakeRange(args).advance(3), &unconsumedArgs); | |
| if (!unconsumedArgs.empty()) { | |
| status.SetError("COPY_FILE unknown argument:\n " + | |
| unconsumedArgs.front()); | |
| return false; | |
| } | |
| bool result = true; | |
| if (cmsys::SystemTools::FileIsDirectory(oldname)) { | |
| if (!arguments.Result.empty()) { | |
| status.GetMakefile().AddDefinition(arguments.Result, | |
| "cannot copy a directory"); | |
| } else { | |
| status.SetError( | |
| cmStrCat("COPY_FILE cannot copy a directory\n ", oldname)); | |
| result = false; | |
| } | |
| return result; | |
| } | |
| if (cmsys::SystemTools::FileIsDirectory(newname)) { | |
| if (!arguments.Result.empty()) { | |
| status.GetMakefile().AddDefinition(arguments.Result, | |
| "cannot copy to a directory"); | |
| } else { | |
| status.SetError( | |
| cmStrCat("COPY_FILE cannot copy to a directory\n ", newname)); | |
| result = false; | |
| } | |
| return result; | |
| } | |
| cmSystemTools::CopyWhen when; | |
| if (arguments.OnlyIfDifferent) { | |
| when = cmSystemTools::CopyWhen::OnlyIfDifferent; | |
| } else { | |
| when = cmSystemTools::CopyWhen::Unconditional; | |
| } | |
| cmSystemTools::CopyInputRecent const inputRecent = arguments.InputMayBeRecent | |
| ? cmSystemTools::CopyInputRecent::Yes | |
| : cmSystemTools::CopyInputRecent::No; | |
| std::string err; | |
| if (cmSystemTools::CopySingleFile(oldname, newname, when, inputRecent, | |
| &err) == | |
| cmSystemTools::CopyResult::Success) { | |
| if (!arguments.Result.empty()) { | |
| status.GetMakefile().AddDefinition(arguments.Result, "0"); | |
| } | |
| } else { | |
| if (!arguments.Result.empty()) { | |
| status.GetMakefile().AddDefinition(arguments.Result, err); | |
| } else { | |
| status.SetError(cmStrCat("COPY_FILE failed to copy\n ", oldname, | |
| "\nto\n ", newname, "\nbecause: ", err, '\n')); | |
| result = false; | |
| } | |
| } | |
| return result; | |
| } | |
| bool HandleRemoveImpl(std::vector<std::string> const& args, bool recurse, | |
| cmExecutionStatus& status) | |
| { | |
| for (std::string const& arg : | |
| cmMakeRange(args).advance(1)) // Get rid of subcommand | |
| { | |
| std::string fileName = arg; | |
| if (fileName.empty()) { | |
| std::string r = recurse ? "REMOVE_RECURSE" : "REMOVE"; | |
| status.GetMakefile().IssueDiagnostic( | |
| cmDiagnostics::CMD_AUTHOR, | |
| cmStrCat("Ignoring empty file name in ", std::move(r), '.')); | |
| continue; | |
| } | |
| if (!cmsys::SystemTools::FileIsFullPath(fileName)) { | |
| fileName = | |
| cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', arg); | |
| } | |
| if (cmSystemTools::FileIsDirectory(fileName) && | |
| !cmSystemTools::FileIsSymlink(fileName) && recurse) { | |
| cmSystemTools::RepeatedRemoveDirectory(fileName); | |
| } else { | |
| cmSystemTools::RemoveFile(fileName); | |
| } | |
| } | |
| return true; | |
| } | |
| bool HandleRemove(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| return HandleRemoveImpl(args, false, status); | |
| } | |
| bool HandleRemoveRecurse(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| return HandleRemoveImpl(args, true, status); | |
| } | |
| std::string ToNativePath(std::string const& path) | |
| { | |
| auto const& outPath = cmSystemTools::ConvertToOutputPath(path); | |
| if (outPath.size() > 1 && outPath.front() == '\"' && | |
| outPath.back() == '\"') { | |
| return outPath.substr(1, outPath.size() - 2); | |
| } | |
| return outPath; | |
| } | |
| std::string ToCMakePath(std::string const& path) | |
| { | |
| auto temp = path; | |
| cmSystemTools::ConvertToUnixSlashes(temp); | |
| return temp; | |
| } | |
| bool HandlePathCommand(std::vector<std::string> const& args, | |
| std::string (*convert)(std::string const&), | |
| cmExecutionStatus& status) | |
| { | |
| if (args.size() != 3) { | |
| status.SetError("FILE([TO_CMAKE_PATH|TO_NATIVE_PATH] path result) must be " | |
| "called with exactly three arguments."); | |
| return false; | |
| } | |
| #if defined(_WIN32) && !defined(__CYGWIN__) | |
| char pathSep = ';'; | |
| #else | |
| char pathSep = ':'; | |
| #endif | |
| std::vector<std::string> path = cmSystemTools::SplitString(args[1], pathSep); | |
| std::string value = cmList::to_string(cmMakeRange(path).transform(convert)); | |
| status.GetMakefile().AddDefinition(args[2], value); | |
| return true; | |
| } | |
| bool HandleCMakePathCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| return HandlePathCommand(args, ToCMakePath, status); | |
| } | |
| bool HandleNativePathCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| return HandlePathCommand(args, ToNativePath, status); | |
| } | |
| #if !defined(CMAKE_BOOTSTRAP) | |
| bool const TLS_VERIFY_DEFAULT = true; | |
| std::string const TLS_VERSION_DEFAULT = "1.2"; | |
| // Stuff for curl download/upload | |
| using cmFileCommandVectorOfChar = std::vector<char>; | |
| std::string CurlCompatFileURL(std::string url) | |
| { | |
| if (cmHasLiteralPrefix(url, "file://")) { | |
| // libcurl 7.77 and below accidentally allowed spaces in file:// URLs, | |
| // which CMake has long accepted as a result. Explicitly encode spaces. | |
| cmSystemTools::ReplaceString(url, " ", "%20"); | |
| } | |
| return url; | |
| } | |
| size_t cmWriteToFileCallback(void* ptr, size_t size, size_t nmemb, void* data) | |
| { | |
| int realsize = static_cast<int>(size * nmemb); | |
| cmsys::ofstream* fout = static_cast<cmsys::ofstream*>(data); | |
| if (fout) { | |
| char const* chPtr = static_cast<char*>(ptr); | |
| if (!fout->write(chPtr, realsize)) { | |
| return CURL_WRITEFUNC_ERROR; | |
| } | |
| } | |
| return realsize; | |
| } | |
| size_t cmWriteToMemoryCallback(void* ptr, size_t size, size_t nmemb, | |
| void* data) | |
| { | |
| int realsize = static_cast<int>(size * nmemb); | |
| char const* chPtr = static_cast<char*>(ptr); | |
| cm::append(*static_cast<cmFileCommandVectorOfChar*>(data), chPtr, | |
| chPtr + realsize); | |
| return realsize; | |
| } | |
| int cmFileCommandCurlDebugCallback(CURL*, curl_infotype type, char* chPtr, | |
| size_t size, void* data) | |
| { | |
| cmFileCommandVectorOfChar& vec = | |
| *static_cast<cmFileCommandVectorOfChar*>(data); | |
| switch (type) { | |
| case CURLINFO_TEXT: | |
| case CURLINFO_HEADER_IN: | |
| case CURLINFO_HEADER_OUT: | |
| cm::append(vec, chPtr, chPtr + size); | |
| break; | |
| case CURLINFO_DATA_IN: | |
| case CURLINFO_DATA_OUT: | |
| case CURLINFO_SSL_DATA_IN: | |
| case CURLINFO_SSL_DATA_OUT: { | |
| char buf[128]; | |
| int n = | |
| snprintf(buf, sizeof(buf), "[%" KWIML_INT_PRIu64 " bytes data]\n", | |
| static_cast<KWIML_INT_uint64_t>(size)); | |
| if (n > 0) { | |
| cm::append(vec, buf, buf + n); | |
| } | |
| } break; | |
| default: | |
| break; | |
| } | |
| return 0; | |
| } | |
| # if defined(LIBCURL_VERSION_NUM) && LIBCURL_VERSION_NUM >= 0x072000 | |
| CURLoption const CM_CURLOPT_XFERINFOFUNCTION = CURLOPT_XFERINFOFUNCTION; | |
| using cm_curl_off_t = curl_off_t; | |
| # else | |
| CURLoption const CM_CURLOPT_XFERINFOFUNCTION = CURLOPT_PROGRESSFUNCTION; | |
| using cm_curl_off_t = double; | |
| # endif | |
| class cURLProgressHelper | |
| { | |
| public: | |
| cURLProgressHelper(cmMakefile* mf, char const* text) | |
| : Makefile(mf) | |
| , Text(text) | |
| { | |
| } | |
| bool UpdatePercentage(cm_curl_off_t value, cm_curl_off_t total, | |
| std::string& status) | |
| { | |
| long OldPercentage = this->CurrentPercentage; | |
| if (total > 0) { | |
| this->CurrentPercentage = std::lround( | |
| static_cast<double>(value) / static_cast<double>(total) * 100.0); | |
| if (this->CurrentPercentage > 100) { | |
| // Avoid extra progress reports for unexpected data beyond total. | |
| this->CurrentPercentage = 100; | |
| } | |
| } | |
| bool updated = (OldPercentage != this->CurrentPercentage); | |
| if (updated) { | |
| status = | |
| cmStrCat('[', this->Text, ' ', this->CurrentPercentage, "% complete]"); | |
| } | |
| return updated; | |
| } | |
| cmMakefile* GetMakefile() { return this->Makefile; } | |
| private: | |
| long CurrentPercentage = -1; | |
| cmMakefile* Makefile; | |
| std::string Text; | |
| }; | |
| int cmFileDownloadProgressCallback(void* clientp, cm_curl_off_t dltotal, | |
| cm_curl_off_t dlnow, cm_curl_off_t ultotal, | |
| cm_curl_off_t ulnow) | |
| { | |
| cURLProgressHelper* helper = reinterpret_cast<cURLProgressHelper*>(clientp); | |
| static_cast<void>(ultotal); | |
| static_cast<void>(ulnow); | |
| std::string status; | |
| if (helper->UpdatePercentage(dlnow, dltotal, status)) { | |
| cmMakefile* mf = helper->GetMakefile(); | |
| mf->DisplayStatus(status, -1); | |
| } | |
| return 0; | |
| } | |
| int cmFileUploadProgressCallback(void* clientp, cm_curl_off_t dltotal, | |
| cm_curl_off_t dlnow, cm_curl_off_t ultotal, | |
| cm_curl_off_t ulnow) | |
| { | |
| cURLProgressHelper* helper = reinterpret_cast<cURLProgressHelper*>(clientp); | |
| static_cast<void>(dltotal); | |
| static_cast<void>(dlnow); | |
| std::string status; | |
| if (helper->UpdatePercentage(ulnow, ultotal, status)) { | |
| cmMakefile* mf = helper->GetMakefile(); | |
| mf->DisplayStatus(status, -1); | |
| } | |
| return 0; | |
| } | |
| class cURLEasyGuard | |
| { | |
| public: | |
| cURLEasyGuard(CURL* easy) | |
| : Easy(easy) | |
| { | |
| } | |
| ~cURLEasyGuard() | |
| { | |
| if (this->Easy) { | |
| ::curl_easy_cleanup(this->Easy); | |
| } | |
| } | |
| cURLEasyGuard(cURLEasyGuard const&) = delete; | |
| cURLEasyGuard& operator=(cURLEasyGuard const&) = delete; | |
| void release() { this->Easy = nullptr; } | |
| private: | |
| ::CURL* Easy; | |
| }; | |
| #endif | |
| #define check_curl_result(result, errstr) \ | |
| do { \ | |
| if (result != CURLE_OK) { \ | |
| std::string e(errstr); \ | |
| e += ::curl_easy_strerror(result); \ | |
| status.SetError(e); \ | |
| return false; \ | |
| } \ | |
| } while (false) | |
| bool HandleDownloadCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| #if !defined(CMAKE_BOOTSTRAP) | |
| auto i = args.begin(); | |
| if (args.size() < 2) { | |
| status.SetError("DOWNLOAD must be called with at least two arguments."); | |
| return false; | |
| } | |
| ++i; // Get rid of subcommand | |
| std::string url = *i; | |
| ++i; | |
| std::string file; | |
| long timeout = 0; | |
| long inactivity_timeout = 0; | |
| std::string logVar; | |
| std::string statusVar; | |
| cm::optional<std::string> tlsVersionOpt; | |
| cm::optional<bool> tlsVerifyOpt; | |
| cmValue cainfo = status.GetMakefile().GetDefinition("CMAKE_TLS_CAINFO"); | |
| std::string netrc_level = | |
| status.GetMakefile().GetSafeDefinition("CMAKE_NETRC"); | |
| std::string netrc_file = | |
| status.GetMakefile().GetSafeDefinition("CMAKE_NETRC_FILE"); | |
| std::string expectedHash; | |
| std::string hashMatchMSG; | |
| std::unique_ptr<cmCryptoHash> hash; | |
| bool showProgress = false; | |
| std::string userpwd; | |
| std::vector<std::string> curl_headers; | |
| std::vector<std::pair<std::string, cm::optional<std::string>>> curl_ranges; | |
| while (i != args.end()) { | |
| if (*i == "TIMEOUT") { | |
| ++i; | |
| if (i != args.end()) { | |
| timeout = atol(i->c_str()); | |
| } else { | |
| status.SetError("DOWNLOAD missing time for TIMEOUT."); | |
| return false; | |
| } | |
| } else if (*i == "INACTIVITY_TIMEOUT") { | |
| ++i; | |
| if (i != args.end()) { | |
| inactivity_timeout = atol(i->c_str()); | |
| } else { | |
| status.SetError("DOWNLOAD missing time for INACTIVITY_TIMEOUT."); | |
| return false; | |
| } | |
| } else if (*i == "LOG") { | |
| ++i; | |
| if (i == args.end()) { | |
| status.SetError("DOWNLOAD missing VAR for LOG."); | |
| return false; | |
| } | |
| logVar = *i; | |
| } else if (*i == "STATUS") { | |
| ++i; | |
| if (i == args.end()) { | |
| status.SetError("DOWNLOAD missing VAR for STATUS."); | |
| return false; | |
| } | |
| statusVar = *i; | |
| } else if (*i == "TLS_VERSION") { | |
| ++i; | |
| if (i != args.end()) { | |
| tlsVersionOpt = *i; | |
| } else { | |
| status.SetError("DOWNLOAD missing value for TLS_VERSION."); | |
| return false; | |
| } | |
| } else if (*i == "TLS_VERIFY") { | |
| ++i; | |
| if (i != args.end()) { | |
| tlsVerifyOpt = cmIsOn(*i); | |
| } else { | |
| status.SetError("DOWNLOAD missing bool value for TLS_VERIFY."); | |
| return false; | |
| } | |
| } else if (*i == "TLS_CAINFO") { | |
| ++i; | |
| if (i != args.end()) { | |
| cainfo = cmValue(*i); | |
| } else { | |
| status.SetError("DOWNLOAD missing file value for TLS_CAINFO."); | |
| return false; | |
| } | |
| } else if (*i == "NETRC_FILE") { | |
| ++i; | |
| if (i != args.end()) { | |
| netrc_file = *i; | |
| } else { | |
| status.SetError("DOWNLOAD missing file value for NETRC_FILE."); | |
| return false; | |
| } | |
| } else if (*i == "NETRC") { | |
| ++i; | |
| if (i != args.end()) { | |
| netrc_level = *i; | |
| } else { | |
| status.SetError("DOWNLOAD missing level value for NETRC."); | |
| return false; | |
| } | |
| } else if (*i == "EXPECTED_MD5") { | |
| ++i; | |
| if (i == args.end()) { | |
| status.SetError("DOWNLOAD missing sum value for EXPECTED_MD5."); | |
| return false; | |
| } | |
| hash = cm::make_unique<cmCryptoHash>(cmCryptoHash::AlgoMD5); | |
| hashMatchMSG = "MD5 sum"; | |
| expectedHash = cmSystemTools::LowerCase(*i); | |
| } else if (*i == "SHOW_PROGRESS") { | |
| showProgress = true; | |
| } else if (*i == "EXPECTED_HASH") { | |
| ++i; | |
| if (i == args.end()) { | |
| status.SetError("DOWNLOAD missing ALGO=value for EXPECTED_HASH."); | |
| return false; | |
| } | |
| std::string::size_type pos = i->find("="); | |
| if (pos == std::string::npos) { | |
| std::string err = | |
| cmStrCat("DOWNLOAD EXPECTED_HASH expects ALGO=value but got: ", *i); | |
| status.SetError(err); | |
| return false; | |
| } | |
| std::string algo = i->substr(0, pos); | |
| expectedHash = cmSystemTools::LowerCase(i->substr(pos + 1)); | |
| hash = cmCryptoHash::New(algo); | |
| if (!hash) { | |
| std::string err = | |
| cmStrCat("DOWNLOAD EXPECTED_HASH given unknown ALGO: ", algo); | |
| status.SetError(err); | |
| return false; | |
| } | |
| hashMatchMSG = algo + " hash"; | |
| } else if (*i == "USERPWD") { | |
| ++i; | |
| if (i == args.end()) { | |
| status.SetError("DOWNLOAD missing string for USERPWD."); | |
| return false; | |
| } | |
| userpwd = *i; | |
| } else if (*i == "HTTPHEADER") { | |
| ++i; | |
| if (i == args.end()) { | |
| status.SetError("DOWNLOAD missing string for HTTPHEADER."); | |
| return false; | |
| } | |
| curl_headers.push_back(*i); | |
| } else if (*i == "RANGE_START") { | |
| ++i; | |
| if (i == args.end()) { | |
| status.SetError("DOWNLOAD missing value for RANGE_START."); | |
| return false; | |
| } | |
| curl_ranges.emplace_back(*i, cm::nullopt); | |
| } else if (*i == "RANGE_END") { | |
| ++i; | |
| if (curl_ranges.empty()) { | |
| curl_ranges.emplace_back("0", *i); | |
| } else { | |
| auto& last_range = curl_ranges.back(); | |
| if (!last_range.second.has_value()) { | |
| last_range.second = *i; | |
| } else { | |
| status.SetError("Multiple RANGE_END values is provided without " | |
| "the corresponding RANGE_START."); | |
| return false; | |
| } | |
| } | |
| } else if (file.empty()) { | |
| file = *i; | |
| } else { | |
| // Do not return error for compatibility reason. | |
| status.GetMakefile().IssueDiagnostic( | |
| cmDiagnostics::CMD_AUTHOR, cmStrCat("Unexpected argument: ", *i)); | |
| } | |
| ++i; | |
| } | |
| if (!tlsVerifyOpt.has_value()) { | |
| if (cmValue v = status.GetMakefile().GetDefinition("CMAKE_TLS_VERIFY")) { | |
| tlsVerifyOpt = v.IsOn(); | |
| } | |
| } | |
| if (!tlsVerifyOpt.has_value()) { | |
| if (cm::optional<std::string> v = | |
| cmSystemTools::GetEnvVar("CMAKE_TLS_VERIFY")) { | |
| tlsVerifyOpt = cmIsOn(*v); | |
| } | |
| } | |
| bool tlsVerifyDefaulted = false; | |
| if (!tlsVerifyOpt.has_value()) { | |
| tlsVerifyOpt = TLS_VERIFY_DEFAULT; | |
| tlsVerifyDefaulted = true; | |
| } | |
| if (!tlsVersionOpt.has_value()) { | |
| if (cmValue v = status.GetMakefile().GetDefinition("CMAKE_TLS_VERSION")) { | |
| tlsVersionOpt = *v; | |
| } | |
| } | |
| if (!tlsVersionOpt.has_value()) { | |
| if (cm::optional<std::string> v = | |
| cmSystemTools::GetEnvVar("CMAKE_TLS_VERSION")) { | |
| tlsVersionOpt = std::move(v); | |
| } | |
| } | |
| bool tlsVersionDefaulted = false; | |
| if (!tlsVersionOpt.has_value()) { | |
| tlsVersionOpt = TLS_VERSION_DEFAULT; | |
| tlsVersionDefaulted = true; | |
| } | |
| // Can't calculate hash if we don't save the file. | |
| // TODO Incrementally calculate hash in the write callback as the file is | |
| // being downloaded so this check can be relaxed. | |
| if (file.empty() && hash) { | |
| status.SetError("DOWNLOAD cannot calculate hash if file is not saved."); | |
| return false; | |
| } | |
| // If file exists already, and caller specified an expected md5 or sha, | |
| // and the existing file already has the expected hash, then simply | |
| // return. | |
| // | |
| if (!file.empty() && cmSystemTools::FileExists(file) && hash.get()) { | |
| std::string msg; | |
| std::string actualHash = hash->HashFile(file); | |
| if (actualHash == expectedHash) { | |
| msg = cmStrCat("skipping download as file already exists with expected ", | |
| hashMatchMSG, '"'); | |
| if (!statusVar.empty()) { | |
| status.GetMakefile().AddDefinition(statusVar, cmStrCat(0, ";\"", msg)); | |
| } | |
| return true; | |
| } | |
| } | |
| // Make sure parent directory exists so we can write to the file | |
| // as we receive downloaded bits from curl... | |
| // | |
| if (!file.empty()) { | |
| std::string dir = cmSystemTools::GetFilenamePath(file); | |
| if (!dir.empty() && !cmSystemTools::FileExists(dir) && | |
| !cmSystemTools::MakeDirectory(dir)) { | |
| std::string errstring = | |
| cmStrCat("DOWNLOAD error: cannot create directory '", dir, | |
| "' - Specify file by full path name and verify that you " | |
| "have directory creation and file write privileges."); | |
| status.SetError(errstring); | |
| return false; | |
| } | |
| } | |
| cmsys::ofstream fout; | |
| if (!file.empty()) { | |
| fout.open(file.c_str(), std::ios::binary); | |
| if (!fout) { | |
| status.SetError(cmStrCat("DOWNLOAD cannot open file for write\n" | |
| " file: \"", | |
| file, '"')); | |
| return false; | |
| } | |
| } | |
| url = CurlCompatFileURL(url); | |
| ::CURL* curl; | |
| cm_curl_global_init(CURL_GLOBAL_DEFAULT); | |
| curl = cm_curl_easy_init(); | |
| if (!curl) { | |
| status.SetError("DOWNLOAD error initializing curl."); | |
| return false; | |
| } | |
| cURLEasyGuard g_curl(curl); | |
| ::CURLcode res = ::curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); | |
| check_curl_result(res, "DOWNLOAD cannot set url: "); | |
| // enable HTTP ERROR parsing | |
| res = ::curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); | |
| check_curl_result(res, "DOWNLOAD cannot set http failure option: "); | |
| curl_version_info_data* cv = curl_version_info(CURLVERSION_FIRST); | |
| res = ::curl_easy_setopt( | |
| curl, CURLOPT_USERAGENT, | |
| cmStrCat("curl/", cv ? cv->version : LIBCURL_VERSION).c_str()); | |
| check_curl_result(res, "DOWNLOAD cannot set user agent option: "); | |
| res = ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cmWriteToFileCallback); | |
| check_curl_result(res, "DOWNLOAD cannot set write function: "); | |
| res = ::curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, | |
| cmFileCommandCurlDebugCallback); | |
| check_curl_result(res, "DOWNLOAD cannot set debug function: "); | |
| if (tlsVersionOpt.has_value()) { | |
| if (cm::optional<int> v = cmCurlParseTLSVersion(*tlsVersionOpt)) { | |
| res = ::curl_easy_setopt(curl, CURLOPT_SSLVERSION, *v); | |
| // If the caller did not explicitly ask for TLS, and libcurl was | |
| // built without any TLS backend, ignore failure to set our default. | |
| // Note that libcurl reports the absence of any TLS backend with | |
| // one of two distinct errors depending on how it was built: | |
| // - CURLE_NOT_BUILT_IN from the Curl_setopt_SSLVERSION | |
| // stub macro in setopt.h, or | |
| // - CURLE_UNKNOWN_OPTION from the function-level #else | |
| // arm of setopt_long_ssl() in setopt.c. | |
| if (tlsVersionDefaulted && | |
| (res == CURLE_NOT_BUILT_IN || res == CURLE_UNKNOWN_OPTION)) { | |
| res = CURLE_OK; | |
| } | |
| check_curl_result(res, | |
| cmStrCat("DOWNLOAD cannot set TLS/SSL version ", | |
| *tlsVersionOpt, ": ")); | |
| } else { | |
| status.SetError( | |
| cmStrCat("DOWNLOAD given unknown TLS/SSL version ", *tlsVersionOpt)); | |
| return false; | |
| } | |
| } | |
| // check to see if TLS verification is requested | |
| if (tlsVerifyOpt.has_value() && tlsVerifyOpt.value()) { | |
| res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1); | |
| check_curl_result(res, "DOWNLOAD cannot set TLS/SSL Verify on: "); | |
| } else { | |
| res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); | |
| check_curl_result(res, "DOWNLOAD cannot set TLS/SSL Verify off: "); | |
| } | |
| for (auto const& range : curl_ranges) { | |
| std::string curl_range = | |
| cmStrCat(range.first, '-', | |
| (range.second.has_value() ? range.second.value() : "")); | |
| res = ::curl_easy_setopt(curl, CURLOPT_RANGE, curl_range.c_str()); | |
| check_curl_result(res, "DOWNLOAD cannot set range: "); | |
| } | |
| // check to see if a CAINFO file has been specified | |
| // command arg comes first | |
| std::string const& cainfo_err = cmCurlSetCAInfo(curl, cainfo); | |
| if (!cainfo_err.empty()) { | |
| status.SetError(cainfo_err); | |
| return false; | |
| } | |
| // check to see if netrc parameters have been specified | |
| // local command args takes precedence over CMAKE_NETRC* | |
| netrc_level = cmSystemTools::UpperCase(netrc_level); | |
| std::string const& netrc_option_err = | |
| cmCurlSetNETRCOption(curl, netrc_level, netrc_file); | |
| if (!netrc_option_err.empty()) { | |
| status.SetError(netrc_option_err); | |
| return false; | |
| } | |
| cmFileCommandVectorOfChar chunkDebug; | |
| res = ::curl_easy_setopt(curl, CURLOPT_WRITEDATA, | |
| file.empty() ? nullptr : &fout); | |
| check_curl_result(res, "DOWNLOAD cannot set write data: "); | |
| res = ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &chunkDebug); | |
| check_curl_result(res, "DOWNLOAD cannot set debug data: "); | |
| res = ::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); | |
| check_curl_result(res, "DOWNLOAD cannot set follow-redirect option: "); | |
| if (!logVar.empty()) { | |
| res = ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); | |
| check_curl_result(res, "DOWNLOAD cannot set verbose: "); | |
| } | |
| if (timeout > 0) { | |
| res = ::curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout); | |
| check_curl_result(res, "DOWNLOAD cannot set timeout: "); | |
| } | |
| if (inactivity_timeout > 0) { | |
| // Give up if there is no progress for a long time. | |
| ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1); | |
| ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, inactivity_timeout); | |
| } | |
| // Need the progress helper's scope to last through the duration of | |
| // the curl_easy_perform call... so this object is declared at function | |
| // scope intentionally, rather than inside the "if(showProgress)" | |
| // block... | |
| // | |
| cURLProgressHelper helper(&status.GetMakefile(), "download"); | |
| if (showProgress) { | |
| res = ::curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); | |
| check_curl_result(res, "DOWNLOAD cannot set noprogress value: "); | |
| res = ::curl_easy_setopt(curl, CM_CURLOPT_XFERINFOFUNCTION, | |
| cmFileDownloadProgressCallback); | |
| check_curl_result(res, "DOWNLOAD cannot set progress function: "); | |
| res = ::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, | |
| reinterpret_cast<void*>(&helper)); | |
| check_curl_result(res, "DOWNLOAD cannot set progress data: "); | |
| } | |
| if (!userpwd.empty()) { | |
| res = ::curl_easy_setopt(curl, CURLOPT_USERPWD, userpwd.c_str()); | |
| check_curl_result(res, "DOWNLOAD cannot set user password: "); | |
| } | |
| struct curl_slist* headers = nullptr; | |
| for (std::string const& h : curl_headers) { | |
| headers = ::curl_slist_append(headers, h.c_str()); | |
| } | |
| ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); | |
| res = ::curl_easy_perform(curl); | |
| ::curl_slist_free_all(headers); | |
| /* always cleanup */ | |
| g_curl.release(); | |
| ::curl_easy_cleanup(curl); | |
| // Explicitly close the file so we can check for write errors. | |
| if (!file.empty()) { | |
| fout.close(); | |
| if (!fout) { | |
| res = CURLE_WRITE_ERROR; | |
| } | |
| } | |
| if (!statusVar.empty()) { | |
| std::string m = curl_easy_strerror(res); | |
| if ((res == CURLE_SSL_CONNECT_ERROR || | |
| res == CURLE_PEER_FAILED_VERIFICATION) && | |
| tlsVerifyDefaulted) { | |
| m = cmStrCat( | |
| std::move(m), | |
| ". If this is due to https certificate verification failure, one may " | |
| "set environment variable CMAKE_TLS_VERIFY=0 to suppress it."); | |
| } | |
| status.GetMakefile().AddDefinition( | |
| statusVar, cmStrCat(static_cast<int>(res), ";\"", std::move(m), '"')); | |
| } | |
| ::curl_global_cleanup(); | |
| // Ensure requested curl logs are returned (especially in case of failure) | |
| // | |
| if (!logVar.empty()) { | |
| chunkDebug.push_back(0); | |
| status.GetMakefile().AddDefinition(logVar, chunkDebug.data()); | |
| } | |
| // Verify MD5 sum if requested: | |
| // | |
| if (hash) { | |
| if (res != CURLE_OK) { | |
| status.SetError( | |
| cmStrCat("DOWNLOAD cannot compute hash on failed download\n" | |
| " from url: \"", | |
| url, "\"\n status: [", static_cast<int>(res), ";\"", | |
| ::curl_easy_strerror(res), "\"]")); | |
| return false; | |
| } | |
| std::string actualHash = hash->HashFile(file); | |
| if (actualHash.empty()) { | |
| status.SetError(cmStrCat("DOWNLOAD cannot compute hash on download\n" | |
| " for file: \"", | |
| file, '"')); | |
| return false; | |
| } | |
| if (expectedHash != actualHash) { | |
| if (!statusVar.empty() && res == 0) { | |
| status.GetMakefile().AddDefinition(statusVar, | |
| cmStrCat("1;HASH mismatch: " | |
| "expected: ", | |
| expectedHash, | |
| " actual: ", actualHash)); | |
| } | |
| status.SetError(cmStrCat("DOWNLOAD HASH mismatch\n" | |
| " for file: \"", | |
| file, | |
| "\"\n" | |
| " expected hash: \"", | |
| expectedHash, | |
| "\"\n" | |
| " actual hash: \"", | |
| actualHash, "\"\n")); | |
| return false; | |
| } | |
| } | |
| return true; | |
| #else | |
| status.SetError("DOWNLOAD not supported by bootstrap cmake."); | |
| return false; | |
| #endif | |
| } | |
| bool HandleUploadCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| #if !defined(CMAKE_BOOTSTRAP) | |
| if (args.size() < 3) { | |
| status.SetError("UPLOAD must be called with at least three arguments."); | |
| return false; | |
| } | |
| auto i = args.begin(); | |
| ++i; | |
| std::string filename = *i; | |
| ++i; | |
| std::string url = *i; | |
| ++i; | |
| long timeout = 0; | |
| long inactivity_timeout = 0; | |
| std::string logVar; | |
| std::string statusVar; | |
| bool showProgress = false; | |
| cm::optional<std::string> tlsVersionOpt; | |
| cm::optional<bool> tlsVerifyOpt; | |
| cmValue cainfo = status.GetMakefile().GetDefinition("CMAKE_TLS_CAINFO"); | |
| std::string userpwd; | |
| std::string netrc_level = | |
| status.GetMakefile().GetSafeDefinition("CMAKE_NETRC"); | |
| std::string netrc_file = | |
| status.GetMakefile().GetSafeDefinition("CMAKE_NETRC_FILE"); | |
| std::vector<std::string> curl_headers; | |
| while (i != args.end()) { | |
| if (*i == "TIMEOUT") { | |
| ++i; | |
| if (i != args.end()) { | |
| timeout = atol(i->c_str()); | |
| } else { | |
| status.SetError("UPLOAD missing time for TIMEOUT."); | |
| return false; | |
| } | |
| } else if (*i == "INACTIVITY_TIMEOUT") { | |
| ++i; | |
| if (i != args.end()) { | |
| inactivity_timeout = atol(i->c_str()); | |
| } else { | |
| status.SetError("UPLOAD missing time for INACTIVITY_TIMEOUT."); | |
| return false; | |
| } | |
| } else if (*i == "LOG") { | |
| ++i; | |
| if (i == args.end()) { | |
| status.SetError("UPLOAD missing VAR for LOG."); | |
| return false; | |
| } | |
| logVar = *i; | |
| } else if (*i == "STATUS") { | |
| ++i; | |
| if (i == args.end()) { | |
| status.SetError("UPLOAD missing VAR for STATUS."); | |
| return false; | |
| } | |
| statusVar = *i; | |
| } else if (*i == "SHOW_PROGRESS") { | |
| showProgress = true; | |
| } else if (*i == "TLS_VERSION") { | |
| ++i; | |
| if (i != args.end()) { | |
| tlsVersionOpt = *i; | |
| } else { | |
| status.SetError("UPLOAD missing value for TLS_VERSION."); | |
| return false; | |
| } | |
| } else if (*i == "TLS_VERIFY") { | |
| ++i; | |
| if (i != args.end()) { | |
| tlsVerifyOpt = cmIsOn(*i); | |
| } else { | |
| status.SetError("UPLOAD missing bool value for TLS_VERIFY."); | |
| return false; | |
| } | |
| } else if (*i == "TLS_CAINFO") { | |
| ++i; | |
| if (i != args.end()) { | |
| cainfo = cmValue(*i); | |
| } else { | |
| status.SetError("UPLOAD missing file value for TLS_CAINFO."); | |
| return false; | |
| } | |
| } else if (*i == "NETRC_FILE") { | |
| ++i; | |
| if (i != args.end()) { | |
| netrc_file = *i; | |
| } else { | |
| status.SetError("UPLOAD missing file value for NETRC_FILE."); | |
| return false; | |
| } | |
| } else if (*i == "NETRC") { | |
| ++i; | |
| if (i != args.end()) { | |
| netrc_level = *i; | |
| } else { | |
| status.SetError("UPLOAD missing level value for NETRC."); | |
| return false; | |
| } | |
| } else if (*i == "USERPWD") { | |
| ++i; | |
| if (i == args.end()) { | |
| status.SetError("UPLOAD missing string for USERPWD."); | |
| return false; | |
| } | |
| userpwd = *i; | |
| } else if (*i == "HTTPHEADER") { | |
| ++i; | |
| if (i == args.end()) { | |
| status.SetError("UPLOAD missing string for HTTPHEADER."); | |
| return false; | |
| } | |
| curl_headers.push_back(*i); | |
| } else { | |
| // Do not return error for compatibility reason. | |
| status.GetMakefile().IssueDiagnostic( | |
| cmDiagnostics::CMD_AUTHOR, cmStrCat("Unexpected argument: ", *i)); | |
| } | |
| ++i; | |
| } | |
| if (!tlsVerifyOpt.has_value()) { | |
| if (cmValue v = status.GetMakefile().GetDefinition("CMAKE_TLS_VERIFY")) { | |
| tlsVerifyOpt = v.IsOn(); | |
| } | |
| } | |
| if (!tlsVerifyOpt.has_value()) { | |
| if (cm::optional<std::string> v = | |
| cmSystemTools::GetEnvVar("CMAKE_TLS_VERIFY")) { | |
| tlsVerifyOpt = cmIsOn(*v); | |
| } | |
| } | |
| bool tlsVerifyDefaulted = false; | |
| if (!tlsVerifyOpt.has_value()) { | |
| tlsVerifyOpt = TLS_VERIFY_DEFAULT; | |
| tlsVerifyDefaulted = true; | |
| } | |
| if (!tlsVersionOpt.has_value()) { | |
| if (cmValue v = status.GetMakefile().GetDefinition("CMAKE_TLS_VERSION")) { | |
| tlsVersionOpt = *v; | |
| } | |
| } | |
| if (!tlsVersionOpt.has_value()) { | |
| if (cm::optional<std::string> v = | |
| cmSystemTools::GetEnvVar("CMAKE_TLS_VERSION")) { | |
| tlsVersionOpt = std::move(v); | |
| } | |
| } | |
| bool tlsVersionDefaulted = false; | |
| if (!tlsVersionOpt.has_value()) { | |
| tlsVersionOpt = TLS_VERSION_DEFAULT; | |
| tlsVersionDefaulted = true; | |
| } | |
| // Open file for reading: | |
| // | |
| FILE* fin = cmsys::SystemTools::Fopen(filename, "rb"); | |
| if (!fin) { | |
| std::string errStr = | |
| cmStrCat("UPLOAD cannot open file '", filename, "' for reading."); | |
| status.SetError(errStr); | |
| return false; | |
| } | |
| unsigned long file_size = cmsys::SystemTools::FileLength(filename); | |
| url = CurlCompatFileURL(url); | |
| ::CURL* curl; | |
| cm_curl_global_init(CURL_GLOBAL_DEFAULT); | |
| curl = cm_curl_easy_init(); | |
| if (!curl) { | |
| status.SetError("UPLOAD error initializing curl."); | |
| fclose(fin); | |
| return false; | |
| } | |
| cURLEasyGuard g_curl(curl); | |
| // enable HTTP ERROR parsing | |
| ::CURLcode res = ::curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); | |
| check_curl_result(res, "UPLOAD cannot set fail on error flag: "); | |
| // enable uploading | |
| res = ::curl_easy_setopt(curl, CURLOPT_UPLOAD, 1); | |
| check_curl_result(res, "UPLOAD cannot set upload flag: "); | |
| res = ::curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); | |
| check_curl_result(res, "UPLOAD cannot set url: "); | |
| res = | |
| ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cmWriteToMemoryCallback); | |
| check_curl_result(res, "UPLOAD cannot set write function: "); | |
| res = ::curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, | |
| cmFileCommandCurlDebugCallback); | |
| check_curl_result(res, "UPLOAD cannot set debug function: "); | |
| if (tlsVersionOpt.has_value()) { | |
| if (cm::optional<int> v = cmCurlParseTLSVersion(*tlsVersionOpt)) { | |
| res = ::curl_easy_setopt(curl, CURLOPT_SSLVERSION, *v); | |
| // See HandleDownloadCommand for the rationale behind accepting both | |
| // CURLE_NOT_BUILT_IN and CURLE_UNKNOWN_OPTION here. | |
| if (tlsVersionDefaulted && | |
| (res == CURLE_NOT_BUILT_IN || res == CURLE_UNKNOWN_OPTION)) { | |
| res = CURLE_OK; | |
| } | |
| check_curl_result( | |
| res, | |
| cmStrCat("UPLOAD cannot set TLS/SSL version ", *tlsVersionOpt, ": ")); | |
| } else { | |
| status.SetError( | |
| cmStrCat("UPLOAD given unknown TLS/SSL version ", *tlsVersionOpt)); | |
| return false; | |
| } | |
| } | |
| // check to see if TLS verification is requested | |
| if (tlsVerifyOpt.has_value() && tlsVerifyOpt.value()) { | |
| res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1); | |
| check_curl_result(res, "UPLOAD cannot set TLS/SSL Verify on: "); | |
| } else { | |
| res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); | |
| check_curl_result(res, "UPLOAD cannot set TLS/SSL Verify off: "); | |
| } | |
| // check to see if a CAINFO file has been specified | |
| // command arg comes first | |
| std::string const& cainfo_err = cmCurlSetCAInfo(curl, cainfo); | |
| if (!cainfo_err.empty()) { | |
| status.SetError(cainfo_err); | |
| return false; | |
| } | |
| cmFileCommandVectorOfChar chunkResponse; | |
| cmFileCommandVectorOfChar chunkDebug; | |
| res = ::curl_easy_setopt(curl, CURLOPT_WRITEDATA, &chunkResponse); | |
| check_curl_result(res, "UPLOAD cannot set write data: "); | |
| res = ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &chunkDebug); | |
| check_curl_result(res, "UPLOAD cannot set debug data: "); | |
| res = ::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); | |
| check_curl_result(res, "UPLOAD cannot set follow-redirect option: "); | |
| if (!logVar.empty()) { | |
| res = ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); | |
| check_curl_result(res, "UPLOAD cannot set verbose: "); | |
| } | |
| if (timeout > 0) { | |
| res = ::curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout); | |
| check_curl_result(res, "UPLOAD cannot set timeout: "); | |
| } | |
| if (inactivity_timeout > 0) { | |
| // Give up if there is no progress for a long time. | |
| ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1); | |
| ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, inactivity_timeout); | |
| } | |
| // Need the progress helper's scope to last through the duration of | |
| // the curl_easy_perform call... so this object is declared at function | |
| // scope intentionally, rather than inside the "if(showProgress)" | |
| // block... | |
| // | |
| cURLProgressHelper helper(&status.GetMakefile(), "upload"); | |
| if (showProgress) { | |
| res = ::curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); | |
| check_curl_result(res, "UPLOAD cannot set noprogress value: "); | |
| res = ::curl_easy_setopt(curl, CM_CURLOPT_XFERINFOFUNCTION, | |
| cmFileUploadProgressCallback); | |
| check_curl_result(res, "UPLOAD cannot set progress function: "); | |
| res = ::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, | |
| reinterpret_cast<void*>(&helper)); | |
| check_curl_result(res, "UPLOAD cannot set progress data: "); | |
| } | |
| // now specify which file to upload | |
| res = ::curl_easy_setopt(curl, CURLOPT_INFILE, fin); | |
| check_curl_result(res, "UPLOAD cannot set input file: "); | |
| // and give the size of the upload (optional) | |
| res = | |
| ::curl_easy_setopt(curl, CURLOPT_INFILESIZE, static_cast<long>(file_size)); | |
| check_curl_result(res, "UPLOAD cannot set input file size: "); | |
| if (!userpwd.empty()) { | |
| res = ::curl_easy_setopt(curl, CURLOPT_USERPWD, userpwd.c_str()); | |
| check_curl_result(res, "UPLOAD cannot set user password: "); | |
| } | |
| // check to see if netrc parameters have been specified | |
| // local command args takes precedence over CMAKE_NETRC* | |
| netrc_level = cmSystemTools::UpperCase(netrc_level); | |
| std::string const& netrc_option_err = | |
| cmCurlSetNETRCOption(curl, netrc_level, netrc_file); | |
| if (!netrc_option_err.empty()) { | |
| status.SetError(netrc_option_err); | |
| return false; | |
| } | |
| struct curl_slist* headers = nullptr; | |
| for (std::string const& h : curl_headers) { | |
| headers = ::curl_slist_append(headers, h.c_str()); | |
| } | |
| ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); | |
| res = ::curl_easy_perform(curl); | |
| ::curl_slist_free_all(headers); | |
| /* always cleanup */ | |
| g_curl.release(); | |
| ::curl_easy_cleanup(curl); | |
| if (!statusVar.empty()) { | |
| std::string m = curl_easy_strerror(res); | |
| if ((res == CURLE_SSL_CONNECT_ERROR || | |
| res == CURLE_PEER_FAILED_VERIFICATION) && | |
| tlsVerifyDefaulted) { | |
| m = cmStrCat( | |
| std::move(m), | |
| ". If this is due to https certificate verification failure, one may " | |
| "set environment variable CMAKE_TLS_VERIFY=0 to suppress it."); | |
| } | |
| status.GetMakefile().AddDefinition( | |
| statusVar, cmStrCat(static_cast<int>(res), ";\"", std::move(m), '"')); | |
| } | |
| ::curl_global_cleanup(); | |
| fclose(fin); | |
| fin = nullptr; | |
| if (!logVar.empty()) { | |
| std::string log; | |
| if (!chunkResponse.empty()) { | |
| chunkResponse.push_back(0); | |
| log += "Response:\n"; | |
| log += chunkResponse.data(); | |
| log += "\n"; | |
| } | |
| if (!chunkDebug.empty()) { | |
| chunkDebug.push_back(0); | |
| log += "Debug:\n"; | |
| log += chunkDebug.data(); | |
| log += "\n"; | |
| } | |
| status.GetMakefile().AddDefinition(logVar, log); | |
| } | |
| return true; | |
| #else | |
| status.SetError("UPLOAD not supported by bootstrap cmake."); | |
| return false; | |
| #endif | |
| } | |
| void AddEvaluationFile(std::string const& inputName, | |
| std::string const& targetName, | |
| std::string const& outputExpr, | |
| std::string const& condition, bool inputIsContent, | |
| std::string const& newLineCharacter, mode_t permissions, | |
| cmExecutionStatus& status) | |
| { | |
| cmListFileBacktrace lfbt = status.GetMakefile().GetBacktrace(); | |
| cmGeneratorExpression outputGe(*status.GetMakefile().GetCMakeInstance(), | |
| lfbt); | |
| std::unique_ptr<cmCompiledGeneratorExpression> outputCge = | |
| outputGe.Parse(outputExpr); | |
| cmGeneratorExpression conditionGe(*status.GetMakefile().GetCMakeInstance(), | |
| lfbt); | |
| std::unique_ptr<cmCompiledGeneratorExpression> conditionCge = | |
| conditionGe.Parse(condition); | |
| status.GetMakefile().AddEvaluationFile( | |
| inputName, targetName, std::move(outputCge), std::move(conditionCge), | |
| newLineCharacter, permissions, inputIsContent); | |
| } | |
| bool HandleGenerateCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| if (args.size() < 5) { | |
| status.SetError("Incorrect arguments to GENERATE subcommand."); | |
| return false; | |
| } | |
| struct Arguments : public ArgumentParser::ParseResult | |
| { | |
| cm::optional<std::string> Output; | |
| cm::optional<std::string> Input; | |
| cm::optional<std::string> Content; | |
| cm::optional<std::string> Condition; | |
| cm::optional<std::string> Target; | |
| cm::optional<std::string> NewLineStyle; | |
| bool NoSourcePermissions = false; | |
| bool UseSourcePermissions = false; | |
| ArgumentParser::NonEmpty<std::vector<std::string>> FilePermissions; | |
| std::vector<cm::string_view> ParsedKeywords; | |
| }; | |
| static auto const parser = | |
| cmArgumentParser<Arguments>{} | |
| .Bind("OUTPUT"_s, &Arguments::Output) | |
| .Bind("INPUT"_s, &Arguments::Input) | |
| .Bind("CONTENT"_s, &Arguments::Content) | |
| .Bind("CONDITION"_s, &Arguments::Condition) | |
| .Bind("TARGET"_s, &Arguments::Target) | |
| .Bind("NO_SOURCE_PERMISSIONS"_s, &Arguments::NoSourcePermissions) | |
| .Bind("USE_SOURCE_PERMISSIONS"_s, &Arguments::UseSourcePermissions) | |
| .Bind("FILE_PERMISSIONS"_s, &Arguments::FilePermissions) | |
| .Bind("NEWLINE_STYLE"_s, &Arguments::NewLineStyle) | |
| .BindParsedKeywords(&Arguments::ParsedKeywords); | |
| std::vector<std::string> unparsedArguments; | |
| Arguments const arguments = | |
| parser.Parse(cmMakeRange(args).advance(1), &unparsedArguments); | |
| if (arguments.MaybeReportError(status.GetMakefile())) { | |
| return true; | |
| } | |
| if (!unparsedArguments.empty()) { | |
| status.SetError("Unknown argument to GENERATE subcommand."); | |
| return false; | |
| } | |
| if (!arguments.Output || arguments.ParsedKeywords[0] != "OUTPUT"_s) { | |
| status.SetError("GENERATE requires OUTPUT as first option."); | |
| return false; | |
| } | |
| std::string const& output = *arguments.Output; | |
| if (!arguments.Input && !arguments.Content) { | |
| status.SetError("GENERATE requires INPUT or CONTENT option."); | |
| return false; | |
| } | |
| bool const inputIsContent = arguments.ParsedKeywords[1] == "CONTENT"_s; | |
| if (!inputIsContent && arguments.ParsedKeywords[1] != "INPUT") { | |
| status.SetError("Unknown argument to GENERATE subcommand."); | |
| return false; | |
| } | |
| std::string const& input = | |
| inputIsContent ? *arguments.Content : *arguments.Input; | |
| if (arguments.Condition && arguments.Condition->empty()) { | |
| status.SetError("CONDITION of sub-command GENERATE must not be empty " | |
| "if specified."); | |
| return false; | |
| } | |
| std::string const& condition = | |
| arguments.Condition ? *arguments.Condition : std::string(); | |
| if (arguments.Target && arguments.Target->empty()) { | |
| status.SetError("TARGET of sub-command GENERATE must not be empty " | |
| "if specified."); | |
| return false; | |
| } | |
| std::string const& target = | |
| arguments.Target ? *arguments.Target : std::string(); | |
| cmNewLineStyle newLineStyle; | |
| if (arguments.NewLineStyle) { | |
| std::string errorMessage; | |
| if (!newLineStyle.ReadFromArguments(args, errorMessage)) { | |
| status.SetError(cmStrCat("GENERATE ", errorMessage)); | |
| return false; | |
| } | |
| } | |
| if (arguments.NoSourcePermissions && arguments.UseSourcePermissions) { | |
| status.SetError("given both NO_SOURCE_PERMISSIONS and " | |
| "USE_SOURCE_PERMISSIONS. Only one option allowed."); | |
| return false; | |
| } | |
| if (!arguments.FilePermissions.empty()) { | |
| if (arguments.NoSourcePermissions) { | |
| status.SetError("given both NO_SOURCE_PERMISSIONS and " | |
| "FILE_PERMISSIONS. Only one option allowed."); | |
| return false; | |
| } | |
| if (arguments.UseSourcePermissions) { | |
| status.SetError("given both USE_SOURCE_PERMISSIONS and " | |
| "FILE_PERMISSIONS. Only one option allowed."); | |
| return false; | |
| } | |
| } | |
| if (arguments.UseSourcePermissions) { | |
| if (inputIsContent) { | |
| status.SetError("given USE_SOURCE_PERMISSIONS without a file INPUT."); | |
| return false; | |
| } | |
| } | |
| mode_t permissions = 0; | |
| if (arguments.NoSourcePermissions) { | |
| permissions |= cmFSPermissions::mode_owner_read; | |
| permissions |= cmFSPermissions::mode_owner_write; | |
| permissions |= cmFSPermissions::mode_group_read; | |
| permissions |= cmFSPermissions::mode_world_read; | |
| } | |
| if (!arguments.FilePermissions.empty()) { | |
| std::vector<std::string> invalidOptions; | |
| for (auto const& e : arguments.FilePermissions) { | |
| if (!cmFSPermissions::stringToModeT(e, permissions)) { | |
| invalidOptions.push_back(e); | |
| } | |
| } | |
| if (!invalidOptions.empty()) { | |
| std::ostringstream oss; | |
| oss << "given invalid permission "; | |
| for (auto i = 0u; i < invalidOptions.size(); i++) { | |
| if (i == 0u) { | |
| oss << "\"" << invalidOptions[i] << "\""; | |
| } else { | |
| oss << ",\"" << invalidOptions[i] << "\""; | |
| } | |
| } | |
| oss << "."; | |
| status.SetError(oss.str()); | |
| return false; | |
| } | |
| } | |
| AddEvaluationFile(input, target, output, condition, inputIsContent, | |
| newLineStyle.GetCharacters(), permissions, status); | |
| return true; | |
| } | |
| bool HandleLockCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| #if !defined(CMAKE_BOOTSTRAP) | |
| // Default values | |
| bool directory = false; | |
| bool release = false; | |
| enum Guard | |
| { | |
| GUARD_FUNCTION, | |
| GUARD_FILE, | |
| GUARD_PROCESS | |
| }; | |
| Guard guard = GUARD_PROCESS; | |
| std::string resultVariable; | |
| unsigned long timeout = static_cast<unsigned long>(-1); | |
| // Parse arguments | |
| if (args.size() < 2) { | |
| status.GetMakefile().IssueMessage( | |
| MessageType::FATAL_ERROR, | |
| "sub-command LOCK requires at least two arguments."); | |
| return false; | |
| } | |
| std::string path = args[1]; | |
| for (unsigned i = 2; i < args.size(); ++i) { | |
| if (args[i] == "DIRECTORY") { | |
| directory = true; | |
| } else if (args[i] == "RELEASE") { | |
| release = true; | |
| } else if (args[i] == "GUARD") { | |
| ++i; | |
| char const* merr = "expected FUNCTION, FILE or PROCESS after GUARD"; | |
| if (i >= args.size()) { | |
| status.GetMakefile().IssueMessage(MessageType::FATAL_ERROR, merr); | |
| return false; | |
| } | |
| if (args[i] == "FUNCTION") { | |
| guard = GUARD_FUNCTION; | |
| } else if (args[i] == "FILE") { | |
| guard = GUARD_FILE; | |
| } else if (args[i] == "PROCESS") { | |
| guard = GUARD_PROCESS; | |
| } else { | |
| status.GetMakefile().IssueMessage( | |
| MessageType::FATAL_ERROR, | |
| cmStrCat(merr, ", but got:\n \"", args[i], "\".")); | |
| return false; | |
| } | |
| } else if (args[i] == "RESULT_VARIABLE") { | |
| ++i; | |
| if (i >= args.size()) { | |
| status.GetMakefile().IssueMessage( | |
| MessageType::FATAL_ERROR, | |
| "expected variable name after RESULT_VARIABLE"); | |
| return false; | |
| } | |
| resultVariable = args[i]; | |
| } else if (args[i] == "TIMEOUT") { | |
| ++i; | |
| if (i >= args.size()) { | |
| status.GetMakefile().IssueMessage( | |
| MessageType::FATAL_ERROR, "expected timeout value after TIMEOUT"); | |
| return false; | |
| } | |
| long scanned; | |
| if (!cmStrToLong(args[i], &scanned) || scanned < 0) { | |
| status.GetMakefile().IssueMessage( | |
| MessageType::FATAL_ERROR, | |
| cmStrCat("TIMEOUT value \"", args[i], | |
| "\" is not an unsigned integer.")); | |
| return false; | |
| } | |
| timeout = static_cast<unsigned long>(scanned); | |
| } else { | |
| status.GetMakefile().IssueMessage( | |
| MessageType::FATAL_ERROR, | |
| cmStrCat("expected DIRECTORY, RELEASE, GUARD, RESULT_VARIABLE or " | |
| "TIMEOUT\nbut got: \"", | |
| args[i], "\".")); | |
| return false; | |
| } | |
| } | |
| if (directory) { | |
| path += "/cmake.lock"; | |
| } | |
| // Unify path (remove '//', '/../', ...) | |
| path = cmSystemTools::CollapseFullPath( | |
| path, status.GetMakefile().GetCurrentSourceDirectory()); | |
| // Create file and directories if needed | |
| std::string parentDir = cmSystemTools::GetParentDirectory(path); | |
| if (!cmSystemTools::MakeDirectory(parentDir)) { | |
| status.GetMakefile().IssueMessage( | |
| MessageType::FATAL_ERROR, | |
| cmStrCat("directory\n \"", parentDir, | |
| "\"\ncreation failed (check permissions).")); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| FILE* file = cmsys::SystemTools::Fopen(path, "a"); | |
| if (!file) { | |
| status.GetMakefile().IssueMessage( | |
| MessageType::FATAL_ERROR, | |
| cmStrCat("file\n \"", path, | |
| "\"\ncreation failed (check permissions).")); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| fclose(file); | |
| // Actual lock/unlock | |
| cmFileLockPool& lockPool = | |
| status.GetMakefile().GetGlobalGenerator()->GetFileLockPool(); | |
| cmFileLockResult fileLockResult(cmFileLockResult::MakeOk()); | |
| if (release) { | |
| fileLockResult = lockPool.Release(path); | |
| } else { | |
| switch (guard) { | |
| case GUARD_FUNCTION: | |
| fileLockResult = lockPool.LockFunctionScope(path, timeout); | |
| break; | |
| case GUARD_FILE: | |
| fileLockResult = lockPool.LockFileScope(path, timeout); | |
| break; | |
| case GUARD_PROCESS: | |
| fileLockResult = lockPool.LockProcessScope(path, timeout); | |
| break; | |
| default: | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| } | |
| std::string const result = fileLockResult.GetOutputMessage(); | |
| if (resultVariable.empty() && !fileLockResult.IsOk()) { | |
| status.GetMakefile().IssueMessage( | |
| MessageType::FATAL_ERROR, | |
| cmStrCat("error locking file\n \"", path, "\"\n", result, '.')); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| if (!resultVariable.empty()) { | |
| status.GetMakefile().AddDefinition(resultVariable, result); | |
| } | |
| return true; | |
| #else | |
| static_cast<void>(args); | |
| status.SetError("sub-command LOCK not implemented in bootstrap cmake"); | |
| return false; | |
| #endif | |
| } | |
| bool HandleTimestampCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| if (args.size() < 3) { | |
| status.SetError("sub-command TIMESTAMP requires at least two arguments."); | |
| return false; | |
| } | |
| if (args.size() > 5) { | |
| status.SetError("sub-command TIMESTAMP takes at most four arguments."); | |
| return false; | |
| } | |
| unsigned int argsIndex = 1; | |
| std::string filename = args[argsIndex++]; | |
| if (!cmsys::SystemTools::FileIsFullPath(filename)) { | |
| filename = cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', | |
| std::move(filename)); | |
| } | |
| std::string const& outputVariable = args[argsIndex++]; | |
| std::string formatString; | |
| if (args.size() > argsIndex && args[argsIndex] != "UTC") { | |
| formatString = args[argsIndex++]; | |
| } | |
| bool utcFlag = false; | |
| if (args.size() > argsIndex) { | |
| if (args[argsIndex] == "UTC") { | |
| utcFlag = true; | |
| } else { | |
| std::string e = | |
| cmStrCat(" TIMESTAMP sub-command does not recognize option ", | |
| args[argsIndex], '.'); | |
| status.SetError(e); | |
| return false; | |
| } | |
| } | |
| cmTimestamp timestamp; | |
| std::string result = | |
| timestamp.FileModificationTime(filename.c_str(), formatString, utcFlag); | |
| status.GetMakefile().AddDefinition(outputVariable, result); | |
| return true; | |
| } | |
| bool HandleSizeCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| if (args.size() != 3) { | |
| status.SetError( | |
| cmStrCat(args[0], " requires a file name and output variable")); | |
| return false; | |
| } | |
| unsigned int argsIndex = 1; | |
| std::string const& filename = args[argsIndex++]; | |
| std::string const& outputVariable = args[argsIndex++]; | |
| if (!cmSystemTools::FileExists(filename, true)) { | |
| status.SetError( | |
| cmStrCat("SIZE requested of path that is not readable:\n ", filename)); | |
| return false; | |
| } | |
| status.GetMakefile().AddDefinition( | |
| outputVariable, std::to_string(cmSystemTools::FileLength(filename))); | |
| return true; | |
| } | |
| bool HandleReadSymlinkCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| if (args.size() < 3) { | |
| status.SetError( | |
| cmStrCat(args[0], " requires a file name and output variable")); | |
| return false; | |
| } | |
| std::string const& filename = args[1]; | |
| std::string const& outputVariable = args[2]; | |
| struct Arguments | |
| { | |
| std::string Result; | |
| }; | |
| static auto const parser = | |
| cmArgumentParser<Arguments>{}.Bind("RESULT"_s, &Arguments::Result); | |
| std::vector<std::string> unconsumedArgs; | |
| Arguments const arguments = | |
| parser.Parse(cmMakeRange(args).advance(3), &unconsumedArgs); | |
| if (!unconsumedArgs.empty()) { | |
| status.SetError( | |
| cmStrCat("READ_SYMLINK unknown argument:\n ", unconsumedArgs.front())); | |
| return false; | |
| } | |
| std::string result; | |
| if (!cmSystemTools::ReadSymlink(filename, result)) { | |
| std::string const error = cmStrCat( | |
| "READ_SYMLINK requested of path that is not a symlink:\n ", filename); | |
| if (!arguments.Result.empty()) { | |
| status.GetMakefile().AddDefinition(arguments.Result, error); | |
| return true; | |
| } | |
| status.SetError(error); | |
| return false; | |
| } | |
| status.GetMakefile().AddDefinition(outputVariable, result); | |
| if (!arguments.Result.empty()) { | |
| status.GetMakefile().AddDefinition(arguments.Result, "0"); | |
| } | |
| return true; | |
| } | |
| bool HandleCreateLinkCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| if (args.size() < 3) { | |
| status.SetError("CREATE_LINK must be called with at least two additional " | |
| "arguments"); | |
| return false; | |
| } | |
| std::string const& fileName = args[1]; | |
| std::string const& newFileName = args[2]; | |
| struct Arguments | |
| { | |
| std::string Result; | |
| bool CopyOnError = false; | |
| bool Symbolic = false; | |
| }; | |
| static auto const parser = | |
| cmArgumentParser<Arguments>{} | |
| .Bind("RESULT"_s, &Arguments::Result) | |
| .Bind("COPY_ON_ERROR"_s, &Arguments::CopyOnError) | |
| .Bind("SYMBOLIC"_s, &Arguments::Symbolic); | |
| std::vector<std::string> unconsumedArgs; | |
| Arguments const arguments = | |
| parser.Parse(cmMakeRange(args).advance(3), &unconsumedArgs); | |
| if (!unconsumedArgs.empty()) { | |
| status.SetError(cmStrCat("unknown argument: \"", | |
| std::move(unconsumedArgs.front()), '\"')); | |
| return false; | |
| } | |
| // The system error message generated in the operation. | |
| std::string result; | |
| // Check if the paths are distinct. | |
| if (fileName == newFileName) { | |
| result = "CREATE_LINK cannot use same file and newfile"; | |
| if (!arguments.Result.empty()) { | |
| status.GetMakefile().AddDefinition(arguments.Result, result); | |
| return true; | |
| } | |
| status.SetError(result); | |
| return false; | |
| } | |
| cmPolicies::PolicyStatus const cmp0205 = | |
| status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0205); | |
| // Hard link requires original file to exist. | |
| if (!arguments.Symbolic && | |
| (!cmSystemTools::PathExists(fileName) || | |
| (cmp0205 != cmPolicies::NEW && !cmSystemTools::FileExists(fileName)))) { | |
| result = | |
| cmStrCat("Cannot hard link \'", fileName, "\' as it does not exist."); | |
| if (!arguments.Result.empty()) { | |
| status.GetMakefile().AddDefinition(arguments.Result, result); | |
| return true; | |
| } | |
| status.SetError(result); | |
| return false; | |
| } | |
| // Check if the new file already exists and remove it. | |
| if (cmSystemTools::PathExists(newFileName)) { | |
| cmsys::Status rmStatus; | |
| if (cmp0205 == cmPolicies::NEW && arguments.CopyOnError && | |
| cmSystemTools::FileIsDirectory(newFileName) && | |
| !cmSystemTools::FileIsSymlink(newFileName)) { | |
| rmStatus = cmSystemTools::RepeatedRemoveDirectory(newFileName); | |
| } else { | |
| rmStatus = cmSystemTools::RemoveFile(newFileName); | |
| } | |
| if (!rmStatus) { | |
| std::string err = cmStrCat("Failed to create link '", newFileName, | |
| "' because existing path cannot be removed: ", | |
| rmStatus.GetString(), '\n'); | |
| if (!arguments.Result.empty()) { | |
| status.GetMakefile().AddDefinition(arguments.Result, err); | |
| return true; | |
| } | |
| status.SetError(err); | |
| return false; | |
| } | |
| } | |
| bool const sourceIsDirectory = cmSystemTools::FileIsDirectory(fileName); | |
| // Whether the operation completed successfully. | |
| bool completed = false; | |
| // Check if the command requires a symbolic link. | |
| if (arguments.Symbolic) { | |
| cmsys::Status linked = | |
| cmSystemTools::CreateSymlinkQuietly(fileName, newFileName); | |
| if (linked) { | |
| completed = true; | |
| } else { | |
| result = cmStrCat("failed to create symbolic link '", newFileName, | |
| "': ", linked.GetString()); | |
| } | |
| } else { | |
| bool needToTry = true; | |
| if (sourceIsDirectory) { | |
| if (cmp0205 == cmPolicies::NEW) { | |
| needToTry = false; | |
| } else if (cmp0205 == cmPolicies::WARN && arguments.CopyOnError) { | |
| status.GetMakefile().IssuePolicyWarning( | |
| cmPolicies::CMP0205, | |
| cmStrCat("Path\n ", fileName, | |
| "\nis a directory. Hard link creation is not supported " | |
| "for directories.")); | |
| } | |
| } | |
| if (needToTry) { | |
| cmsys::Status linked = | |
| cmSystemTools::CreateLinkQuietly(fileName, newFileName); | |
| if (linked) { | |
| completed = true; | |
| } else { | |
| result = cmStrCat("failed to create link '", newFileName, | |
| "': ", linked.GetString()); | |
| } | |
| } else { | |
| result = | |
| cmStrCat("failed to create link '", newFileName, "': not supported"); | |
| } | |
| } | |
| if (cmp0205 == cmPolicies::WARN && arguments.CopyOnError && | |
| sourceIsDirectory) { | |
| status.GetMakefile().IssuePolicyWarning( | |
| cmPolicies::CMP0205, | |
| cmStrCat("Path\n ", fileName, | |
| "\nis a directory. It will be copied " | |
| "recursively when CMP0205 is set to NEW.")); | |
| } | |
| // Check if copy-on-error is enabled in the arguments. | |
| if (!completed && arguments.CopyOnError) { | |
| cmsys::Status copied; | |
| if (cmp0205 == cmPolicies::NEW && sourceIsDirectory) { | |
| copied = cmsys::SystemTools::CopyADirectory(fileName, newFileName); | |
| } else { | |
| copied = cmsys::SystemTools::CopyFileAlways(fileName, newFileName); | |
| } | |
| if (copied) { | |
| completed = true; | |
| } else { | |
| result = "Copy failed: " + copied.GetString(); | |
| } | |
| } | |
| // Check if the operation was successful. | |
| if (completed) { | |
| result = "0"; | |
| } else if (arguments.Result.empty()) { | |
| // The operation failed and the result is not reported in a variable. | |
| status.SetError(result); | |
| return false; | |
| } | |
| if (!arguments.Result.empty()) { | |
| status.GetMakefile().AddDefinition(arguments.Result, result); | |
| } | |
| return true; | |
| } | |
| bool HandleGetRuntimeDependenciesCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| std::string platform = | |
| status.GetMakefile().GetSafeDefinition("CMAKE_HOST_SYSTEM_NAME"); | |
| if (!cmRuntimeDependencyArchive::PlatformSupportsRuntimeDependencies( | |
| platform)) { | |
| status.SetError( | |
| cmStrCat("GET_RUNTIME_DEPENDENCIES is not supported on system \"", | |
| platform, '"')); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| if (status.GetMakefile().GetState()->GetRole() == cmState::Role::Project) { | |
| status.GetMakefile().IssueDiagnostic( | |
| cmDiagnostics::CMD_AUTHOR, | |
| "You have used file(GET_RUNTIME_DEPENDENCIES)" | |
| " in project mode. This is probably not what " | |
| "you intended to do. Instead, please consider" | |
| " using it in an install(CODE) or " | |
| "install(SCRIPT) command. For example:" | |
| "\n install(CODE [[" | |
| "\n file(GET_RUNTIME_DEPENDENCIES" | |
| "\n # ..." | |
| "\n )" | |
| "\n ]])"); | |
| } | |
| struct Arguments : public ArgumentParser::ParseResult | |
| { | |
| std::string ResolvedDependenciesVar; | |
| std::string UnresolvedDependenciesVar; | |
| std::string ConflictingDependenciesPrefix; | |
| std::string RPathPrefix; | |
| std::string BundleExecutable; | |
| ArgumentParser::MaybeEmpty<std::vector<std::string>> Executables; | |
| ArgumentParser::MaybeEmpty<std::vector<std::string>> Libraries; | |
| ArgumentParser::MaybeEmpty<std::vector<std::string>> Directories; | |
| ArgumentParser::MaybeEmpty<std::vector<std::string>> Modules; | |
| ArgumentParser::MaybeEmpty<std::vector<std::string>> PreIncludeRegexes; | |
| ArgumentParser::MaybeEmpty<std::vector<std::string>> PreExcludeRegexes; | |
| ArgumentParser::MaybeEmpty<std::vector<std::string>> PostIncludeRegexes; | |
| ArgumentParser::MaybeEmpty<std::vector<std::string>> PostExcludeRegexes; | |
| ArgumentParser::MaybeEmpty<std::vector<std::string>> PostIncludeFiles; | |
| ArgumentParser::MaybeEmpty<std::vector<std::string>> PostExcludeFiles; | |
| ArgumentParser::MaybeEmpty<std::vector<std::string>> | |
| PostExcludeFilesStrict; | |
| }; | |
| static auto const parser = | |
| cmArgumentParser<Arguments>{} | |
| .Bind("RESOLVED_DEPENDENCIES_VAR"_s, &Arguments::ResolvedDependenciesVar) | |
| .Bind("UNRESOLVED_DEPENDENCIES_VAR"_s, | |
| &Arguments::UnresolvedDependenciesVar) | |
| .Bind("CONFLICTING_DEPENDENCIES_PREFIX"_s, | |
| &Arguments::ConflictingDependenciesPrefix) | |
| .Bind("RPATH_PREFIX"_s, &Arguments::RPathPrefix) | |
| .Bind("BUNDLE_EXECUTABLE"_s, &Arguments::BundleExecutable) | |
| .Bind("EXECUTABLES"_s, &Arguments::Executables) | |
| .Bind("LIBRARIES"_s, &Arguments::Libraries) | |
| .Bind("MODULES"_s, &Arguments::Modules) | |
| .Bind("DIRECTORIES"_s, &Arguments::Directories) | |
| .Bind("PRE_INCLUDE_REGEXES"_s, &Arguments::PreIncludeRegexes) | |
| .Bind("PRE_EXCLUDE_REGEXES"_s, &Arguments::PreExcludeRegexes) | |
| .Bind("POST_INCLUDE_REGEXES"_s, &Arguments::PostIncludeRegexes) | |
| .Bind("POST_EXCLUDE_REGEXES"_s, &Arguments::PostExcludeRegexes) | |
| .Bind("POST_INCLUDE_FILES"_s, &Arguments::PostIncludeFiles) | |
| .Bind("POST_EXCLUDE_FILES"_s, &Arguments::PostExcludeFiles) | |
| .Bind("POST_EXCLUDE_FILES_STRICT"_s, &Arguments::PostExcludeFilesStrict); | |
| std::vector<std::string> unrecognizedArguments; | |
| auto parsedArgs = | |
| parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments); | |
| auto argIt = unrecognizedArguments.begin(); | |
| if (argIt != unrecognizedArguments.end()) { | |
| status.SetError(cmStrCat("Unrecognized argument: \"", *argIt, '"')); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| if (parsedArgs.MaybeReportError(status.GetMakefile())) { | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return true; | |
| } | |
| cmRuntimeDependencyArchive archive( | |
| status, parsedArgs.Directories, parsedArgs.BundleExecutable, | |
| parsedArgs.PreIncludeRegexes, parsedArgs.PreExcludeRegexes, | |
| parsedArgs.PostIncludeRegexes, parsedArgs.PostExcludeRegexes, | |
| std::move(parsedArgs.PostIncludeFiles), | |
| std::move(parsedArgs.PostExcludeFiles), | |
| std::move(parsedArgs.PostExcludeFilesStrict)); | |
| if (!archive.Prepare()) { | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| if (!archive.GetRuntimeDependencies( | |
| parsedArgs.Executables, parsedArgs.Libraries, parsedArgs.Modules)) { | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| std::vector<std::string> deps; | |
| std::vector<std::string> unresolvedDeps; | |
| std::vector<std::string> conflictingDeps; | |
| for (auto const& val : archive.GetResolvedPaths()) { | |
| bool unique = true; | |
| auto it = val.second.begin(); | |
| assert(it != val.second.end()); | |
| auto const& firstPath = *it; | |
| while (++it != val.second.end()) { | |
| if (!cmSystemTools::SameFile(firstPath, *it)) { | |
| unique = false; | |
| break; | |
| } | |
| } | |
| if (unique) { | |
| deps.push_back(firstPath); | |
| if (!parsedArgs.RPathPrefix.empty()) { | |
| status.GetMakefile().AddDefinition( | |
| cmStrCat(parsedArgs.RPathPrefix, '_', firstPath), | |
| cmList::to_string(archive.GetRPaths().at(firstPath))); | |
| } | |
| } else if (!parsedArgs.ConflictingDependenciesPrefix.empty()) { | |
| conflictingDeps.push_back(val.first); | |
| std::vector<std::string> paths; | |
| paths.insert(paths.begin(), val.second.begin(), val.second.end()); | |
| std::string varName = | |
| cmStrCat(parsedArgs.ConflictingDependenciesPrefix, '_', val.first); | |
| std::string pathsStr = cmList::to_string(paths); | |
| status.GetMakefile().AddDefinition(varName, pathsStr); | |
| } else { | |
| status.SetError(cmStrCat("Multiple conflicting paths found for ", | |
| val.first, ':', | |
| cmWrap("\n ", val.second, "", ""))); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| } | |
| if (!archive.GetUnresolvedPaths().empty()) { | |
| if (!parsedArgs.UnresolvedDependenciesVar.empty()) { | |
| unresolvedDeps.insert(unresolvedDeps.begin(), | |
| archive.GetUnresolvedPaths().begin(), | |
| archive.GetUnresolvedPaths().end()); | |
| } else { | |
| status.SetError( | |
| cmStrCat("Could not resolve runtime dependencies:", | |
| cmWrap("\n ", archive.GetUnresolvedPaths(), "", ""))); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| } | |
| if (!parsedArgs.ResolvedDependenciesVar.empty()) { | |
| std::string val = cmList::to_string(deps); | |
| status.GetMakefile().AddDefinition(parsedArgs.ResolvedDependenciesVar, | |
| val); | |
| } | |
| if (!parsedArgs.UnresolvedDependenciesVar.empty()) { | |
| std::string val = cmList::to_string(unresolvedDeps); | |
| status.GetMakefile().AddDefinition(parsedArgs.UnresolvedDependenciesVar, | |
| val); | |
| } | |
| if (!parsedArgs.ConflictingDependenciesPrefix.empty()) { | |
| std::string val = cmList::to_string(conflictingDeps); | |
| status.GetMakefile().AddDefinition( | |
| parsedArgs.ConflictingDependenciesPrefix + "_FILENAMES", val); | |
| } | |
| return true; | |
| } | |
| bool HandleConfigureCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| struct Arguments : public ArgumentParser::ParseResult | |
| { | |
| cm::optional<std::string> Output; | |
| cm::optional<std::string> Content; | |
| bool EscapeQuotes = false; | |
| bool AtOnly = false; | |
| // "NEWLINE_STYLE" requires one value, but we use a custom check below. | |
| ArgumentParser::Maybe<std::string> NewlineStyle; | |
| }; | |
| static auto const parser = | |
| cmArgumentParser<Arguments>{} | |
| .Bind("OUTPUT"_s, &Arguments::Output) | |
| .Bind("CONTENT"_s, &Arguments::Content) | |
| .Bind("ESCAPE_QUOTES"_s, &Arguments::EscapeQuotes) | |
| .Bind("@ONLY"_s, &Arguments::AtOnly) | |
| .Bind("NEWLINE_STYLE"_s, &Arguments::NewlineStyle); | |
| std::vector<std::string> unrecognizedArguments; | |
| auto parsedArgs = | |
| parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments); | |
| auto argIt = unrecognizedArguments.begin(); | |
| if (argIt != unrecognizedArguments.end()) { | |
| status.SetError( | |
| cmStrCat("CONFIGURE Unrecognized argument: \"", *argIt, '"')); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| if (parsedArgs.MaybeReportError(status.GetMakefile())) { | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return true; | |
| } | |
| if (!parsedArgs.Output) { | |
| status.SetError("CONFIGURE OUTPUT option is mandatory."); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| if (!parsedArgs.Content) { | |
| status.SetError("CONFIGURE CONTENT option is mandatory."); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| std::string errorMessage; | |
| cmNewLineStyle newLineStyle; | |
| if (!newLineStyle.ReadFromArguments(args, errorMessage)) { | |
| status.SetError(cmStrCat("CONFIGURE ", errorMessage)); | |
| return false; | |
| } | |
| // Check for generator expressions | |
| std::string outputFile = cmSystemTools::CollapseFullPath( | |
| *parsedArgs.Output, status.GetMakefile().GetCurrentBinaryDirectory()); | |
| std::string::size_type pos = outputFile.find_first_of("<>"); | |
| if (pos != std::string::npos) { | |
| status.SetError(cmStrCat("CONFIGURE called with OUTPUT containing a \"", | |
| outputFile[pos], | |
| "\". This character is not allowed.")); | |
| return false; | |
| } | |
| cmMakefile& makeFile = status.GetMakefile(); | |
| if (!makeFile.CanIWriteThisFile(outputFile)) { | |
| cmSystemTools::Error(cmStrCat("Attempt to write file: ", outputFile, | |
| " into a source directory.")); | |
| return false; | |
| } | |
| cmSystemTools::ConvertToUnixSlashes(outputFile); | |
| // Re-generate if non-temporary outputs are missing. | |
| // when we finalize the configuration we will remove all | |
| // output files that now don't exist. | |
| makeFile.AddCMakeOutputFile(outputFile); | |
| // Create output directory | |
| std::string::size_type const slashPos = outputFile.rfind('/'); | |
| if (slashPos != std::string::npos) { | |
| std::string const path = outputFile.substr(0, slashPos); | |
| cmSystemTools::MakeDirectory(path); | |
| } | |
| std::string newLineCharacters = "\n"; | |
| bool open_with_binary_flag = false; | |
| if (newLineStyle.IsValid()) { | |
| newLineCharacters = newLineStyle.GetCharacters(); | |
| open_with_binary_flag = true; | |
| } | |
| cmGeneratedFileStream fout; | |
| fout.Open(outputFile, false, open_with_binary_flag); | |
| if (!fout) { | |
| cmSystemTools::Error("Could not open file for write in copy operation " + | |
| outputFile); | |
| cmSystemTools::ReportLastSystemError(""); | |
| return false; | |
| } | |
| fout.SetCopyIfDifferent(true); | |
| // copy input to output and expand variables from input at the same time | |
| std::stringstream sin(*parsedArgs.Content, std::ios::in); | |
| std::string inLine; | |
| std::string outLine; | |
| bool hasNewLine = false; | |
| while (cmSystemTools::GetLineFromStream(sin, inLine, &hasNewLine)) { | |
| outLine.clear(); | |
| makeFile.ConfigureString(inLine, outLine, parsedArgs.AtOnly, | |
| parsedArgs.EscapeQuotes); | |
| fout << outLine; | |
| if (hasNewLine || newLineStyle.IsValid()) { | |
| fout << newLineCharacters; | |
| } | |
| } | |
| // close file before attempting to copy | |
| fout.close(); | |
| return true; | |
| } | |
| bool HandleArchiveCreateCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| struct Arguments : public ArgumentParser::ParseResult | |
| { | |
| std::string Output; | |
| std::string Format; | |
| std::string Compression; | |
| std::string CompressionLevel; | |
| std::string Encoding; | |
| // "MTIME" should require one value, but it has long been accidentally | |
| // accepted without one and treated as if an empty value were given. | |
| // Fixing this would require a policy. | |
| ArgumentParser::Maybe<std::string> MTime; | |
| std::string Threads; | |
| std::string WorkingDirectory; | |
| bool Verbose = false; | |
| // "PATHS" requires at least one value, but use a custom check below. | |
| ArgumentParser::MaybeEmpty<std::vector<std::string>> Paths; | |
| ArgumentParser::MaybeEmpty<std::vector<std::string>> PatternsExclude; | |
| }; | |
| static auto const parser = | |
| cmArgumentParser<Arguments>{} | |
| .Bind("OUTPUT"_s, &Arguments::Output) | |
| .Bind("FORMAT"_s, &Arguments::Format) | |
| .Bind("COMPRESSION"_s, &Arguments::Compression) | |
| .Bind("COMPRESSION_LEVEL"_s, &Arguments::CompressionLevel) | |
| .Bind("ENCODING"_s, &Arguments::Encoding) | |
| .Bind("MTIME"_s, &Arguments::MTime) | |
| .Bind("THREADS"_s, &Arguments::Threads) | |
| .Bind("WORKING_DIRECTORY"_s, &Arguments::WorkingDirectory) | |
| .Bind("VERBOSE"_s, &Arguments::Verbose) | |
| .Bind("PATHS"_s, &Arguments::Paths) | |
| .Bind("PATTERNS_EXCLUDE"_s, &Arguments::PatternsExclude); | |
| std::vector<std::string> unrecognizedArguments; | |
| auto parsedArgs = | |
| parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments); | |
| auto argIt = unrecognizedArguments.begin(); | |
| if (argIt != unrecognizedArguments.end()) { | |
| status.SetError(cmStrCat("Unrecognized argument: \"", *argIt, '"')); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| if (parsedArgs.MaybeReportError(status.GetMakefile())) { | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return true; | |
| } | |
| char const* knownFormats[] = { | |
| "7zip", "gnutar", "pax", "paxr", "raw", "zip" | |
| }; | |
| if (!parsedArgs.Format.empty() && | |
| !cm::contains(knownFormats, parsedArgs.Format)) { | |
| status.SetError( | |
| cmStrCat("archive format ", parsedArgs.Format, " not supported")); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| static std::map<std::string, cmSystemTools::cmTarCompression> | |
| compressionTypeMap = { { "None", cmSystemTools::TarCompressNone }, | |
| { "BZip2", cmSystemTools::TarCompressBZip2 }, | |
| { "Deflate", cmSystemTools::TarCompressGZip }, | |
| { "GZip", cmSystemTools::TarCompressGZip }, | |
| { "LZMA", cmSystemTools::TarCompressLZMA }, | |
| { "LZMA2", cmSystemTools::TarCompressXZ }, | |
| { "PPMd", cmSystemTools::TarCompressPPMd }, | |
| { "XZ", cmSystemTools::TarCompressXZ }, | |
| { "Zstd", cmSystemTools::TarCompressZstd } }; | |
| cmSystemTools::cmTarCompression compress = cmSystemTools::TarCompressAuto; | |
| auto typeIt = compressionTypeMap.find(parsedArgs.Compression); | |
| if (typeIt != compressionTypeMap.end()) { | |
| compress = typeIt->second; | |
| } else if (!parsedArgs.Compression.empty()) { | |
| status.SetError(cmStrCat("compression type ", parsedArgs.Compression, | |
| " is not supported")); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| if (compress == cmSystemTools::TarCompressPPMd && | |
| parsedArgs.Format != "7zip") { | |
| status.SetError(cmStrCat("PPMd compression is not supported for ", | |
| parsedArgs.Format, " format")); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| int compressionLevel = 0; | |
| constexpr int minCompressionLevel = 0; | |
| int maxCompressionLevel = 9; | |
| if (compress == cmSystemTools::TarCompressZstd && | |
| parsedArgs.Format != "zip") { | |
| maxCompressionLevel = 19; | |
| } | |
| if (!parsedArgs.CompressionLevel.empty()) { | |
| if (parsedArgs.CompressionLevel.size() != 1 && | |
| !cmsysString_isdigit(parsedArgs.CompressionLevel[0])) { | |
| status.SetError( | |
| cmStrCat("compression level ", parsedArgs.CompressionLevel, " for ", | |
| parsedArgs.Compression, " should be in range ", | |
| minCompressionLevel, " to ", maxCompressionLevel)); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| compressionLevel = std::stoi(parsedArgs.CompressionLevel); | |
| if (compressionLevel < minCompressionLevel || | |
| compressionLevel > maxCompressionLevel) { | |
| status.SetError( | |
| cmStrCat("compression level ", parsedArgs.CompressionLevel, " for ", | |
| parsedArgs.Compression, " should be in range ", | |
| minCompressionLevel, " to ", maxCompressionLevel)); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| if (compress == cmSystemTools::TarCompressNone) { | |
| status.SetError( | |
| cmStrCat("compression level is not supported for " | |
| "compression \"None\". Provided compression level: ", | |
| parsedArgs.CompressionLevel)); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| } | |
| // Use the single thread by default for backward compatibility | |
| int threads = 1; | |
| constexpr int minThreads = 0; | |
| if (!parsedArgs.Threads.empty()) { | |
| if (parsedArgs.Threads.size() != 1 && | |
| !cmsysString_isdigit(parsedArgs.Threads[0])) { | |
| status.SetError(cmStrCat("number of threads ", parsedArgs.Threads, | |
| " should be at least ", minThreads)); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| threads = std::stoi(parsedArgs.Threads); | |
| if (threads < minThreads) { | |
| status.SetError(cmStrCat("number of threads ", parsedArgs.Threads, | |
| " should be at least ", minThreads)); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| } | |
| if (parsedArgs.Paths.empty()) { | |
| status.SetError("ARCHIVE_CREATE requires a non-empty list of PATHS"); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| if (parsedArgs.Encoding.empty()) { | |
| if (status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0213) == | |
| cmPolicies::NEW) { | |
| parsedArgs.Encoding = "UTF-8"; | |
| } else { | |
| parsedArgs.Encoding = "OEM"; | |
| } | |
| } | |
| if (!cmSystemTools::CreateTar( | |
| parsedArgs.Output, parsedArgs.Paths, parsedArgs.PatternsExclude, | |
| parsedArgs.WorkingDirectory, compress, parsedArgs.Encoding, | |
| parsedArgs.Verbose, parsedArgs.MTime, parsedArgs.Format, | |
| compressionLevel, threads)) { | |
| status.SetError(cmStrCat("failed to compress: ", parsedArgs.Output)); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool HandleArchiveExtractCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| struct Arguments : public ArgumentParser::ParseResult | |
| { | |
| std::string Input; | |
| std::string Encoding; | |
| bool Verbose = false; | |
| bool ListOnly = false; | |
| std::string Destination; | |
| ArgumentParser::MaybeEmpty<std::vector<std::string>> Patterns; | |
| ArgumentParser::MaybeEmpty<std::vector<std::string>> PatternsExclude; | |
| bool Touch = false; | |
| }; | |
| static auto const parser = | |
| cmArgumentParser<Arguments>{} | |
| .Bind("INPUT"_s, &Arguments::Input) | |
| .Bind("ENCODING"_s, &Arguments::Encoding) | |
| .Bind("VERBOSE"_s, &Arguments::Verbose) | |
| .Bind("LIST_ONLY"_s, &Arguments::ListOnly) | |
| .Bind("DESTINATION"_s, &Arguments::Destination) | |
| .Bind("PATTERNS"_s, &Arguments::Patterns) | |
| .Bind("PATTERNS_EXCLUDE"_s, &Arguments::PatternsExclude) | |
| .Bind("TOUCH"_s, &Arguments::Touch); | |
| std::vector<std::string> unrecognizedArguments; | |
| auto parsedArgs = | |
| parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments); | |
| auto argIt = unrecognizedArguments.begin(); | |
| if (argIt != unrecognizedArguments.end()) { | |
| status.SetError(cmStrCat("Unrecognized argument: \"", *argIt, '"')); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| if (parsedArgs.MaybeReportError(status.GetMakefile())) { | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return true; | |
| } | |
| std::string inFile = parsedArgs.Input; | |
| if (parsedArgs.Encoding.empty()) { | |
| if (status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0213) == | |
| cmPolicies::NEW) { | |
| parsedArgs.Encoding = "UTF-8"; | |
| } else { | |
| parsedArgs.Encoding = "OEM"; | |
| } | |
| } | |
| if (parsedArgs.ListOnly) { | |
| if (!cmSystemTools::ListTar(inFile, parsedArgs.Patterns, | |
| parsedArgs.PatternsExclude, | |
| parsedArgs.Encoding, parsedArgs.Verbose)) { | |
| status.SetError(cmStrCat("failed to list: ", inFile)); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| } else { | |
| std::string destDir = status.GetMakefile().GetCurrentBinaryDirectory(); | |
| if (!parsedArgs.Destination.empty()) { | |
| if (cmSystemTools::FileIsFullPath(parsedArgs.Destination)) { | |
| destDir = parsedArgs.Destination; | |
| } else { | |
| destDir = cmStrCat(std::move(destDir), '/', parsedArgs.Destination); | |
| } | |
| if (!cmSystemTools::MakeDirectory(destDir)) { | |
| status.SetError(cmStrCat("failed to create directory: ", destDir)); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| if (!cmSystemTools::FileIsFullPath(inFile)) { | |
| inFile = cmStrCat(cmSystemTools::GetLogicalWorkingDirectory(), '/', | |
| std::move(inFile)); | |
| } | |
| } | |
| cmWorkingDirectory workdir(destDir); | |
| if (workdir.Failed()) { | |
| status.SetError(workdir.GetError()); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| if (!cmSystemTools::ExtractTar( | |
| inFile, parsedArgs.Patterns, parsedArgs.PatternsExclude, | |
| parsedArgs.Touch ? cmSystemTools::cmTarExtractTimestamps::No | |
| : cmSystemTools::cmTarExtractTimestamps::Yes, | |
| parsedArgs.Encoding, parsedArgs.Verbose)) { | |
| status.SetError(cmStrCat("failed to extract:\n ", inFile)); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool ValidateAndConvertPermissions( | |
| cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>> const& | |
| permissions, | |
| mode_t& perms, cmExecutionStatus& status) | |
| { | |
| if (!permissions) { | |
| return true; | |
| } | |
| for (auto const& i : *permissions) { | |
| if (!cmFSPermissions::stringToModeT(i, perms)) { | |
| status.SetError(i + " is an invalid permission specifier"); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool SetPermissions(std::string const& filename, mode_t perms, | |
| cmExecutionStatus& status) | |
| { | |
| if (!cmSystemTools::SetPermissions(filename, perms)) { | |
| status.SetError("Failed to set permissions for " + filename); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool HandleChmodCommandImpl(std::vector<std::string> const& args, bool recurse, | |
| cmExecutionStatus& status) | |
| { | |
| mode_t perms = 0; | |
| mode_t fperms = 0; | |
| mode_t dperms = 0; | |
| cmsys::Glob globber; | |
| globber.SetRecurse(recurse); | |
| globber.SetRecurseListDirs(recurse); | |
| struct Arguments : public ArgumentParser::ParseResult | |
| { | |
| cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>> | |
| Permissions; | |
| cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>> | |
| FilePermissions; | |
| cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>> | |
| DirectoryPermissions; | |
| }; | |
| static auto const parser = | |
| cmArgumentParser<Arguments>{} | |
| .Bind("PERMISSIONS"_s, &Arguments::Permissions) | |
| .Bind("FILE_PERMISSIONS"_s, &Arguments::FilePermissions) | |
| .Bind("DIRECTORY_PERMISSIONS"_s, &Arguments::DirectoryPermissions); | |
| std::vector<std::string> pathEntries; | |
| Arguments parsedArgs = | |
| parser.Parse(cmMakeRange(args).advance(1), &pathEntries); | |
| // check validity of arguments | |
| if (!parsedArgs.Permissions && !parsedArgs.FilePermissions && | |
| !parsedArgs.DirectoryPermissions) // no permissions given | |
| { | |
| status.SetError("No permissions given"); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| if (parsedArgs.Permissions && parsedArgs.FilePermissions && | |
| parsedArgs.DirectoryPermissions) // all keywords are used | |
| { | |
| status.SetError("Remove either PERMISSIONS or FILE_PERMISSIONS or " | |
| "DIRECTORY_PERMISSIONS from the invocation"); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| if (parsedArgs.MaybeReportError(status.GetMakefile())) { | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return true; | |
| } | |
| // validate permissions | |
| bool validatePermissions = | |
| ValidateAndConvertPermissions(parsedArgs.Permissions, perms, status) && | |
| ValidateAndConvertPermissions(parsedArgs.FilePermissions, fperms, | |
| status) && | |
| ValidateAndConvertPermissions(parsedArgs.DirectoryPermissions, dperms, | |
| status); | |
| if (!validatePermissions) { | |
| return false; | |
| } | |
| std::vector<std::string> allPathEntries; | |
| if (recurse) { | |
| std::vector<std::string> tempPathEntries; | |
| for (auto const& i : pathEntries) { | |
| if (cmSystemTools::FileIsDirectory(i)) { | |
| globber.FindFiles(i + "/*"); | |
| tempPathEntries = globber.GetFiles(); | |
| allPathEntries.insert(allPathEntries.end(), tempPathEntries.begin(), | |
| tempPathEntries.end()); | |
| allPathEntries.emplace_back(i); | |
| } else { | |
| allPathEntries.emplace_back(i); // We validate path entries below | |
| } | |
| } | |
| } else { | |
| allPathEntries = std::move(pathEntries); | |
| } | |
| // chmod | |
| for (auto const& i : allPathEntries) { | |
| if (!(cmSystemTools::FileExists(i) || cmSystemTools::FileIsDirectory(i))) { | |
| status.SetError(cmStrCat("does not exist:\n ", i)); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return false; | |
| } | |
| if (cmSystemTools::FileExists(i, true)) { | |
| bool success = true; | |
| mode_t filePermissions = parsedArgs.FilePermissions ? fperms : perms; | |
| if (filePermissions) { | |
| success = SetPermissions(i, filePermissions, status); | |
| } | |
| if (!success) { | |
| return false; | |
| } | |
| } | |
| else if (cmSystemTools::FileIsDirectory(i)) { | |
| bool success = true; | |
| mode_t directoryPermissions = | |
| parsedArgs.DirectoryPermissions ? dperms : perms; | |
| if (directoryPermissions) { | |
| success = SetPermissions(i, directoryPermissions, status); | |
| } | |
| if (!success) { | |
| return false; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| bool HandleChmodCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| return HandleChmodCommandImpl(args, false, status); | |
| } | |
| bool HandleChmodRecurseCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| return HandleChmodCommandImpl(args, true, status); | |
| } | |
| } // namespace | |
| bool cmFileCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| if (args.empty()) { | |
| status.SetError( | |
| "given no arguments, but it requires at least a sub-command."); | |
| return false; | |
| } | |
| static cmSubcommandTable const subcommand{ | |
| { "WRITE"_s, HandleWriteCommand }, | |
| { "APPEND"_s, HandleAppendCommand }, | |
| { "DOWNLOAD"_s, HandleDownloadCommand }, | |
| { "UPLOAD"_s, HandleUploadCommand }, | |
| { "READ"_s, HandleReadCommand }, | |
| { "MD5"_s, HandleHashCommand }, | |
| { "SHA1"_s, HandleHashCommand }, | |
| { "SHA224"_s, HandleHashCommand }, | |
| { "SHA256"_s, HandleHashCommand }, | |
| { "SHA384"_s, HandleHashCommand }, | |
| { "SHA512"_s, HandleHashCommand }, | |
| { "SHA3_224"_s, HandleHashCommand }, | |
| { "SHA3_256"_s, HandleHashCommand }, | |
| { "SHA3_384"_s, HandleHashCommand }, | |
| { "SHA3_512"_s, HandleHashCommand }, | |
| { "STRINGS"_s, HandleStringsCommand }, | |
| { "GLOB"_s, HandleGlobCommand }, | |
| { "GLOB_RECURSE"_s, HandleGlobRecurseCommand }, | |
| { "MAKE_DIRECTORY"_s, HandleMakeDirectoryCommand }, | |
| { "RENAME"_s, HandleRename }, | |
| { "COPY_FILE"_s, HandleCopyFile }, | |
| { "REMOVE"_s, HandleRemove }, | |
| { "REMOVE_RECURSE"_s, HandleRemoveRecurse }, | |
| { "COPY"_s, HandleCopyCommand }, | |
| { "INSTALL"_s, HandleInstallCommand }, | |
| { "DIFFERENT"_s, HandleDifferentCommand }, | |
| { "RPATH_CHANGE"_s, HandleRPathChangeCommand }, | |
| { "CHRPATH"_s, HandleRPathChangeCommand }, | |
| { "RPATH_SET"_s, HandleRPathSetCommand }, | |
| { "RPATH_CHECK"_s, HandleRPathCheckCommand }, | |
| { "RPATH_REMOVE"_s, HandleRPathRemoveCommand }, | |
| { "READ_ELF"_s, HandleReadElfCommand }, | |
| { "READ_MACHO"_s, HandleReadMachoCommand }, | |
| { "REAL_PATH"_s, HandleRealPathCommand }, | |
| { "RELATIVE_PATH"_s, HandleRelativePathCommand }, | |
| { "TO_CMAKE_PATH"_s, HandleCMakePathCommand }, | |
| { "TO_NATIVE_PATH"_s, HandleNativePathCommand }, | |
| { "TOUCH"_s, HandleTouchCommand }, | |
| { "TOUCH_NOCREATE"_s, HandleTouchNocreateCommand }, | |
| { "TIMESTAMP"_s, HandleTimestampCommand }, | |
| { "GENERATE"_s, HandleGenerateCommand }, | |
| { "LOCK"_s, HandleLockCommand }, | |
| { "SIZE"_s, HandleSizeCommand }, | |
| { "READ_SYMLINK"_s, HandleReadSymlinkCommand }, | |
| { "CREATE_LINK"_s, HandleCreateLinkCommand }, | |
| { "GET_RUNTIME_DEPENDENCIES"_s, HandleGetRuntimeDependenciesCommand }, | |
| { "CONFIGURE"_s, HandleConfigureCommand }, | |
| { "ARCHIVE_CREATE"_s, HandleArchiveCreateCommand }, | |
| { "ARCHIVE_EXTRACT"_s, HandleArchiveExtractCommand }, | |
| { "CHMOD"_s, HandleChmodCommand }, | |
| { "CHMOD_RECURSE"_s, HandleChmodRecurseCommand }, | |
| }; | |
| return subcommand(args[0], args, status); | |
| } |