install: Add BUILD_DEPENDS option for install_prep targets

Add a `BUILD_DEPENDS` option to multiple `install` command signatures
for projects to specify dependencies which CMake cannot infer.

Co-authored-by: Tyler Yankee <tyler.yankee@kitware.com>
Closes: #27643
This commit is contained in:
Taylor SasserandTyler Yankee authored and Tyler Yankee committed 2026-10-12 13:58:14 -04:00
1 parent e36cd0ac29
commit 41bb5b5e96
53 files changed
+552 -34

No files matched your search

+37
View File
@@ -141,6 +141,21 @@ signatures that specify them. The common options are:
Specify that it is not an error if the file to be installed does
not exist.
``BUILD_DEPENDS <dependency>...``
.. versionadded:: 4.5
Specify targets or files that need to be built before the install rule
runs. They are added as dependencies of the corresponding
:ref:`Install Preparation Targets`. This is intended for dependencies
that CMake cannot infer.
Each entry naming an existing target or alias is a target dependency.
Any other entry names a file, which must exist or be generated by the build.
A relative file path is interpreted with respect to the current source
directory if the file exists there, and otherwise with respect to the current
binary directory. If this option is used multiple times in a single call,
its list of dependencies accumulates.
.. versionadded:: 3.1
Command signatures that install files may print messages during
installation. Use the :variable:`CMAKE_INSTALL_MESSAGE` variable
@@ -600,6 +615,7 @@ Signatures
[PERMISSIONS <permission>...]
[CONFIGURATIONS <config>...]
[COMPONENT <component>]
[BUILD_DEPENDS <dependency>...]
[RENAME <name>] [OPTIONAL] [EXCLUDE_FROM_ALL])
The ``FILES`` form specifies rules for installing files for a project.
@@ -614,6 +630,11 @@ Signatures
programs that are not targets, such as shell scripts. Use the ``TARGETS``
form to install targets built within the project.
.. versionadded:: 4.5
Files named by the command that the build generates are inferred as
dependencies of the :ref:`Install Preparation Targets`. Use the
``BUILD_DEPENDS`` option to name any other dependencies.
The list of files given to ``FILES`` or ``PROGRAMS`` may use
"generator expressions" with the syntax ``$<...>``. See the
:manual:`cmake-generator-expressions(7)` manual for available expressions.
@@ -713,6 +734,7 @@ Signatures
[USE_SOURCE_PERMISSIONS] [OPTIONAL] [MESSAGE_NEVER]
[CONFIGURATIONS <config>...]
[COMPONENT <component>] [EXCLUDE_FROM_ALL]
[BUILD_DEPENDS <dependency>...]
[FILES_MATCHING]
[<match-rule> <match-option>...]...
)
@@ -815,6 +837,15 @@ Signatures
Disable file installation status output.
``BUILD_DEPENDS <dependency>...``
.. versionadded:: 4.5
See `Common Options`_. CMake enumerates the content of an installed
directory during installation, so it cannot infer any dependencies of
directories populated by build steps.
This option may not be given after a ``<match-rule>``.
``FILES_MATCHING``
This option may be given before the first ``<match-rule>`` to
disable installation of files (but not directories) not matched
@@ -883,6 +914,7 @@ Signatures
install({ SCRIPT <file> | CODE <code> }...
[ALL_COMPONENTS | COMPONENT <component>]
[BUILD_DEPENDS <dependency>...]
[EXCLUDE_FROM_ALL])
The ``SCRIPT`` form will invoke the given CMake script files during
@@ -898,6 +930,11 @@ Signatures
will print a message during installation.
.. versionadded:: 4.5
CMake cannot infer dependencies of installation code. Dependencies named
by the ``BUILD_DEPENDS`` option apply to every ``SCRIPT`` or ``CODE``
install rule created by the call.
.. versionadded:: 3.21
When the ``ALL_COMPONENTS`` option is given, the custom installation
script code will be executed for every component of a component-specific
+6 -3
View File
@@ -1886,9 +1886,12 @@ For example:
set_property(TARGET B PROPERTY PUBLIC_HEADER
"${CMAKE_CURRENT_BINARY_DIR}/byproduct.h")
Rules created by the ``DIRECTORY``, ``SCRIPT``, and ``CODE`` signatures of
the :command:`install` command, or by the deprecated
:command:`install_targets` command, do not imply build dependencies.
The ``BUILD_DEPENDS`` option of the ``FILES``, ``PROGRAMS``, ``DIRECTORY``,
``SCRIPT``, and ``CODE`` signatures of the :command:`install` command can
be used to specify targets or files that CMake cannot infer. ``DIRECTORY``,
``SCRIPT``, and ``CODE`` rules do not imply build dependencies without
``BUILD_DEPENDS``. The deprecated :command:`install_targets` command does not
contribute dependencies.
Install rules below a directory added with :prop_dir:`EXCLUDE_FROM_ALL` are
ignored, matching installation from the parent directory.
+15
View File
@@ -75,6 +75,16 @@ Instrumentation
Generators
----------
* The :ref:`Ninja Generators`, :ref:`Makefile Generators`, and
:generator:`FASTBuild` generator now provide ``install_prep/all`` and
``install_prep/<component>`` convenience build targets by default. The
:variable:`CMAKE_SKIP_INSTALL_PREP_TARGETS` variable may be enabled to
skip generating them. These targets build all known build dependencies for
an installation that does not select a component, or for the named install
component, respectively. This includes targets that produce installed C++
module BMIs, module export metadata, or generated files. See
:ref:`Install Preparation Targets`.
* The :generator:`FASTBuild` generator and the :ref:`Makefile Generators`
now generate ``test_prep/<test-name>`` and ``test_prep/all`` convenience
build targets when the :variable:`CMAKE_TEST_BUILD_DEPENDS` variable is
@@ -186,6 +196,11 @@ Commands
The option cannot be used with ``EXPORT`` and may only install runtime
artifacts.
* The :command:`install` command's ``FILES``, ``PROGRAMS``, ``DIRECTORY``,
``SCRIPT``, and ``CODE`` signatures gained a ``BUILD_DEPENDS`` option to
specify targets or files needed to prepare files or run installation
code. See :ref:`Install Preparation Targets`.
* The :command:`math` command gained new ``INCREMENT`` and ``DECREMENT`` modes.
* The :command:`project` and :command:`enable_language` commands, when
+2 -1
View File
@@ -3571,7 +3571,8 @@ void cmGlobalGenerator::CheckInstallPrepTargets()
"\nwhich is generated but is not the unique output of "
"a build target, so \"install_prep/\" targets cannot "
"build it with this generator. Produce the file with "
"a custom command attached to a single target to "
"a custom command attached to a single target, or name "
"the target that produces it with BUILD_DEPENDS, to "
"prepare it."),
installer->GetDiagnosticContext());
}
+32 -11
View File
@@ -300,16 +300,16 @@ std::unique_ptr<cmInstallTargetGenerator> CreateInstallTargetGenerator(
std::unique_ptr<cmInstallFilesGenerator> CreateInstallFilesGenerator(
cmMakefile* mf, std::vector<std::string> const& absFiles,
cmInstallCommandArguments const& args, bool programs,
std::string const& destination,
std::string const& destination, std::vector<std::string> const& buildDepends,
cm::optional<std::vector<std::string>> const& configurations = {})
{
cmInstallGenerator::MessageLevel message =
cmInstallGenerator::SelectMessageLevel(mf);
return cm::make_unique<cmInstallFilesGenerator>(
absFiles, destination, programs, args.GetPermissions(),
SelectConfigurations(args, configurations), args.GetComponent(), message,
args.GetExcludeFromAll(), args.GetRename(), args.GetOptional(),
cmInstallGenerator::CaptureContext(mf));
SelectConfigurations(args, configurations), args.GetComponent(),
buildDepends, message, args.GetExcludeFromAll(), args.GetRename(),
args.GetOptional(), cmInstallGenerator::CaptureContext(mf));
}
std::unique_ptr<cmInstallFileSetGenerator> CreateInstallFileSetGenerator(
@@ -865,7 +865,8 @@ bool CreateInstallGeneratorsForTarget(
if (!artifactsSpecified || !privateHeaderArgs.GetDestination().empty()) {
privateHeaderGenerator = CreateInstallFilesGenerator(
helper.Makefile, absFiles, privateHeaderArgs, false,
helper.GetIncludeDestination(&privateHeaderArgs), configurations);
helper.GetIncludeDestination(&privateHeaderArgs),
std::vector<std::string>(), configurations);
privateHeaderGenerator->SetOwningTarget(target.GetName());
} else {
helper.Makefile->IssueDiagnostic(
@@ -889,7 +890,8 @@ bool CreateInstallGeneratorsForTarget(
if (!artifactsSpecified || !publicHeaderArgs.GetDestination().empty()) {
publicHeaderGenerator = CreateInstallFilesGenerator(
helper.Makefile, absFiles, publicHeaderArgs, false,
helper.GetIncludeDestination(&publicHeaderArgs), configurations);
helper.GetIncludeDestination(&publicHeaderArgs),
std::vector<std::string>(), configurations);
publicHeaderGenerator->SetOwningTarget(target.GetName());
} else {
helper.Makefile->IssueDiagnostic(
@@ -913,7 +915,8 @@ bool CreateInstallGeneratorsForTarget(
if (!resourceArgs.GetDestination().empty()) {
resourceGenerator = CreateInstallFilesGenerator(
helper.Makefile, absFiles, resourceArgs, false,
resourceArgs.GetDestination(), configurations);
resourceArgs.GetDestination(), std::vector<std::string>(),
configurations);
resourceGenerator->SetOwningTarget(target.GetName());
} else if (!target.IsAppBundleOnApple()) {
helper.Makefile->IssueDiagnostic(
@@ -1076,6 +1079,7 @@ bool HandleScriptMode(std::vector<std::string> const& args,
std::vector<Script> Scripts;
cm::optional<std::string> Component;
ArgumentParser::MaybeEmpty<std::vector<std::string>> BuildDepends;
bool ExcludeFromAll = false;
bool AllComponents = false;
};
@@ -1086,6 +1090,7 @@ bool HandleScriptMode(std::vector<std::string> const& args,
.Bind("CODE"_s, &Arguments::AddScript)
.Bind("COMPONENT"_s, &Arguments::Component)
.Bind("EXCLUDE_FROM_ALL"_s, &Arguments::ExcludeFromAll)
.Bind("BUILD_DEPENDS"_s, &Arguments::BuildDepends)
.Bind("ALL_COMPONENTS"_s, &Arguments::AllComponents);
std::vector<std::string> unknownArgs;
@@ -1123,7 +1128,7 @@ bool HandleScriptMode(std::vector<std::string> const& args,
cm::make_unique<cmInstallScriptGenerator>(
std::move(script.Value), script.IsCode, component,
arguments.ExcludeFromAll, arguments.AllComponents,
helper.Makefile->GetBacktrace()));
arguments.BuildDepends, helper.Makefile->GetBacktrace()));
}
// Tell the global generator about any installation component names
@@ -1831,7 +1836,9 @@ bool HandleFilesMode(std::vector<std::string> const& args,
bool programs = (args[0] == "PROGRAMS");
cmInstallCommandArguments ica(helper.DefaultComponentName, *helper.Makefile);
ArgumentParser::MaybeEmpty<std::vector<std::string>> files;
ArgumentParser::MaybeEmpty<std::vector<std::string>> depends;
ica.Bind(programs ? "PROGRAMS"_s : "FILES"_s, files);
ica.Bind("BUILD_DEPENDS"_s, depends);
std::vector<std::string> unknownArgs;
ica.Parse(args, &unknownArgs);
@@ -1850,6 +1857,7 @@ bool HandleFilesMode(std::vector<std::string> const& args,
}
std::vector<std::string> const& filesVector = files;
std::vector<std::string> const& buildDepends = depends;
// Check if there is something to do.
if (filesVector.empty()) {
@@ -1903,7 +1911,7 @@ bool HandleFilesMode(std::vector<std::string> const& args,
// Create the files install generator.
helper.Makefile->AddInstallGenerator(CreateInstallFilesGenerator(
helper.Makefile, absFiles, ica, programs, destination));
helper.Makefile, absFiles, ica, programs, destination, buildDepends));
// Tell the global generator about any installation component names
// specified.
@@ -1930,6 +1938,7 @@ bool HandleDirectoryMode(std::vector<std::string> const& args,
DoingPermsMatch,
DoingConfigurations,
DoingComponent,
DoingBuildDepends,
DoingType
};
Doing doing = DoingDirs;
@@ -1943,6 +1952,7 @@ bool HandleDirectoryMode(std::vector<std::string> const& args,
std::string permissionsDir;
std::vector<std::string> configurations;
std::string component = helper.DefaultComponentName;
std::vector<std::string> buildDepends;
std::string literalArgs;
std::string type;
for (unsigned int i = 1; i < args.size(); ++i) {
@@ -2067,6 +2077,15 @@ bool HandleDirectoryMode(std::vector<std::string> const& args,
// Switch to setting the component property.
doing = DoingComponent;
} else if (args[i] == "BUILD_DEPENDS") {
if (inMatchMode) {
status.SetError(cmStrCat(args[0], " does not allow \"", args[i],
"\" after PATTERN or REGEX."));
return false;
}
// Switch to adding build dependencies.
doing = DoingBuildDepends;
} else if (args[i] == "EXCLUDE_FROM_ALL") {
if (inMatchMode) {
status.SetError(cmStrCat(args[0], " does not allow \"", args[i],
@@ -2151,6 +2170,8 @@ bool HandleDirectoryMode(std::vector<std::string> const& args,
} else if (doing == DoingComponent) {
component = args[i];
doing = DoingNone;
} else if (doing == DoingBuildDepends) {
buildDepends.push_back(args[i]);
} else if (doing == DoingPermsFile) {
// Check the requested permission.
if (!cmInstallCommandArguments::CheckPermissions(args[i],
@@ -2215,8 +2236,8 @@ bool HandleDirectoryMode(std::vector<std::string> const& args,
helper.Makefile->AddInstallGenerator(
cm::make_unique<cmInstallDirectoryGenerator>(
dirs, *destination, permissionsFile, permissionsDir, configurations,
component, message, excludeFromAll, literalArgs, optional,
helper.CaptureContext()));
component, std::move(buildDepends), message, excludeFromAll, literalArgs,
optional, helper.CaptureContext()));
// Tell the global generator about any installation component names
// specified.
+13 -3
View File
@@ -18,14 +18,16 @@ cmInstallDirectoryGenerator::cmInstallDirectoryGenerator(
std::vector<std::string> const& dirs, std::string const& dest,
std::string filePermissions, std::string dirPermissions,
std::vector<std::string> const& configurations, std::string const& component,
MessageLevel message, bool excludeFromAll, std::string literalArgs,
bool optional, cmDiagnosticContext context)
std::vector<std::string> buildDepends, MessageLevel message,
bool excludeFromAll, std::string literalArgs, bool optional,
cmDiagnosticContext context)
: cmInstallGenerator(dest, configurations, component, message,
excludeFromAll, false, std::move(context))
, Directories(dirs)
, FilePermissions(std::move(filePermissions))
, DirPermissions(std::move(dirPermissions))
, LiteralArguments(std::move(literalArgs))
, BuildDepends(std::move(buildDepends))
, Optional(optional)
{
// We need per-config actions if destination have generator expressions.
@@ -49,7 +51,15 @@ cmInstallDirectoryGenerator::~cmInstallDirectoryGenerator() = default;
bool cmInstallDirectoryGenerator::Compute(cmLocalGenerator* lg)
{
this->LocalGenerator = lg;
return true;
return this->ResolveBuildDepends(lg, "DIRECTORY", this->BuildDepends,
this->ResolvedBuildDepends);
}
void cmInstallDirectoryGenerator::GetInstallBuildDependencies(
std::string const& config, std::vector<BuildDependency>& dependencies) const
{
AddBuildDependsDependencies(this->LocalGenerator, config,
this->ResolvedBuildDepends, dependencies);
}
std::vector<std::string> cmInstallDirectoryGenerator::GetDirectories(
+9 -2
View File
@@ -23,12 +23,17 @@ public:
std::vector<std::string> const& dirs, std::string const& dest,
std::string filePermissions, std::string dirPermissions,
std::vector<std::string> const& configurations,
std::string const& component, MessageLevel message, bool excludeFromAll,
std::string literalArgs, bool optional, cmDiagnosticContext context);
std::string const& component, std::vector<std::string> buildDepends,
MessageLevel message, bool excludeFromAll, std::string literalArgs,
bool optional, cmDiagnosticContext context);
~cmInstallDirectoryGenerator() override;
bool Compute(cmLocalGenerator* lg) override;
void GetInstallBuildDependencies(
std::string const& config,
std::vector<BuildDependency>& dependencies) const override;
std::string GetDestination(std::string const& config) const;
std::vector<std::string> GetDirectories(std::string const& config) const;
@@ -46,5 +51,7 @@ protected:
std::string const FilePermissions;
std::string const DirPermissions;
std::string const LiteralArguments;
std::vector<std::string> const BuildDepends;
BuildDependsEntries ResolvedBuildDepends;
bool const Optional;
};
+1 -1
View File
@@ -131,7 +131,7 @@ static void CreateInstallGenerator(cmMakefile& makefile,
cmInstallGenerator::SelectMessageLevel(&makefile);
makefile.AddInstallGenerator(cm::make_unique<cmInstallFilesGenerator>(
files, destination, false, no_permissions, no_configurations, no_component,
message, no_exclude_from_all, no_rename, false,
std::vector<std::string>(), message, no_exclude_from_all, no_rename, false,
cmInstallGenerator::CaptureContext(makefile)));
}
+17 -3
View File
@@ -14,13 +14,15 @@ cmInstallFilesGenerator::cmInstallFilesGenerator(
std::vector<std::string> const& files, std::string const& dest,
bool programs, std::string filePermissions,
std::vector<std::string> const& configurations, std::string const& component,
MessageLevel message, bool excludeFromAll, std::string rename, bool optional,
std::vector<std::string> buildDepends, MessageLevel message,
bool excludeFromAll, std::string rename, bool optional,
cmDiagnosticContext context)
: cmInstallGenerator(dest, configurations, component, message,
excludeFromAll, false, std::move(context))
, Files(files)
, FilePermissions(std::move(filePermissions))
, Rename(std::move(rename))
, BuildDepends(std::move(buildDepends))
, Programs(programs)
, Optional(optional)
{
@@ -52,15 +54,27 @@ bool cmInstallFilesGenerator::Compute(cmLocalGenerator* lg)
if (!this->OwningTargetName.empty()) {
this->OwningTarget = lg->FindGeneratorTargetToUse(this->OwningTargetName);
}
return true;
return this->ResolveBuildDepends(lg, this->Programs ? "PROGRAMS" : "FILES",
this->BuildDepends,
this->ResolvedBuildDepends);
}
void cmInstallFilesGenerator::GetInstallBuildDependencies(
std::string const& config, std::vector<BuildDependency>& dependencies) const
{
AddBuildDependsDependencies(this->LocalGenerator, config,
this->ResolvedBuildDepends, dependencies);
std::vector<BuildDependency> generated;
AddGeneratedFileDependencies(this->LocalGenerator->GetGlobalGenerator(),
config, this->OwningTarget,
this->GetFiles(config), dependencies);
this->GetFiles(config), generated);
for (BuildDependency& dependency : generated) {
// A generated file that no target prepares is assumed to be produced by
// the targets named by BUILD_DEPENDS, if any.
if (dependency.Target || this->ResolvedBuildDepends.Targets.empty()) {
dependencies.push_back(std::move(dependency));
}
}
}
std::string cmInstallFilesGenerator::GetDestination(
+7 -3
View File
@@ -25,9 +25,11 @@ public:
std::string const& dest, bool programs,
std::string filePermissions,
std::vector<std::string> const& configurations,
std::string const& component, MessageLevel message,
bool excludeFromAll, std::string rename,
bool optional, cmDiagnosticContext context);
std::string const& component,
std::vector<std::string> buildDepends,
MessageLevel message, bool excludeFromAll,
std::string rename, bool optional,
cmDiagnosticContext context);
~cmInstallFilesGenerator() override;
bool Compute(cmLocalGenerator* lg) override;
@@ -58,6 +60,8 @@ protected:
std::vector<std::string> const Files;
std::string const FilePermissions;
std::string const Rename;
std::vector<std::string> const BuildDepends;
BuildDependsEntries ResolvedBuildDepends;
std::string OwningTargetName;
cmGeneratorTarget const* OwningTarget = nullptr;
bool const Programs;
+53
View File
@@ -2,6 +2,7 @@
file LICENSE.rst or https://cmake.org/licensing for details. */
#include "cmInstallGenerator.h"
#include <algorithm>
#include <sstream>
#include <utility>
@@ -12,6 +13,7 @@
#include "cmGlobalGenerator.h"
#include "cmLocalGenerator.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
@@ -272,6 +274,57 @@ cmDiagnosticContext cmInstallGenerator::CaptureContext(cmMakefile const& mf)
return context;
}
bool cmInstallGenerator::ResolveBuildDepends(
cmLocalGenerator* lg, std::string const& signature,
std::vector<std::string> const& names, BuildDependsEntries& entries)
{
entries = BuildDependsEntries();
cmGlobalGenerator* gg = lg->GetGlobalGenerator();
std::vector<std::string> const configs =
lg->GetMakefile()->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
auto const isArtifact = [gg, &configs](std::string const& file) {
return std::any_of(configs.begin(), configs.end(),
[gg, &file](std::string const& config) {
return gg->FindArtifactOwningTarget(file, config);
});
};
for (std::string const& name : names) {
if (cmGeneratorTarget const* target = lg->FindGeneratorTargetToUse(name)) {
entries.Targets.push_back(target);
continue;
}
// A relative path is interpreted with respect to the current source
// directory if the file exists there, and otherwise the current binary
// directory.
std::string file = lg->GetRealFileDependency(name);
if (gg->IsGeneratedByBuild(file) || isArtifact(file) ||
cmSystemTools::FileExists(file)) {
entries.Files.push_back(std::move(file));
} else {
lg->IssueMessage(
MessageType::FATAL_ERROR,
cmStrCat("install(", signature, ") BUILD_DEPENDS entry \"", name,
"\" is neither a target nor a file that exists or is "
"generated by the build."),
this->GetBacktrace());
return false;
}
}
return true;
}
void cmInstallGenerator::AddBuildDependsDependencies(
cmLocalGenerator* lg, std::string const& config,
BuildDependsEntries const& entries,
std::vector<BuildDependency>& dependencies)
{
for (cmGeneratorTarget const* target : entries.Targets) {
dependencies.push_back({ target, BuildDependencyType::Normal });
}
AddGeneratedFileDependencies(lg->GetGlobalGenerator(), config, nullptr,
entries.Files, dependencies);
}
std::string cmInstallGenerator::GetDestDirPath(std::string const& file)
{
// Construct the path of the file on disk after installation on
+20
View File
@@ -148,6 +148,26 @@ protected:
std::vector<std::string> const& files,
TweakMethod const& tweak);
// BUILD_DEPENDS entries of an install(<signature>) rule, resolved.
struct BuildDependsEntries
{
std::vector<cmGeneratorTarget const*> Targets;
std::vector<std::string> Files;
};
// Resolve each BUILD_DEPENDS entry to a target or file. Returns false after
// reporting an error if an entry names neither a target nor a file that
// exists or that the build produces.
bool ResolveBuildDepends(cmLocalGenerator* lg, std::string const& signature,
std::vector<std::string> const& names,
BuildDependsEntries& entries);
// Add dependencies for resolved BUILD_DEPENDS entries.
static void AddBuildDependsDependencies(
cmLocalGenerator* lg, std::string const& config,
BuildDependsEntries const& entries,
std::vector<BuildDependency>& dependencies);
// Add dependencies for the files that the build produces among the given
// files. A file linked by a target depends on building that target. Any
// other generated file is attributed to the target owning the custom
+1 -1
View File
@@ -104,7 +104,7 @@ static void FinalAction(cmMakefile& makefile, std::string const& dest,
cmInstallGenerator::SelectMessageLevel(&makefile);
makefile.AddInstallGenerator(cm::make_unique<cmInstallFilesGenerator>(
files, destination, true, no_permissions, no_configurations, no_component,
message, no_exclude_from_all, no_rename, false,
std::vector<std::string>(), message, no_exclude_from_all, no_rename, false,
cmInstallGenerator::CaptureContext(makefile)));
}
+15 -1
View File
@@ -14,12 +14,14 @@
cmInstallScriptGenerator::cmInstallScriptGenerator(
std::string script, bool code, std::string const& component,
bool excludeFromAll, bool allComponents, cmListFileBacktrace backtrace)
bool excludeFromAll, bool allComponents,
std::vector<std::string> buildDepends, cmListFileBacktrace backtrace)
: cmInstallGenerator("", std::vector<std::string>(), component,
MessageDefault, excludeFromAll, allComponents,
cmDiagnosticContext{ std::move(backtrace) })
, Script(std::move(script))
, Code(code)
, BuildDepends(std::move(buildDepends))
{
// We need per-config actions if the script has generator expressions.
if (cmGeneratorExpression::Find(this->Script) != std::string::npos) {
@@ -32,6 +34,11 @@ cmInstallScriptGenerator::~cmInstallScriptGenerator() = default;
bool cmInstallScriptGenerator::Compute(cmLocalGenerator* lg)
{
this->LocalGenerator = lg;
if (!this->ResolveBuildDepends(lg, this->Code ? "CODE" : "SCRIPT",
this->BuildDepends,
this->ResolvedBuildDepends)) {
return false;
}
if (this->ActionsPerConfig) {
switch (this->LocalGenerator->GetPolicyStatus(cmPolicies::CMP0087)) {
@@ -49,6 +56,13 @@ bool cmInstallScriptGenerator::Compute(cmLocalGenerator* lg)
return true;
}
void cmInstallScriptGenerator::GetInstallBuildDependencies(
std::string const& config, std::vector<BuildDependency>& dependencies) const
{
AddBuildDependsDependencies(this->LocalGenerator, config,
this->ResolvedBuildDepends, dependencies);
}
std::string cmInstallScriptGenerator::GetScript(
std::string const& config) const
{
+8
View File
@@ -6,6 +6,7 @@
#include <iosfwd>
#include <string>
#include <vector>
#include "cmInstallGenerator.h"
#include "cmListFileCache.h"
@@ -21,11 +22,16 @@ public:
cmInstallScriptGenerator(
std::string script, bool code, std::string const& component,
bool excludeFromAll, bool allComponents,
std::vector<std::string> buildDepends = {},
cmListFileBacktrace backtrace = cmListFileBacktrace());
~cmInstallScriptGenerator() override;
bool Compute(cmLocalGenerator* lg) override;
void GetInstallBuildDependencies(
std::string const& config,
std::vector<BuildDependency>& dependencies) const override;
bool IsCode() const { return this->Code; }
std::string GetScript(std::string const& config) const;
@@ -39,6 +45,8 @@ protected:
std::string const Script;
bool const Code;
std::vector<std::string> const BuildDepends;
BuildDependsEntries ResolvedBuildDepends;
cmLocalGenerator* LocalGenerator;
bool AllowGenex = false;
};
@@ -0,0 +1,76 @@
enable_language(C)
# Files generated by custom targets can only be installed after the
# preparation target has built them.
set(file_payload "${CMAKE_CURRENT_BINARY_DIR}/file-payload.txt")
add_custom_target(generate_file_payload
COMMAND ${CMAKE_COMMAND} -E touch "${file_payload}"
BYPRODUCTS "${file_payload}"
)
install(FILES "${file_payload}"
DESTINATION share/install-prep
COMPONENT Payload
BUILD_DEPENDS generate_file_payload
)
set(code_payload "${CMAKE_CURRENT_BINARY_DIR}/code-payload.txt")
add_custom_target(generate_code_payload
COMMAND ${CMAKE_COMMAND} -E touch "${code_payload}"
BYPRODUCTS "${code_payload}"
)
install(CODE "
if(NOT EXISTS \"${code_payload}\")
message(FATAL_ERROR \"Preparation did not generate code-payload.txt\")
endif()
"
COMPONENT Payload
BUILD_DEPENDS generate_code_payload
)
# Directory content generated by a custom target can only be installed
# after the preparation target has generated it.
set(dir_payload "${CMAKE_CURRENT_BINARY_DIR}/dir-payload")
add_custom_target(generate_dir_payload
COMMAND ${CMAKE_COMMAND} -E make_directory "${dir_payload}"
COMMAND ${CMAKE_COMMAND} -E touch "${dir_payload}/dir-payload.txt"
)
install(DIRECTORY "${dir_payload}/"
DESTINATION share/install-prep
COMPONENT Payload
BUILD_DEPENDS generate_dir_payload
)
# BUILD_DEPENDS may also name files. A file that the build generates is
# prepared like an installed file, and an existing file needs no preparation.
set(code_file "${CMAKE_CURRENT_BINARY_DIR}/code-file.txt")
add_custom_command(OUTPUT "${code_file}"
COMMAND ${CMAKE_COMMAND} -E touch "${code_file}"
)
add_custom_target(generate_code_file DEPENDS "${code_file}")
install(CODE "
if(NOT EXISTS \"${code_file}\")
message(FATAL_ERROR \"Preparation did not generate code-file.txt\")
endif()
"
COMPONENT Payload
BUILD_DEPENDS "${code_file}" "${CMAKE_CURRENT_SOURCE_DIR}/lib.c"
)
add_subdirectory(build-depends)
# BUILD_DEPENDS may also name the artifact linked by a target.
set(artifact_dir "${CMAKE_CURRENT_BINARY_DIR}/artifacts")
add_library(depends_artifact STATIC EXCLUDE_FROM_ALL lib.c)
set_property(TARGET depends_artifact
PROPERTY ARCHIVE_OUTPUT_DIRECTORY "$<1:${artifact_dir}>")
set(depends_artifact_file "${artifact_dir}/${CMAKE_STATIC_LIBRARY_PREFIX}depends_artifact${CMAKE_STATIC_LIBRARY_SUFFIX}")
install(CODE "
if(NOT EXISTS \"${depends_artifact_file}\")
message(FATAL_ERROR \"Preparation did not build depends_artifact\")
endif()
"
COMPONENT Payload
BUILD_DEPENDS "${depends_artifact_file}"
)
add_library(unit_tests STATIC neverbuild.c)
@@ -122,6 +122,7 @@ run_install_prep(Imported install_prep/Imported Imported)
run_install_prep(Documentation install_prep/Documentation Documentation)
run_install_prep(ExcludedSubdirectory
install_prep/ExcludedSubdirectory ExcludedSubdirectory)
run_install_prep(BuildDepends install_prep/Payload Payload)
run_install_prep(GeneratedFileSetHeaders install_prep/Headers Headers)
run_install_prep(GeneratedTargetProperties install_prep/Headers Headers)
run_install_prep(GeneratedByOtherTarget install_prep/Headers Headers)
@@ -0,0 +1,15 @@
# Relative BUILD_DEPENDS files are interpreted with respect to this directory:
# the source directory if the file exists there, and otherwise the binary
# directory.
add_custom_command(OUTPUT relative-file.txt
COMMAND ${CMAKE_COMMAND} -E touch relative-file.txt
)
add_custom_target(generate_relative_file DEPENDS relative-file.txt)
install(CODE "
if(NOT EXISTS \"${CMAKE_CURRENT_BINARY_DIR}/relative-file.txt\")
message(FATAL_ERROR \"Preparation did not generate relative-file.txt\")
endif()
"
COMPONENT Payload
BUILD_DEPENDS relative-file.txt CMakeLists.txt
)
@@ -0,0 +1,2 @@
check_installed([[^$]])
check_prepared(all_components_dependency)
@@ -0,0 +1,9 @@
enable_language(C)
add_library(all_components_dependency STATIC obj1.c)
install(CODE [[message(STATUS "CODE-BUILD_DEPENDS-all-components executed")]]
BUILD_DEPENDS all_components_dependency ALL_COMPONENTS)
install(CODE "message(STATUS dev)" COMPONENT dev)
install(CODE "message(STATUS other)" COMPONENT other)
add_library(unrelated STATIC never-build.c)
@@ -0,0 +1,2 @@
check_installed([[^$]])
check_prepared(code_dependency_one code_dependency_two)
@@ -0,0 +1,13 @@
enable_language(C)
add_library(code_dependency_one STATIC obj1.c)
add_library(code_dependency_two STATIC obj1.c)
install(CODE [[message(STATUS "CODE-BUILD_DEPENDS executed")]]
BUILD_DEPENDS code_dependency_one
BUILD_DEPENDS code_dependency_two
COMPONENT dev)
add_library(unrelated STATIC never-build.c)
add_library(other_dependency STATIC never-build.c)
install(CODE "message(STATUS other)" BUILD_DEPENDS other_dependency COMPONENT other)
@@ -0,0 +1,2 @@
check_installed([[^empty\.txt$]])
check_prepared(dir_dependency_one dir_dependency_two)
@@ -0,0 +1,8 @@
^CMake Error at DIRECTORY-BUILD_DEPENDS-missing-target\.cmake:[0-9]+ \(install\):
install\(DIRECTORY\) BUILD_DEPENDS entry "missing_target" is neither a target
nor a file that exists or is generated by the build\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
CMake Generate step failed\. Build files cannot be regenerated correctly\.$
@@ -0,0 +1 @@
install(DIRECTORY dir/ DESTINATION . BUILD_DEPENDS missing_target)
@@ -0,0 +1,19 @@
enable_language(C)
install(DIRECTORY dir/
DESTINATION .
BUILD_DEPENDS dir_dependency_one
BUILD_DEPENDS dir_dependency_two
COMPONENT dev)
# Resolve dependency names during generation, after these targets are defined.
add_library(dir_dependency_one STATIC obj1.c)
add_library(dir_dependency_two STATIC obj1.c)
# Only rules for the selected component and configuration may add dependencies.
add_library(unrelated STATIC never-build.c)
add_library(other_dependency STATIC never-build.c)
install(DIRECTORY missing/ DESTINATION . OPTIONAL
BUILD_DEPENDS other_dependency COMPONENT other)
install(DIRECTORY missing/ DESTINATION . OPTIONAL
BUILD_DEPENDS other_dependency COMPONENT dev CONFIGURATIONS Release)
@@ -0,0 +1,4 @@
^CMake Error at DIRECTORY-PATTERN-BUILD_DEPENDS\.cmake:[0-9]+ \(install\):
install DIRECTORY does not allow "BUILD_DEPENDS" after PATTERN or REGEX\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)$
@@ -0,0 +1 @@
install(DIRECTORY src DESTINATION src PATTERN *.txt BUILD_DEPENDS dep)
@@ -0,0 +1,2 @@
check_installed([[^one\.txt$]])
check_prepared(alias_dependency)
@@ -0,0 +1,10 @@
enable_language(C)
add_library(alias_dependency STATIC obj1.c)
add_library(Upstream::alias_dependency ALIAS alias_dependency)
install(FILES one.txt DESTINATION .
BUILD_DEPENDS Upstream::alias_dependency
COMPONENT dev)
add_library(unrelated STATIC never-build.c)
@@ -0,0 +1,2 @@
check_installed([[^one\.txt;two\.txt$]])
check_prepared(file_dependency_one file_dependency_two)
@@ -0,0 +1,8 @@
^CMake Error at FILES-BUILD_DEPENDS-missing-target\.cmake:[0-9]+ \(install\):
install\(FILES\) BUILD_DEPENDS entry "missing_target" is neither a target nor
a file that exists or is generated by the build\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
CMake Generate step failed\. Build files cannot be regenerated correctly\.$
@@ -0,0 +1 @@
install(FILES empty.c DESTINATION . BUILD_DEPENDS missing_target)
@@ -0,0 +1,19 @@
enable_language(C)
install(FILES one.txt two.txt
DESTINATION .
BUILD_DEPENDS file_dependency_one
BUILD_DEPENDS file_dependency_two
COMPONENT dev)
# Resolve dependency names during generation, after these targets are defined.
add_library(file_dependency_one STATIC obj1.c)
add_library(file_dependency_two STATIC obj1.c)
# Only rules for the selected component and configuration may add dependencies.
add_library(unrelated STATIC never-build.c)
add_library(other_dependency STATIC never-build.c)
install(FILES missing.txt DESTINATION .
BUILD_DEPENDS other_dependency COMPONENT other)
install(FILES missing.txt DESTINATION .
BUILD_DEPENDS other_dependency COMPONENT dev CONFIGURATIONS Release)
@@ -0,0 +1,2 @@
check_installed([[^program\.sh$]])
check_prepared(program_dependency)
@@ -0,0 +1,8 @@
^CMake Error at PROGRAMS-BUILD_DEPENDS-missing-target\.cmake:[0-9]+ \(install\):
install\(PROGRAMS\) BUILD_DEPENDS entry "missing_target" is neither a target
nor a file that exists or is generated by the build\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
CMake Generate step failed\. Build files cannot be regenerated correctly\.$
@@ -0,0 +1 @@
install(PROGRAMS empty.c DESTINATION . BUILD_DEPENDS missing_target)
@@ -0,0 +1,10 @@
enable_language(C)
add_library(program_dependency STATIC obj1.c)
install(PROGRAMS program.sh
DESTINATION . BUILD_DEPENDS program_dependency COMPONENT tools)
add_library(unrelated STATIC never-build.c)
add_library(excluded_dependency STATIC never-build.c)
install(PROGRAMS missing.sh DESTINATION .
BUILD_DEPENDS excluded_dependency COMPONENT hidden EXCLUDE_FROM_ALL)
+66 -5
View File
@@ -3,20 +3,47 @@ include(RunCMake)
# Function to build and install a project. The latter step *-check.cmake
# scripts can check installed files using the check_installed function.
# Optional second and third arguments select an install component and configuration.
function(run_install_test case)
set(COMPONENT all)
set(COMPONENT_ARGS)
if(ARGC GREATER 1)
set(COMPONENT "${ARGV1}")
set(COMPONENT_ARGS "-DCOMPONENT=${COMPONENT}")
endif()
set(CONFIG Debug)
if(ARGC GREATER 2)
set(CONFIG "${ARGV2}")
endif()
set(RunCMake_TEST_CONFIG "")
if(RunCMake_GENERATOR_IS_MULTI_CONFIG)
set(RunCMake_TEST_CONFIG "${CONFIG}")
endif()
set(BUILD_ARGS)
if(run_install_test_prep)
set(BUILD_ARGS --target install_prep/${COMPONENT})
endif()
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${case}-build)
set(RunCMake_TEST_NO_CLEAN 1)
file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
set(RunCMake_TEST_RAW_ARGS -DCMAKE_BUILD_TYPE:STRING=Debug)
set(CMAKE_INSTALL_PREFIX ${RunCMake_TEST_BINARY_DIR}/root-${COMPONENT})
if(run_install_test_prep)
list(APPEND RunCMake_TEST_OPTIONS "-DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}")
endif()
set(RunCMake_TEST_RAW_ARGS -DCMAKE_BUILD_TYPE:STRING=${CONFIG})
run_cmake(${case})
unset(RunCMake_TEST_RAW_ARGS)
set(RunCMake_TEST_OUTPUT_MERGE 1)
run_cmake_command(${case}-build ${CMAKE_COMMAND} --build . --config Debug)
run_cmake_command(${case}-build ${CMAKE_COMMAND} --build . --config ${CONFIG} ${BUILD_ARGS})
unset(RunCMake_TEST_OUTPUT_MERGE)
# Check "all" components.
set(CMAKE_INSTALL_PREFIX ${RunCMake_TEST_BINARY_DIR}/root-all)
run_cmake_command(${case}-all ${CMAKE_COMMAND} -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} -DBUILD_TYPE=Debug -P cmake_install.cmake)
if(run_install_test_prep)
if(EXISTS "${CMAKE_INSTALL_PREFIX}")
message(SEND_ERROR "${case}: preparation ran installation.")
endif()
set(RunCMake-check-file ${case}-install-check.cmake)
endif()
run_cmake_command(${case}-${COMPONENT} ${CMAKE_COMMAND} -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} -DBUILD_TYPE=${CONFIG} ${COMPONENT_ARGS} -P cmake_install.cmake)
if(run_install_test_components)
# Check unspecified component.
@@ -68,6 +95,19 @@ function(check_namelink link expect)
set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}" PARENT_SCOPE)
endfunction()
# Function called in *-check.cmake scripts to check that preparing for
# installation built the given static library targets.
function(check_prepared)
foreach(target IN LISTS ARGN)
if(NOT EXISTS "${RunCMake_TEST_BINARY_DIR}/${RunCMake_TEST_CONFIG}/lib${target}.a" AND
NOT EXISTS "${RunCMake_TEST_BINARY_DIR}/${RunCMake_TEST_CONFIG}/${target}.lib" AND
NOT EXISTS "${RunCMake_TEST_BINARY_DIR}/${RunCMake_TEST_CONFIG}/${target}.l")
set(RunCMake_TEST_FAILED "Build did not produce ${target}." PARENT_SCOPE)
return()
endif()
endforeach()
endfunction()
# Wrapper for run_cmake() that skips platforms on which we do not support editing the RPATH.
function(run_cmake_EDIT_RPATH_only case)
if(UNIX AND CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG AND CMAKE_EXECUTABLE_FORMAT MATCHES "^(ELF|XCOFF)$")
@@ -93,6 +133,27 @@ if (CMake_TEST_install_VARIANT STREQUAL "Files")
run_cmake(SkipInstallRulesNoWarning1)
run_cmake(SkipInstallRulesNoWarning2)
run_cmake(SCRIPT-CODE-arguments)
run_cmake(DIRECTORY-PATTERN-BUILD_DEPENDS)
run_cmake(SCRIPT-BUILD_DEPENDS-missing-target)
run_cmake(FILES-BUILD_DEPENDS-missing-target)
run_cmake(PROGRAMS-BUILD_DEPENDS-missing-target)
run_cmake(DIRECTORY-BUILD_DEPENDS-missing-target)
if(RunCMake_GENERATOR MATCHES "Ninja|Make|FASTBuild")
block()
set(run_install_test_prep 1)
run_install_test(CODE-BUILD_DEPENDS dev)
run_install_test(CODE-BUILD_DEPENDS-all-components)
run_install_test(CODE-BUILD_DEPENDS-all-components dev)
run_install_test(CODE-BUILD_DEPENDS-all-components other)
run_install_test(SCRIPT-BUILD_DEPENDS)
run_install_test(SCRIPT-BUILD_DEPENDS Unspecified)
run_install_test(FILES-BUILD_DEPENDS dev)
run_install_test(PROGRAMS-BUILD_DEPENDS)
run_install_test(PROGRAMS-BUILD_DEPENDS tools)
run_install_test(FILES-BUILD_DEPENDS-alias dev)
run_install_test(DIRECTORY-BUILD_DEPENDS dev)
endblock()
endif()
run_cmake(DIRECTORY-DIRECTORY-bad)
run_cmake(DIRECTORY-DESTINATION-bad)
run_cmake(FILES-DESTINATION-bad)
@@ -0,0 +1,2 @@
check_installed([[^empty1\.txt$]])
check_prepared(script_dependency)
@@ -0,0 +1,8 @@
^CMake Error at SCRIPT-BUILD_DEPENDS-missing-target\.cmake:[0-9]+ \(install\):
install\(SCRIPT\) BUILD_DEPENDS entry "missing_target" is neither a target
nor a file that exists or is generated by the build\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
CMake Generate step failed\. Build files cannot be regenerated correctly\.$
@@ -0,0 +1,3 @@
install(SCRIPT install_script.cmake
BUILD_DEPENDS missing_target
)
@@ -0,0 +1,10 @@
enable_language(C)
add_library(script_dependency STATIC obj1.c)
install(SCRIPT install_script.cmake BUILD_DEPENDS script_dependency)
# Full preparation must omit both unrelated and excluded dependencies.
add_library(unrelated STATIC never-build.c)
add_library(excluded_dependency STATIC never-build.c)
install(CODE "message(STATUS hidden)" BUILD_DEPENDS excluded_dependency
COMPONENT hidden EXCLUDE_FROM_ALL)
+1
View File
@@ -0,0 +1 @@
#error install_prep built an unexpected target
+1
View File
@@ -0,0 +1 @@
one
+2
View File
@@ -0,0 +1,2 @@
#!/bin/sh
exit 0
+1
View File
@@ -0,0 +1 @@
script payload
+1
View File
@@ -0,0 +1 @@
two