/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmFileInstaller.h" #include #include "cm_sys_stat.h" #include "cmExecutionStatus.h" #include "cmFSPermissions.h" #include "cmMakefile.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" using namespace cmFSPermissions; cmFileInstaller::cmFileInstaller(cmExecutionStatus& status) : cmFileCopier(status, "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 = cmIsOn(install_always); } // Get the current manifest. this->Manifest = this->Makefile->GetSafeDefinition("CMAKE_INSTALL_MANIFEST_FILES"); } cmFileInstaller::~cmFileInstaller() { // Save the updated install manifest. this->Makefile->AddDefinition("CMAKE_INSTALL_MANIFEST_FILES", this->Manifest); } void cmFileInstaller::ManifestAppend(std::string const& file) { if (!this->Manifest.empty()) { this->Manifest += ";"; } this->Manifest += file.substr(this->DestDirLength); } std::string const& cmFileInstaller::ToName(std::string const& fromName) { return this->Rename.empty() ? fromName : this->Rename; } void cmFileInstaller::ReportCopy(const std::string& toFile, Type type, bool copy) { if (!this->MessageNever && (copy || !this->MessageLazy)) { std::string message = cmStrCat((copy ? "Installing: " : "Up-to-date: "), toFile); this->Makefile->DisplayStatus(message, -1); } if (type != TypeDir) { // Add the file to the manifest. this->ManifestAppend(toFile); } } bool cmFileInstaller::ReportMissing(const std::string& fromFile) { return (this->Optional || this->cmFileCopier::ReportMissing(fromFile)); } bool cmFileInstaller::Install(const std::string& fromFile, const std::string& toFile) { // Support installing from empty source to make a directory. if (this->InstallType == cmInstallType_DIRECTORY && fromFile.empty()) { return this->InstallDirectory(fromFile, toFile, MatchProperties()); } return this->cmFileCopier::Install(fromFile, toFile); } void cmFileInstaller::DefaultFilePermissions() { this->cmFileCopier::DefaultFilePermissions(); // Add execute permissions based on the target type. switch (this->InstallType) { case cmInstallType_SHARED_LIBRARY: case cmInstallType_MODULE_LIBRARY: if (this->Makefile->IsOn("CMAKE_INSTALL_SO_NO_EXE")) { break; } CM_FALLTHROUGH; case cmInstallType_EXECUTABLE: case cmInstallType_PROGRAMS: this->FilePermissions |= mode_owner_execute; this->FilePermissions |= mode_group_execute; this->FilePermissions |= mode_world_execute; break; default: break; } } bool cmFileInstaller::Parse(std::vector const& args) { if (!this->cmFileCopier::Parse(args)) { return false; } if (!this->Rename.empty()) { if (!this->FilesFromDir.empty()) { this->Status.SetError("INSTALL option RENAME may not be " "combined with FILES_FROM_DIR."); return false; } if (this->InstallType != cmInstallType_FILES && this->InstallType != cmInstallType_PROGRAMS) { this->Status.SetError("INSTALL option RENAME may be used " "only with FILES or PROGRAMS."); return false; } if (this->Files.size() > 1) { this->Status.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->Status.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->Status.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->Status.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->Status.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->Status.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 = cmStrCat("called with network path DESTINATION. This " "does not make sense when using DESTDIR. Specify local " "absolute path or remove DESTDIR environment variable." "\nDESTINATION=\n", destination); this->Status.SetError(message); return false; } } destination = sdestdir + destination.substr(skip); this->DestDirLength = int(sdestdir.size()); } // check if default dir creation permissions were set mode_t default_dir_mode_v = 0; mode_t* default_dir_mode = &default_dir_mode_v; if (!this->GetDefaultDirectoryPermissions(&default_dir_mode)) { return false; } if (this->InstallType != cmInstallType_DIRECTORY) { if (!cmSystemTools::FileExists(destination)) { if (!cmSystemTools::MakeDirectory(destination, default_dir_mode)) { std::string errstring = "cannot create directory: " + destination + ". Maybe need administrative privileges."; this->Status.SetError(errstring); return false; } } if (!cmSystemTools::FileIsDirectory(destination)) { std::string errstring = "INSTALL destination: " + destination + " is not a directory."; this->Status.SetError(errstring); return false; } } return true; }