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
This commit is contained in:
Tyler Yankee
2026-02-05 19:33:47 -05:00
parent f52e3cc4ee
commit 4e7e6928cb
4 changed files with 29 additions and 8 deletions
+3 -2
View File
@@ -1791,10 +1791,11 @@ bool HandleDirectoryMode(std::vector<std::string> const& args,
exclude_from_all = true;
doing = DoingNone;
} else if (doing == DoingDirs) {
// Convert this directory to a full path.
// If the given directory is not a full path, convert it to one by
// assuming it's relative to the current source directory.
std::string dir = args[i];
std::string::size_type gpos = cmGeneratorExpression::Find(dir);
if (gpos != 0 && !cmSystemTools::FileIsFullPath(dir)) {
if (!dir.empty() && gpos != 0 && !cmSystemTools::FileIsFullPath(dir)) {
dir =
cmStrCat(helper.Makefile->GetCurrentSourceDirectory(), '/', args[i]);
}
+15 -5
View File
@@ -2,6 +2,7 @@
file LICENSE.rst or https://cmake.org/licensing for details. */
#include "cmInstallDirectoryGenerator.h"
#include <algorithm>
#include <utility>
#include "cmGeneratorExpression.h"
@@ -54,6 +55,13 @@ bool cmInstallDirectoryGenerator::Compute(cmLocalGenerator* lg)
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) {
@@ -81,11 +89,13 @@ void cmInstallDirectoryGenerator::GenerateScriptForConfig(
{
std::vector<std::string> dirs = this->GetDirectories(config);
// 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);
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);
}
}
}
@@ -1 +1 @@
check_installed([[^dir1;dir1/empty\.c;dir1/empty\.h;dir2;dir2/pattern;dir2/pattern/empty\.txt;dir3;dir3/empty\.c;dir3/empty\.h;dir3/empty\.txt;dir4;dir4/empty\.c;dir4/empty\.h;dir4/empty\.txt;empty$]])
check_installed([[^dir1;dir1/empty\.c;dir1/empty\.h;dir2;dir2/pattern;dir2/pattern/empty\.txt;dir3;dir3/empty\.c;dir3/empty\.h;dir3/empty\.txt;dir4;dir4/empty\.c;dir4/empty\.h;dir4/empty\.txt;empty;empty_str;empty_with_genex$]])
@@ -34,3 +34,13 @@ install(
DIRECTORY
DESTINATION empty
)
install(
DIRECTORY "" ""
DESTINATION empty_str
)
install(
DIRECTORY
DESTINATION $<LOWER_CASE:EMPTY_WITH_GENEX>
)