Explorar o código

Ninja: Allow compilation before generation of dependencies' private sources

This requires knowing when a generated header is public, which we can
model using file sets.  Add policy CMP0154 to treat generated sources
as private by default in targets with file sets.  Generated public
headers can be specified in public file sets.

Fixes: #24959
Issue: #15555
Martin Duffy %!s(int64=2) %!d(string=hai) anos
pai
achega
ec2ba29ac5
Modificáronse 33 ficheiros con 320 adicións e 15 borrados
  1. 10 0
      Help/command/add_custom_command.rst
  2. 5 0
      Help/command/add_custom_target.rst
  3. 2 0
      Help/command/target_sources.rst
  4. 1 0
      Help/manual/cmake-policies.7.rst
  5. 53 0
      Help/policy/CMP0154.rst
  6. 7 0
      Help/release/dev/fileset-private-dep.rst
  7. 14 0
      Source/cmGlobalNinjaGenerator.cxx
  8. 6 0
      Source/cmGlobalNinjaGenerator.h
  9. 81 13
      Source/cmNinjaTargetGenerator.cxx
  10. 2 0
      Source/cmNinjaTargetGenerator.h
  11. 7 2
      Source/cmPolicies.h
  12. 5 0
      Source/cmTarget.cxx
  13. 2 0
      Source/cmTarget.h
  14. 3 0
      Tests/RunCMake/CMakeLists.txt
  15. 3 0
      Tests/RunCMake/NinjaPrivateDeps/CMP0154-NEW-Build-EmptyFileSet-Source-check.cmake
  16. 3 0
      Tests/RunCMake/NinjaPrivateDeps/CMP0154-NEW-Build-EmptyFileSet-check.cmake
  17. 3 0
      Tests/RunCMake/NinjaPrivateDeps/CMP0154-NEW-Build-NoFileSet-Source-check.cmake
  18. 3 0
      Tests/RunCMake/NinjaPrivateDeps/CMP0154-NEW-Build-PrivateFileSet-Source-check.cmake
  19. 3 0
      Tests/RunCMake/NinjaPrivateDeps/CMP0154-NEW-Build-PrivateFileSet-check.cmake
  20. 3 0
      Tests/RunCMake/NinjaPrivateDeps/CMP0154-NEW-Build-PublicFileSet-Source-check.cmake
  21. 2 0
      Tests/RunCMake/NinjaPrivateDeps/CMP0154-NEW.cmake
  22. 3 0
      Tests/RunCMake/NinjaPrivateDeps/CMP0154-OLD-Build-EmptyFileSet-Source-check.cmake
  23. 3 0
      Tests/RunCMake/NinjaPrivateDeps/CMP0154-OLD-Build-PrivateFileSet-Source-check.cmake
  24. 2 0
      Tests/RunCMake/NinjaPrivateDeps/CMP0154-OLD.cmake
  25. 45 0
      Tests/RunCMake/NinjaPrivateDeps/CMP0154-common.cmake
  26. 3 0
      Tests/RunCMake/NinjaPrivateDeps/CMakeLists.txt
  27. 30 0
      Tests/RunCMake/NinjaPrivateDeps/RunCMakeTest.cmake
  28. 0 0
      Tests/RunCMake/NinjaPrivateDeps/header.h.in
  29. 6 0
      Tests/RunCMake/NinjaPrivateDeps/hello.cpp
  30. 8 0
      Tests/RunCMake/NinjaPrivateDeps/hello_lib.cpp
  31. 1 0
      Tests/RunCMake/NinjaPrivateDeps/hello_lib.h
  32. 0 0
      Tests/RunCMake/NinjaPrivateDeps/source.cpp.in
  33. 1 0
      Tests/RunCMake/TargetPolicies/PolicyList-stderr.txt

+ 10 - 0
Help/command/add_custom_command.rst

@@ -87,6 +87,11 @@ The options are:
     :ref:`Target-dependent expressions <Target-Dependent Queries>` are not
     :ref:`Target-dependent expressions <Target-Dependent Queries>` are not
     permitted.
     permitted.
 
 
+  .. versionchanged:: 3.28
+    In targets using :ref:`file sets`, custom command byproducts are now
+    considered private unless they are listed in a non-private file set.
+    See policy :policy:`CMP0154`.
+
 ``COMMAND``
 ``COMMAND``
   Specify the command-line(s) to execute at build time.
   Specify the command-line(s) to execute at build time.
   If more than one ``COMMAND`` is specified they will be executed in order,
   If more than one ``COMMAND`` is specified they will be executed in order,
@@ -270,6 +275,11 @@ The options are:
     :ref:`Target-dependent expressions <Target-Dependent Queries>` are not
     :ref:`Target-dependent expressions <Target-Dependent Queries>` are not
     permitted.
     permitted.
 
 
+  .. versionchanged:: 3.28
+    In targets using :ref:`file sets`, custom command outputs are now
+    considered private unless they are listed in a non-private file set.
+    See policy :policy:`CMP0154`.
+
 ``USES_TERMINAL``
 ``USES_TERMINAL``
   .. versionadded:: 3.2
   .. versionadded:: 3.2
 
 

+ 5 - 0
Help/command/add_custom_target.rst

@@ -63,6 +63,11 @@ The options are:
     :ref:`Target-dependent expressions <Target-Dependent Queries>` are not
     :ref:`Target-dependent expressions <Target-Dependent Queries>` are not
     permitted.
     permitted.
 
 
+  .. versionchanged:: 3.28
+    In custom targets using :ref:`file sets`, byproducts are now
+    considered private unless they are listed in a non-private file set.
+    See policy :policy:`CMP0154`.
+
 ``COMMAND``
 ``COMMAND``
   Specify the command-line(s) to execute at build time.
   Specify the command-line(s) to execute at build time.
   If more than one ``COMMAND`` is specified they will be executed in order,
   If more than one ``COMMAND`` is specified they will be executed in order,

+ 2 - 0
Help/command/target_sources.rst

@@ -60,6 +60,8 @@ expressions to ensure the sources are correctly assigned to the target.
 See the :manual:`cmake-buildsystem(7)` manual for more on defining
 See the :manual:`cmake-buildsystem(7)` manual for more on defining
 buildsystem properties.
 buildsystem properties.
 
 
+.. _`File Sets`:
+
 File Sets
 File Sets
 ^^^^^^^^^
 ^^^^^^^^^
 
 

+ 1 - 0
Help/manual/cmake-policies.7.rst

@@ -57,6 +57,7 @@ Policies Introduced by CMake 3.28
 .. toctree::
 .. toctree::
    :maxdepth: 1
    :maxdepth: 1
 
 
+   CMP0154: Generated files are private by default in targets using file sets. </policy/CMP0154>
    CMP0153: The exec_program command should not be called. </policy/CMP0153>
    CMP0153: The exec_program command should not be called. </policy/CMP0153>
    CMP0152: file(REAL_PATH) resolves symlinks before collapsing ../ components.  </policy/CMP0152>
    CMP0152: file(REAL_PATH) resolves symlinks before collapsing ../ components.  </policy/CMP0152>
 
 

+ 53 - 0
Help/policy/CMP0154.rst

@@ -0,0 +1,53 @@
+CMP0154
+-------
+
+.. versionadded:: 3.28
+
+Generated files are private by default in targets using :ref:`file sets`.
+
+CMake 3.27 and below assume that any file generated as an output or byproduct
+of :command:`add_custom_command` or :command:`add_custom_target` may be a
+public header file meant for inclusion by dependents' source files.  This
+requires :ref:`Ninja Generators` to add conservative order-only dependencies
+that prevent a target's source files from compiling before custom commands
+from the target's dependencies are finished, even if those custom commands
+only produce sources private to their own target.
+
+:ref:`File Sets`, introduced by CMake 3.23, provide a way to express the
+visibility of generated header files.  CMake 3.28 and above prefer to
+assume that, in targets using file sets, generated files are private to
+their own target by default.  Generated public headers must be specified
+as members of a ``PUBLIC`` (or ``INTERFACE``) ``FILE_SET``, typically of
+type ``HEADERS``.  With this information, :ref:`Ninja Generators` may omit
+the above-mentioned conservative dependencies and produce more efficient
+build graphs.
+
+This policy provides compatibility for projects using file sets in targets
+with generated header files that have not been updated.  Such projects
+should be updated to express generated public headers in a file set.
+For example:
+
+.. code-block:: cmake
+
+  add_custom_command(
+    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/foo.h
+    ...
+  )
+  target_sources(foo
+    PUBLIC FILE_SET HEADERS
+      BASE_DIRS ${CMAKE_CURRENT_BINARY_DIR}
+      FILES     ${CMAKE_CURRENT_BINARY_DIR}/foo.h
+  )
+
+The ``OLD`` behavior for this policy is to assume generated files are
+public, even in targets using file sets, and for :ref:`Ninja Generators`
+to produce conservative build graphs.  The ``NEW`` behavior for this
+policy is to assume generated files are private in targets using file sets,
+and for :ref:`Ninja Generators` to produce more efficient build graphs.
+
+This policy was introduced in CMake version 3.28.  Use the
+:command:`cmake_policy` command to set it to ``OLD`` or ``NEW`` explicitly.
+Unlike many policies, CMake version |release| does *not* warn
+when this policy is not set and simply uses ``OLD`` behavior.
+
+.. include:: DEPRECATED.txt

+ 7 - 0
Help/release/dev/fileset-private-dep.rst

@@ -0,0 +1,7 @@
+fileset-private-dep
+-------------------
+
+* Generated files, in targets using :ref:`file sets`, are now considered
+  private by default.  Generated public headers must be specified using
+  file sets.  This allows :ref:`Ninja Generators` to produce more
+  efficient build graphs.  See policy :policy:`CMP0154`.

+ 14 - 0
Source/cmGlobalNinjaGenerator.cxx

@@ -1278,6 +1278,13 @@ std::string cmGlobalNinjaGenerator::OrderDependsTargetForTarget(
   return cmStrCat("cmake_object_order_depends_target_", target->GetName());
   return cmStrCat("cmake_object_order_depends_target_", target->GetName());
 }
 }
 
 
+std::string cmGlobalNinjaGenerator::OrderDependsTargetForTargetPrivate(
+  cmGeneratorTarget const* target, const std::string& config) const
+{
+  return cmStrCat(this->OrderDependsTargetForTarget(target, config),
+                  "_private");
+}
+
 void cmGlobalNinjaGenerator::AppendTargetOutputs(
 void cmGlobalNinjaGenerator::AppendTargetOutputs(
   cmGeneratorTarget const* target, cmNinjaDeps& outputs,
   cmGeneratorTarget const* target, cmNinjaDeps& outputs,
   const std::string& config, cmNinjaTargetDepends depends) const
   const std::string& config, cmNinjaTargetDepends depends) const
@@ -3176,3 +3183,10 @@ std::string cmGlobalNinjaMultiGenerator::OrderDependsTargetForTarget(
   return cmStrCat("cmake_object_order_depends_target_", target->GetName(), '_',
   return cmStrCat("cmake_object_order_depends_target_", target->GetName(), '_',
                   cmSystemTools::UpperCase(config));
                   cmSystemTools::UpperCase(config));
 }
 }
+
+std::string cmGlobalNinjaMultiGenerator::OrderDependsTargetForTargetPrivate(
+  cmGeneratorTarget const* target, const std::string& config) const
+{
+  return cmStrCat(this->OrderDependsTargetForTarget(target, config),
+                  "_private");
+}

+ 6 - 0
Source/cmGlobalNinjaGenerator.h

@@ -349,6 +349,9 @@ public:
   virtual std::string OrderDependsTargetForTarget(
   virtual std::string OrderDependsTargetForTarget(
     cmGeneratorTarget const* target, const std::string& config) const;
     cmGeneratorTarget const* target, const std::string& config) const;
 
 
+  virtual std::string OrderDependsTargetForTargetPrivate(
+    cmGeneratorTarget const* target, const std::string& config) const;
+
   void AppendTargetOutputs(cmGeneratorTarget const* target,
   void AppendTargetOutputs(cmGeneratorTarget const* target,
                            cmNinjaDeps& outputs, const std::string& config,
                            cmNinjaDeps& outputs, const std::string& config,
                            cmNinjaTargetDepends depends) const;
                            cmNinjaTargetDepends depends) const;
@@ -738,6 +741,9 @@ public:
   std::string OrderDependsTargetForTarget(
   std::string OrderDependsTargetForTarget(
     cmGeneratorTarget const* target, const std::string& config) const override;
     cmGeneratorTarget const* target, const std::string& config) const override;
 
 
+  std::string OrderDependsTargetForTargetPrivate(
+    cmGeneratorTarget const* target, const std::string& config) const override;
+
 protected:
 protected:
   bool OpenBuildFileStreams() override;
   bool OpenBuildFileStreams() override;
   void CloseBuildFileStreams() override;
   void CloseBuildFileStreams() override;

+ 81 - 13
Source/cmNinjaTargetGenerator.cxx

@@ -38,6 +38,7 @@
 #include "cmNinjaNormalTargetGenerator.h"
 #include "cmNinjaNormalTargetGenerator.h"
 #include "cmNinjaUtilityTargetGenerator.h"
 #include "cmNinjaUtilityTargetGenerator.h"
 #include "cmOutputConverter.h"
 #include "cmOutputConverter.h"
+#include "cmPolicies.h"
 #include "cmRange.h"
 #include "cmRange.h"
 #include "cmRulePlaceholderExpander.h"
 #include "cmRulePlaceholderExpander.h"
 #include "cmSourceFile.h"
 #include "cmSourceFile.h"
@@ -169,6 +170,13 @@ std::string cmNinjaTargetGenerator::OrderDependsTargetForTarget(
     this->GeneratorTarget, config);
     this->GeneratorTarget, config);
 }
 }
 
 
+std::string cmNinjaTargetGenerator::OrderDependsTargetForTargetPrivate(
+  const std::string& config)
+{
+  return this->GetGlobalGenerator()->OrderDependsTargetForTargetPrivate(
+    this->GeneratorTarget, config);
+}
+
 // TODO: Most of the code is picked up from
 // TODO: Most of the code is picked up from
 // void cmMakefileExecutableTargetGenerator::WriteExecutableRule(bool relink),
 // void cmMakefileExecutableTargetGenerator::WriteExecutableRule(bool relink),
 // void cmMakefileTargetGenerator::WriteTargetLanguageFlags()
 // void cmMakefileTargetGenerator::WriteTargetLanguageFlags()
@@ -1020,6 +1028,45 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatements(
       cmStrCat("Order-only phony target for ", this->GetTargetName());
       cmStrCat("Order-only phony target for ", this->GetTargetName());
     build.Outputs.push_back(this->OrderDependsTargetForTarget(config));
     build.Outputs.push_back(this->OrderDependsTargetForTarget(config));
 
 
+    // Gather order-only dependencies on custom command outputs.
+    std::vector<std::string> ccouts;
+    std::vector<std::string> ccouts_private;
+    bool usePrivateGeneratedSources = false;
+    if (this->GeneratorTarget->Target->HasFileSets()) {
+      switch (this->GetGeneratorTarget()->GetPolicyStatusCMP0154()) {
+        case cmPolicies::WARN:
+        case cmPolicies::OLD:
+          break;
+        case cmPolicies::REQUIRED_ALWAYS:
+        case cmPolicies::REQUIRED_IF_USED:
+        case cmPolicies::NEW:
+          usePrivateGeneratedSources = true;
+          break;
+      }
+    }
+    for (cmCustomCommand const* cc : customCommands) {
+      cmCustomCommandGenerator ccg(*cc, config, this->GetLocalGenerator());
+      const std::vector<std::string>& ccoutputs = ccg.GetOutputs();
+      const std::vector<std::string>& ccbyproducts = ccg.GetByproducts();
+      ccouts.insert(ccouts.end(), ccoutputs.begin(), ccoutputs.end());
+      ccouts.insert(ccouts.end(), ccbyproducts.begin(), ccbyproducts.end());
+      if (usePrivateGeneratedSources) {
+        auto it = ccouts.begin();
+        while (it != ccouts.end()) {
+          cmFileSet const* fileset =
+            this->GeneratorTarget->GetFileSetForSource(
+              config, this->Makefile->GetOrCreateGeneratedSource(*it));
+          if (fileset &&
+              fileset->GetVisibility() != cmFileSetVisibility::Private) {
+            ++it;
+          } else {
+            ccouts_private.push_back(*it);
+            it = ccouts.erase(it);
+          }
+        }
+      }
+    }
+
     cmNinjaDeps& orderOnlyDeps = build.OrderOnlyDeps;
     cmNinjaDeps& orderOnlyDeps = build.OrderOnlyDeps;
     this->GetLocalGenerator()->AppendTargetDepends(
     this->GetLocalGenerator()->AppendTargetDepends(
       this->GeneratorTarget, orderOnlyDeps, config, fileConfig,
       this->GeneratorTarget, orderOnlyDeps, config, fileConfig,
@@ -1029,17 +1076,8 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatements(
     cm::append(orderOnlyDeps, this->Configs[config].ExtraFiles);
     cm::append(orderOnlyDeps, this->Configs[config].ExtraFiles);
 
 
     // Add order-only dependencies on custom command outputs.
     // Add order-only dependencies on custom command outputs.
-    for (cmCustomCommand const* cc : customCommands) {
-      cmCustomCommandGenerator ccg(*cc, config, this->GetLocalGenerator());
-      const std::vector<std::string>& ccoutputs = ccg.GetOutputs();
-      const std::vector<std::string>& ccbyproducts = ccg.GetByproducts();
-      std::transform(ccoutputs.begin(), ccoutputs.end(),
-                     std::back_inserter(orderOnlyDeps),
-                     this->MapToNinjaPath());
-      std::transform(ccbyproducts.begin(), ccbyproducts.end(),
-                     std::back_inserter(orderOnlyDeps),
-                     this->MapToNinjaPath());
-    }
+    std::transform(ccouts.begin(), ccouts.end(),
+                   std::back_inserter(orderOnlyDeps), this->MapToNinjaPath());
 
 
     std::sort(orderOnlyDeps.begin(), orderOnlyDeps.end());
     std::sort(orderOnlyDeps.begin(), orderOnlyDeps.end());
     orderOnlyDeps.erase(
     orderOnlyDeps.erase(
@@ -1060,8 +1098,32 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatements(
 
 
     this->GetGlobalGenerator()->WriteBuild(this->GetImplFileStream(fileConfig),
     this->GetGlobalGenerator()->WriteBuild(this->GetImplFileStream(fileConfig),
                                            build);
                                            build);
-  }
 
 
+    // Add order-only dependencies on custom command outputs that are
+    // private to this target.
+    this->HasPrivateGeneratedSources = !ccouts_private.empty();
+    if (this->HasPrivateGeneratedSources) {
+      cmNinjaBuild buildPrivate("phony");
+      cmNinjaDeps& orderOnlyDepsPrivate = buildPrivate.OrderOnlyDeps;
+      orderOnlyDepsPrivate.push_back(
+        this->OrderDependsTargetForTarget(config));
+
+      buildPrivate.Outputs.push_back(
+        this->OrderDependsTargetForTargetPrivate(config));
+
+      std::transform(ccouts_private.begin(), ccouts_private.end(),
+                     std::back_inserter(orderOnlyDepsPrivate),
+                     this->MapToNinjaPath());
+
+      std::sort(orderOnlyDepsPrivate.begin(), orderOnlyDepsPrivate.end());
+      orderOnlyDepsPrivate.erase(
+        std::unique(orderOnlyDepsPrivate.begin(), orderOnlyDepsPrivate.end()),
+        orderOnlyDepsPrivate.end());
+
+      this->GetGlobalGenerator()->WriteBuild(
+        this->GetImplFileStream(fileConfig), buildPrivate);
+    }
+  }
   {
   {
     std::vector<cmSourceFile const*> objectSources;
     std::vector<cmSourceFile const*> objectSources;
     this->GeneratorTarget->GetObjectSources(objectSources, config);
     this->GeneratorTarget->GetObjectSources(objectSources, config);
@@ -1388,7 +1450,13 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
                    this->MapToNinjaPath());
                    this->MapToNinjaPath());
   }
   }
 
 
-  objBuild.OrderOnlyDeps.push_back(this->OrderDependsTargetForTarget(config));
+  if (this->HasPrivateGeneratedSources) {
+    objBuild.OrderOnlyDeps.push_back(
+      this->OrderDependsTargetForTargetPrivate(config));
+  } else {
+    objBuild.OrderOnlyDeps.push_back(
+      this->OrderDependsTargetForTarget(config));
+  }
 
 
   // If the source file is GENERATED and does not have a custom command
   // If the source file is GENERATED and does not have a custom command
   // (either attached to this source file or another one), assume that one of
   // (either attached to this source file or another one), assume that one of

+ 2 - 0
Source/cmNinjaTargetGenerator.h

@@ -81,6 +81,7 @@ protected:
   bool CompileWithDefines(std::string const& lang) const;
   bool CompileWithDefines(std::string const& lang) const;
 
 
   std::string OrderDependsTargetForTarget(const std::string& config);
   std::string OrderDependsTargetForTarget(const std::string& config);
+  std::string OrderDependsTargetForTargetPrivate(const std::string& config);
 
 
   std::string ComputeOrderDependsForTarget();
   std::string ComputeOrderDependsForTarget();
 
 
@@ -228,6 +229,7 @@ protected:
 
 
 private:
 private:
   cmLocalNinjaGenerator* LocalGenerator;
   cmLocalNinjaGenerator* LocalGenerator;
+  bool HasPrivateGeneratedSources = false;
 
 
   struct ScanningFiles
   struct ScanningFiles
   {
   {

+ 7 - 2
Source/cmPolicies.h

@@ -465,7 +465,11 @@ class cmMakefile;
     "file(REAL_PATH) resolves symlinks before collapsing ../ components.", 3, \
     "file(REAL_PATH) resolves symlinks before collapsing ../ components.", 3, \
     28, 0, cmPolicies::WARN)                                                  \
     28, 0, cmPolicies::WARN)                                                  \
   SELECT(POLICY, CMP0153, "The exec_program command should not be called.",   \
   SELECT(POLICY, CMP0153, "The exec_program command should not be called.",   \
-         3, 28, 0, cmPolicies::WARN)
+         3, 28, 0, cmPolicies::WARN)                                          \
+  SELECT(                                                                     \
+    POLICY, CMP0154,                                                          \
+    "Generated files are private by default in targets using file sets.", 3,  \
+    28, 0, cmPolicies::WARN)
 
 
 #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
 #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
 #define CM_FOR_EACH_POLICY_ID(POLICY)                                         \
 #define CM_FOR_EACH_POLICY_ID(POLICY)                                         \
@@ -503,7 +507,8 @@ class cmMakefile;
   F(CMP0113)                                                                  \
   F(CMP0113)                                                                  \
   F(CMP0119)                                                                  \
   F(CMP0119)                                                                  \
   F(CMP0131)                                                                  \
   F(CMP0131)                                                                  \
-  F(CMP0142)
+  F(CMP0142)                                                                  \
+  F(CMP0154)
 
 
 #define CM_FOR_EACH_CUSTOM_COMMAND_POLICY(F)                                  \
 #define CM_FOR_EACH_CUSTOM_COMMAND_POLICY(F)                                  \
   F(CMP0116)                                                                  \
   F(CMP0116)                                                                  \

+ 5 - 0
Source/cmTarget.cxx

@@ -3201,6 +3201,11 @@ std::vector<std::string> cmTarget::GetAllInterfaceFileSets() const
   return result;
   return result;
 }
 }
 
 
+bool cmTarget::HasFileSets() const
+{
+  return !this->impl->FileSets.empty();
+}
+
 bool cmTargetInternals::CheckImportedLibName(std::string const& prop,
 bool cmTargetInternals::CheckImportedLibName(std::string const& prop,
                                              std::string const& value) const
                                              std::string const& value) const
 {
 {

+ 2 - 0
Source/cmTarget.h

@@ -321,6 +321,8 @@ public:
   static std::string GetFileSetsPropertyName(const std::string& type);
   static std::string GetFileSetsPropertyName(const std::string& type);
   static std::string GetInterfaceFileSetsPropertyName(const std::string& type);
   static std::string GetInterfaceFileSetsPropertyName(const std::string& type);
 
 
+  bool HasFileSets() const;
+
 private:
 private:
   template <typename ValueType>
   template <typename ValueType>
   void StoreProperty(const std::string& prop, ValueType value);
   void StoreProperty(const std::string& prop, ValueType value);

+ 3 - 0
Tests/RunCMake/CMakeLists.txt

@@ -242,6 +242,9 @@ if(CMAKE_GENERATOR MATCHES "Ninja")
   add_RunCMake_test(NinjaMultiConfig)
   add_RunCMake_test(NinjaMultiConfig)
   set_property(TEST RunCMake.NinjaMultiConfig APPEND
   set_property(TEST RunCMake.NinjaMultiConfig APPEND
     PROPERTY LABELS "CUDA")
     PROPERTY LABELS "CUDA")
+  add_RunCMake_test(NinjaPrivateDeps
+    -DCMAKE_C_OUTPUT_EXTENSION=${CMAKE_C_OUTPUT_EXTENSION}
+    -DRunCMake_GENERATOR_IS_MULTI_CONFIG=${_isMultiConfig})
 endif()
 endif()
 add_RunCMake_test(CTest)
 add_RunCMake_test(CTest)
 
 

+ 3 - 0
Tests/RunCMake/NinjaPrivateDeps/CMP0154-NEW-Build-EmptyFileSet-Source-check.cmake

@@ -0,0 +1,3 @@
+if (EXISTS ${RunCMake_TEST_BINARY_DIR}/empty.cpp)
+  set(RunCMake_TEST_FAILED "Compiled source generated before compilation of consumers.")
+endif()

+ 3 - 0
Tests/RunCMake/NinjaPrivateDeps/CMP0154-NEW-Build-EmptyFileSet-check.cmake

@@ -0,0 +1,3 @@
+if (NOT EXISTS ${RunCMake_TEST_BINARY_DIR}/empty.cpp)
+  set(RunCMake_TEST_FAILED "Compiled source did not generate.")
+endif()

+ 3 - 0
Tests/RunCMake/NinjaPrivateDeps/CMP0154-NEW-Build-NoFileSet-Source-check.cmake

@@ -0,0 +1,3 @@
+if (NOT EXISTS ${RunCMake_TEST_BINARY_DIR}/none.cpp)
+  set(RunCMake_TEST_FAILED "Private source dependency used for target without filesets.")
+endif()

+ 3 - 0
Tests/RunCMake/NinjaPrivateDeps/CMP0154-NEW-Build-PrivateFileSet-Source-check.cmake

@@ -0,0 +1,3 @@
+if (EXISTS ${RunCMake_TEST_BINARY_DIR}/private.h)
+  set(RunCMake_TEST_FAILED "Private header generated before compilation.")
+endif()

+ 3 - 0
Tests/RunCMake/NinjaPrivateDeps/CMP0154-NEW-Build-PrivateFileSet-check.cmake

@@ -0,0 +1,3 @@
+if (NOT EXISTS ${RunCMake_TEST_BINARY_DIR}/private.h)
+  set(RunCMake_TEST_FAILED "Private header generated before compilation.")
+endif()

+ 3 - 0
Tests/RunCMake/NinjaPrivateDeps/CMP0154-NEW-Build-PublicFileSet-Source-check.cmake

@@ -0,0 +1,3 @@
+if (NOT EXISTS ${RunCMake_TEST_BINARY_DIR}/public.h)
+  set(RunCMake_TEST_FAILED "Public header did not generate before compilation.")
+endif()

+ 2 - 0
Tests/RunCMake/NinjaPrivateDeps/CMP0154-NEW.cmake

@@ -0,0 +1,2 @@
+cmake_policy(SET CMP0154 NEW)
+include(CMP0154-common.cmake)

+ 3 - 0
Tests/RunCMake/NinjaPrivateDeps/CMP0154-OLD-Build-EmptyFileSet-Source-check.cmake

@@ -0,0 +1,3 @@
+if (NOT EXISTS ${RunCMake_TEST_BINARY_DIR}/empty.cpp)
+  set(RunCMake_TEST_FAILED "Policy CMP0154 set to OLD but using new behavior compiled sources.")
+endif()

+ 3 - 0
Tests/RunCMake/NinjaPrivateDeps/CMP0154-OLD-Build-PrivateFileSet-Source-check.cmake

@@ -0,0 +1,3 @@
+if (NOT EXISTS ${RunCMake_TEST_BINARY_DIR}/private.h)
+  set(RunCMake_TEST_FAILED "Policy CMP0154 set to OLD but using new behavior private headers.")
+endif()

+ 2 - 0
Tests/RunCMake/NinjaPrivateDeps/CMP0154-OLD.cmake

@@ -0,0 +1,2 @@
+cmake_policy(SET CMP0154 OLD)
+include(CMP0154-common.cmake)

+ 45 - 0
Tests/RunCMake/NinjaPrivateDeps/CMP0154-common.cmake

@@ -0,0 +1,45 @@
+enable_language(CXX)
+
+function(copy_file file dest)
+  add_custom_command(
+    OUTPUT ${CMAKE_BINARY_DIR}/${dest}
+    COMMAND ${CMAKE_COMMAND} -E copy
+      ${CMAKE_SOURCE_DIR}/${file} ${CMAKE_BINARY_DIR}/${dest}
+  )
+endfunction()
+
+copy_file(header.h.in private.h)
+copy_file(header.h.in public.h)
+copy_file(source.cpp.in empty.cpp)
+copy_file(source.cpp.in none.cpp)
+
+add_library(HelloLib_PrivateFileSet STATIC hello_lib.cpp)
+target_sources(HelloLib_PrivateFileSet
+  PRIVATE FILE_SET HEADERS
+    BASE_DIRS ${CMAKE_BINARY_DIR}
+    FILES ${CMAKE_BINARY_DIR}/private.h
+)
+
+add_library(HelloLib_PublicFileSet STATIC hello_lib.cpp)
+target_sources(HelloLib_PublicFileSet
+  PUBLIC FILE_SET HEADERS
+    BASE_DIRS ${CMAKE_BINARY_DIR}
+    FILES ${CMAKE_BINARY_DIR}/public.h
+)
+
+add_library(HelloLib_EmptyFileSet STATIC hello_lib.cpp empty.cpp)
+target_sources(HelloLib_EmptyFileSet
+  PUBLIC FILE_SET HEADERS
+)
+
+add_library(HelloLib_NoFileSet STATIC hello_lib.cpp none.cpp)
+
+function(hello_executable name)
+  add_executable(Hello_${name} hello.cpp)
+  target_link_libraries(Hello_${name} PRIVATE HelloLib_${name})
+endfunction()
+
+hello_executable(PrivateFileSet)
+hello_executable(PublicFileSet)
+hello_executable(EmptyFileSet)
+hello_executable(NoFileSet)

+ 3 - 0
Tests/RunCMake/NinjaPrivateDeps/CMakeLists.txt

@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.26)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)

+ 30 - 0
Tests/RunCMake/NinjaPrivateDeps/RunCMakeTest.cmake

@@ -0,0 +1,30 @@
+include(RunCMake)
+
+function(compile_source test target)
+  if (RunCMake_GENERATOR_IS_MULTI_CONFIG)
+    set(config "Debug/")
+  endif()
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${test}-build)
+  set(RunCMake_TEST_NO_CLEAN 1)
+  run_cmake_command(${test}-Build-${target}-Source ${CMAKE_COMMAND} --build .
+    --target CMakeFiles/Hello_${target}.dir/${config}hello.cpp${CMAKE_C_OUTPUT_EXTENSION})
+endfunction()
+
+function(compile_target test target)
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${test}-build)
+  set(RunCMake_TEST_NO_CLEAN 1)
+  run_cmake_command(${test}-Build-${target} ${CMAKE_COMMAND} --build .
+    --target Hello_${target})
+endfunction()
+
+run_cmake(CMP0154-OLD)
+compile_source(CMP0154-OLD PrivateFileSet)
+compile_source(CMP0154-OLD EmptyFileSet)
+
+run_cmake(CMP0154-NEW)
+compile_source(CMP0154-NEW PrivateFileSet)
+compile_target(CMP0154-NEW PrivateFileSet)
+compile_source(CMP0154-NEW PublicFileSet)
+compile_source(CMP0154-NEW NoFileSet)
+compile_source(CMP0154-NEW EmptyFileSet)
+compile_target(CMP0154-NEW EmptyFileSet)

+ 0 - 0
Tests/RunCMake/NinjaPrivateDeps/header.h.in


+ 6 - 0
Tests/RunCMake/NinjaPrivateDeps/hello.cpp

@@ -0,0 +1,6 @@
+#include "hello_lib.h"
+
+int main()
+{
+  say_hello();
+}

+ 8 - 0
Tests/RunCMake/NinjaPrivateDeps/hello_lib.cpp

@@ -0,0 +1,8 @@
+#include "hello_lib.h"
+
+#include <iostream>
+
+void say_hello()
+{
+  std::cout << "hello" << std::endl;
+}

+ 1 - 0
Tests/RunCMake/NinjaPrivateDeps/hello_lib.h

@@ -0,0 +1 @@
+void say_hello();

+ 0 - 0
Tests/RunCMake/NinjaPrivateDeps/source.cpp.in


+ 1 - 0
Tests/RunCMake/TargetPolicies/PolicyList-stderr.txt

@@ -37,6 +37,7 @@
    \* CMP0119
    \* CMP0119
    \* CMP0131
    \* CMP0131
    \* CMP0142
    \* CMP0142
+   \* CMP0154
 
 
 Call Stack \(most recent call first\):
 Call Stack \(most recent call first\):
   CMakeLists.txt:3 \(include\)
   CMakeLists.txt:3 \(include\)