浏览代码

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
Abdelmaged Khalifa 2 年之前
父节点
当前提交
082ccd7530

+ 17 - 1
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
 ^^^^^^^^^^^^^^^^^^^^^^^^^^
 

+ 7 - 0
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.

+ 7 - 1
Source/cmAddCustomCommandCommand.cxx

@@ -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));

+ 10 - 0
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;

+ 6 - 0
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.

+ 27 - 25
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<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();

+ 18 - 0
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)

+ 12 - 0
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)