blob: 5cfb2cfcf578f08b4c4df558db8db35f6f612f30 [file] [log] [blame]
/* 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")
{
// 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 = static_cast<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;
}