FILE_SET: Add INDEPENDENT_FILES property for Ninja order dependency control

Fixes: #25370
This commit is contained in:
Marc Chevrier
2026-04-05 15:38:55 +02:00
parent 3b5342b305
commit bc7d8bd26f
17 changed files with 209 additions and 24 deletions

View File

@@ -545,6 +545,7 @@ Properties on File Sets
/prop_fs/COMPILE_OPTIONS
/prop_fs/CXX_SCAN_FOR_MODULES
/prop_fs/INCLUDE_DIRECTORIES
/prop_fs/INDEPENDENT_FILES
/prop_fs/INTERFACE_COMPILE_DEFINITIONS
/prop_fs/INTERFACE_COMPILE_OPTIONS
/prop_fs/INTERFACE_INCLUDE_DIRECTORIES

View File

@@ -0,0 +1,33 @@
INDEPENDENT_FILES
-----------------
.. versionadded:: 4.4
``INDEPENDENT_FILES`` is a boolean specifying that any :prop_sf:`GENERATED`
sources in the file set are not necessary for the compilation of other sources
in the same target. Stated another way, these files are "independent" and their
presence is never necessary for compilation of other sources (e.g., via
``#include``).
When this property is ``ON``, :ref:`Ninja Generators` will omit 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 independent from other sources in
the same target.
When this property is ``OFF``, :ref:`Ninja Generators` will apply 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 independent from other sources in
the same target.
If this property is not defined, the following default will be applied:
* ``ON`` for ``CXX_MODULES`` file set type.
* ``OFF`` for all other file set types.
In the case of the ``CXX_MODULES`` file set type, the only supported mode is
``ON``. So if the property is set to ``OFF``, it will be ignored.
In the case of the ``HEADERS`` file set type, if the property is ``ON``, an
author warning will be emit because this mode is not supported.
This property is effective only when the policy :policy:`CMP0154` is ``NEW``.

View File

@@ -0,0 +1,5 @@
FILE_SET-INDEPENDENT_FILES
--------------------------
* :ref:`File sets <file sets>` learned to support the
:prop_fs:`INDEPENDENT_FILES` property.

View File

@@ -83,13 +83,19 @@ namespace {
std::map<cm::string_view, FileSetDescriptor> const FileSetDescriptors{
{ cm::FileSetMetadata::HEADERS,
{ cm::FileSetMetadata::HEADERS,
cm::FileSetMetadata::FileSetLookup::Target } },
cm::FileSetMetadata::FileSetLookup::Target,
{ DependencyMode ::Includables },
DependencyMode ::Includables } },
{ cm::FileSetMetadata::SOURCES,
{ cm::FileSetMetadata::SOURCES,
cm::FileSetMetadata::FileSetLookup::Dependencies } },
cm::FileSetMetadata::FileSetLookup::Dependencies,
{ DependencyMode ::IndependentFiles, DependencyMode ::Includables },
DependencyMode ::Includables } },
{ cm::FileSetMetadata::CXX_MODULES,
{ cm::FileSetMetadata::CXX_MODULES,
cm::FileSetMetadata::FileSetLookup::Target } },
cm::FileSetMetadata::FileSetLookup::Target,
{ DependencyMode ::IndependentFiles },
DependencyMode ::IndependentFiles } },
};
std::vector<cm::string_view> KnownTypes{ HEADERS, SOURCES, CXX_MODULES };
@@ -106,6 +112,29 @@ cm::optional<FileSetDescriptor> GetFileSetDescriptor(cm::string_view type)
return cm::nullopt;
}
DependencyMode GetDependencyMode(cm::string_view type)
{
auto descriptor = GetFileSetDescriptor(type);
if (descriptor) {
return descriptor->DefaultDependency;
}
return DependencyMode::Includables;
}
DependencyMode GetDependencyMode(cm::string_view type,
DependencyMode requestedMode)
{
auto descriptor = GetFileSetDescriptor(type);
if (descriptor) {
// Select the requested mode or the next-weakest mode that is supported by
// the file set type
auto mode = descriptor->SupportedDependencies.lower_bound(requestedMode);
return mode == descriptor->SupportedDependencies.end()
? descriptor->DefaultDependency
: *mode;
}
return DependencyMode::Includables;
}
std::vector<cm::string_view> const& GetKnownTypes()
{
return KnownTypes;

View File

@@ -2,6 +2,8 @@
file LICENSE.rst or https://cmake.org/licensing for details. */
#pragma once
#include <set>
#include <utility>
#include <vector>
#include <cm/optional>
@@ -37,19 +39,49 @@ enum class FileSetLookup
Dependencies
};
// Define the various modes regarding graph dependency for
// the generated files (Ninja specific)
// items must be kept in this order: "Lower" modes are "stronger" in that they
// have more restrictions (and therefore allow for more build graph
// optimization).
// std::set rely on it.
enum class DependencyMode
{
IndependentFiles, // files in the file set are independent from each other
Includables, // files can be used by another source during compilation
};
using DependencySet = std::set<DependencyMode>;
struct FileSetDescriptor
{
FileSetDescriptor(cm::string_view type, FileSetLookup lookup)
FileSetDescriptor(cm::string_view type, FileSetLookup lookup,
DependencySet dependencies,
DependencyMode defaultDependency)
: Type(type)
, Lookup(lookup)
, SupportedDependencies(std::move(dependencies))
, DefaultDependency(defaultDependency)
{
}
FileSetDescriptor(FileSetLookup lookup)
: Type()
, Lookup(lookup)
, SupportedDependencies({ DependencyMode::Includables })
, DefaultDependency(DependencyMode::Includables)
{
}
cm::string_view const Type;
FileSetLookup const Lookup;
DependencySet const SupportedDependencies;
DependencyMode const DefaultDependency;
};
cm::optional<FileSetDescriptor> GetFileSetDescriptor(cm::string_view type);
DependencyMode GetDependencyMode(cm::string_view type);
DependencyMode GetDependencyMode(cm::string_view type,
DependencyMode requestedMode);
std::vector<cm::string_view> const& GetKnownTypes();
bool IsKnownType(cm::string_view type);

View File

@@ -12,7 +12,6 @@
#include <cm/memory>
#include <cm/optional>
#include <cmext/algorithm>
#include <cmext/string_view>
#include "cmFileSetMetadata.h"
#include "cmGenExContext.h"
@@ -132,8 +131,7 @@ cmGeneratorFileSet const* cmGeneratorFileSets::GetFileSetForSource(
auto const it2 = info.InterfaceFileSetCache.find(path);
if (it2 != info.InterfaceFileSetCache.end() &&
cm::FileSetMetadata::GetFileSetDescriptor(it2->second->GetType())
.value_or(
cm::FileSetMetadata::FileSetDescriptor{ ""_s, Lookup::Target })
.value_or(cm::FileSetMetadata::FileSetDescriptor{ Lookup::Target })
.Lookup == Lookup::Dependencies) {
return it2->second;
}

View File

@@ -1089,17 +1089,9 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatements(
// 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->HasFileSets()) {
switch (this->GetGeneratorTarget()->GetPolicyStatusCMP0154()) {
case cmPolicies::WARN:
case cmPolicies::OLD:
break;
case cmPolicies::NEW:
usePrivateGeneratedSources = true;
break;
}
}
bool usePrivateGeneratedSources = this->GeneratorTarget->HasFileSets() &&
this->GetGeneratorTarget()->GetPolicyStatusCMP0154() == cmPolicies::NEW;
for (cmCustomCommand const* cc : customCommands) {
cmCustomCommandGenerator ccg(*cc, config, this->GetLocalGenerator());
std::vector<std::string> const& ccoutputs = ccg.GetOutputs();
@@ -1115,15 +1107,51 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatements(
cmGeneratorFileSet const* fileset =
this->GeneratorTarget->GetFileSetForSource(
config, this->Makefile->GetOrCreateGeneratedSource(*it));
bool isVisible = fileset && fileset->IsForInterface();
bool isIncludeable = !fileset || fileset->CanBeIncluded();
if (fileset && isVisible && isIncludeable) {
++it;
if (!fileset) {
// use private order dependency
ccouts_private.push_back(*it);
it = ccouts.erase(it);
continue;
}
if (!fileset || isIncludeable) {
ccouts_private.push_back(*it);
using DependencyMode = cm::FileSetMetadata::DependencyMode;
cmValue independentFiles = fileset->GetProperty("INDEPENDENT_FILES");
// retrieve default mode
DependencyMode dependencyMode =
cm::FileSetMetadata::GetDependencyMode(fileset->GetType());
// if property is defined, try to enforce mode requested
if (independentFiles) {
dependencyMode = cm::FileSetMetadata::GetDependencyMode(
fileset->GetType(),
independentFiles.IsOn() ? DependencyMode::IndependentFiles
: DependencyMode::Includables);
}
if (independentFiles.IsOn() &&
dependencyMode != DependencyMode::IndependentFiles) {
// requested dependency mode not supported
this->GetMakefile()->IssueMessage(
MessageType::AUTHOR_WARNING,
cmStrCat(R"(the "INDEPENDENT_FILES" property of the file set ")",
fileset->GetName(), R"(" of the target ")",
this->GeneratorTarget->GetName(),
R"(" will be ignored because it is incompatible with )"
R"(the file set type ")",
fileset->GetType(), R"(".)"));
}
if (dependencyMode == DependencyMode::Includables) {
if (fileset->IsForInterface()) {
// use public order dependency
++it;
} else {
// use private order dependency
ccouts_private.push_back(*it);
it = ccouts.erase(it);
}
continue;
}
// no order dependency is required
it = ccouts.erase(it);
}
}

View File

@@ -624,6 +624,7 @@ add_RunCMake_test(TargetObjects)
add_RunCMake_test(TargetProperties)
add_RunCMake_test(FileSetProperties)
add_RunCMake_test(FileSet-SOURCES -DCMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID}
-DCMAKE_C_OUTPUT_EXTENSION=${CMAKE_C_OUTPUT_EXTENSION}
-DMSVC_VERSION=${MSVC_VERSION})
add_RunCMake_test(ToolchainFile)

View File

@@ -27,5 +27,7 @@ target_compile_features(ninja_cmp0154 PUBLIC cxx_std_20)
set_property(SOURCE unrelated.cxx
PROPERTY
CXX_SCAN_FOR_MODULES 0)
# ensure property INDEPENDENT_FILES=OFF is ignored
set_property(FILE_SET CXX_MODULES TARGET ninja_cmp0154 PROPERTY INDEPENDENT_FILES OFF)
add_test(NAME ninja_cmp0154 COMMAND ninja_cmp0154)

View File

@@ -0,0 +1,4 @@
if (EXISTS "${RunCMake_TEST_BINARY_DIR}/gen.c")
list(APPEND RunCMake_TEST_FAILED
"The `gen.c` file should not be generated to compile `unrelated`'s object")
endif ()

View File

@@ -0,0 +1,16 @@
enable_language(C)
set(CACHE{CMAKE_INTERMEDIATE_DIR_STRATEGY} TYPE STRING FORCE VALUE FULL)
add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/gen.c"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/gen.c.in"
"${CMAKE_CURRENT_BINARY_DIR}/gen.c"
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/gen.c.in")
add_library(lib1 STATIC)
target_sources(lib1 PRIVATE independent.c
PRIVATE FILE_SET SOURCES BASE_DIRS "${CMAKE_CURRENT_BINARY_DIR}"
FILES "${CMAKE_CURRENT_BINARY_DIR}/gen.c")
set_property(FILE_SET SOURCES TARGET lib1 PROPERTY INDEPENDENT_FILES ON)

View File

@@ -0,0 +1,5 @@
CMake Warning \(dev\) in CMakeLists\.txt:
the "INDEPENDENT_FILES" property of the file set "HEADERS" of the target
"lib1" will be ignored because it is incompatible with the file set type
"HEADERS"\.
This warning is for project developers\. Use -Wno-dev to suppress it\.

View File

@@ -0,0 +1,13 @@
enable_language(C)
add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/gen.h"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/gen.c.in"
"${CMAKE_CURRENT_BINARY_DIR}/gen.h"
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/gen.c.in")
add_library(lib1 STATIC lib1.c)
target_sources(lib1 PRIVATE FILE_SET HEADERS BASE_DIRS "${CMAKE_CURRENT_BINARY_DIR}"
FILES "${CMAKE_CURRENT_BINARY_DIR}/gen.h")
set_property(FILE_SET HEADERS TARGET lib1 PROPERTY INDEPENDENT_FILES ON)

View File

@@ -34,6 +34,16 @@ run_and_build(CustomCommandInput)
run_and_build(IncludeDirectoriesOrder)
run_and_build(FileSetTransitivity)
run_and_check(CompileOptionsOrder)
if (RunCMake_GENERATOR MATCHES "Ninja")
run_cmake(IndependentFilesWarning)
if (RunCMake_GENERATOR_IS_MULTI_CONFIG)
set(IndependentFiles_target "CMakeFiles/lib1.dir/Debug/independent.c${CMAKE_C_OUTPUT_EXTENSION}")
else ()
set(IndependentFiles_target "CMakeFiles/lib1.dir/independent.c${CMAKE_C_OUTPUT_EXTENSION}")
endif ()
set(RunCMake-check-file IndependentFiles-check.cmake)
run_and_build(IndependentFiles ${IndependentFiles_target})
endif()
# Some environments are excluded because they are not able to honor verbose mode
if ((RunCMake_GENERATOR MATCHES "Makefiles|Ninja|Xcode"

View File

@@ -0,0 +1,4 @@
void gen(void)
{
}

View File

@@ -0,0 +1,4 @@
void independent(void)
{
}

View File