Files
CMake/Source/cmInstallDirectoryGenerator.cxx
Tyler Yankee 4e7e6928cb install: Fix bugs around empty directories
The conversion between internal list representations led to empty
directories being left behind when processing generator expressions in
the destination. This ensures the intended behavior of creating a
destination directory but installing nothing into it when no <dirs> are
specified, regardless of the presence or absence of genex.

Further, an explicit empty string <dir> (via `""`, or an undefined
variable) led to the current source directory being expanded as a file
to install, leading to the generation of an infinitely recursive
install command. Disallow this behavior, forcing projects to explicitly
specify the current source directory if that is their intention.

Fixes: #27568
2026-02-05 19:33:47 -05:00

124 lines
4.2 KiB
C++

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#include "cmInstallDirectoryGenerator.h"
#include <algorithm>
#include <utility>
#include "cmGeneratorExpression.h"
#include "cmInstallType.h"
#include "cmList.h"
#include "cmListFileCache.h"
#include "cmLocalGenerator.h"
#include "cmMakefile.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
cmInstallDirectoryGenerator::cmInstallDirectoryGenerator(
std::vector<std::string> const& dirs, std::string const& dest,
std::string file_permissions, std::string dir_permissions,
std::vector<std::string> const& configurations, std::string const& component,
MessageLevel message, bool exclude_from_all, std::string literal_args,
bool optional, cmListFileBacktrace backtrace)
: cmInstallGenerator(dest, configurations, component, message,
exclude_from_all, false, std::move(backtrace))
, Directories(dirs)
, FilePermissions(std::move(file_permissions))
, DirPermissions(std::move(dir_permissions))
, LiteralArguments(std::move(literal_args))
, Optional(optional)
{
// We need per-config actions if destination have generator expressions.
if (cmGeneratorExpression::Find(this->Destination) != std::string::npos) {
this->ActionsPerConfig = true;
}
// We need per-config actions if any directories have generator expressions.
if (!this->ActionsPerConfig) {
for (std::string const& dir : dirs) {
if (cmGeneratorExpression::Find(dir) != std::string::npos) {
this->ActionsPerConfig = true;
break;
}
}
}
}
cmInstallDirectoryGenerator::~cmInstallDirectoryGenerator() = default;
bool cmInstallDirectoryGenerator::Compute(cmLocalGenerator* lg)
{
this->LocalGenerator = lg;
return true;
}
std::vector<std::string> cmInstallDirectoryGenerator::GetDirectories(
std::string const& config) const
{
// If given only empty directories, collapse into a single specification to
// avoid redundant calls. This supports the use case of installing an empty
// directory into a destination when a directory is not specified.
if (std::all_of(this->Directories.begin(), this->Directories.end(),
[](std::string const& d) { return d.empty(); })) {
return std::vector<std::string>{ "" };
}
cmList directories;
if (this->ActionsPerConfig) {
for (std::string const& f : this->Directories) {
directories.append(
cmGeneratorExpression::Evaluate(f, this->LocalGenerator, config));
}
} else {
directories = this->Directories;
}
return std::move(directories.data());
}
void cmInstallDirectoryGenerator::GenerateScriptActions(std::ostream& os,
Indent indent)
{
if (this->ActionsPerConfig) {
this->cmInstallGenerator::GenerateScriptActions(os, indent);
} else {
this->AddDirectoryInstallRule(os, "", indent, this->Directories);
}
}
void cmInstallDirectoryGenerator::GenerateScriptForConfig(
std::ostream& os, std::string const& config, Indent indent)
{
std::vector<std::string> dirs = this->GetDirectories(config);
if (!(dirs.size() == 1 && dirs[0].empty())) {
// Make sure all dirs have absolute paths.
cmMakefile const& mf = *this->LocalGenerator->GetMakefile();
for (std::string& d : dirs) {
if (!cmSystemTools::FileIsFullPath(d)) {
d = cmStrCat(mf.GetCurrentSourceDirectory(), '/', d);
}
}
}
this->AddDirectoryInstallRule(os, config, indent, dirs);
}
void cmInstallDirectoryGenerator::AddDirectoryInstallRule(
std::ostream& os, std::string const& config, Indent indent,
std::vector<std::string> const& dirs)
{
// Write code to install the directories.
char const* no_rename = nullptr;
this->AddInstallRule(os, this->GetDestination(config),
cmInstallType_DIRECTORY, dirs, this->Optional,
this->FilePermissions.c_str(),
this->DirPermissions.c_str(), no_rename,
this->LiteralArguments.c_str(), indent);
}
std::string cmInstallDirectoryGenerator::GetDestination(
std::string const& config) const
{
return cmGeneratorExpression::Evaluate(this->Destination,
this->LocalGenerator, config);
}