add_custom_command: Add DEPENDS_EXPLICIT_ONLY option for Ninja

Add option `DEPENDS_EXPLICIT_ONLY` to `add_custom_command` to indicate
that implicit dependencies coming from users of the output are not
needed, and only consider dependencies explicitly specified in the
custom command.

Fixes: #17097
This commit is contained in:
Abdelmaged Khalifa 2023-02-12 01:00:40 +02:00 committed by Brad King
parent 48c69eeafe
commit 082ccd7530
8 changed files with 104 additions and 27 deletions

View File

@ -25,7 +25,8 @@ The first signature is for adding a custom command to produce an output:
[DEPFILE depfile]
[JOB_POOL job_pool]
[VERBATIM] [APPEND] [USES_TERMINAL]
[COMMAND_EXPAND_LISTS])
[COMMAND_EXPAND_LISTS]
[DEPENDS_EXPLICIT_ONLY])
This defines a command to generate specified ``OUTPUT`` file(s).
A target created in the same directory (``CMakeLists.txt`` file)
@ -357,6 +358,21 @@ The options are:
:ref:`Makefile Generators`, :ref:`Visual Studio Generators`,
and the :generator:`Xcode` generator.
``DEPENDS_EXPLICIT_ONLY``
.. versionadded:: 3.27
Indicate that the command's ``DEPENDS`` argument represents all files
required by the command and implicit dependencies are not required.
Without this option, if any target uses the output of the custom command,
CMake will consider that target's dependencies as implicit dependencies for
the custom command in case this custom command requires files implicitly
created by those targets.
Only the :ref:`Ninja Generators` actually use this information to remove
unnecessary implicit dependencies.
Examples: Generating Files
^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -0,0 +1,7 @@
ninja-custom-command-depends
----------------------------
* The :command:`add_custom_command` command gained a new
``DEPENDS_EXPLICIT_ONLY`` option to tell the :ref:`Ninja Generators`
not to add any dependencies implied by the target to which it is
attached.

View File

@ -49,6 +49,7 @@ bool cmAddCustomCommandCommand(std::vector<std::string> const& args,
bool append = false;
bool uses_terminal = false;
bool command_expand_lists = false;
bool depends_explicit_only = false;
std::string implicit_depends_lang;
cmImplicitDependsList implicit_depends;
@ -104,6 +105,7 @@ bool cmAddCustomCommandCommand(std::vector<std::string> const& args,
MAKE_STATIC_KEYWORD(USES_TERMINAL);
MAKE_STATIC_KEYWORD(VERBATIM);
MAKE_STATIC_KEYWORD(WORKING_DIRECTORY);
MAKE_STATIC_KEYWORD(DEPENDS_EXPLICIT_ONLY);
#undef MAKE_STATIC_KEYWORD
static std::unordered_set<std::string> const keywords{
keyAPPEND,
@ -126,7 +128,8 @@ bool cmAddCustomCommandCommand(std::vector<std::string> const& args,
keyTARGET,
keyUSES_TERMINAL,
keyVERBATIM,
keyWORKING_DIRECTORY
keyWORKING_DIRECTORY,
keyDEPENDS_EXPLICIT_ONLY
};
for (std::string const& copy : args) {
@ -155,6 +158,8 @@ bool cmAddCustomCommandCommand(std::vector<std::string> const& args,
uses_terminal = true;
} else if (copy == keyCOMMAND_EXPAND_LISTS) {
command_expand_lists = true;
} else if (copy == keyDEPENDS_EXPLICIT_ONLY) {
depends_explicit_only = true;
} else if (copy == keyTARGET) {
doing = doing_target;
} else if (copy == keyARGS) {
@ -329,6 +334,7 @@ bool cmAddCustomCommandCommand(std::vector<std::string> const& args,
cc->SetDepfile(depfile);
cc->SetJobPool(job_pool);
cc->SetCommandExpandLists(command_expand_lists);
cc->SetDependsExplicitOnly(depends_explicit_only);
if (source.empty() && output.empty()) {
// Source is empty, use the target.
mf.AddCustomCommandToTarget(target, cctype, std::move(cc));

View File

@ -164,6 +164,16 @@ void cmCustomCommand::SetCommandExpandLists(bool b)
this->CommandExpandLists = b;
}
bool cmCustomCommand::GetDependsExplicitOnly() const
{
return this->DependsExplicitOnly;
}
void cmCustomCommand::SetDependsExplicitOnly(bool b)
{
this->DependsExplicitOnly = b;
}
const std::string& cmCustomCommand::GetDepfile() const
{
return this->Depfile;

View File

@ -102,6 +102,11 @@ public:
bool GetCommandExpandLists() const;
void SetCommandExpandLists(bool b);
/** Set/Get whether to use additional dependencies coming from
users of OUTPUT of the custom command. */
bool GetDependsExplicitOnly() const;
void SetDependsExplicitOnly(bool b);
/** Set/Get the depfile (used by the Ninja generator) */
const std::string& GetDepfile() const;
void SetDepfile(const std::string& depfile);
@ -141,6 +146,7 @@ private:
bool CommandExpandLists = false;
bool StdPipesUTF8 = false;
bool HasMainDependency_ = false;
bool DependsExplicitOnly = false;
// Policies are NEW for synthesized custom commands, and set by cmMakefile for
// user-created custom commands.

View File

@ -586,32 +586,34 @@ void cmLocalNinjaGenerator::WriteCustomCommandBuildStatement(
cmNinjaDeps orderOnlyDeps;
// A custom command may appear on multiple targets. However, some build
// systems exist where the target dependencies on some of the targets are
// overspecified, leading to a dependency cycle. If we assume all target
// dependencies are a superset of the true target dependencies for this
// custom command, we can take the set intersection of all target
// dependencies to obtain a correct dependency list.
//
// FIXME: This won't work in certain obscure scenarios involving indirect
// dependencies.
auto j = targets.begin();
assert(j != targets.end());
this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure(
*j, orderOnlyDeps, ccg.GetOutputConfig(), fileConfig, ccgs.size() > 1);
std::sort(orderOnlyDeps.begin(), orderOnlyDeps.end());
++j;
for (; j != targets.end(); ++j) {
std::vector<std::string> jDeps;
std::vector<std::string> depsIntersection;
if (!cc->GetDependsExplicitOnly()) {
// A custom command may appear on multiple targets. However, some build
// systems exist where the target dependencies on some of the targets are
// overspecified, leading to a dependency cycle. If we assume all target
// dependencies are a superset of the true target dependencies for this
// custom command, we can take the set intersection of all target
// dependencies to obtain a correct dependency list.
//
// FIXME: This won't work in certain obscure scenarios involving indirect
// dependencies.
auto j = targets.begin();
assert(j != targets.end());
this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure(
*j, jDeps, ccg.GetOutputConfig(), fileConfig, ccgs.size() > 1);
std::sort(jDeps.begin(), jDeps.end());
std::set_intersection(orderOnlyDeps.begin(), orderOnlyDeps.end(),
jDeps.begin(), jDeps.end(),
std::back_inserter(depsIntersection));
orderOnlyDeps = depsIntersection;
*j, orderOnlyDeps, ccg.GetOutputConfig(), fileConfig, ccgs.size() > 1);
std::sort(orderOnlyDeps.begin(), orderOnlyDeps.end());
++j;
for (; j != targets.end(); ++j) {
std::vector<std::string> jDeps;
std::vector<std::string> depsIntersection;
this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure(
*j, jDeps, ccg.GetOutputConfig(), fileConfig, ccgs.size() > 1);
std::sort(jDeps.begin(), jDeps.end());
std::set_intersection(orderOnlyDeps.begin(), orderOnlyDeps.end(),
jDeps.begin(), jDeps.end(),
std::back_inserter(depsIntersection));
orderOnlyDeps = depsIntersection;
}
}
const std::vector<std::string>& outputs = ccg.GetOutputs();

View File

@ -0,0 +1,18 @@
cmake_minimum_required(VERSION 3.26)
project(CustomCommandExplicitDepends C)
add_custom_command(
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/command.h"
COMMAND "${CMAKE_COMMAND}" -E touch
"${CMAKE_CURRENT_BINARY_DIR}/command.h"
COMMENT "Creating command.h"
DEPENDS_EXPLICIT_ONLY
)
add_library(dep STATIC dep.c)
add_library(top STATIC
top.c
"${CMAKE_CURRENT_BINARY_DIR}/command.h"
)
target_link_libraries(top PRIVATE dep)

View File

@ -190,6 +190,18 @@ function (run_LooseObjectDepends)
endfunction ()
run_LooseObjectDepends()
function (run_CustomCommandExplictDepends)
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/CustomCommandExplicitDepends-build)
run_cmake(CustomCommandExplicitDepends)
run_ninja("${RunCMake_TEST_BINARY_DIR}" "command.h")
if (EXISTS "${RunCMake_TEST_BINARY_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}dep${CMAKE_STATIC_LIBRARY_SUFFIX}")
message(FATAL_ERROR
"The `dep` library was created when requesting an custom command to be "
"generated; this should no longer be necessary when passing DEPENDS_EXPLICIT_ONLY keyword.")
endif ()
endfunction ()
run_CustomCommandExplictDepends()
function (run_AssumedSources)
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/AssumedSources-build)
run_cmake(AssumedSources)