summaryrefslogtreecommitdiffstats
path: root/Source/cmFileCommand.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'Source/cmFileCommand.cxx')
-rw-r--r--Source/cmFileCommand.cxx3783
1 files changed, 3783 insertions, 0 deletions
diff --git a/Source/cmFileCommand.cxx b/Source/cmFileCommand.cxx
new file mode 100644
index 0000000..59ef48d
--- /dev/null
+++ b/Source/cmFileCommand.cxx
@@ -0,0 +1,3783 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmFileCommand.h"
+
+#include "cm_kwiml.h"
+#include "cmsys/Directory.hxx"
+#include "cmsys/FStream.hxx"
+#include "cmsys/Glob.hxx"
+#include "cmsys/RegularExpression.hxx"
+
+#include <algorithm>
+#include <assert.h>
+#include <cmath>
+#include <ctype.h>
+#include <memory> // IWYU pragma: keep
+#include <sstream>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <utility>
+#include <vector>
+
+#include "cmAlgorithms.h"
+#include "cmCommandArgumentsHelper.h"
+#include "cmCryptoHash.h"
+#include "cmFSPermissions.h"
+#include "cmFileLockPool.h"
+#include "cmFileTimeComparison.h"
+#include "cmGeneratorExpression.h"
+#include "cmGlobalGenerator.h"
+#include "cmHexFileConverter.h"
+#include "cmInstallType.h"
+#include "cmListFileCache.h"
+#include "cmMakefile.h"
+#include "cmMessageType.h"
+#include "cmPolicies.h"
+#include "cmRange.h"
+#include "cmSystemTools.h"
+#include "cmTimestamp.h"
+#include "cm_sys_stat.h"
+#include "cmake.h"
+
+#if defined(CMAKE_BUILD_WITH_CMAKE)
+# include "cmCurl.h"
+# include "cmFileLockResult.h"
+# include "cm_curl.h"
+#endif
+
+#if defined(CMAKE_USE_ELF_PARSER)
+# include "cmELF.h"
+#endif
+
+#if defined(_WIN32)
+# include <windows.h>
+#endif
+
+class cmSystemToolsFileTime;
+
+using namespace cmFSPermissions;
+
+#if defined(_WIN32)
+// libcurl doesn't support file:// urls for unicode filenames on Windows.
+// Convert string from UTF-8 to ACP if this is a file:// URL.
+static std::string fix_file_url_windows(const std::string& url)
+{
+ std::string ret = url;
+ if (strncmp(url.c_str(), "file://", 7) == 0) {
+ std::wstring wurl = cmsys::Encoding::ToWide(url);
+ if (!wurl.empty()) {
+ int mblen =
+ WideCharToMultiByte(CP_ACP, 0, wurl.c_str(), -1, NULL, 0, NULL, NULL);
+ if (mblen > 0) {
+ std::vector<char> chars(mblen);
+ mblen = WideCharToMultiByte(CP_ACP, 0, wurl.c_str(), -1, &chars[0],
+ mblen, NULL, NULL);
+ if (mblen > 0) {
+ ret = &chars[0];
+ }
+ }
+ }
+ }
+ return ret;
+}
+#endif
+
+// cmLibraryCommand
+bool cmFileCommand::InitialPass(std::vector<std::string> const& args,
+ cmExecutionStatus&)
+{
+ if (args.size() < 2) {
+ this->SetError("must be called with at least two arguments.");
+ return false;
+ }
+ std::string const& subCommand = args[0];
+ if (subCommand == "WRITE") {
+ return this->HandleWriteCommand(args, false);
+ }
+ if (subCommand == "APPEND") {
+ return this->HandleWriteCommand(args, true);
+ }
+ if (subCommand == "DOWNLOAD") {
+ return this->HandleDownloadCommand(args);
+ }
+ if (subCommand == "UPLOAD") {
+ return this->HandleUploadCommand(args);
+ }
+ if (subCommand == "READ") {
+ return this->HandleReadCommand(args);
+ }
+ if (subCommand == "MD5" || subCommand == "SHA1" || subCommand == "SHA224" ||
+ subCommand == "SHA256" || subCommand == "SHA384" ||
+ subCommand == "SHA512" || subCommand == "SHA3_224" ||
+ subCommand == "SHA3_256" || subCommand == "SHA3_384" ||
+ subCommand == "SHA3_512") {
+ return this->HandleHashCommand(args);
+ }
+ if (subCommand == "STRINGS") {
+ return this->HandleStringsCommand(args);
+ }
+ if (subCommand == "GLOB") {
+ return this->HandleGlobCommand(args, false);
+ }
+ if (subCommand == "GLOB_RECURSE") {
+ return this->HandleGlobCommand(args, true);
+ }
+ if (subCommand == "MAKE_DIRECTORY") {
+ return this->HandleMakeDirectoryCommand(args);
+ }
+ if (subCommand == "RENAME") {
+ return this->HandleRename(args);
+ }
+ if (subCommand == "REMOVE") {
+ return this->HandleRemove(args, false);
+ }
+ if (subCommand == "REMOVE_RECURSE") {
+ return this->HandleRemove(args, true);
+ }
+ if (subCommand == "COPY") {
+ return this->HandleCopyCommand(args);
+ }
+ if (subCommand == "INSTALL") {
+ return this->HandleInstallCommand(args);
+ }
+ if (subCommand == "DIFFERENT") {
+ return this->HandleDifferentCommand(args);
+ }
+ if (subCommand == "RPATH_CHANGE" || subCommand == "CHRPATH") {
+ return this->HandleRPathChangeCommand(args);
+ }
+ if (subCommand == "RPATH_CHECK") {
+ return this->HandleRPathCheckCommand(args);
+ }
+ if (subCommand == "RPATH_REMOVE") {
+ return this->HandleRPathRemoveCommand(args);
+ }
+ if (subCommand == "READ_ELF") {
+ return this->HandleReadElfCommand(args);
+ }
+ if (subCommand == "RELATIVE_PATH") {
+ return this->HandleRelativePathCommand(args);
+ }
+ if (subCommand == "TO_CMAKE_PATH") {
+ return this->HandleCMakePathCommand(args, false);
+ }
+ if (subCommand == "TO_NATIVE_PATH") {
+ return this->HandleCMakePathCommand(args, true);
+ }
+ if (subCommand == "TOUCH") {
+ return this->HandleTouchCommand(args, true);
+ }
+ if (subCommand == "TOUCH_NOCREATE") {
+ return this->HandleTouchCommand(args, false);
+ }
+ if (subCommand == "TIMESTAMP") {
+ return this->HandleTimestampCommand(args);
+ }
+ if (subCommand == "GENERATE") {
+ return this->HandleGenerateCommand(args);
+ }
+ if (subCommand == "LOCK") {
+ return this->HandleLockCommand(args);
+ }
+ if (subCommand == "SIZE") {
+ return this->HandleSizeCommand(args);
+ }
+ if (subCommand == "READ_SYMLINK") {
+ return this->HandleReadSymlinkCommand(args);
+ }
+ if (subCommand == "CREATE_LINK") {
+ return this->HandleCreateLinkCommand(args);
+ }
+
+ std::string e = "does not recognize sub-command " + subCommand;
+ this->SetError(e);
+ return false;
+}
+
+bool cmFileCommand::HandleWriteCommand(std::vector<std::string> const& args,
+ bool append)
+{
+ std::vector<std::string>::const_iterator i = args.begin();
+
+ i++; // Get rid of subcommand
+
+ std::string fileName = *i;
+ if (!cmsys::SystemTools::FileIsFullPath(*i)) {
+ fileName = this->Makefile->GetCurrentSourceDirectory();
+ fileName += "/" + *i;
+ }
+
+ i++;
+
+ if (!this->Makefile->CanIWriteThisFile(fileName)) {
+ std::string e =
+ "attempted to write a file: " + fileName + " into a source directory.";
+ this->SetError(e);
+ cmSystemTools::SetFatalErrorOccured();
+ 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 = "failed to open for writing (";
+ error += cmSystemTools::GetLastSystemError();
+ error += "):\n ";
+ error += fileName;
+ this->SetError(error);
+ return false;
+ }
+ std::string message = cmJoin(cmMakeRange(i, args.end()), std::string());
+ file << message;
+ if (!file) {
+ std::string error = "write failed (";
+ error += cmSystemTools::GetLastSystemError();
+ error += "):\n ";
+ error += fileName;
+ this->SetError(error);
+ return false;
+ }
+ file.close();
+ if (mode && !writable) {
+ cmSystemTools::SetPermissions(fileName, mode);
+ }
+ return true;
+}
+
+bool cmFileCommand::HandleReadCommand(std::vector<std::string> const& args)
+{
+ if (args.size() < 3) {
+ this->SetError("READ must be called with at least two additional "
+ "arguments");
+ return false;
+ }
+
+ cmCommandArgumentsHelper argHelper;
+ cmCommandArgumentGroup group;
+
+ cmCAString readArg(&argHelper, "READ");
+ cmCAString fileNameArg(&argHelper, nullptr);
+ cmCAString resultArg(&argHelper, nullptr);
+
+ cmCAString offsetArg(&argHelper, "OFFSET", &group);
+ cmCAString limitArg(&argHelper, "LIMIT", &group);
+ cmCAEnabler hexOutputArg(&argHelper, "HEX", &group);
+ readArg.Follows(nullptr);
+ fileNameArg.Follows(&readArg);
+ resultArg.Follows(&fileNameArg);
+ group.Follows(&resultArg);
+ argHelper.Parse(&args, nullptr);
+
+ std::string fileName = fileNameArg.GetString();
+ if (!cmsys::SystemTools::FileIsFullPath(fileName)) {
+ fileName = this->Makefile->GetCurrentSourceDirectory();
+ fileName += "/" + fileNameArg.GetString();
+ }
+
+ std::string variable = resultArg.GetString();
+
+// Open the specified file.
+#if defined(_WIN32) || defined(__CYGWIN__)
+ cmsys::ifstream file(
+ fileName.c_str(),
+ std::ios::in |
+ (hexOutputArg.IsEnabled() ? std::ios::binary : std::ios::in));
+#else
+ cmsys::ifstream file(fileName.c_str());
+#endif
+
+ if (!file) {
+ std::string error = "failed to open for reading (";
+ error += cmSystemTools::GetLastSystemError();
+ error += "):\n ";
+ error += fileName;
+ this->SetError(error);
+ return false;
+ }
+
+ // is there a limit?
+ long sizeLimit = -1;
+ if (!limitArg.GetString().empty()) {
+ sizeLimit = atoi(limitArg.GetCString());
+ }
+
+ // is there an offset?
+ long offset = 0;
+ if (!offsetArg.GetString().empty()) {
+ offset = atoi(offsetArg.GetCString());
+ }
+
+ file.seekg(offset, std::ios::beg); // explicit ios::beg for IBM VisualAge 6
+
+ std::string output;
+
+ if (hexOutputArg.IsEnabled()) {
+ // Convert part of the file into hex code
+ char c;
+ while ((sizeLimit != 0) && (file.get(c))) {
+ char hex[4];
+ sprintf(hex, "%.2x", c & 0xff);
+ output += hex;
+ if (sizeLimit > 0) {
+ sizeLimit--;
+ }
+ }
+ } else {
+ std::string line;
+ bool has_newline = false;
+ while (
+ sizeLimit != 0 &&
+ cmSystemTools::GetLineFromStream(file, line, &has_newline, sizeLimit)) {
+ if (sizeLimit > 0) {
+ sizeLimit = sizeLimit - static_cast<long>(line.size());
+ if (has_newline) {
+ sizeLimit--;
+ }
+ if (sizeLimit < 0) {
+ sizeLimit = 0;
+ }
+ }
+ output += line;
+ if (has_newline) {
+ output += "\n";
+ }
+ }
+ }
+ this->Makefile->AddDefinition(variable, output.c_str());
+ return true;
+}
+
+bool cmFileCommand::HandleHashCommand(std::vector<std::string> const& args)
+{
+#if defined(CMAKE_BUILD_WITH_CMAKE)
+ if (args.size() != 3) {
+ std::ostringstream e;
+ e << args[0] << " requires a file name and output variable";
+ this->SetError(e.str());
+ return false;
+ }
+
+ std::unique_ptr<cmCryptoHash> hash(cmCryptoHash::New(args[0].c_str()));
+ if (hash) {
+ std::string out = hash->HashFile(args[1]);
+ if (!out.empty()) {
+ this->Makefile->AddDefinition(args[2], out.c_str());
+ return true;
+ }
+ std::ostringstream e;
+ e << args[0] << " failed to read file \"" << args[1]
+ << "\": " << cmSystemTools::GetLastSystemError();
+ this->SetError(e.str());
+ }
+ return false;
+#else
+ std::ostringstream e;
+ e << args[0] << " not available during bootstrap";
+ this->SetError(e.str());
+ return false;
+#endif
+}
+
+bool cmFileCommand::HandleStringsCommand(std::vector<std::string> const& args)
+{
+ if (args.size() < 3) {
+ this->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 = this->Makefile->GetCurrentSourceDirectory();
+ fileName += "/" + args[1];
+ }
+
+ // Get the variable in which to store the results.
+ std::string 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 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) {
+ std::ostringstream e;
+ e << "STRINGS option LIMIT_INPUT value \"" << args[i]
+ << "\" is not an unsigned integer.";
+ this->SetError(e.str());
+ 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) {
+ std::ostringstream e;
+ e << "STRINGS option LIMIT_OUTPUT value \"" << args[i]
+ << "\" is not an unsigned integer.";
+ this->SetError(e.str());
+ 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) {
+ std::ostringstream e;
+ e << "STRINGS option LIMIT_COUNT value \"" << args[i]
+ << "\" is not an unsigned integer.";
+ this->SetError(e.str());
+ 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) {
+ std::ostringstream e;
+ e << "STRINGS option LENGTH_MINIMUM value \"" << args[i]
+ << "\" is not an unsigned integer.";
+ this->SetError(e.str());
+ 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) {
+ std::ostringstream e;
+ e << "STRINGS option LENGTH_MAXIMUM value \"" << args[i]
+ << "\" is not an unsigned integer.";
+ this->SetError(e.str());
+ return false;
+ }
+ maxlen = len;
+ arg_mode = arg_none;
+ } else if (arg_mode == arg_regex) {
+ if (!regex.compile(args[i])) {
+ std::ostringstream e;
+ e << "STRINGS option REGEX value \"" << args[i]
+ << "\" could not be compiled.";
+ this->SetError(e.str());
+ return false;
+ }
+ have_regex = true;
+ 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 {
+ std::ostringstream e;
+ e << "STRINGS option ENCODING \"" << args[i] << "\" not recognized.";
+ this->SetError(e.str());
+ return false;
+ }
+ arg_mode = arg_none;
+ } else {
+ std::ostringstream e;
+ e << "STRINGS given unknown argument \"" << args[i] << "\"";
+ this->SetError(e.str());
+ return false;
+ }
+ }
+
+ if (hex_conversion_enabled) {
+ // TODO: should work without temp file, but just on a memory buffer
+ std::string binaryFileName = this->Makefile->GetCurrentBinaryDirectory();
+ binaryFileName += "/CMakeFiles";
+ binaryFileName += "/FileCommandStringsBinaryFile";
+ if (cmHexFileConverter::TryConvert(fileName.c_str(),
+ binaryFileName.c_str())) {
+ 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) {
+ std::ostringstream e;
+ e << "STRINGS file \"" << fileName << "\" cannot be read.";
+ this->SetError(e.str());
+ 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 &&
+ (isprint(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 const unsigned char 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++) {
+ c = current_str[current_str.size() - 1 - j];
+ fin.putback(static_cast<char>(c));
+ }
+ 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))) {
+ 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))) {
+ 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))) {
+ 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))) {
+ 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.
+ const char* 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.
+ this->Makefile->AddDefinition(outVar, output.c_str());
+ return true;
+}
+
+bool cmFileCommand::HandleGlobCommand(std::vector<std::string> const& args,
+ bool recurse)
+{
+ // File commands has at least one argument
+ assert(args.size() > 1);
+
+ std::vector<std::string>::const_iterator i = args.begin();
+
+ i++; // Get rid of subcommand
+
+ std::string variable = *i;
+ i++;
+ cmsys::Glob g;
+ g.SetRecurse(recurse);
+
+ bool explicitFollowSymlinks = false;
+ cmPolicies::PolicyStatus status =
+ this->Makefile->GetPolicyStatus(cmPolicies::CMP0009);
+ if (recurse) {
+ switch (status) {
+ case cmPolicies::REQUIRED_IF_USED:
+ case cmPolicies::REQUIRED_ALWAYS:
+ case cmPolicies::NEW:
+ g.RecurseThroughSymlinksOff();
+ break;
+ case cmPolicies::OLD:
+ case cmPolicies::WARN:
+ g.RecurseThroughSymlinksOn();
+ break;
+ }
+ }
+
+ std::vector<std::string> files;
+ bool configureDepends = false;
+ bool warnConfigureLate = false;
+ bool warnFollowedSymlinks = false;
+ const cmake::WorkingMode workingMode =
+ this->Makefile->GetCMakeInstance()->GetWorkingMode();
+ while (i != args.end()) {
+ if (*i == "LIST_DIRECTORIES") {
+ ++i; // skip LIST_DIRECTORIES
+ if (i != args.end()) {
+ if (cmSystemTools::IsOn(*i)) {
+ g.SetListDirs(true);
+ g.SetRecurseListDirs(true);
+ } else if (cmSystemTools::IsOff(*i)) {
+ g.SetListDirs(false);
+ g.SetRecurseListDirs(false);
+ } else {
+ this->SetError("LIST_DIRECTORIES missing bool value.");
+ return false;
+ }
+ ++i;
+ } else {
+ this->SetError("LIST_DIRECTORIES missing bool value.");
+ return false;
+ }
+ } else if (*i == "FOLLOW_SYMLINKS") {
+ ++i; // skip FOLLOW_SYMLINKS
+ if (recurse) {
+ explicitFollowSymlinks = true;
+ g.RecurseThroughSymlinksOn();
+ if (i == args.end()) {
+ this->SetError(
+ "GLOB_RECURSE requires a glob expression after FOLLOW_SYMLINKS.");
+ return false;
+ }
+ }
+ } else if (*i == "RELATIVE") {
+ ++i; // skip RELATIVE
+ if (i == args.end()) {
+ this->SetError("GLOB requires a directory after the RELATIVE tag.");
+ return false;
+ }
+ g.SetRelative(i->c_str());
+ ++i;
+ if (i == args.end()) {
+ this->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) {
+ this->Makefile->IssueMessage(
+ MessageType::AUTHOR_WARNING,
+ "CONFIGURE_DEPENDS flag was given after a glob expression was "
+ "already evaluated.");
+ }
+ if (workingMode != cmake::NORMAL_MODE) {
+ this->Makefile->IssueMessage(
+ MessageType::FATAL_ERROR,
+ "CONFIGURE_DEPENDS is invalid for script and find package modes.");
+ return false;
+ }
+ configureDepends = true;
+ ++i;
+ if (i == args.end()) {
+ this->SetError(
+ "GLOB requires a glob expression after CONFIGURE_DEPENDS.");
+ return false;
+ }
+ } else {
+ std::string expr = *i;
+ if (!cmsys::SystemTools::FileIsFullPath(*i)) {
+ expr = this->Makefile->GetCurrentSourceDirectory();
+ // Handle script mode
+ if (!expr.empty()) {
+ 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) {
+ this->Makefile->IssueMessage(
+ MessageType::AUTHOR_WARNING,
+ "Cyclic recursion detected while globbing for '" + *i + "':\n" +
+ globMessage.content);
+ } else {
+ this->Makefile->IssueMessage(
+ MessageType::FATAL_ERROR,
+ "Error has occurred while globbing for '" + *i + "' - " +
+ globMessage.content);
+ shouldExit = true;
+ }
+ }
+ if (shouldExit) {
+ return false;
+ }
+ }
+
+ if (recurse && !explicitFollowSymlinks &&
+ g.GetFollowedSymlinkCount() != 0) {
+ warnFollowedSymlinks = true;
+ }
+
+ std::vector<std::string>& foundFiles = g.GetFiles();
+ files.insert(files.end(), foundFiles.begin(), foundFiles.end());
+
+ if (configureDepends) {
+ std::sort(foundFiles.begin(), foundFiles.end());
+ foundFiles.erase(std::unique(foundFiles.begin(), foundFiles.end()),
+ foundFiles.end());
+ this->Makefile->GetCMakeInstance()->AddGlobCacheEntry(
+ recurse, (recurse ? g.GetRecurseListDirs() : g.GetListDirs()),
+ (recurse ? g.GetRecurseThroughSymlinks() : false),
+ (g.GetRelative() ? g.GetRelative() : ""), expr, foundFiles, variable,
+ this->Makefile->GetBacktrace());
+ } else {
+ warnConfigureLate = true;
+ }
+ ++i;
+ }
+ }
+
+ switch (status) {
+ case cmPolicies::REQUIRED_IF_USED:
+ case cmPolicies::REQUIRED_ALWAYS:
+ case cmPolicies::NEW:
+ // Correct behavior, yay!
+ break;
+ case cmPolicies::OLD:
+ // Probably not really the expected behavior, but the author explicitly
+ // asked for the old behavior... no warning.
+ case cmPolicies::WARN:
+ // Possibly unexpected old behavior *and* we actually traversed
+ // symlinks without being explicitly asked to: warn the author.
+ if (warnFollowedSymlinks) {
+ this->Makefile->IssueMessage(
+ MessageType::AUTHOR_WARNING,
+ cmPolicies::GetPolicyWarning(cmPolicies::CMP0009));
+ }
+ break;
+ }
+
+ std::sort(files.begin(), files.end());
+ files.erase(std::unique(files.begin(), files.end()), files.end());
+ this->Makefile->AddDefinition(variable, cmJoin(files, ";").c_str());
+ return true;
+}
+
+bool cmFileCommand::HandleMakeDirectoryCommand(
+ std::vector<std::string> const& args)
+{
+ // File command has at least one argument
+ assert(args.size() > 1);
+
+ std::string expr;
+ for (std::string const& arg :
+ cmMakeRange(args).advance(1)) // Get rid of subcommand
+ {
+ const std::string* cdir = &arg;
+ if (!cmsys::SystemTools::FileIsFullPath(arg)) {
+ expr = this->Makefile->GetCurrentSourceDirectory();
+ expr += "/" + arg;
+ cdir = &expr;
+ }
+ if (!this->Makefile->CanIWriteThisFile(*cdir)) {
+ std::string e = "attempted to create a directory: " + *cdir +
+ " into a source directory.";
+ this->SetError(e);
+ cmSystemTools::SetFatalErrorOccured();
+ return false;
+ }
+ if (!cmSystemTools::MakeDirectory(*cdir)) {
+ std::string error = "problem creating directory: " + *cdir;
+ this->SetError(error);
+ return false;
+ }
+ }
+ return true;
+}
+
+bool cmFileCommand::HandleTouchCommand(std::vector<std::string> const& args,
+ bool create)
+{
+ // File command has at least one argument
+ assert(args.size() > 1);
+
+ for (std::string const& arg :
+ cmMakeRange(args).advance(1)) // Get rid of subcommand
+ {
+ std::string tfile = arg;
+ if (!cmsys::SystemTools::FileIsFullPath(tfile)) {
+ tfile = this->Makefile->GetCurrentSourceDirectory();
+ tfile += "/" + arg;
+ }
+ if (!this->Makefile->CanIWriteThisFile(tfile)) {
+ std::string e =
+ "attempted to touch a file: " + tfile + " in a source directory.";
+ this->SetError(e);
+ cmSystemTools::SetFatalErrorOccured();
+ return false;
+ }
+ if (!cmSystemTools::Touch(tfile, create)) {
+ std::string error = "problem touching file: " + tfile;
+ this->SetError(error);
+ return false;
+ }
+ }
+ return true;
+}
+
+bool cmFileCommand::HandleDifferentCommand(
+ std::vector<std::string> const& args)
+{
+ /*
+ FILE(DIFFERENT <variable> FILES <lhs> <rhs>)
+ */
+
+ // Evaluate arguments.
+ const char* file_lhs = nullptr;
+ const char* file_rhs = nullptr;
+ const char* 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 {
+ std::ostringstream e;
+ e << "DIFFERENT given unknown argument " << args[i];
+ this->SetError(e.str());
+ return false;
+ }
+ }
+ if (!var) {
+ this->SetError("DIFFERENT not given result variable name.");
+ return false;
+ }
+ if (!file_lhs || !file_rhs) {
+ this->SetError("DIFFERENT not given FILES option with two file names.");
+ return false;
+ }
+
+ // Compare the files.
+ const char* result =
+ cmSystemTools::FilesDiffer(file_lhs, file_rhs) ? "1" : "0";
+ this->Makefile->AddDefinition(var, result);
+ return true;
+}
+
+// File installation helper class.
+struct cmFileCopier
+{
+ cmFileCopier(cmFileCommand* command, const char* name = "COPY")
+ : FileCommand(command)
+ , Makefile(command->GetMakefile())
+ , Name(name)
+ , Always(false)
+ , MatchlessFiles(true)
+ , FilePermissions(0)
+ , DirPermissions(0)
+ , CurrentMatchRule(nullptr)
+ , UseGivenPermissionsFile(false)
+ , UseGivenPermissionsDir(false)
+ , UseSourcePermissions(true)
+ , Doing(DoingNone)
+ {
+ }
+ virtual ~cmFileCopier() = default;
+
+ bool Run(std::vector<std::string> const& args);
+
+protected:
+ cmFileCommand* FileCommand;
+ cmMakefile* Makefile;
+ const char* Name;
+ bool Always;
+ cmFileTimeComparison FileTimes;
+
+ // Whether to install a file not matching any expression.
+ bool MatchlessFiles;
+
+ // Permissions for files and directories installed by this object.
+ mode_t FilePermissions;
+ mode_t DirPermissions;
+
+ // Properties set by pattern and regex match rules.
+ struct MatchProperties
+ {
+ bool Exclude = false;
+ mode_t Permissions = 0;
+ };
+ struct MatchRule
+ {
+ cmsys::RegularExpression Regex;
+ MatchProperties Properties;
+ std::string RegexString;
+ MatchRule(std::string const& regex)
+ : Regex(regex)
+ , RegexString(regex)
+ {
+ }
+ };
+ std::vector<MatchRule> MatchRules;
+
+ // Get the properties from rules matching this input file.
+ MatchProperties CollectMatchProperties(const std::string& file)
+ {
+// Match rules are case-insensitive on some platforms.
+#if defined(_WIN32) || defined(__APPLE__) || defined(__CYGWIN__)
+ const std::string file_to_match = cmSystemTools::LowerCase(file);
+#else
+ const std::string& file_to_match = file;
+#endif
+
+ // Collect properties from all matching rules.
+ bool matched = false;
+ MatchProperties result;
+ for (MatchRule& mr : this->MatchRules) {
+ if (mr.Regex.find(file_to_match)) {
+ matched = true;
+ result.Exclude |= mr.Properties.Exclude;
+ result.Permissions |= mr.Properties.Permissions;
+ }
+ }
+ if (!matched && !this->MatchlessFiles) {
+ result.Exclude = !cmSystemTools::FileIsDirectory(file);
+ }
+ return result;
+ }
+
+ bool SetPermissions(const std::string& toFile, mode_t permissions)
+ {
+ if (permissions) {
+#ifdef WIN32
+ if (Makefile->IsOn("CMAKE_CROSSCOMPILING")) {
+ // Store the mode in an NTFS alternate stream.
+ std::string mode_t_adt_filename = toFile + ":cmake_mode_t";
+
+ // Writing to an NTFS alternate stream changes the modification
+ // time, so we need to save and restore its original value.
+ cmSystemToolsFileTime* file_time_orig = cmSystemTools::FileTimeNew();
+ cmSystemTools::FileTimeGet(toFile, file_time_orig);
+
+ cmsys::ofstream permissionStream(mode_t_adt_filename.c_str());
+
+ if (permissionStream) {
+ permissionStream << std::oct << permissions << std::endl;
+ }
+
+ permissionStream.close();
+
+ cmSystemTools::FileTimeSet(toFile, file_time_orig);
+
+ cmSystemTools::FileTimeDelete(file_time_orig);
+ }
+#endif
+
+ if (!cmSystemTools::SetPermissions(toFile, permissions)) {
+ std::ostringstream e;
+ e << this->Name << " cannot set permissions on \"" << toFile << "\"";
+ this->FileCommand->SetError(e.str());
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Translate an argument to a permissions bit.
+ bool CheckPermissions(std::string const& arg, mode_t& permissions)
+ {
+ if (!cmFSPermissions::stringToModeT(arg, permissions)) {
+ std::ostringstream e;
+ e << this->Name << " given invalid permission \"" << arg << "\".";
+ this->FileCommand->SetError(e.str());
+ return false;
+ }
+ return true;
+ }
+
+ bool InstallSymlink(const std::string& fromFile, const std::string& toFile);
+ bool InstallFile(const std::string& fromFile, const std::string& toFile,
+ MatchProperties match_properties);
+ bool InstallDirectory(const std::string& source,
+ const std::string& destination,
+ MatchProperties match_properties);
+ virtual bool Install(const std::string& fromFile, const std::string& toFile);
+ virtual std::string const& ToName(std::string const& fromName)
+ {
+ return fromName;
+ }
+
+ enum Type
+ {
+ TypeFile,
+ TypeDir,
+ TypeLink
+ };
+ virtual void ReportCopy(const std::string&, Type, bool) {}
+ virtual bool ReportMissing(const std::string& fromFile)
+ {
+ // The input file does not exist and installation is not optional.
+ std::ostringstream e;
+ e << this->Name << " cannot find \"" << fromFile << "\".";
+ this->FileCommand->SetError(e.str());
+ return false;
+ }
+
+ MatchRule* CurrentMatchRule;
+ bool UseGivenPermissionsFile;
+ bool UseGivenPermissionsDir;
+ bool UseSourcePermissions;
+ std::string Destination;
+ std::string FilesFromDir;
+ std::vector<std::string> Files;
+ int Doing;
+
+ virtual bool Parse(std::vector<std::string> const& args);
+ enum
+ {
+ DoingNone,
+ DoingError,
+ DoingDestination,
+ DoingFilesFromDir,
+ DoingFiles,
+ DoingPattern,
+ DoingRegex,
+ DoingPermissionsFile,
+ DoingPermissionsDir,
+ DoingPermissionsMatch,
+ DoingLast1
+ };
+ virtual bool CheckKeyword(std::string const& arg);
+ virtual bool CheckValue(std::string const& arg);
+
+ void NotBeforeMatch(std::string const& arg)
+ {
+ std::ostringstream e;
+ e << "option " << arg << " may not appear before PATTERN or REGEX.";
+ this->FileCommand->SetError(e.str());
+ this->Doing = DoingError;
+ }
+ void NotAfterMatch(std::string const& arg)
+ {
+ std::ostringstream e;
+ e << "option " << arg << " may not appear after PATTERN or REGEX.";
+ this->FileCommand->SetError(e.str());
+ this->Doing = DoingError;
+ }
+ virtual void DefaultFilePermissions()
+ {
+ // Use read/write permissions.
+ this->FilePermissions = 0;
+ this->FilePermissions |= mode_owner_read;
+ this->FilePermissions |= mode_owner_write;
+ this->FilePermissions |= mode_group_read;
+ this->FilePermissions |= mode_world_read;
+ }
+ virtual void DefaultDirectoryPermissions()
+ {
+ // Use read/write/executable permissions.
+ this->DirPermissions = 0;
+ this->DirPermissions |= mode_owner_read;
+ this->DirPermissions |= mode_owner_write;
+ this->DirPermissions |= mode_owner_execute;
+ this->DirPermissions |= mode_group_read;
+ this->DirPermissions |= mode_group_execute;
+ this->DirPermissions |= mode_world_read;
+ this->DirPermissions |= mode_world_execute;
+ }
+
+ bool GetDefaultDirectoryPermissions(mode_t** mode)
+ {
+ // check if default dir creation permissions were set
+ const char* default_dir_install_permissions =
+ this->Makefile->GetDefinition(
+ "CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS");
+ if (default_dir_install_permissions && *default_dir_install_permissions) {
+ std::vector<std::string> items;
+ cmSystemTools::ExpandListArgument(default_dir_install_permissions,
+ items);
+ for (const auto& arg : items) {
+ if (!this->CheckPermissions(arg, **mode)) {
+ std::ostringstream e;
+ e << this->FileCommand->GetError()
+ << " Set with CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS "
+ "variable.";
+ this->FileCommand->SetError(e.str());
+ return false;
+ }
+ }
+ } else {
+ *mode = nullptr;
+ }
+
+ return true;
+ }
+};
+
+bool cmFileCopier::Parse(std::vector<std::string> const& args)
+{
+ this->Doing = DoingFiles;
+ for (unsigned int i = 1; i < args.size(); ++i) {
+ // Check this argument.
+ if (!this->CheckKeyword(args[i]) && !this->CheckValue(args[i])) {
+ std::ostringstream e;
+ e << "called with unknown argument \"" << args[i] << "\".";
+ this->FileCommand->SetError(e.str());
+ return false;
+ }
+
+ // Quit if an argument is invalid.
+ if (this->Doing == DoingError) {
+ return false;
+ }
+ }
+
+ // Require a destination.
+ if (this->Destination.empty()) {
+ std::ostringstream e;
+ e << this->Name << " given no DESTINATION";
+ this->FileCommand->SetError(e.str());
+ return false;
+ }
+
+ // If file permissions were not specified set default permissions.
+ if (!this->UseGivenPermissionsFile && !this->UseSourcePermissions) {
+ this->DefaultFilePermissions();
+ }
+
+ // If directory permissions were not specified set default permissions.
+ if (!this->UseGivenPermissionsDir && !this->UseSourcePermissions) {
+ this->DefaultDirectoryPermissions();
+ }
+
+ return true;
+}
+
+bool cmFileCopier::CheckKeyword(std::string const& arg)
+{
+ if (arg == "DESTINATION") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingDestination;
+ }
+ } else if (arg == "FILES_FROM_DIR") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingFilesFromDir;
+ }
+ } else if (arg == "PATTERN") {
+ this->Doing = DoingPattern;
+ } else if (arg == "REGEX") {
+ this->Doing = DoingRegex;
+ } else if (arg == "EXCLUDE") {
+ // Add this property to the current match rule.
+ if (this->CurrentMatchRule) {
+ this->CurrentMatchRule->Properties.Exclude = true;
+ this->Doing = DoingNone;
+ } else {
+ this->NotBeforeMatch(arg);
+ }
+ } else if (arg == "PERMISSIONS") {
+ if (this->CurrentMatchRule) {
+ this->Doing = DoingPermissionsMatch;
+ } else {
+ this->NotBeforeMatch(arg);
+ }
+ } else if (arg == "FILE_PERMISSIONS") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingPermissionsFile;
+ this->UseGivenPermissionsFile = true;
+ }
+ } else if (arg == "DIRECTORY_PERMISSIONS") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingPermissionsDir;
+ this->UseGivenPermissionsDir = true;
+ }
+ } else if (arg == "USE_SOURCE_PERMISSIONS") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingNone;
+ this->UseSourcePermissions = true;
+ }
+ } else if (arg == "NO_SOURCE_PERMISSIONS") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingNone;
+ this->UseSourcePermissions = false;
+ }
+ } else if (arg == "FILES_MATCHING") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingNone;
+ this->MatchlessFiles = false;
+ }
+ } else {
+ return false;
+ }
+ return true;
+}
+
+bool cmFileCopier::CheckValue(std::string const& arg)
+{
+ switch (this->Doing) {
+ case DoingFiles:
+ this->Files.push_back(arg);
+ break;
+ case DoingDestination:
+ if (arg.empty() || cmSystemTools::FileIsFullPath(arg)) {
+ this->Destination = arg;
+ } else {
+ this->Destination = this->Makefile->GetCurrentBinaryDirectory();
+ this->Destination += "/" + arg;
+ }
+ this->Doing = DoingNone;
+ break;
+ case DoingFilesFromDir:
+ if (cmSystemTools::FileIsFullPath(arg)) {
+ this->FilesFromDir = arg;
+ } else {
+ this->FilesFromDir = this->Makefile->GetCurrentSourceDirectory();
+ this->FilesFromDir += "/" + arg;
+ }
+ cmSystemTools::ConvertToUnixSlashes(this->FilesFromDir);
+ this->Doing = DoingNone;
+ break;
+ case DoingPattern: {
+ // Convert the pattern to a regular expression. Require a
+ // leading slash and trailing end-of-string in the matched
+ // string to make sure the pattern matches only whole file
+ // names.
+ std::string regex = "/";
+ regex += cmsys::Glob::PatternToRegex(arg, false);
+ regex += "$";
+ this->MatchRules.emplace_back(regex);
+ this->CurrentMatchRule = &*(this->MatchRules.end() - 1);
+ if (this->CurrentMatchRule->Regex.is_valid()) {
+ this->Doing = DoingNone;
+ } else {
+ std::ostringstream e;
+ e << "could not compile PATTERN \"" << arg << "\".";
+ this->FileCommand->SetError(e.str());
+ this->Doing = DoingError;
+ }
+ } break;
+ case DoingRegex:
+ this->MatchRules.emplace_back(arg);
+ this->CurrentMatchRule = &*(this->MatchRules.end() - 1);
+ if (this->CurrentMatchRule->Regex.is_valid()) {
+ this->Doing = DoingNone;
+ } else {
+ std::ostringstream e;
+ e << "could not compile REGEX \"" << arg << "\".";
+ this->FileCommand->SetError(e.str());
+ this->Doing = DoingError;
+ }
+ break;
+ case DoingPermissionsFile:
+ if (!this->CheckPermissions(arg, this->FilePermissions)) {
+ this->Doing = DoingError;
+ }
+ break;
+ case DoingPermissionsDir:
+ if (!this->CheckPermissions(arg, this->DirPermissions)) {
+ this->Doing = DoingError;
+ }
+ break;
+ case DoingPermissionsMatch:
+ if (!this->CheckPermissions(
+ arg, this->CurrentMatchRule->Properties.Permissions)) {
+ this->Doing = DoingError;
+ }
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+bool cmFileCopier::Run(std::vector<std::string> const& args)
+{
+ if (!this->Parse(args)) {
+ return false;
+ }
+
+ for (std::string const& f : this->Files) {
+ std::string file;
+ if (!f.empty() && !cmSystemTools::FileIsFullPath(f)) {
+ if (!this->FilesFromDir.empty()) {
+ file = this->FilesFromDir;
+ } else {
+ file = this->Makefile->GetCurrentSourceDirectory();
+ }
+ file += "/";
+ file += f;
+ } else if (!this->FilesFromDir.empty()) {
+ this->FileCommand->SetError("option FILES_FROM_DIR requires all files "
+ "to be specified as relative paths.");
+ return false;
+ } else {
+ file = f;
+ }
+
+ // Split the input file into its directory and name components.
+ std::vector<std::string> fromPathComponents;
+ cmSystemTools::SplitPath(file, fromPathComponents);
+ std::string fromName = *(fromPathComponents.end() - 1);
+ std::string fromDir = cmSystemTools::JoinPath(
+ fromPathComponents.begin(), fromPathComponents.end() - 1);
+
+ // Compute the full path to the destination file.
+ std::string toFile = this->Destination;
+ if (!this->FilesFromDir.empty()) {
+ std::string dir = cmSystemTools::GetFilenamePath(f);
+ if (!dir.empty()) {
+ toFile += "/";
+ toFile += dir;
+ }
+ }
+ std::string const& toName = this->ToName(fromName);
+ if (!toName.empty()) {
+ toFile += "/";
+ toFile += toName;
+ }
+
+ // Construct the full path to the source file. The file name may
+ // have been changed above.
+ std::string fromFile = fromDir;
+ if (!fromName.empty()) {
+ fromFile += "/";
+ fromFile += fromName;
+ }
+
+ if (!this->Install(fromFile, toFile)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool cmFileCopier::Install(const std::string& fromFile,
+ const std::string& toFile)
+{
+ if (fromFile.empty()) {
+ std::ostringstream e;
+ e << "INSTALL encountered an empty string input file name.";
+ this->FileCommand->SetError(e.str());
+ return false;
+ }
+
+ // Collect any properties matching this file name.
+ MatchProperties match_properties = this->CollectMatchProperties(fromFile);
+
+ // Skip the file if it is excluded.
+ if (match_properties.Exclude) {
+ return true;
+ }
+
+ if (cmSystemTools::SameFile(fromFile, toFile)) {
+ return true;
+ }
+ if (cmSystemTools::FileIsSymlink(fromFile)) {
+ return this->InstallSymlink(fromFile, toFile);
+ }
+ if (cmSystemTools::FileIsDirectory(fromFile)) {
+ return this->InstallDirectory(fromFile, toFile, match_properties);
+ }
+ if (cmSystemTools::FileExists(fromFile)) {
+ return this->InstallFile(fromFile, toFile, match_properties);
+ }
+ return this->ReportMissing(fromFile);
+}
+
+bool cmFileCopier::InstallSymlink(const std::string& fromFile,
+ const std::string& toFile)
+{
+ // Read the original symlink.
+ std::string symlinkTarget;
+ if (!cmSystemTools::ReadSymlink(fromFile, symlinkTarget)) {
+ std::ostringstream e;
+ e << this->Name << " cannot read symlink \"" << fromFile
+ << "\" to duplicate at \"" << toFile << "\".";
+ this->FileCommand->SetError(e.str());
+ return false;
+ }
+
+ // Compare the symlink value to that at the destination if not
+ // always installing.
+ bool copy = true;
+ if (!this->Always) {
+ std::string oldSymlinkTarget;
+ if (cmSystemTools::ReadSymlink(toFile, oldSymlinkTarget)) {
+ if (symlinkTarget == oldSymlinkTarget) {
+ copy = false;
+ }
+ }
+ }
+
+ // Inform the user about this file installation.
+ this->ReportCopy(toFile, TypeLink, copy);
+
+ if (copy) {
+ // Remove the destination file so we can always create the symlink.
+ cmSystemTools::RemoveFile(toFile);
+
+ // Create destination directory if it doesn't exist
+ cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(toFile));
+
+ // Create the symlink.
+ if (!cmSystemTools::CreateSymlink(symlinkTarget, toFile)) {
+ std::ostringstream e;
+ e << this->Name << " cannot duplicate symlink \"" << fromFile
+ << "\" at \"" << toFile << "\".";
+ this->FileCommand->SetError(e.str());
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool cmFileCopier::InstallFile(const std::string& fromFile,
+ const std::string& toFile,
+ MatchProperties match_properties)
+{
+ // Determine whether we will copy the file.
+ bool copy = true;
+ if (!this->Always) {
+ // If both files exist with the same time do not copy.
+ if (!this->FileTimes.FileTimesDiffer(fromFile, toFile)) {
+ copy = false;
+ }
+ }
+
+ // Inform the user about this file installation.
+ this->ReportCopy(toFile, TypeFile, copy);
+
+ // Copy the file.
+ if (copy && !cmSystemTools::CopyAFile(fromFile, toFile, true)) {
+ std::ostringstream e;
+ e << this->Name << " cannot copy file \"" << fromFile << "\" to \""
+ << toFile << "\".";
+ this->FileCommand->SetError(e.str());
+ return false;
+ }
+
+ // Set the file modification time of the destination file.
+ if (copy && !this->Always) {
+ // Add write permission so we can set the file time.
+ // Permissions are set unconditionally below anyway.
+ mode_t perm = 0;
+ if (cmSystemTools::GetPermissions(toFile, perm)) {
+ cmSystemTools::SetPermissions(toFile, perm | mode_owner_write);
+ }
+ if (!cmSystemTools::CopyFileTime(fromFile, toFile)) {
+ std::ostringstream e;
+ e << this->Name << " cannot set modification time on \"" << toFile
+ << "\"";
+ this->FileCommand->SetError(e.str());
+ return false;
+ }
+ }
+
+ // Set permissions of the destination file.
+ mode_t permissions =
+ (match_properties.Permissions ? match_properties.Permissions
+ : this->FilePermissions);
+ if (!permissions) {
+ // No permissions were explicitly provided but the user requested
+ // that the source file permissions be used.
+ cmSystemTools::GetPermissions(fromFile, permissions);
+ }
+ return this->SetPermissions(toFile, permissions);
+}
+
+bool cmFileCopier::InstallDirectory(const std::string& source,
+ const std::string& destination,
+ MatchProperties match_properties)
+{
+ // Inform the user about this directory installation.
+ this->ReportCopy(destination, TypeDir,
+ !cmSystemTools::FileIsDirectory(destination));
+
+ // check if default dir creation permissions were set
+ mode_t default_dir_mode_v = 0;
+ mode_t* default_dir_mode = &default_dir_mode_v;
+ if (!this->GetDefaultDirectoryPermissions(&default_dir_mode)) {
+ return false;
+ }
+
+ // Make sure the destination directory exists.
+ if (!cmSystemTools::MakeDirectory(destination, default_dir_mode)) {
+ std::ostringstream e;
+ e << this->Name << " cannot make directory \"" << destination
+ << "\": " << cmSystemTools::GetLastSystemError();
+ this->FileCommand->SetError(e.str());
+ return false;
+ }
+
+ // Compute the requested permissions for the destination directory.
+ mode_t permissions =
+ (match_properties.Permissions ? match_properties.Permissions
+ : this->DirPermissions);
+ if (!permissions) {
+ // No permissions were explicitly provided but the user requested
+ // that the source directory permissions be used.
+ cmSystemTools::GetPermissions(source, permissions);
+ }
+
+ // Compute the set of permissions required on this directory to
+ // recursively install files and subdirectories safely.
+ mode_t required_permissions =
+ mode_owner_read | mode_owner_write | mode_owner_execute;
+
+ // If the required permissions are specified it is safe to set the
+ // final permissions now. Otherwise we must add the required
+ // permissions temporarily during file installation.
+ mode_t permissions_before = 0;
+ mode_t permissions_after = 0;
+ if ((permissions & required_permissions) == required_permissions) {
+ permissions_before = permissions;
+ } else {
+ permissions_before = permissions | required_permissions;
+ permissions_after = permissions;
+ }
+
+ // Set the required permissions of the destination directory.
+ if (!this->SetPermissions(destination, permissions_before)) {
+ return false;
+ }
+
+ // Load the directory contents to traverse it recursively.
+ cmsys::Directory dir;
+ if (!source.empty()) {
+ dir.Load(source);
+ }
+ unsigned long numFiles = static_cast<unsigned long>(dir.GetNumberOfFiles());
+ for (unsigned long fileNum = 0; fileNum < numFiles; ++fileNum) {
+ if (!(strcmp(dir.GetFile(fileNum), ".") == 0 ||
+ strcmp(dir.GetFile(fileNum), "..") == 0)) {
+ std::string fromPath = source;
+ fromPath += "/";
+ fromPath += dir.GetFile(fileNum);
+ std::string toPath = destination;
+ toPath += "/";
+ toPath += dir.GetFile(fileNum);
+ if (!this->Install(fromPath, toPath)) {
+ return false;
+ }
+ }
+ }
+
+ // Set the requested permissions of the destination directory.
+ return this->SetPermissions(destination, permissions_after);
+}
+
+bool cmFileCommand::HandleCopyCommand(std::vector<std::string> const& args)
+{
+ cmFileCopier copier(this);
+ return copier.Run(args);
+}
+
+struct cmFileInstaller : public cmFileCopier
+{
+ cmFileInstaller(cmFileCommand* command)
+ : cmFileCopier(command, "INSTALL")
+ , InstallType(cmInstallType_FILES)
+ , Optional(false)
+ , MessageAlways(false)
+ , MessageLazy(false)
+ , MessageNever(false)
+ , DestDirLength(0)
+ {
+ // Installation does not use source permissions by default.
+ this->UseSourcePermissions = false;
+ // Check whether to copy files always or only if they have changed.
+ std::string install_always;
+ if (cmSystemTools::GetEnv("CMAKE_INSTALL_ALWAYS", install_always)) {
+ this->Always = cmSystemTools::IsOn(install_always);
+ }
+ // Get the current manifest.
+ this->Manifest =
+ this->Makefile->GetSafeDefinition("CMAKE_INSTALL_MANIFEST_FILES");
+ }
+ ~cmFileInstaller() override
+ {
+ // Save the updated install manifest.
+ this->Makefile->AddDefinition("CMAKE_INSTALL_MANIFEST_FILES",
+ this->Manifest.c_str());
+ }
+
+protected:
+ cmInstallType InstallType;
+ bool Optional;
+ bool MessageAlways;
+ bool MessageLazy;
+ bool MessageNever;
+ int DestDirLength;
+ std::string Rename;
+
+ std::string Manifest;
+ void ManifestAppend(std::string const& file)
+ {
+ if (!this->Manifest.empty()) {
+ this->Manifest += ";";
+ }
+ this->Manifest += file.substr(this->DestDirLength);
+ }
+
+ std::string const& ToName(std::string const& fromName) override
+ {
+ return this->Rename.empty() ? fromName : this->Rename;
+ }
+
+ void ReportCopy(const std::string& toFile, Type type, bool copy) override
+ {
+ if (!this->MessageNever && (copy || !this->MessageLazy)) {
+ std::string message = (copy ? "Installing: " : "Up-to-date: ");
+ message += toFile;
+ this->Makefile->DisplayStatus(message, -1);
+ }
+ if (type != TypeDir) {
+ // Add the file to the manifest.
+ this->ManifestAppend(toFile);
+ }
+ }
+ bool ReportMissing(const std::string& fromFile) override
+ {
+ return (this->Optional || this->cmFileCopier::ReportMissing(fromFile));
+ }
+ bool Install(const std::string& fromFile, const std::string& toFile) override
+ {
+ // Support installing from empty source to make a directory.
+ if (this->InstallType == cmInstallType_DIRECTORY && fromFile.empty()) {
+ return this->InstallDirectory(fromFile, toFile, MatchProperties());
+ }
+ return this->cmFileCopier::Install(fromFile, toFile);
+ }
+
+ bool Parse(std::vector<std::string> const& args) override;
+ enum
+ {
+ DoingType = DoingLast1,
+ DoingRename,
+ DoingLast2
+ };
+ bool CheckKeyword(std::string const& arg) override;
+ bool CheckValue(std::string const& arg) override;
+ void DefaultFilePermissions() override
+ {
+ this->cmFileCopier::DefaultFilePermissions();
+ // Add execute permissions based on the target type.
+ switch (this->InstallType) {
+ case cmInstallType_SHARED_LIBRARY:
+ case cmInstallType_MODULE_LIBRARY:
+ if (this->Makefile->IsOn("CMAKE_INSTALL_SO_NO_EXE")) {
+ break;
+ }
+ CM_FALLTHROUGH;
+ case cmInstallType_EXECUTABLE:
+ case cmInstallType_PROGRAMS:
+ this->FilePermissions |= mode_owner_execute;
+ this->FilePermissions |= mode_group_execute;
+ this->FilePermissions |= mode_world_execute;
+ break;
+ default:
+ break;
+ }
+ }
+ bool GetTargetTypeFromString(const std::string& stype);
+ bool HandleInstallDestination();
+};
+
+bool cmFileInstaller::Parse(std::vector<std::string> const& args)
+{
+ if (!this->cmFileCopier::Parse(args)) {
+ return false;
+ }
+
+ if (!this->Rename.empty()) {
+ if (!this->FilesFromDir.empty()) {
+ this->FileCommand->SetError("INSTALL option RENAME may not be "
+ "combined with FILES_FROM_DIR.");
+ return false;
+ }
+ if (this->InstallType != cmInstallType_FILES &&
+ this->InstallType != cmInstallType_PROGRAMS) {
+ this->FileCommand->SetError("INSTALL option RENAME may be used "
+ "only with FILES or PROGRAMS.");
+ return false;
+ }
+ if (this->Files.size() > 1) {
+ this->FileCommand->SetError("INSTALL option RENAME may be used "
+ "only with one file.");
+ return false;
+ }
+ }
+
+ if (!this->HandleInstallDestination()) {
+ return false;
+ }
+
+ if (((this->MessageAlways ? 1 : 0) + (this->MessageLazy ? 1 : 0) +
+ (this->MessageNever ? 1 : 0)) > 1) {
+ this->FileCommand->SetError("INSTALL options MESSAGE_ALWAYS, "
+ "MESSAGE_LAZY, and MESSAGE_NEVER "
+ "are mutually exclusive.");
+ return false;
+ }
+
+ return true;
+}
+
+bool cmFileInstaller::CheckKeyword(std::string const& arg)
+{
+ if (arg == "TYPE") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingType;
+ }
+ } else if (arg == "FILES") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingFiles;
+ }
+ } else if (arg == "RENAME") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingRename;
+ }
+ } else if (arg == "OPTIONAL") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingNone;
+ this->Optional = true;
+ }
+ } else if (arg == "MESSAGE_ALWAYS") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingNone;
+ this->MessageAlways = true;
+ }
+ } else if (arg == "MESSAGE_LAZY") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingNone;
+ this->MessageLazy = true;
+ }
+ } else if (arg == "MESSAGE_NEVER") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingNone;
+ this->MessageNever = true;
+ }
+ } else if (arg == "PERMISSIONS") {
+ if (this->CurrentMatchRule) {
+ this->Doing = DoingPermissionsMatch;
+ } else {
+ // file(INSTALL) aliases PERMISSIONS to FILE_PERMISSIONS
+ this->Doing = DoingPermissionsFile;
+ this->UseGivenPermissionsFile = true;
+ }
+ } else if (arg == "DIR_PERMISSIONS") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ // file(INSTALL) aliases DIR_PERMISSIONS to DIRECTORY_PERMISSIONS
+ this->Doing = DoingPermissionsDir;
+ this->UseGivenPermissionsDir = true;
+ }
+ } else if (arg == "COMPONENTS" || arg == "CONFIGURATIONS" ||
+ arg == "PROPERTIES") {
+ std::ostringstream e;
+ e << "INSTALL called with old-style " << arg << " argument. "
+ << "This script was generated with an older version of CMake. "
+ << "Re-run this cmake version on your build tree.";
+ this->FileCommand->SetError(e.str());
+ this->Doing = DoingError;
+ } else {
+ return this->cmFileCopier::CheckKeyword(arg);
+ }
+ return true;
+}
+
+bool cmFileInstaller::CheckValue(std::string const& arg)
+{
+ switch (this->Doing) {
+ case DoingType:
+ if (!this->GetTargetTypeFromString(arg)) {
+ this->Doing = DoingError;
+ }
+ break;
+ case DoingRename:
+ this->Rename = arg;
+ break;
+ default:
+ return this->cmFileCopier::CheckValue(arg);
+ }
+ return true;
+}
+
+bool cmFileInstaller::GetTargetTypeFromString(const std::string& stype)
+{
+ if (stype == "EXECUTABLE") {
+ this->InstallType = cmInstallType_EXECUTABLE;
+ } else if (stype == "FILE") {
+ this->InstallType = cmInstallType_FILES;
+ } else if (stype == "PROGRAM") {
+ this->InstallType = cmInstallType_PROGRAMS;
+ } else if (stype == "STATIC_LIBRARY") {
+ this->InstallType = cmInstallType_STATIC_LIBRARY;
+ } else if (stype == "SHARED_LIBRARY") {
+ this->InstallType = cmInstallType_SHARED_LIBRARY;
+ } else if (stype == "MODULE") {
+ this->InstallType = cmInstallType_MODULE_LIBRARY;
+ } else if (stype == "DIRECTORY") {
+ this->InstallType = cmInstallType_DIRECTORY;
+ } else {
+ std::ostringstream e;
+ e << "Option TYPE given unknown value \"" << stype << "\".";
+ this->FileCommand->SetError(e.str());
+ return false;
+ }
+ return true;
+}
+
+bool cmFileInstaller::HandleInstallDestination()
+{
+ std::string& destination = this->Destination;
+
+ // allow for / to be a valid destination
+ if (destination.size() < 2 && destination != "/") {
+ this->FileCommand->SetError("called with inappropriate arguments. "
+ "No DESTINATION provided or .");
+ return false;
+ }
+
+ std::string sdestdir;
+ if (cmSystemTools::GetEnv("DESTDIR", sdestdir) && !sdestdir.empty()) {
+ cmSystemTools::ConvertToUnixSlashes(sdestdir);
+ char ch1 = destination[0];
+ char ch2 = destination[1];
+ char ch3 = 0;
+ if (destination.size() > 2) {
+ ch3 = destination[2];
+ }
+ int skip = 0;
+ if (ch1 != '/') {
+ int relative = 0;
+ if (((ch1 >= 'a' && ch1 <= 'z') || (ch1 >= 'A' && ch1 <= 'Z')) &&
+ ch2 == ':') {
+ // Assume windows
+ // let's do some destdir magic:
+ skip = 2;
+ if (ch3 != '/') {
+ relative = 1;
+ }
+ } else {
+ relative = 1;
+ }
+ if (relative) {
+ // This is relative path on unix or windows. Since we are doing
+ // destdir, this case does not make sense.
+ this->FileCommand->SetError(
+ "called with relative DESTINATION. This "
+ "does not make sense when using DESTDIR. Specify "
+ "absolute path or remove DESTDIR environment variable.");
+ return false;
+ }
+ } else {
+ if (ch2 == '/') {
+ // looks like a network path.
+ std::string message =
+ "called with network path DESTINATION. This "
+ "does not make sense when using DESTDIR. Specify local "
+ "absolute path or remove DESTDIR environment variable."
+ "\nDESTINATION=\n";
+ message += destination;
+ this->FileCommand->SetError(message);
+ return false;
+ }
+ }
+ destination = sdestdir + (destination.c_str() + skip);
+ this->DestDirLength = int(sdestdir.size());
+ }
+
+ // check if default dir creation permissions were set
+ mode_t default_dir_mode_v = 0;
+ mode_t* default_dir_mode = &default_dir_mode_v;
+ if (!this->GetDefaultDirectoryPermissions(&default_dir_mode)) {
+ return false;
+ }
+
+ if (this->InstallType != cmInstallType_DIRECTORY) {
+ if (!cmSystemTools::FileExists(destination)) {
+ if (!cmSystemTools::MakeDirectory(destination, default_dir_mode)) {
+ std::string errstring = "cannot create directory: " + destination +
+ ". Maybe need administrative privileges.";
+ this->FileCommand->SetError(errstring);
+ return false;
+ }
+ }
+ if (!cmSystemTools::FileIsDirectory(destination)) {
+ std::string errstring =
+ "INSTALL destination: " + destination + " is not a directory.";
+ this->FileCommand->SetError(errstring);
+ return false;
+ }
+ }
+ return true;
+}
+
+bool cmFileCommand::HandleRPathChangeCommand(
+ std::vector<std::string> const& args)
+{
+ // Evaluate arguments.
+ std::string file;
+ const char* oldRPath = nullptr;
+ const char* newRPath = nullptr;
+ enum Doing
+ {
+ DoingNone,
+ DoingFile,
+ DoingOld,
+ DoingNew
+ };
+ Doing doing = DoingNone;
+ for (unsigned int i = 1; i < args.size(); ++i) {
+ if (args[i] == "OLD_RPATH") {
+ doing = DoingOld;
+ } else if (args[i] == "NEW_RPATH") {
+ doing = DoingNew;
+ } else if (args[i] == "FILE") {
+ doing = DoingFile;
+ } else if (doing == DoingFile) {
+ file = args[i];
+ doing = DoingNone;
+ } else if (doing == DoingOld) {
+ oldRPath = args[i].c_str();
+ doing = DoingNone;
+ } else if (doing == DoingNew) {
+ newRPath = args[i].c_str();
+ doing = DoingNone;
+ } else {
+ std::ostringstream e;
+ e << "RPATH_CHANGE given unknown argument " << args[i];
+ this->SetError(e.str());
+ return false;
+ }
+ }
+ if (file.empty()) {
+ this->SetError("RPATH_CHANGE not given FILE option.");
+ return false;
+ }
+ if (!oldRPath) {
+ this->SetError("RPATH_CHANGE not given OLD_RPATH option.");
+ return false;
+ }
+ if (!newRPath) {
+ this->SetError("RPATH_CHANGE not given NEW_RPATH option.");
+ return false;
+ }
+ if (!cmSystemTools::FileExists(file, true)) {
+ std::ostringstream e;
+ e << "RPATH_CHANGE given FILE \"" << file << "\" that does not exist.";
+ this->SetError(e.str());
+ return false;
+ }
+ bool success = true;
+ cmSystemToolsFileTime* ft = cmSystemTools::FileTimeNew();
+ bool have_ft = cmSystemTools::FileTimeGet(file, ft);
+ std::string emsg;
+ bool changed;
+ if (!cmSystemTools::ChangeRPath(file, oldRPath, newRPath, &emsg, &changed)) {
+ std::ostringstream e;
+ /* clang-format off */
+ e << "RPATH_CHANGE could not write new RPATH:\n"
+ << " " << newRPath << "\n"
+ << "to the file:\n"
+ << " " << file << "\n"
+ << emsg;
+ /* clang-format on */
+ this->SetError(e.str());
+ success = false;
+ }
+ if (success) {
+ if (changed) {
+ std::string message = "Set runtime path of \"";
+ message += file;
+ message += "\" to \"";
+ message += newRPath;
+ message += "\"";
+ this->Makefile->DisplayStatus(message, -1);
+ }
+ if (have_ft) {
+ cmSystemTools::FileTimeSet(file, ft);
+ }
+ }
+ cmSystemTools::FileTimeDelete(ft);
+ return success;
+}
+
+bool cmFileCommand::HandleRPathRemoveCommand(
+ std::vector<std::string> const& args)
+{
+ // Evaluate arguments.
+ std::string file;
+ enum Doing
+ {
+ DoingNone,
+ DoingFile
+ };
+ Doing doing = DoingNone;
+ for (unsigned int i = 1; i < args.size(); ++i) {
+ if (args[i] == "FILE") {
+ doing = DoingFile;
+ } else if (doing == DoingFile) {
+ file = args[i];
+ doing = DoingNone;
+ } else {
+ std::ostringstream e;
+ e << "RPATH_REMOVE given unknown argument " << args[i];
+ this->SetError(e.str());
+ return false;
+ }
+ }
+ if (file.empty()) {
+ this->SetError("RPATH_REMOVE not given FILE option.");
+ return false;
+ }
+ if (!cmSystemTools::FileExists(file, true)) {
+ std::ostringstream e;
+ e << "RPATH_REMOVE given FILE \"" << file << "\" that does not exist.";
+ this->SetError(e.str());
+ return false;
+ }
+ bool success = true;
+ cmSystemToolsFileTime* ft = cmSystemTools::FileTimeNew();
+ bool have_ft = cmSystemTools::FileTimeGet(file, ft);
+ std::string emsg;
+ bool removed;
+ if (!cmSystemTools::RemoveRPath(file, &emsg, &removed)) {
+ std::ostringstream e;
+ /* clang-format off */
+ e << "RPATH_REMOVE could not remove RPATH from file:\n"
+ << " " << file << "\n"
+ << emsg;
+ /* clang-format on */
+ this->SetError(e.str());
+ success = false;
+ }
+ if (success) {
+ if (removed) {
+ std::string message = "Removed runtime path from \"";
+ message += file;
+ message += "\"";
+ this->Makefile->DisplayStatus(message, -1);
+ }
+ if (have_ft) {
+ cmSystemTools::FileTimeSet(file, ft);
+ }
+ }
+ cmSystemTools::FileTimeDelete(ft);
+ return success;
+}
+
+bool cmFileCommand::HandleRPathCheckCommand(
+ std::vector<std::string> const& args)
+{
+ // Evaluate arguments.
+ std::string file;
+ const char* rpath = nullptr;
+ enum Doing
+ {
+ DoingNone,
+ DoingFile,
+ DoingRPath
+ };
+ Doing doing = DoingNone;
+ for (unsigned int i = 1; i < args.size(); ++i) {
+ if (args[i] == "RPATH") {
+ doing = DoingRPath;
+ } else if (args[i] == "FILE") {
+ doing = DoingFile;
+ } else if (doing == DoingFile) {
+ file = args[i];
+ doing = DoingNone;
+ } else if (doing == DoingRPath) {
+ rpath = args[i].c_str();
+ doing = DoingNone;
+ } else {
+ std::ostringstream e;
+ e << "RPATH_CHECK given unknown argument " << args[i];
+ this->SetError(e.str());
+ return false;
+ }
+ }
+ if (file.empty()) {
+ this->SetError("RPATH_CHECK not given FILE option.");
+ return false;
+ }
+ if (!rpath) {
+ this->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 cmFileCommand::HandleReadElfCommand(std::vector<std::string> const& args)
+{
+ if (args.size() < 4) {
+ this->SetError("READ_ELF must be called with at least three additional "
+ "arguments.");
+ return false;
+ }
+
+ cmCommandArgumentsHelper argHelper;
+ cmCommandArgumentGroup group;
+
+ cmCAString readArg(&argHelper, "READ_ELF");
+ cmCAString fileNameArg(&argHelper, nullptr);
+
+ cmCAString rpathArg(&argHelper, "RPATH", &group);
+ cmCAString runpathArg(&argHelper, "RUNPATH", &group);
+ cmCAString errorArg(&argHelper, "CAPTURE_ERROR", &group);
+
+ readArg.Follows(nullptr);
+ fileNameArg.Follows(&readArg);
+ group.Follows(&fileNameArg);
+ argHelper.Parse(&args, nullptr);
+
+ if (!cmSystemTools::FileExists(fileNameArg.GetString(), true)) {
+ std::ostringstream e;
+ e << "READ_ELF given FILE \"" << fileNameArg.GetString()
+ << "\" that does not exist.";
+ this->SetError(e.str());
+ return false;
+ }
+
+#if defined(CMAKE_USE_ELF_PARSER)
+ cmELF elf(fileNameArg.GetCString());
+
+ if (!rpathArg.GetString().empty()) {
+ if (cmELF::StringEntry const* se_rpath = elf.GetRPath()) {
+ std::string rpath(se_rpath->Value);
+ std::replace(rpath.begin(), rpath.end(), ':', ';');
+ this->Makefile->AddDefinition(rpathArg.GetString(), rpath.c_str());
+ }
+ }
+ if (!runpathArg.GetString().empty()) {
+ if (cmELF::StringEntry const* se_runpath = elf.GetRunPath()) {
+ std::string runpath(se_runpath->Value);
+ std::replace(runpath.begin(), runpath.end(), ':', ';');
+ this->Makefile->AddDefinition(runpathArg.GetString(), runpath.c_str());
+ }
+ }
+
+ return true;
+#else
+ std::string error = "ELF parser not available on this platform.";
+ if (errorArg.GetString().empty()) {
+ this->SetError(error);
+ return false;
+ }
+ this->Makefile->AddDefinition(errorArg.GetString(), error.c_str());
+ return true;
+#endif
+}
+
+bool cmFileCommand::HandleInstallCommand(std::vector<std::string> const& args)
+{
+ cmFileInstaller installer(this);
+ return installer.Run(args);
+}
+
+bool cmFileCommand::HandleRelativePathCommand(
+ std::vector<std::string> const& args)
+{
+ if (args.size() != 4) {
+ this->SetError("RELATIVE_PATH called with incorrect number of arguments");
+ return false;
+ }
+
+ const std::string& outVar = args[1];
+ const std::string& directoryName = args[2];
+ const std::string& fileName = args[3];
+
+ if (!cmSystemTools::FileIsFullPath(directoryName)) {
+ std::string errstring =
+ "RELATIVE_PATH must be passed a full path to the directory: " +
+ directoryName;
+ this->SetError(errstring);
+ return false;
+ }
+ if (!cmSystemTools::FileIsFullPath(fileName)) {
+ std::string errstring =
+ "RELATIVE_PATH must be passed a full path to the file: " + fileName;
+ this->SetError(errstring);
+ return false;
+ }
+
+ std::string res = cmSystemTools::RelativePath(directoryName, fileName);
+ this->Makefile->AddDefinition(outVar, res.c_str());
+ return true;
+}
+
+bool cmFileCommand::HandleRename(std::vector<std::string> const& args)
+{
+ if (args.size() != 3) {
+ this->SetError("RENAME given incorrect number of arguments.");
+ return false;
+ }
+
+ // Compute full path for old and new names.
+ std::string oldname = args[1];
+ if (!cmsys::SystemTools::FileIsFullPath(oldname)) {
+ oldname = this->Makefile->GetCurrentSourceDirectory();
+ oldname += "/" + args[1];
+ }
+ std::string newname = args[2];
+ if (!cmsys::SystemTools::FileIsFullPath(newname)) {
+ newname = this->Makefile->GetCurrentSourceDirectory();
+ newname += "/" + args[2];
+ }
+
+ if (!cmSystemTools::RenameFile(oldname, newname)) {
+ std::string err = cmSystemTools::GetLastSystemError();
+ std::ostringstream e;
+ /* clang-format off */
+ e << "RENAME failed to rename\n"
+ << " " << oldname << "\n"
+ << "to\n"
+ << " " << newname << "\n"
+ << "because: " << err << "\n";
+ /* clang-format on */
+ this->SetError(e.str());
+ return false;
+ }
+ return true;
+}
+
+bool cmFileCommand::HandleRemove(std::vector<std::string> const& args,
+ bool recurse)
+{
+
+ std::string message;
+
+ for (std::string const& arg :
+ cmMakeRange(args).advance(1)) // Get rid of subcommand
+ {
+ std::string fileName = arg;
+ if (!cmsys::SystemTools::FileIsFullPath(fileName)) {
+ fileName = this->Makefile->GetCurrentSourceDirectory();
+ fileName += "/" + arg;
+ }
+
+ if (cmSystemTools::FileIsDirectory(fileName) &&
+ !cmSystemTools::FileIsSymlink(fileName) && recurse) {
+ cmSystemTools::RemoveADirectory(fileName);
+ } else {
+ cmSystemTools::RemoveFile(fileName);
+ }
+ }
+ return true;
+}
+
+bool cmFileCommand::HandleCMakePathCommand(
+ std::vector<std::string> const& args, bool nativePath)
+{
+ std::vector<std::string>::const_iterator i = args.begin();
+ if (args.size() != 3) {
+ this->SetError("FILE([TO_CMAKE_PATH|TO_NATIVE_PATH] path result) must be "
+ "called with exactly three arguments.");
+ return false;
+ }
+ i++; // Get rid of subcommand
+#if defined(_WIN32) && !defined(__CYGWIN__)
+ char pathSep = ';';
+#else
+ char pathSep = ':';
+#endif
+ std::vector<std::string> path = cmSystemTools::SplitString(*i, pathSep);
+ i++;
+ const char* var = i->c_str();
+ std::string value;
+ for (std::vector<std::string>::iterator j = path.begin(); j != path.end();
+ ++j) {
+ if (j != path.begin()) {
+ value += ";";
+ }
+ if (!nativePath) {
+ cmSystemTools::ConvertToUnixSlashes(*j);
+ } else {
+ *j = cmSystemTools::ConvertToOutputPath(*j);
+ // remove double quotes in the path
+ std::string& s = *j;
+
+ if (s.size() > 1 && s.front() == '\"' && s.back() == '\"') {
+ s = s.substr(1, s.size() - 2);
+ }
+ }
+ value += *j;
+ }
+ this->Makefile->AddDefinition(var, value.c_str());
+ return true;
+}
+
+#if defined(CMAKE_BUILD_WITH_CMAKE)
+
+// Stuff for curl download/upload
+typedef std::vector<char> cmFileCommandVectorOfChar;
+
+namespace {
+
+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);
+ const char* chPtr = static_cast<char*>(ptr);
+ fout->write(chPtr, realsize);
+ return realsize;
+}
+
+size_t cmWriteToMemoryCallback(void* ptr, size_t size, size_t nmemb,
+ void* data)
+{
+ int realsize = static_cast<int>(size * nmemb);
+ cmFileCommandVectorOfChar* vec =
+ static_cast<cmFileCommandVectorOfChar*>(data);
+ const char* chPtr = static_cast<char*>(ptr);
+ vec->insert(vec->end(), chPtr, chPtr + realsize);
+ return realsize;
+}
+
+size_t 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:
+ vec->insert(vec->end(), 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 = sprintf(buf, "[%" KWIML_INT_PRIu64 " bytes data]\n",
+ static_cast<KWIML_INT_uint64_t>(size));
+ if (n > 0) {
+ vec->insert(vec->end(), buf, buf + n);
+ }
+ } break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+class cURLProgressHelper
+{
+public:
+ cURLProgressHelper(cmFileCommand* fc, const char* text)
+ {
+ this->CurrentPercentage = -1;
+ this->FileCommand = fc;
+ this->Text = text;
+ }
+
+ bool UpdatePercentage(double value, double total, std::string& status)
+ {
+ long OldPercentage = this->CurrentPercentage;
+
+ if (total > 0.0) {
+ this->CurrentPercentage = std::lround(value / 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) {
+ std::ostringstream oss;
+ oss << "[" << this->Text << " " << this->CurrentPercentage
+ << "% complete]";
+ status = oss.str();
+ }
+
+ return updated;
+ }
+
+ cmFileCommand* GetFileCommand() { return this->FileCommand; }
+
+private:
+ long CurrentPercentage;
+ cmFileCommand* FileCommand;
+ std::string Text;
+};
+
+int cmFileDownloadProgressCallback(void* clientp, double dltotal, double dlnow,
+ double ultotal, double ulnow)
+{
+ cURLProgressHelper* helper = reinterpret_cast<cURLProgressHelper*>(clientp);
+
+ static_cast<void>(ultotal);
+ static_cast<void>(ulnow);
+
+ std::string status;
+ if (helper->UpdatePercentage(dlnow, dltotal, status)) {
+ cmFileCommand* fc = helper->GetFileCommand();
+ cmMakefile* mf = fc->GetMakefile();
+ mf->DisplayStatus(status, -1);
+ }
+
+ return 0;
+}
+
+int cmFileUploadProgressCallback(void* clientp, double dltotal, double dlnow,
+ double ultotal, double ulnow)
+{
+ cURLProgressHelper* helper = reinterpret_cast<cURLProgressHelper*>(clientp);
+
+ static_cast<void>(dltotal);
+ static_cast<void>(dlnow);
+
+ std::string status;
+ if (helper->UpdatePercentage(ulnow, ultotal, status)) {
+ cmFileCommand* fc = helper->GetFileCommand();
+ cmMakefile* mf = fc->GetMakefile();
+ mf->DisplayStatus(status, -1);
+ }
+
+ return 0;
+}
+}
+
+namespace {
+
+class cURLEasyGuard
+{
+public:
+ cURLEasyGuard(CURL* easy)
+ : Easy(easy)
+ {
+ }
+
+ ~cURLEasyGuard()
+ {
+ if (this->Easy) {
+ ::curl_easy_cleanup(this->Easy);
+ }
+ }
+
+ cURLEasyGuard(const cURLEasyGuard&) = delete;
+ cURLEasyGuard& operator=(const cURLEasyGuard&) = 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); \
+ this->SetError(e); \
+ return false; \
+ } \
+ } while (false)
+
+bool cmFileCommand::HandleDownloadCommand(std::vector<std::string> const& args)
+{
+#if defined(CMAKE_BUILD_WITH_CMAKE)
+ std::vector<std::string>::const_iterator i = args.begin();
+ if (args.size() < 3) {
+ this->SetError("DOWNLOAD must be called with at least three arguments.");
+ return false;
+ }
+ ++i; // Get rid of subcommand
+ std::string url = *i;
+ ++i;
+ std::string file = *i;
+ ++i;
+
+ long timeout = 0;
+ long inactivity_timeout = 0;
+ std::string logVar;
+ std::string statusVar;
+ bool tls_verify = this->Makefile->IsOn("CMAKE_TLS_VERIFY");
+ const char* cainfo = this->Makefile->GetDefinition("CMAKE_TLS_CAINFO");
+ std::string netrc_level = this->Makefile->GetSafeDefinition("CMAKE_NETRC");
+ std::string netrc_file =
+ this->Makefile->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;
+
+ while (i != args.end()) {
+ if (*i == "TIMEOUT") {
+ ++i;
+ if (i != args.end()) {
+ timeout = atol(i->c_str());
+ } else {
+ this->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 {
+ this->SetError("DOWNLOAD missing time for INACTIVITY_TIMEOUT.");
+ return false;
+ }
+ } else if (*i == "LOG") {
+ ++i;
+ if (i == args.end()) {
+ this->SetError("DOWNLOAD missing VAR for LOG.");
+ return false;
+ }
+ logVar = *i;
+ } else if (*i == "STATUS") {
+ ++i;
+ if (i == args.end()) {
+ this->SetError("DOWNLOAD missing VAR for STATUS.");
+ return false;
+ }
+ statusVar = *i;
+ } else if (*i == "TLS_VERIFY") {
+ ++i;
+ if (i != args.end()) {
+ tls_verify = cmSystemTools::IsOn(*i);
+ } else {
+ this->SetError("TLS_VERIFY missing bool value.");
+ return false;
+ }
+ } else if (*i == "TLS_CAINFO") {
+ ++i;
+ if (i != args.end()) {
+ cainfo = i->c_str();
+ } else {
+ this->SetError("TLS_CAFILE missing file value.");
+ return false;
+ }
+ } else if (*i == "NETRC_FILE") {
+ ++i;
+ if (i != args.end()) {
+ netrc_file = *i;
+ } else {
+ this->SetError("DOWNLOAD missing file value for NETRC_FILE.");
+ return false;
+ }
+ } else if (*i == "NETRC") {
+ ++i;
+ if (i != args.end()) {
+ netrc_level = *i;
+ } else {
+ this->SetError("DOWNLOAD missing level value for NETRC.");
+ return false;
+ }
+ } else if (*i == "EXPECTED_MD5") {
+ ++i;
+ if (i == args.end()) {
+ this->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()) {
+ this->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 =
+ "DOWNLOAD EXPECTED_HASH expects ALGO=value but got: ";
+ err += *i;
+ this->SetError(err);
+ return false;
+ }
+ std::string algo = i->substr(0, pos);
+ expectedHash = cmSystemTools::LowerCase(i->substr(pos + 1));
+ hash = std::unique_ptr<cmCryptoHash>(cmCryptoHash::New(algo.c_str()));
+ if (!hash) {
+ std::string err = "DOWNLOAD EXPECTED_HASH given unknown ALGO: ";
+ err += algo;
+ this->SetError(err);
+ return false;
+ }
+ hashMatchMSG = algo + " hash";
+ } else if (*i == "USERPWD") {
+ ++i;
+ if (i == args.end()) {
+ this->SetError("DOWNLOAD missing string for USERPWD.");
+ return false;
+ }
+ userpwd = *i;
+ } else if (*i == "HTTPHEADER") {
+ ++i;
+ if (i == args.end()) {
+ this->SetError("DOWNLOAD missing string for HTTPHEADER.");
+ return false;
+ }
+ curl_headers.push_back(*i);
+ } else {
+ // Do not return error for compatibility reason.
+ std::string err = "Unexpected argument: ";
+ err += *i;
+ this->Makefile->IssueMessage(MessageType::AUTHOR_WARNING, err);
+ }
+ ++i;
+ }
+ // 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 (cmSystemTools::FileExists(file) && hash.get()) {
+ std::string msg;
+ std::string actualHash = hash->HashFile(file);
+ if (actualHash == expectedHash) {
+ msg = "returning early; file already exists with expected ";
+ msg += hashMatchMSG;
+ msg += "\"";
+ if (!statusVar.empty()) {
+ std::ostringstream result;
+ result << 0 << ";\"" << msg;
+ this->Makefile->AddDefinition(statusVar, result.str().c_str());
+ }
+ return true;
+ }
+ }
+ // Make sure parent directory exists so we can write to the file
+ // as we receive downloaded bits from curl...
+ //
+ std::string dir = cmSystemTools::GetFilenamePath(file);
+ if (!cmSystemTools::FileExists(dir) && !cmSystemTools::MakeDirectory(dir)) {
+ std::string errstring = "DOWNLOAD error: cannot create directory '" + dir +
+ "' - Specify file by full path name and verify that you "
+ "have directory creation and file write privileges.";
+ this->SetError(errstring);
+ return false;
+ }
+
+ cmsys::ofstream fout(file.c_str(), std::ios::binary);
+ if (!fout) {
+ this->SetError("DOWNLOAD cannot open file for write.");
+ return false;
+ }
+
+# if defined(_WIN32)
+ url = fix_file_url_windows(url);
+# endif
+
+ ::CURL* curl;
+ ::curl_global_init(CURL_GLOBAL_DEFAULT);
+ curl = ::curl_easy_init();
+ if (!curl) {
+ this->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: ");
+
+ res = ::curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/" LIBCURL_VERSION);
+ 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: ");
+
+ // check to see if TLS verification is requested
+ if (tls_verify) {
+ res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1);
+ check_curl_result(res, "Unable to set TLS/SSL Verify on: ");
+ } else {
+ res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
+ check_curl_result(res, "Unable to 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()) {
+ this->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()) {
+ this->SetError(netrc_option_err);
+ return false;
+ }
+
+ cmFileCommandVectorOfChar chunkDebug;
+
+ res = ::curl_easy_setopt(curl, CURLOPT_WRITEDATA, &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(this, "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, CURLOPT_PROGRESSFUNCTION,
+ 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);
+
+ if (!statusVar.empty()) {
+ std::ostringstream result;
+ result << static_cast<int>(res) << ";\"" << ::curl_easy_strerror(res)
+ << "\"";
+ this->Makefile->AddDefinition(statusVar, result.str().c_str());
+ }
+
+ ::curl_global_cleanup();
+
+ // Explicitly flush/close so we can measure the md5 accurately.
+ //
+ fout.flush();
+ fout.close();
+
+ // Verify MD5 sum if requested:
+ //
+ if (hash) {
+ std::string actualHash = hash->HashFile(file);
+ if (actualHash.empty()) {
+ this->SetError("DOWNLOAD cannot compute hash on downloaded file");
+ return false;
+ }
+
+ if (expectedHash != actualHash) {
+ std::ostringstream oss;
+ oss << "DOWNLOAD HASH mismatch" << std::endl
+ << " for file: [" << file << "]" << std::endl
+ << " expected hash: [" << expectedHash << "]" << std::endl
+ << " actual hash: [" << actualHash << "]" << std::endl
+ << " status: [" << static_cast<int>(res) << ";\""
+ << ::curl_easy_strerror(res) << "\"]" << std::endl;
+
+ if (!statusVar.empty() && res == 0) {
+ std::string status = "1;HASH mismatch: "
+ "expected: " +
+ expectedHash + " actual: " + actualHash;
+ this->Makefile->AddDefinition(statusVar, status.c_str());
+ }
+
+ this->SetError(oss.str());
+ return false;
+ }
+ }
+
+ if (!logVar.empty()) {
+ chunkDebug.push_back(0);
+ this->Makefile->AddDefinition(logVar, chunkDebug.data());
+ }
+
+ return true;
+#else
+ this->SetError("DOWNLOAD not supported by bootstrap cmake.");
+ return false;
+#endif
+}
+
+bool cmFileCommand::HandleUploadCommand(std::vector<std::string> const& args)
+{
+#if defined(CMAKE_BUILD_WITH_CMAKE)
+ if (args.size() < 3) {
+ this->SetError("UPLOAD must be called with at least three arguments.");
+ return false;
+ }
+ std::vector<std::string>::const_iterator 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;
+ std::string userpwd;
+ std::string netrc_level = this->Makefile->GetSafeDefinition("CMAKE_NETRC");
+ std::string netrc_file =
+ this->Makefile->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 {
+ this->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 {
+ this->SetError("UPLOAD missing time for INACTIVITY_TIMEOUT.");
+ return false;
+ }
+ } else if (*i == "LOG") {
+ ++i;
+ if (i == args.end()) {
+ this->SetError("UPLOAD missing VAR for LOG.");
+ return false;
+ }
+ logVar = *i;
+ } else if (*i == "STATUS") {
+ ++i;
+ if (i == args.end()) {
+ this->SetError("UPLOAD missing VAR for STATUS.");
+ return false;
+ }
+ statusVar = *i;
+ } else if (*i == "SHOW_PROGRESS") {
+ showProgress = true;
+ } else if (*i == "NETRC_FILE") {
+ ++i;
+ if (i != args.end()) {
+ netrc_file = *i;
+ } else {
+ this->SetError("UPLOAD missing file value for NETRC_FILE.");
+ return false;
+ }
+ } else if (*i == "NETRC") {
+ ++i;
+ if (i != args.end()) {
+ netrc_level = *i;
+ } else {
+ this->SetError("UPLOAD missing level value for NETRC.");
+ return false;
+ }
+ } else if (*i == "USERPWD") {
+ ++i;
+ if (i == args.end()) {
+ this->SetError("UPLOAD missing string for USERPWD.");
+ return false;
+ }
+ userpwd = *i;
+ } else if (*i == "HTTPHEADER") {
+ ++i;
+ if (i == args.end()) {
+ this->SetError("UPLOAD missing string for HTTPHEADER.");
+ return false;
+ }
+ curl_headers.push_back(*i);
+ } else {
+ // Do not return error for compatibility reason.
+ std::string err = "Unexpected argument: ";
+ err += *i;
+ this->Makefile->IssueMessage(MessageType::AUTHOR_WARNING, err);
+ }
+
+ ++i;
+ }
+
+ // Open file for reading:
+ //
+ FILE* fin = cmsys::SystemTools::Fopen(filename, "rb");
+ if (!fin) {
+ std::string errStr = "UPLOAD cannot open file '";
+ errStr += filename + "' for reading.";
+ this->SetError(errStr);
+ return false;
+ }
+
+ unsigned long file_size = cmsys::SystemTools::FileLength(filename);
+
+# if defined(_WIN32)
+ url = fix_file_url_windows(url);
+# endif
+
+ ::CURL* curl;
+ ::curl_global_init(CURL_GLOBAL_DEFAULT);
+ curl = ::curl_easy_init();
+ if (!curl) {
+ this->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: ");
+
+ 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(this, "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, CURLOPT_PROGRESSFUNCTION,
+ 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()) {
+ this->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::ostringstream result;
+ result << static_cast<int>(res) << ";\"" << ::curl_easy_strerror(res)
+ << "\"";
+ this->Makefile->AddDefinition(statusVar, result.str().c_str());
+ }
+
+ ::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";
+ }
+
+ this->Makefile->AddDefinition(logVar, log.c_str());
+ }
+
+ return true;
+#else
+ this->SetError("UPLOAD not supported by bootstrap cmake.");
+ return false;
+#endif
+}
+
+void cmFileCommand::AddEvaluationFile(const std::string& inputName,
+ const std::string& outputExpr,
+ const std::string& condition,
+ bool inputIsContent)
+{
+ cmListFileBacktrace lfbt = this->Makefile->GetBacktrace();
+
+ cmGeneratorExpression outputGe(lfbt);
+ std::unique_ptr<cmCompiledGeneratorExpression> outputCge =
+ outputGe.Parse(outputExpr);
+
+ cmGeneratorExpression conditionGe(lfbt);
+ std::unique_ptr<cmCompiledGeneratorExpression> conditionCge =
+ conditionGe.Parse(condition);
+
+ this->Makefile->AddEvaluationFile(inputName, std::move(outputCge),
+ std::move(conditionCge), inputIsContent);
+}
+
+bool cmFileCommand::HandleGenerateCommand(std::vector<std::string> const& args)
+{
+ if (args.size() < 5) {
+ this->SetError("Incorrect arguments to GENERATE subcommand.");
+ return false;
+ }
+ if (args[1] != "OUTPUT") {
+ this->SetError("Incorrect arguments to GENERATE subcommand.");
+ return false;
+ }
+ std::string condition;
+ if (args.size() > 5) {
+ if (args[5] != "CONDITION") {
+ this->SetError("Incorrect arguments to GENERATE subcommand.");
+ return false;
+ }
+ if (args.size() != 7) {
+ this->SetError("Incorrect arguments to GENERATE subcommand.");
+ return false;
+ }
+ condition = args[6];
+ if (condition.empty()) {
+ this->SetError("CONDITION of sub-command GENERATE must not be empty if "
+ "specified.");
+ return false;
+ }
+ }
+ std::string output = args[2];
+ const bool inputIsContent = args[3] != "INPUT";
+ if (inputIsContent && args[3] != "CONTENT") {
+ this->SetError("Incorrect arguments to GENERATE subcommand.");
+ return false;
+ }
+ std::string input = args[4];
+
+ this->AddEvaluationFile(input, output, condition, inputIsContent);
+ return true;
+}
+
+bool cmFileCommand::HandleLockCommand(std::vector<std::string> const& args)
+{
+#if defined(CMAKE_BUILD_WITH_CMAKE)
+ // 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) {
+ this->Makefile->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;
+ const char* merr = "expected FUNCTION, FILE or PROCESS after GUARD";
+ if (i >= args.size()) {
+ this->Makefile->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 {
+ std::ostringstream e;
+ e << merr << ", but got:\n \"" << args[i] << "\".";
+ this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
+ return false;
+ }
+
+ } else if (args[i] == "RESULT_VARIABLE") {
+ ++i;
+ if (i >= args.size()) {
+ this->Makefile->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()) {
+ this->Makefile->IssueMessage(MessageType::FATAL_ERROR,
+ "expected timeout value after TIMEOUT");
+ return false;
+ }
+ long scanned;
+ if (!cmSystemTools::StringToLong(args[i].c_str(), &scanned) ||
+ scanned < 0) {
+ std::ostringstream e;
+ e << "TIMEOUT value \"" << args[i] << "\" is not an unsigned integer.";
+ this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
+ return false;
+ }
+ timeout = static_cast<unsigned long>(scanned);
+ } else {
+ std::ostringstream e;
+ e << "expected DIRECTORY, RELEASE, GUARD, RESULT_VARIABLE or TIMEOUT\n";
+ e << "but got: \"" << args[i] << "\".";
+ this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
+ return false;
+ }
+ }
+
+ if (directory) {
+ path += "/cmake.lock";
+ }
+
+ if (!cmsys::SystemTools::FileIsFullPath(path)) {
+ path = this->Makefile->GetCurrentSourceDirectory() + "/" + path;
+ }
+
+ // Unify path (remove '//', '/../', ...)
+ path = cmSystemTools::CollapseFullPath(path);
+
+ // Create file and directories if needed
+ std::string parentDir = cmSystemTools::GetParentDirectory(path);
+ if (!cmSystemTools::MakeDirectory(parentDir)) {
+ std::ostringstream e;
+ e << "directory\n \"" << parentDir << "\"\ncreation failed ";
+ e << "(check permissions).";
+ this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
+ cmSystemTools::SetFatalErrorOccured();
+ return false;
+ }
+ FILE* file = cmsys::SystemTools::Fopen(path, "w");
+ if (!file) {
+ std::ostringstream e;
+ e << "file\n \"" << path << "\"\ncreation failed (check permissions).";
+ this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
+ cmSystemTools::SetFatalErrorOccured();
+ return false;
+ }
+ fclose(file);
+
+ // Actual lock/unlock
+ cmFileLockPool& lockPool =
+ this->Makefile->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::SetFatalErrorOccured();
+ return false;
+ }
+ }
+
+ const std::string result = fileLockResult.GetOutputMessage();
+
+ if (resultVariable.empty() && !fileLockResult.IsOk()) {
+ std::ostringstream e;
+ e << "error locking file\n \"" << path << "\"\n" << result << ".";
+ this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
+ cmSystemTools::SetFatalErrorOccured();
+ return false;
+ }
+
+ if (!resultVariable.empty()) {
+ this->Makefile->AddDefinition(resultVariable, result.c_str());
+ }
+
+ return true;
+#else
+ static_cast<void>(args);
+ this->SetError("sub-command LOCK not implemented in bootstrap cmake");
+ return false;
+#endif
+}
+
+bool cmFileCommand::HandleTimestampCommand(
+ std::vector<std::string> const& args)
+{
+ if (args.size() < 3) {
+ this->SetError("sub-command TIMESTAMP requires at least two arguments.");
+ return false;
+ }
+ if (args.size() > 5) {
+ this->SetError("sub-command TIMESTAMP takes at most four arguments.");
+ return false;
+ }
+
+ unsigned int argsIndex = 1;
+
+ const std::string& filename = args[argsIndex++];
+
+ const std::string& 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 = " TIMESTAMP sub-command does not recognize option " +
+ args[argsIndex] + ".";
+ this->SetError(e);
+ return false;
+ }
+ }
+
+ cmTimestamp timestamp;
+ std::string result =
+ timestamp.FileModificationTime(filename.c_str(), formatString, utcFlag);
+ this->Makefile->AddDefinition(outputVariable, result.c_str());
+
+ return true;
+}
+
+bool cmFileCommand::HandleSizeCommand(std::vector<std::string> const& args)
+{
+ if (args.size() != 3) {
+ std::ostringstream e;
+ e << args[0] << " requires a file name and output variable";
+ this->SetError(e.str());
+ return false;
+ }
+
+ unsigned int argsIndex = 1;
+
+ const std::string& filename = args[argsIndex++];
+
+ const std::string& outputVariable = args[argsIndex++];
+
+ if (!cmSystemTools::FileExists(filename, true)) {
+ std::ostringstream e;
+ e << "SIZE requested of path that is not readable:\n " << filename;
+ this->SetError(e.str());
+ return false;
+ }
+
+ this->Makefile->AddDefinition(
+ outputVariable,
+ std::to_string(cmSystemTools::FileLength(filename)).c_str());
+
+ return true;
+}
+
+bool cmFileCommand::HandleReadSymlinkCommand(
+ std::vector<std::string> const& args)
+{
+ if (args.size() != 3) {
+ std::ostringstream e;
+ e << args[0] << " requires a file name and output variable";
+ this->SetError(e.str());
+ return false;
+ }
+
+ const std::string& filename = args[1];
+ const std::string& outputVariable = args[2];
+
+ std::string result;
+ if (!cmSystemTools::ReadSymlink(filename, result)) {
+ std::ostringstream e;
+ e << "READ_SYMLINK requested of path that is not a symlink:\n "
+ << filename;
+ this->SetError(e.str());
+ return false;
+ }
+
+ this->Makefile->AddDefinition(outputVariable, result.c_str());
+
+ return true;
+}
+
+bool cmFileCommand::HandleCreateLinkCommand(
+ std::vector<std::string> const& args)
+{
+ if (args.size() < 3) {
+ this->SetError("CREATE_LINK must be called with at least two additional "
+ "arguments");
+ return false;
+ }
+
+ cmCommandArgumentsHelper argHelper;
+ cmCommandArgumentGroup group;
+
+ cmCAString linkArg(&argHelper, "CREATE_LINK");
+ cmCAString fileArg(&argHelper, nullptr);
+ cmCAString newFileArg(&argHelper, nullptr);
+
+ cmCAString resultArg(&argHelper, "RESULT", &group);
+ cmCAEnabler copyOnErrorArg(&argHelper, "COPY_ON_ERROR", &group);
+ cmCAEnabler symbolicArg(&argHelper, "SYMBOLIC", &group);
+
+ linkArg.Follows(nullptr);
+ fileArg.Follows(&linkArg);
+ newFileArg.Follows(&fileArg);
+ group.Follows(&newFileArg);
+
+ std::vector<std::string> unconsumedArgs;
+ argHelper.Parse(&args, &unconsumedArgs);
+
+ if (!unconsumedArgs.empty()) {
+ this->SetError("unknown argument: \"" + unconsumedArgs.front() + '\"');
+ return false;
+ }
+
+ std::string fileName = fileArg.GetString();
+ std::string newFileName = newFileArg.GetString();
+
+ // Output variable for storing the result.
+ const std::string& resultVar = resultArg.GetString();
+
+ // 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 (!resultVar.empty()) {
+ this->Makefile->AddDefinition(resultVar, result.c_str());
+ return true;
+ }
+ this->SetError(result);
+ return false;
+ }
+
+ // Hard link requires original file to exist.
+ if (!symbolicArg.IsEnabled() && !cmSystemTools::FileExists(fileName)) {
+ result = "Cannot hard link \'" + fileName + "\' as it does not exist.";
+ if (!resultVar.empty()) {
+ this->Makefile->AddDefinition(resultVar, result.c_str());
+ return true;
+ }
+ this->SetError(result);
+ return false;
+ }
+
+ // Check if the new file already exists and remove it.
+ if ((cmSystemTools::FileExists(newFileName) ||
+ cmSystemTools::FileIsSymlink(newFileName)) &&
+ !cmSystemTools::RemoveFile(newFileName)) {
+ std::ostringstream e;
+ e << "Failed to create link '" << newFileName
+ << "' because existing path cannot be removed: "
+ << cmSystemTools::GetLastSystemError() << "\n";
+
+ if (!resultVar.empty()) {
+ this->Makefile->AddDefinition(resultVar, e.str().c_str());
+ return true;
+ }
+ this->SetError(e.str());
+ return false;
+ }
+
+ // Whether the operation completed successfully.
+ bool completed = false;
+
+ // Check if the command requires a symbolic link.
+ if (symbolicArg.IsEnabled()) {
+ completed = cmSystemTools::CreateSymlink(fileName, newFileName, &result);
+ } else {
+ completed = cmSystemTools::CreateLink(fileName, newFileName, &result);
+ }
+
+ // Check if copy-on-error is enabled in the arguments.
+ if (!completed && copyOnErrorArg.IsEnabled()) {
+ completed = cmsys::SystemTools::CopyFileAlways(fileName, newFileName);
+ if (!completed) {
+ result = "Copy failed: " + cmSystemTools::GetLastSystemError();
+ }
+ }
+
+ // Check if the operation was successful.
+ if (completed) {
+ result = "0";
+ } else if (resultVar.empty()) {
+ // The operation failed and the result is not reported in a variable.
+ this->SetError(result);
+ return false;
+ }
+
+ if (!resultVar.empty()) {
+ this->Makefile->AddDefinition(resultVar, result.c_str());
+ }
+
+ return true;
+}