|
|
|
/* 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 <map>
|
|
|
|
#include <sstream>
|
|
|
|
#include <utility>
|
|
|
|
|
|
|
|
#include <cm/string_view>
|
|
|
|
#include <cmext/string_view>
|
|
|
|
|
|
|
|
#include "cm_sys_stat.h"
|
|
|
|
|
|
|
|
#include "cmExecutionStatus.h"
|
|
|
|
#include "cmFSPermissions.h"
|
|
|
|
#include "cmMakefile.h"
|
|
|
|
#include "cmStringAlgorithms.h"
|
|
|
|
#include "cmSystemTools.h"
|
|
|
|
#include "cmValue.h"
|
|
|
|
|
|
|
|
using namespace cmFSPermissions;
|
|
|
|
|
|
|
|
cmFileInstaller::cmFileInstaller(cmExecutionStatus& status)
|
|
|
|
: cmFileCopier(status, "INSTALL")
|
|
|
|
, InstallType(cmInstallType_FILES)
|
|
|
|
, InstallMode(cmInstallMode::COPY)
|
|
|
|
, 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);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool cmFileInstaller::InstallFile(const std::string& fromFile,
|
|
|
|
const std::string& toFile,
|
|
|
|
MatchProperties match_properties)
|
|
|
|
{
|
|
|
|
if (this->InstallMode == cmInstallMode::COPY) {
|
|
|
|
return this->cmFileCopier::InstallFile(fromFile, toFile, match_properties);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string newFromFile;
|
|
|
|
|
|
|
|
if (this->InstallMode == cmInstallMode::REL_SYMLINK ||
|
|
|
|
this->InstallMode == cmInstallMode::REL_SYMLINK_OR_COPY ||
|
|
|
|
this->InstallMode == cmInstallMode::SYMLINK ||
|
|
|
|
this->InstallMode == cmInstallMode::SYMLINK_OR_COPY) {
|
|
|
|
// Try to get a relative path.
|
|
|
|
std::string toDir = cmSystemTools::GetParentDirectory(toFile);
|
|
|
|
newFromFile = cmSystemTools::ForceToRelativePath(toDir, fromFile);
|
|
|
|
|
|
|
|
// Double check that we can restore the original path.
|
|
|
|
std::string reassembled =
|
|
|
|
cmSystemTools::CollapseFullPath(newFromFile, toDir);
|
|
|
|
if (!cmSystemTools::ComparePath(reassembled, fromFile)) {
|
|
|
|
if (this->InstallMode == cmInstallMode::SYMLINK ||
|
|
|
|
this->InstallMode == cmInstallMode::SYMLINK_OR_COPY) {
|
|
|
|
// User does not mind, silently proceed with absolute path.
|
|
|
|
newFromFile = fromFile;
|
|
|
|
} else if (this->InstallMode == cmInstallMode::REL_SYMLINK_OR_COPY) {
|
|
|
|
// User expects a relative symbolic link or a copy.
|
|
|
|
// Since an absolute symlink won't do, copy instead.
|
|
|
|
return this->cmFileCopier::InstallFile(fromFile, toFile,
|
|
|
|
match_properties);
|
|
|
|
} else {
|
|
|
|
// We cannot meet user's expectation (REL_SYMLINK)
|
|
|
|
auto e = cmStrCat(this->Name,
|
|
|
|
" cannot determine relative path for symlink to \"",
|
|
|
|
newFromFile, "\" at \"", toFile, "\".");
|
|
|
|
this->Status.SetError(e);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
newFromFile = fromFile; // stick with absolute path
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 (newFromFile == oldSymlinkTarget) {
|
|
|
|
copy = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Inform the user about this file installation.
|
|
|
|
this->ReportCopy(toFile, TypeLink, copy);
|
|
|
|
|
|
|
|
if (copy) {
|
|
|
|
// Remove the destination file so we can always create the symlink.
|
|
|
|
cmSystemTools::RemoveFile(toFile);
|
|
|
|
|
|
|
|
// Create destination directory if it doesn't exist
|
|
|
|
cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(toFile));
|
|
|
|
|
|
|
|
// Create the symlink.
|
|
|
|
if (!cmSystemTools::CreateSymlink(newFromFile, toFile)) {
|
|
|
|
if (this->InstallMode == cmInstallMode::ABS_SYMLINK_OR_COPY ||
|
|
|
|
this->InstallMode == cmInstallMode::REL_SYMLINK_OR_COPY ||
|
|
|
|
this->InstallMode == cmInstallMode::SYMLINK_OR_COPY) {
|
|
|
|
// Failed to create a symbolic link, fall back to copying.
|
|
|
|
return this->cmFileCopier::InstallFile(newFromFile, toFile,
|
|
|
|
match_properties);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto e = cmStrCat(this->Name, " cannot create symlink to \"",
|
|
|
|
newFromFile, "\" at \"", toFile,
|
|
|
|
"\": ", cmSystemTools::GetLastSystemError(), "\".");
|
|
|
|
this->Status.SetError(e);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
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<std::string> 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const std::map<cm::string_view, cmInstallMode> install_mode_dict{
|
|
|
|
{ "ABS_SYMLINK"_s, cmInstallMode::ABS_SYMLINK },
|
|
|
|
{ "ABS_SYMLINK_OR_COPY"_s, cmInstallMode::ABS_SYMLINK_OR_COPY },
|
|
|
|
{ "REL_SYMLINK"_s, cmInstallMode::REL_SYMLINK },
|
|
|
|
{ "REL_SYMLINK_OR_COPY"_s, cmInstallMode::REL_SYMLINK_OR_COPY },
|
|
|
|
{ "SYMLINK"_s, cmInstallMode::SYMLINK },
|
|
|
|
{ "SYMLINK_OR_COPY"_s, cmInstallMode::SYMLINK_OR_COPY }
|
|
|
|
};
|
|
|
|
|
|
|
|
std::string install_mode;
|
|
|
|
cmSystemTools::GetEnv("CMAKE_INSTALL_MODE", install_mode);
|
|
|
|
if (install_mode.empty() || install_mode == "COPY"_s) {
|
|
|
|
this->InstallMode = cmInstallMode::COPY;
|
|
|
|
} else {
|
|
|
|
auto it = install_mode_dict.find(install_mode);
|
|
|
|
if (it != install_mode_dict.end()) {
|
|
|
|
this->InstallMode = it->second;
|
|
|
|
} else {
|
|
|
|
auto e = cmStrCat("Unrecognized value '", install_mode,
|
|
|
|
"' for environment variable CMAKE_INSTALL_MODE");
|
|
|
|
this->Status.SetError(e);
|
|
|
|
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;
|
|
|
|
}
|