diff options
Diffstat (limited to 'Source/cmFileCommand.cxx')
-rw-r--r-- | Source/cmFileCommand.cxx | 3324 |
1 files changed, 3324 insertions, 0 deletions
diff --git a/Source/cmFileCommand.cxx b/Source/cmFileCommand.cxx new file mode 100644 index 0000000..91cecb3 --- /dev/null +++ b/Source/cmFileCommand.cxx @@ -0,0 +1,3324 @@ +/* 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 <algorithm> +#include <assert.h> +#include <cm_kwiml.h> +#include <cmsys/Directory.hxx> +#include <cmsys/FStream.hxx> +#include <cmsys/Glob.hxx> +#include <cmsys/RegularExpression.hxx> +#include <cmsys/String.hxx> +#include <list> +#include <sstream> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <sys/types.h> +// include sys/stat.h after sys/types.h +#include <sys/stat.h> + +#include "cmAlgorithms.h" +#include "cmCommandArgumentsHelper.h" +#include "cmCryptoHash.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 "cmPolicies.h" +#include "cmSystemTools.h" +#include "cmTimestamp.h" +#include "cm_auto_ptr.hxx" +#include "cmake.h" + +#if defined(CMAKE_BUILD_WITH_CMAKE) +#include "cmCurl.h" +#include "cmFileLockResult.h" +#endif + +class cmSystemToolsFileTime; + +// Table of permissions flags. +#if defined(_WIN32) && !defined(__CYGWIN__) +static mode_t mode_owner_read = S_IREAD; +static mode_t mode_owner_write = S_IWRITE; +static mode_t mode_owner_execute = S_IEXEC; +static mode_t mode_group_read = 0; +static mode_t mode_group_write = 0; +static mode_t mode_group_execute = 0; +static mode_t mode_world_read = 0; +static mode_t mode_world_write = 0; +static mode_t mode_world_execute = 0; +static mode_t mode_setuid = 0; +static mode_t mode_setgid = 0; +#else +static mode_t mode_owner_read = S_IRUSR; +static mode_t mode_owner_write = S_IWUSR; +static mode_t mode_owner_execute = S_IXUSR; +static mode_t mode_group_read = S_IRGRP; +static mode_t mode_group_write = S_IWGRP; +static mode_t mode_group_execute = S_IXGRP; +static mode_t mode_world_read = S_IROTH; +static mode_t mode_world_write = S_IWOTH; +static mode_t mode_world_execute = S_IXOTH; +static mode_t mode_setuid = S_ISUID; +static mode_t mode_setgid = S_ISGID; +#endif + +#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 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 == "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 == "TIMESTAMP") { + return this->HandleTimestampCommand(args); + } + if (subCommand == "GENERATE") { + return this->HandleGenerateCommand(args); + } + if (subCommand == "LOCK") { + return this->HandleLockCommand(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->c_str())) { + fileName = this->Makefile->GetCurrentSourceDirectory(); + fileName += "/" + *i; + } + + i++; + + if (!this->Makefile->CanIWriteThisFile(fileName.c_str())) { + 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.c_str()); + + mode_t mode = 0; + + // Set permissions to writable + if (cmSystemTools::GetPermissions(fileName.c_str(), mode)) { + cmSystemTools::SetPermissions(fileName.c_str(), +#if defined(_MSC_VER) || defined(__MINGW32__) + mode | S_IWRITE +#else + mode | S_IWUSR | S_IWGRP +#endif + ); + } + // 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; + file.close(); + if (mode) { + cmSystemTools::SetPermissions(fileName.c_str(), 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, CM_NULLPTR); + cmCAString resultArg(&argHelper, CM_NULLPTR); + + cmCAString offsetArg(&argHelper, "OFFSET", &group); + cmCAString limitArg(&argHelper, "LIMIT", &group); + cmCAEnabler hexOutputArg(&argHelper, "HEX", &group); + readArg.Follows(CM_NULLPTR); + fileNameArg.Follows(&readArg); + resultArg.Follows(&fileNameArg); + group.Follows(&resultArg); + argHelper.Parse(&args, CM_NULLPTR); + + std::string fileName = fileNameArg.GetString(); + if (!cmsys::SystemTools::FileIsFullPath(fileName.c_str())) { + 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; + } + + CM_AUTO_PTR<cmCryptoHash> hash(cmCryptoHash::New(args[0].c_str())); + if (hash.get()) { + 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().c_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.c_str())) { + 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].c_str())) { + 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 += cmake::GetCMakeFilesDirectory(); + 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; + } + + else if ((c >= 0x20 && c < 0x7F) || 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 = ""; + } + } + + 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.c_str()))) { + output_size += static_cast<int>(s.size()) + 1; + if (limit_output >= 0 && output_size >= limit_output) { + s = ""; + break; + } + strings.push_back(s); + } + + // Reset the string to empty. + s = ""; + } 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.c_str()))) { + output_size += static_cast<int>(s.size()) + 1; + if (limit_output >= 0 && output_size >= limit_output) { + s = ""; + break; + } + strings.push_back(s); + } + + // Reset the string to empty. + s = ""; + } 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.c_str()))) { + output_size += static_cast<int>(s.size()) + 1; + if (limit_output >= 0 && output_size >= limit_output) { + s = ""; + break; + } + strings.push_back(s); + } + s = ""; + } + } + + // 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.c_str()))) { + 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::vector<std::string>::const_iterator si = strings.begin(); + si != strings.end(); ++si) { + // 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. + std::string const& sr = *si; + for (unsigned int i = 0; i < sr.size(); ++i) { + if (sr[i] == ';') { + output += '\\'; + } + output += sr[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::string output; + bool first = true; + for (; i != args.end(); ++i) { + if (*i == "LIST_DIRECTORIES") { + ++i; + if (i != args.end()) { + if (cmSystemTools::IsOn(i->c_str())) { + g.SetListDirs(true); + g.SetRecurseListDirs(true); + } else if (cmSystemTools::IsOff(i->c_str())) { + g.SetListDirs(false); + g.SetRecurseListDirs(false); + } else { + this->SetError("LIST_DIRECTORIES missing bool value."); + return false; + } + } else { + this->SetError("LIST_DIRECTORIES missing bool value."); + return false; + } + continue; + } + + if (recurse && (*i == "FOLLOW_SYMLINKS")) { + explicitFollowSymlinks = true; + g.RecurseThroughSymlinksOn(); + ++i; + if (i == args.end()) { + this->SetError( + "GLOB_RECURSE requires a glob expression after FOLLOW_SYMLINKS"); + return false; + } + } + + 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; + } + } + + cmsys::Glob::GlobMessages globMessages; + if (!cmsys::SystemTools::FileIsFullPath(i->c_str())) { + std::string expr = this->Makefile->GetCurrentSourceDirectory(); + // Handle script mode + if (!expr.empty()) { + expr += "/" + *i; + g.FindFiles(expr, &globMessages); + } else { + g.FindFiles(*i, &globMessages); + } + } else { + g.FindFiles(*i, &globMessages); + } + + if (!globMessages.empty()) { + bool shouldExit = false; + for (cmsys::Glob::GlobMessagesIterator it = globMessages.begin(); + it != globMessages.end(); ++it) { + if (it->type == cmsys::Glob::cyclicRecursion) { + this->Makefile->IssueMessage( + cmake::AUTHOR_WARNING, + "Cyclic recursion detected while globbing for '" + *i + "':\n" + + it->content); + } else { + this->Makefile->IssueMessage( + cmake::FATAL_ERROR, "Error has occurred while globbing for '" + + *i + "' - " + it->content); + shouldExit = true; + } + } + if (shouldExit) { + return false; + } + } + + std::vector<std::string>::size_type cc; + std::vector<std::string>& files = g.GetFiles(); + std::sort(files.begin(), files.end()); + for (cc = 0; cc < files.size(); cc++) { + if (!first) { + output += ";"; + } + output += files[cc]; + first = false; + } + } + + if (recurse && !explicitFollowSymlinks) { + 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 (g.GetFollowedSymlinkCount() != 0) { + this->Makefile->IssueMessage( + cmake::AUTHOR_WARNING, + cmPolicies::GetPolicyWarning(cmPolicies::CMP0009)); + } + break; + } + } + + this->Makefile->AddDefinition(variable, output.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::vector<std::string>::const_iterator i = args.begin(); + + i++; // Get rid of subcommand + + std::string expr; + for (; i != args.end(); ++i) { + const std::string* cdir = &(*i); + if (!cmsys::SystemTools::FileIsFullPath(i->c_str())) { + expr = this->Makefile->GetCurrentSourceDirectory(); + expr += "/" + *i; + cdir = &expr; + } + if (!this->Makefile->CanIWriteThisFile(cdir->c_str())) { + std::string e = "attempted to create a directory: " + *cdir + + " into a source directory."; + this->SetError(e); + cmSystemTools::SetFatalErrorOccured(); + return false; + } + if (!cmSystemTools::MakeDirectory(cdir->c_str())) { + std::string error = "problem creating directory: " + *cdir; + 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 = CM_NULLPTR; + const char* file_rhs = CM_NULLPTR; + const char* var = CM_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(CM_NULLPTR) + , UseGivenPermissionsFile(false) + , UseGivenPermissionsDir(false) + , UseSourcePermissions(true) + , Doing(DoingNone) + { + } + virtual ~cmFileCopier() {} + + 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; + mode_t Permissions; + MatchProperties() + : Exclude(false) + , Permissions(0) + { + } + }; + struct MatchRule; + friend struct MatchRule; + struct MatchRule + { + cmsys::RegularExpression Regex; + MatchProperties Properties; + std::string RegexString; + MatchRule(std::string const& regex) + : Regex(regex.c_str()) + , RegexString(regex) + { + } + }; + std::vector<MatchRule> MatchRules; + + // Get the properties from rules matching this input file. + MatchProperties CollectMatchProperties(const char* file) + { +// Match rules are case-insensitive on some platforms. +#if defined(_WIN32) || defined(__APPLE__) || defined(__CYGWIN__) + std::string lower = cmSystemTools::LowerCase(file); + const char* file_to_match = lower.c_str(); +#else + const char* file_to_match = file; +#endif + + // Collect properties from all matching rules. + bool matched = false; + MatchProperties result; + for (std::vector<MatchRule>::iterator mr = this->MatchRules.begin(); + mr != this->MatchRules.end(); ++mr) { + 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 char* toFile, mode_t permissions) + { + if (permissions && !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 (arg == "OWNER_READ") { + permissions |= mode_owner_read; + } else if (arg == "OWNER_WRITE") { + permissions |= mode_owner_write; + } else if (arg == "OWNER_EXECUTE") { + permissions |= mode_owner_execute; + } else if (arg == "GROUP_READ") { + permissions |= mode_group_read; + } else if (arg == "GROUP_WRITE") { + permissions |= mode_group_write; + } else if (arg == "GROUP_EXECUTE") { + permissions |= mode_group_execute; + } else if (arg == "WORLD_READ") { + permissions |= mode_world_read; + } else if (arg == "WORLD_WRITE") { + permissions |= mode_world_write; + } else if (arg == "WORLD_EXECUTE") { + permissions |= mode_world_execute; + } else if (arg == "SETUID") { + permissions |= mode_setuid; + } else if (arg == "SETGID") { + permissions |= mode_setgid; + } else { + std::ostringstream e; + e << this->Name << " given invalid permission \"" << arg << "\"."; + this->FileCommand->SetError(e.str()); + return false; + } + return true; + } + + bool InstallSymlink(const char* fromFile, const char* toFile); + bool InstallFile(const char* fromFile, const char* toFile, + MatchProperties const& match_properties); + bool InstallDirectory(const char* source, const char* destination, + MatchProperties const& match_properties); + virtual bool Install(const char* fromFile, const char* toFile); + virtual std::string const& ToName(std::string const& fromName) + { + return fromName; + } + + enum Type + { + TypeFile, + TypeDir, + TypeLink + }; + virtual void ReportCopy(const char*, Type, bool) {} + virtual bool ReportMissing(const char* 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::vector<std::string> Files; + int Doing; + + virtual bool Parse(std::vector<std::string> const& args); + enum + { + DoingNone, + DoingError, + DoingDestination, + 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 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 == "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: + if (arg.empty() || cmSystemTools::FileIsFullPath(arg.c_str())) { + this->Files.push_back(arg); + } else { + std::string file = this->Makefile->GetCurrentSourceDirectory(); + file += "/" + arg; + this->Files.push_back(file); + } + break; + case DoingDestination: + if (arg.empty() || cmSystemTools::FileIsFullPath(arg.c_str())) { + this->Destination = arg; + } else { + this->Destination = this->Makefile->GetCurrentBinaryDirectory(); + this->Destination += "/" + arg; + } + 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.push_back(MatchRule(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.push_back(MatchRule(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; + } + + std::vector<std::string> const& files = this->Files; + for (std::vector<std::string>::size_type i = 0; i < files.size(); ++i) { + // Split the input file into its directory and name components. + std::vector<std::string> fromPathComponents; + cmSystemTools::SplitPath(files[i], 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; + 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.c_str(), toFile.c_str())) { + return false; + } + } + return true; +} + +bool cmFileCopier::Install(const char* fromFile, const char* toFile) +{ + if (!*fromFile) { + 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 char* fromFile, const char* 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 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 char* fromFile, const char* toFile, + MatchProperties const& 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 char* source, + const char* destination, + MatchProperties const& match_properties) +{ + // Inform the user about this directory installation. + this->ReportCopy(destination, TypeDir, + !cmSystemTools::FileIsDirectory(destination)); + + // Make sure the destination directory exists. + if (!cmSystemTools::MakeDirectory(destination)) { + 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 && *source) { + 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.c_str(), toPath.c_str())) { + 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.c_str()); + } + // Get the current manifest. + this->Manifest = + this->Makefile->GetSafeDefinition("CMAKE_INSTALL_MANIFEST_FILES"); + } + ~cmFileInstaller() CM_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) CM_OVERRIDE + { + return this->Rename.empty() ? fromName : this->Rename; + } + + void ReportCopy(const char* toFile, Type type, bool copy) CM_OVERRIDE + { + if (!this->MessageNever && (copy || !this->MessageLazy)) { + std::string message = (copy ? "Installing: " : "Up-to-date: "); + message += toFile; + this->Makefile->DisplayStatus(message.c_str(), -1); + } + if (type != TypeDir) { + // Add the file to the manifest. + this->ManifestAppend(toFile); + } + } + bool ReportMissing(const char* fromFile) CM_OVERRIDE + { + return (this->Optional || this->cmFileCopier::ReportMissing(fromFile)); + } + bool Install(const char* fromFile, const char* toFile) CM_OVERRIDE + { + // Support installing from empty source to make a directory. + if (this->InstallType == cmInstallType_DIRECTORY && !*fromFile) { + return this->InstallDirectory(fromFile, toFile, MatchProperties()); + } + return this->cmFileCopier::Install(fromFile, toFile); + } + + bool Parse(std::vector<std::string> const& args) CM_OVERRIDE; + enum + { + DoingType = DoingLast1, + DoingRename, + DoingLast2 + }; + bool CheckKeyword(std::string const& arg) CM_OVERRIDE; + bool CheckValue(std::string const& arg) CM_OVERRIDE; + void DefaultFilePermissions() CM_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; + } + 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->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()); + } + + if (this->InstallType != cmInstallType_DIRECTORY) { + if (!cmSystemTools::FileExists(destination.c_str())) { + if (!cmSystemTools::MakeDirectory(destination.c_str())) { + 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. + const char* file = CM_NULLPTR; + const char* oldRPath = CM_NULLPTR; + const char* newRPath = CM_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].c_str(); + 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) { + 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.c_str(), -1); + } + if (have_ft) { + cmSystemTools::FileTimeSet(file, ft); + } + } + cmSystemTools::FileTimeDelete(ft); + return success; +} + +bool cmFileCommand::HandleRPathRemoveCommand( + std::vector<std::string> const& args) +{ + // Evaluate arguments. + const char* file = CM_NULLPTR; + 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].c_str(); + doing = DoingNone; + } else { + std::ostringstream e; + e << "RPATH_REMOVE given unknown argument " << args[i]; + this->SetError(e.str()); + return false; + } + } + if (!file) { + 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.c_str(), -1); + } + if (have_ft) { + cmSystemTools::FileTimeSet(file, ft); + } + } + cmSystemTools::FileTimeDelete(ft); + return success; +} + +bool cmFileCommand::HandleRPathCheckCommand( + std::vector<std::string> const& args) +{ + // Evaluate arguments. + const char* file = CM_NULLPTR; + const char* rpath = CM_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].c_str(); + 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) { + 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::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.c_str())) { + std::string errstring = + "RELATIVE_PATH must be passed a full path to the directory: " + + directoryName; + this->SetError(errstring); + return false; + } + if (!cmSystemTools::FileIsFullPath(fileName.c_str())) { + 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.c_str(), fileName.c_str()); + 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.c_str())) { + oldname = this->Makefile->GetCurrentSourceDirectory(); + oldname += "/" + args[1]; + } + std::string newname = args[2]; + if (!cmsys::SystemTools::FileIsFullPath(newname.c_str())) { + newname = this->Makefile->GetCurrentSourceDirectory(); + newname += "/" + args[2]; + } + + if (!cmSystemTools::RenameFile(oldname.c_str(), newname.c_str())) { + 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; + std::vector<std::string>::const_iterator i = args.begin(); + + i++; // Get rid of subcommand + for (; i != args.end(); ++i) { + std::string fileName = *i; + if (!cmsys::SystemTools::FileIsFullPath(fileName.c_str())) { + fileName = this->Makefile->GetCurrentSourceDirectory(); + fileName += "/" + *i; + } + + 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<cmsys::String> path = cmSystemTools::SplitString(*i, pathSep); + i++; + const char* var = i->c_str(); + std::string value; + for (std::vector<cmsys::String>::iterator j = path.begin(); j != path.end(); + ++j) { + if (j != path.begin()) { + value += ";"; + } + if (!nativePath) { + cmSystemTools::ConvertToUnixSlashes(*j); + } else { + *j = cmSystemTools::ConvertToOutputPath(j->c_str()); + // remove double quotes in the path + cmsys::String& s = *j; + + if (s.size() > 1 && s[0] == '\"' && s[s.size() - 1] == '\"') { + 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 = (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 = (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) + { + int OldPercentage = this->CurrentPercentage; + + if (total > 0.0) { + this->CurrentPercentage = static_cast<int>(value / total * 100.0 + 0.5); + 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: + int 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.c_str(), -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.c_str(), -1); + } + + return 0; +} +} + +namespace { + +class cURLEasyGuard +{ +public: + cURLEasyGuard(CURL* easy) + : Easy(easy) + { + } + + ~cURLEasyGuard() + { + if (this->Easy) { + ::curl_easy_cleanup(this->Easy); + } + } + + void release() { this->Easy = CM_NULLPTR; } + +private: + ::CURL* Easy; +}; +} +#endif + +#define check_curl_result(result, errstr) \ + if (result != CURLE_OK) { \ + std::string e(errstr); \ + e += ::curl_easy_strerror(result); \ + this->SetError(e); \ + return 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 expectedHash; + std::string hashMatchMSG; + CM_AUTO_PTR<cmCryptoHash> hash; + bool showProgress = false; + std::string userpwd; + + std::list<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->c_str()); + } 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 == "EXPECTED_MD5") { + ++i; + if (i == args.end()) { + this->SetError("DOWNLOAD missing sum value for EXPECTED_MD5."); + return false; + } + hash = + CM_AUTO_PTR<cmCryptoHash>(new 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 = CM_AUTO_PTR<cmCryptoHash>(cmCryptoHash::New(algo.c_str())); + if (!hash.get()) { + 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(cmake::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.c_str()) && 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 << (int)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.c_str()) && + !cmSystemTools::MakeDirectory(dir.c_str())) { + 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; + } + + cmFileCommandVectorOfChar chunkDebug; + + res = ::curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&fout); + check_curl_result(res, "DOWNLOAD cannot set write data: "); + + res = ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, (void*)&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 = CM_NULLPTR; + for (std::list<std::string>::const_iterator h = curl_headers.begin(); + h != curl_headers.end(); ++h) { + 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 << (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.get()) { + 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: [" << (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.begin()); + } + + 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::list<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 == "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(cmake::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, (void*)&chunkResponse); + check_curl_result(res, "UPLOAD cannot set write data: "); + + res = ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, (void*)&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: "); + } + + struct curl_slist* headers = CM_NULLPTR; + for (std::list<std::string>::const_iterator h = curl_headers.begin(); + h != curl_headers.end(); ++h) { + 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 << (int)res << ";\"" << ::curl_easy_strerror(res) << "\""; + this->Makefile->AddDefinition(statusVar, result.str().c_str()); + } + + ::curl_global_cleanup(); + + fclose(fin); + fin = CM_NULLPTR; + + if (!logVar.empty()) { + std::string log; + + if (!chunkResponse.empty()) { + chunkResponse.push_back(0); + log += "Response:\n"; + log += &*chunkResponse.begin(); + log += "\n"; + } + + if (!chunkDebug.empty()) { + chunkDebug.push_back(0); + log += "Debug:\n"; + log += &*chunkDebug.begin(); + 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); + CM_AUTO_PTR<cmCompiledGeneratorExpression> outputCge = + outputGe.Parse(outputExpr); + + cmGeneratorExpression conditionGe(lfbt); + CM_AUTO_PTR<cmCompiledGeneratorExpression> conditionCge = + conditionGe.Parse(condition); + + this->Makefile->AddEvaluationFile(inputName, outputCge, 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( + cmake::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(cmake::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(cmake::FATAL_ERROR, e.str()); + return false; + } + + } else if (args[i] == "RESULT_VARIABLE") { + ++i; + if (i >= args.size()) { + this->Makefile->IssueMessage( + cmake::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(cmake::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(cmake::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(cmake::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(cmake::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(cmake::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(cmake::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; +} |