From 082ccd75301cd02efd09bb9882bb9d2a8957193b Mon Sep 17 00:00:00 2001 From: Abdelmaged Khalifa Date: Sun, 12 Feb 2023 01:00:40 +0200 Subject: 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 --- Help/command/add_custom_command.rst | 18 +++++++- Help/release/dev/ninja-custom-command-depends.rst | 7 +++ Source/cmAddCustomCommandCommand.cxx | 8 +++- Source/cmCustomCommand.cxx | 10 +++++ Source/cmCustomCommand.h | 6 +++ Source/cmLocalNinjaGenerator.cxx | 52 +++++++++++----------- .../Ninja/CustomCommandExplicitDepends.cmake | 18 ++++++++ Tests/RunCMake/Ninja/RunCMakeTest.cmake | 12 +++++ 8 files changed, 104 insertions(+), 27 deletions(-) create mode 100644 Help/release/dev/ninja-custom-command-depends.rst create mode 100644 Tests/RunCMake/Ninja/CustomCommandExplicitDepends.cmake diff --git a/Help/command/add_custom_command.rst b/Help/command/add_custom_command.rst index 293d3f0..2cf88fc 100644 --- a/Help/command/add_custom_command.rst +++ b/Help/command/add_custom_command.rst @@ -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 ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/Help/release/dev/ninja-custom-command-depends.rst b/Help/release/dev/ninja-custom-command-depends.rst new file mode 100644 index 0000000..10c68cf --- /dev/null +++ b/Help/release/dev/ninja-custom-command-depends.rst @@ -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. diff --git a/Source/cmAddCustomCommandCommand.cxx b/Source/cmAddCustomCommandCommand.cxx index 831e9c7..65a8eb5 100644 --- a/Source/cmAddCustomCommandCommand.cxx +++ b/Source/cmAddCustomCommandCommand.cxx @@ -49,6 +49,7 @@ bool cmAddCustomCommandCommand(std::vector 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 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 const keywords{ keyAPPEND, @@ -126,7 +128,8 @@ bool cmAddCustomCommandCommand(std::vector 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 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 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)); diff --git a/Source/cmCustomCommand.cxx b/Source/cmCustomCommand.cxx index 5b63996..e12cf70 100644 --- a/Source/cmCustomCommand.cxx +++ b/Source/cmCustomCommand.cxx @@ -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; diff --git a/Source/cmCustomCommand.h b/Source/cmCustomCommand.h index 3671ad9..1e68dbf 100644 --- a/Source/cmCustomCommand.h +++ b/Source/cmCustomCommand.h @@ -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. diff --git a/Source/cmLocalNinjaGenerator.cxx b/Source/cmLocalNinjaGenerator.cxx index 1e2ea2a..f8027c0 100644 --- a/Source/cmLocalNinjaGenerator.cxx +++ b/Source/cmLocalNinjaGenerator.cxx @@ -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 jDeps; - std::vector 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 jDeps; + std::vector 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& outputs = ccg.GetOutputs(); diff --git a/Tests/RunCMake/Ninja/CustomCommandExplicitDepends.cmake b/Tests/RunCMake/Ninja/CustomCommandExplicitDepends.cmake new file mode 100644 index 0000000..e0c2434 --- /dev/null +++ b/Tests/RunCMake/Ninja/CustomCommandExplicitDepends.cmake @@ -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) diff --git a/Tests/RunCMake/Ninja/RunCMakeTest.cmake b/Tests/RunCMake/Ninja/RunCMakeTest.cmake index 9214e90..17ad035 100644 --- a/Tests/RunCMake/Ninja/RunCMakeTest.cmake +++ b/Tests/RunCMake/Ninja/RunCMakeTest.cmake @@ -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) -- cgit v0.12