diff --git a/Help/generator/FASTBuild.rst b/Help/generator/FASTBuild.rst index e737bee997..cd68c045b7 100644 --- a/Help/generator/FASTBuild.rst +++ b/Help/generator/FASTBuild.rst @@ -32,6 +32,22 @@ This writes a FASTBuild configuration file named ``fbuild.bff`` into Built-in Targets ---------------- +.. versionadded:: 4.5 + + If the project has install rules, the generated build file provides the + following targets, unless :variable:`CMAKE_SKIP_INSTALL_PREP_TARGETS` or + :variable:`CMAKE_SKIP_INSTALL_RULES` is enabled: + + ``install_prep/`` + Builds all known build dependencies for the install component named + ```` added by :command:`install`. Components whose names cannot + be represented safely are excluded. See :ref:`Install Preparation Targets` + and :ref:`Target Names`. + + ``install_prep/all`` + Builds all known build dependencies needed for an installation that does + not select a component. See :ref:`Install Preparation Targets`. + .. versionadded:: 4.5 When the :variable:`CMAKE_TEST_BUILD_DEPENDS` variable is enabled, the diff --git a/Help/manual/cmake-buildsystem.7.rst b/Help/manual/cmake-buildsystem.7.rst index 7268157f0c..ed2ffec3e6 100644 --- a/Help/manual/cmake-buildsystem.7.rst +++ b/Help/manual/cmake-buildsystem.7.rst @@ -1782,9 +1782,9 @@ target properties with a limited set of names: Preparation Targets =================== -The :ref:`Ninja Generators` and :ref:`Makefile Generators` provide convenience -targets that build only the prerequisites of a later step, such as running -tests or installing. +The :ref:`Ninja Generators`, :ref:`Makefile Generators`, and +:generator:`FASTBuild` generator provide convenience targets that build only +the prerequisites of a later step, such as running tests or installing. .. _`Install Preparation Targets`: @@ -1860,11 +1860,11 @@ and custom targets. How such a file is prepared depends on how it is produced: * Any other generated file, such as a byproduct or the output of a custom command attached to more than one target, is prepared by depending on the file itself, which runs whatever produces it. The - :ref:`Makefile Generators` cannot do so, so they warn. If a target - installs the file (or the file set to which the file belongs), they build - that target instead, which must therefore depend on the producer, e.g., - through :command:`add_dependencies`. Otherwise, they do not prepare the - file. + :ref:`Makefile Generators` and :generator:`FASTBuild` cannot do so, so they + warn. If a target installs the file (or the file set to which the file + belongs), they build that target instead, which must therefore depend on + the producer, e.g., through :command:`add_dependencies`. Otherwise, they + do not prepare the file. For example: diff --git a/Source/cmGlobalFastbuildGenerator.cxx b/Source/cmGlobalFastbuildGenerator.cxx index 0d9910f3ba..edc7d1c778 100644 --- a/Source/cmGlobalFastbuildGenerator.cxx +++ b/Source/cmGlobalFastbuildGenerator.cxx @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -1588,6 +1589,67 @@ void cmGlobalFastbuildGenerator::AddTargetAll() this->AddTarget(std::move(allAliasNode)); } +void cmGlobalFastbuildGenerator::WriteInstallPrepTargets() +{ + if (!this->AreInstallPrepTargetsEnabled()) { + return; + } + this->CheckInstallPrepTargets(); + + cmGlobalGenerator::InstallPrepTargets prepTargets = + this->GetInstallPrepTargets(this->GetConfigNames().front()); + + // FASTBuild target names are case-insensitive, so merge preparation targets + // whose names differ only in case. Each of them then prepares a superset of + // what its installation needs. + std::map aliases; + std::set unownedGeneratedFiles; + auto addAlias = + [this, &aliases, &unownedGeneratedFiles]( + std::string const& name, + cmGlobalGenerator::InstallPrepTargets::TargetMap const& targets) { + FastbuildAliasNode& alias = aliases[cmSystemTools::LowerCase(name)]; + if (alias.Name.empty()) { + alias.Name = name; + alias.Hidden = false; + } + for (auto const& dependency : targets) { + cmGeneratorTarget const* target = dependency.first; + bool buildTarget = + dependency.second.Normal || dependency.second.InstallArtifact; + // FASTBuild has no node for a generated file that is not the unique + // output of a target, such as a byproduct, so build its installing + // target instead. + for (std::string const& file : dependency.second.GeneratedFiles) { + if (this->CheckInstallPrepGeneratedFile(target, file, + unownedGeneratedFiles)) { + alias.PreBuildDependencies.emplace( + this->ConvertToFastbuildPath(file)); + } else if (target->IsInBuildSystem()) { + buildTarget = true; + } + } + if (buildTarget) { + alias.PreBuildDependencies.emplace(target->GetName()); + } + } + }; + + // Add install_prep/all first so that it keeps its spelling. + addAlias("install_prep/all", prepTargets.All); + for (auto const& component : prepTargets.Components) { + addAlias(cmStrCat("install_prep/", component.first), component.second); + } + + for (auto& entry : aliases) { + FastbuildAliasNode& alias = entry.second; + if (alias.PreBuildDependencies.empty()) { + alias.PreBuildDependencies.emplace(FASTBUILD_NOOP_FILE_NAME); + } + this->WriteAlias(alias); + } +} + void cmGlobalFastbuildGenerator::WriteTestPrepTargets() { if (!this->Makefiles.front()->IsOn("CMAKE_TEST_BUILD_DEPENDS")) { @@ -1978,6 +2040,7 @@ void cmGlobalFastbuildGenerator::WriteTargets() *BuildFileStream << "}\n"; } + WriteInstallPrepTargets(); WriteTestPrepTargets(); if (!this->GetCMakeInstance()->GetIsInTryCompile()) { diff --git a/Source/cmGlobalFastbuildGenerator.h b/Source/cmGlobalFastbuildGenerator.h index c00a84cdc5..958e6ef3a2 100644 --- a/Source/cmGlobalFastbuildGenerator.h +++ b/Source/cmGlobalFastbuildGenerator.h @@ -543,6 +543,7 @@ public: void WriteTargetClean(); void AddTargetAll(); + void WriteInstallPrepTargets(); void WriteTestPrepTargets(); void AddGlobCheckExec(); diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt index 8a2d08bd92..d2a3a9741b 100644 --- a/Tests/RunCMake/CMakeLists.txt +++ b/Tests/RunCMake/CMakeLists.txt @@ -292,7 +292,7 @@ if(CMAKE_GENERATOR MATCHES "Ninja") -DRunCMake_GENERATOR_IS_MULTI_CONFIG=${_isMultiConfig}) endif() add_RunCMake_test(InstallParallel) -if(CMAKE_GENERATOR MATCHES "Make|Ninja") +if(CMAKE_GENERATOR MATCHES "Make|Ninja|FASTBuild") add_RunCMake_test(InstallPrep) endif() add_RunCMake_test(CTest) diff --git a/Tests/RunCMake/InstallPrep/ComponentNames-case-build-check.cmake b/Tests/RunCMake/InstallPrep/ComponentNames-case-build-check.cmake new file mode 100644 index 0000000000..f41616f9c2 --- /dev/null +++ b/Tests/RunCMake/InstallPrep/ComponentNames-case-build-check.cmake @@ -0,0 +1 @@ +check_prepared(${dependency}_case_dependency) diff --git a/Tests/RunCMake/InstallPrep/ComponentNames.cmake b/Tests/RunCMake/InstallPrep/ComponentNames.cmake index 7aeabee98b..c81be53d43 100644 --- a/Tests/RunCMake/InstallPrep/ComponentNames.cmake +++ b/Tests/RunCMake/InstallPrep/ComponentNames.cmake @@ -6,6 +6,19 @@ install(TARGETS colon_dependency COMPONENT "Colon:Name" ) +# Component names that differ only in case each get a working preparation +# target, even where the build tool's target names are case-insensitive. +foreach(case IN ITEMS "Mixed;upper" "mixed;lower" "All;all") + list(GET case 0 component) + list(GET case 1 dependency) + add_library(${dependency}_case_dependency STATIC EXCLUDE_FROM_ALL lib.c) + install(TARGETS ${dependency}_case_dependency + ARCHIVE DESTINATION lib + COMPONENT ${component} + EXCLUDE_FROM_ALL + ) +endforeach() + # Components whose names are not representable get no preparation target, # which is diagnosed when they have build dependencies. add_library(unsafe_dependency STATIC EXCLUDE_FROM_ALL lib.c) diff --git a/Tests/RunCMake/InstallPrep/GeneratedByDependency-stderr-fastbuild.txt b/Tests/RunCMake/InstallPrep/GeneratedByDependency-stderr-fastbuild.txt new file mode 100644 index 0000000000..984e3b0119 --- /dev/null +++ b/Tests/RunCMake/InstallPrep/GeneratedByDependency-stderr-fastbuild.txt @@ -0,0 +1,13 @@ +^CMake Warning \(author\) at GeneratedByDependency\.cmake:[0-9]+ \(add_library\): + Target "generated_headers" installs file + + [^ +]*/GeneratedByDependency-build/generated\.h + + which is generated but is not the unique output of a build target, so + "install_prep/" targets cannot build it with this generator\. They build + target "generated_headers" instead, which must depend on the target that + produces the file \(for example via add_dependencies\)\. +Call Stack \(most recent call first\): + CMakeLists\.txt:[0-9]+ \(include\) +This warning is for project developers\. Use -Wno-author to suppress it\.$ diff --git a/Tests/RunCMake/InstallPrep/GeneratedFilesByproduct-stderr-fastbuild.txt b/Tests/RunCMake/InstallPrep/GeneratedFilesByproduct-stderr-fastbuild.txt new file mode 100644 index 0000000000..ec846b347c --- /dev/null +++ b/Tests/RunCMake/InstallPrep/GeneratedFilesByproduct-stderr-fastbuild.txt @@ -0,0 +1,11 @@ +^CMake Warning \(author\) at GeneratedFilesByproduct\.cmake:[0-9]+ \(install\): + An install rule installs or depends on file + + [^ +]*/GeneratedFilesByproduct-build/byproduct\.txt + + which is generated but is not the unique output of a build target, so + "install_prep/" targets cannot build it with this generator\..* +Call Stack \(most recent call first\): + CMakeLists\.txt:[0-9]+ \(include\) +This warning is for project developers\. Use -Wno-author to suppress it\.$ diff --git a/Tests/RunCMake/InstallPrep/RunCMakeTest.cmake b/Tests/RunCMake/InstallPrep/RunCMakeTest.cmake index 9b1f21cf81..809bd7a90a 100644 --- a/Tests/RunCMake/InstallPrep/RunCMakeTest.cmake +++ b/Tests/RunCMake/InstallPrep/RunCMakeTest.cmake @@ -65,6 +65,8 @@ macro(configure_install_prep test config) set(stderr_variant "") if(RunCMake_GENERATOR MATCHES "Make") set(stderr_variant make) + elseif(RunCMake_GENERATOR STREQUAL "FASTBuild") + set(stderr_variant fastbuild) endif() if(stderr_variant AND EXISTS "${RunCMake_SOURCE_DIR}/${test}-stderr-${stderr_variant}.txt") @@ -152,6 +154,13 @@ block() run_missing_install_prep(ComponentNames-empty-segment install_prep/Empty//Segment) run_missing_install_prep(ComponentNames-dot install_prep/./Dot) + foreach(case IN ITEMS "Mixed;upper" "mixed;lower" "All;all") + list(GET case 0 component) + list(GET case 1 dependency) + set(RunCMake-check-file ComponentNames-case-build-check.cmake) + run_cmake_command(ComponentNames-case-${dependency}-build ${CMAKE_COMMAND} + --build . --config Debug --target install_prep/${component}) + endforeach() endblock() function(run_configured_install_prep test config)