diff --git a/Help/manual/cmake-properties.7.rst b/Help/manual/cmake-properties.7.rst index 9f6777564b..c8ddc04bdd 100644 --- a/Help/manual/cmake-properties.7.rst +++ b/Help/manual/cmake-properties.7.rst @@ -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 diff --git a/Help/prop_fs/INDEPENDENT_FILES.rst b/Help/prop_fs/INDEPENDENT_FILES.rst new file mode 100644 index 0000000000..85362760b2 --- /dev/null +++ b/Help/prop_fs/INDEPENDENT_FILES.rst @@ -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``. diff --git a/Help/release/dev/FILE_SET-INDEPENDENT_FILES.rst b/Help/release/dev/FILE_SET-INDEPENDENT_FILES.rst new file mode 100644 index 0000000000..1274c68d13 --- /dev/null +++ b/Help/release/dev/FILE_SET-INDEPENDENT_FILES.rst @@ -0,0 +1,5 @@ +FILE_SET-INDEPENDENT_FILES +-------------------------- + +* :ref:`File sets ` learned to support the + :prop_fs:`INDEPENDENT_FILES` property. diff --git a/Source/cmFileSetMetadata.cxx b/Source/cmFileSetMetadata.cxx index 24097b37ae..5d98e66f5a 100644 --- a/Source/cmFileSetMetadata.cxx +++ b/Source/cmFileSetMetadata.cxx @@ -83,13 +83,19 @@ namespace { std::map 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 KnownTypes{ HEADERS, SOURCES, CXX_MODULES }; @@ -106,6 +112,29 @@ cm::optional 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 const& GetKnownTypes() { return KnownTypes; diff --git a/Source/cmFileSetMetadata.h b/Source/cmFileSetMetadata.h index e9af93f6e9..9288e87a6c 100644 --- a/Source/cmFileSetMetadata.h +++ b/Source/cmFileSetMetadata.h @@ -2,6 +2,8 @@ file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once +#include +#include #include #include @@ -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; + 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 GetFileSetDescriptor(cm::string_view type); +DependencyMode GetDependencyMode(cm::string_view type); +DependencyMode GetDependencyMode(cm::string_view type, + DependencyMode requestedMode); std::vector const& GetKnownTypes(); bool IsKnownType(cm::string_view type); diff --git a/Source/cmGeneratorFileSets.cxx b/Source/cmGeneratorFileSets.cxx index 98aa067cf5..14b6adfcb9 100644 --- a/Source/cmGeneratorFileSets.cxx +++ b/Source/cmGeneratorFileSets.cxx @@ -12,7 +12,6 @@ #include #include #include -#include #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; } diff --git a/Source/cmNinjaTargetGenerator.cxx b/Source/cmNinjaTargetGenerator.cxx index c951bd5a66..36a3cadf13 100644 --- a/Source/cmNinjaTargetGenerator.cxx +++ b/Source/cmNinjaTargetGenerator.cxx @@ -1089,17 +1089,9 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatements( // Gather order-only dependencies on custom command outputs. std::vector ccouts; std::vector 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 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); } } diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt index e64deb93dc..b763eb72e3 100644 --- a/Tests/RunCMake/CMakeLists.txt +++ b/Tests/RunCMake/CMakeLists.txt @@ -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) diff --git a/Tests/RunCMake/CXXModulesCompile/ninja-cmp0154/CMakeLists.txt b/Tests/RunCMake/CXXModulesCompile/ninja-cmp0154/CMakeLists.txt index 1aa36c14e0..07dc327737 100644 --- a/Tests/RunCMake/CXXModulesCompile/ninja-cmp0154/CMakeLists.txt +++ b/Tests/RunCMake/CXXModulesCompile/ninja-cmp0154/CMakeLists.txt @@ -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) diff --git a/Tests/RunCMake/FileSet-SOURCES/IndependentFiles-check.cmake b/Tests/RunCMake/FileSet-SOURCES/IndependentFiles-check.cmake new file mode 100644 index 0000000000..748665a168 --- /dev/null +++ b/Tests/RunCMake/FileSet-SOURCES/IndependentFiles-check.cmake @@ -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 () diff --git a/Tests/RunCMake/FileSet-SOURCES/IndependentFiles.cmake b/Tests/RunCMake/FileSet-SOURCES/IndependentFiles.cmake new file mode 100644 index 0000000000..821270d3f0 --- /dev/null +++ b/Tests/RunCMake/FileSet-SOURCES/IndependentFiles.cmake @@ -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) diff --git a/Tests/RunCMake/FileSet-SOURCES/IndependentFilesWarning-stderr.txt b/Tests/RunCMake/FileSet-SOURCES/IndependentFilesWarning-stderr.txt new file mode 100644 index 0000000000..1c4e57231b --- /dev/null +++ b/Tests/RunCMake/FileSet-SOURCES/IndependentFilesWarning-stderr.txt @@ -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\. diff --git a/Tests/RunCMake/FileSet-SOURCES/IndependentFilesWarning.cmake b/Tests/RunCMake/FileSet-SOURCES/IndependentFilesWarning.cmake new file mode 100644 index 0000000000..acfed11402 --- /dev/null +++ b/Tests/RunCMake/FileSet-SOURCES/IndependentFilesWarning.cmake @@ -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) diff --git a/Tests/RunCMake/FileSet-SOURCES/RunCMakeTest.cmake b/Tests/RunCMake/FileSet-SOURCES/RunCMakeTest.cmake index 54df951916..c04bd29a71 100644 --- a/Tests/RunCMake/FileSet-SOURCES/RunCMakeTest.cmake +++ b/Tests/RunCMake/FileSet-SOURCES/RunCMakeTest.cmake @@ -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" diff --git a/Tests/RunCMake/FileSet-SOURCES/gen.c.in b/Tests/RunCMake/FileSet-SOURCES/gen.c.in new file mode 100644 index 0000000000..caf53feefa --- /dev/null +++ b/Tests/RunCMake/FileSet-SOURCES/gen.c.in @@ -0,0 +1,4 @@ + +void gen(void) +{ +} diff --git a/Tests/RunCMake/FileSet-SOURCES/independent.c b/Tests/RunCMake/FileSet-SOURCES/independent.c new file mode 100644 index 0000000000..f061210dec --- /dev/null +++ b/Tests/RunCMake/FileSet-SOURCES/independent.c @@ -0,0 +1,4 @@ + +void independent(void) +{ +} diff --git a/Tests/RunCMake/FileSet-SOURCES/lib1.h b/Tests/RunCMake/FileSet-SOURCES/lib1.h new file mode 100644 index 0000000000..e69de29bb2