diff --git a/Help/manual/cmake-qt.7.rst b/Help/manual/cmake-qt.7.rst index fe269c2c79..80b3698ae6 100644 --- a/Help/manual/cmake-qt.7.rst +++ b/Help/manual/cmake-qt.7.rst @@ -231,31 +231,58 @@ Target dependencies may be added to the ``_autogen`` target by adding them to the :prop_tgt:`AUTOGEN_TARGET_DEPENDS` target property. .. note:: - If Qt 5.15 or later is used and the generator is either :generator:`Ninja` or - :ref:`Makefile Generators`, see :ref:`_autogen_timestamp_deps`. + If Qt 5.15 or later is used and a depfile is used to track the ``moc`` + dependencies, see :ref:`_autogen_timestamp_deps`. When using the + :ref:`Visual Studio Generators`, the ``_autogen`` target may not be + created at all, see `Visual Studio Generators`_ below. .. _`_autogen_timestamp_deps`: The ``_autogen_timestamp_deps`` target ============================================== -If Qt 5.15 or later is used and the generator is either :generator:`Ninja` or -:ref:`Makefile Generators`, the ``_autogen_timestamp_deps`` target is -also created in addition to the :ref:`_autogen <_autogen>` +If Qt 5.15 or later is used and the generator is :generator:`Ninja`, a +:ref:`Makefile ` generator, a +:ref:`Visual Studio ` generator or +:generator:`Xcode`, the ``moc`` dependencies are tracked with a depfile. In +that case the ``_autogen_timestamp_deps`` target is also created in +addition to the :ref:`_autogen <_autogen>` target. This target does not have any sources or commands to execute, but it has dependencies that were previously inherited by the pre-Qt 5.15 :ref:`_autogen <_autogen>` target. These dependencies will serve as a list of order-only dependencies for the custom command, without forcing the custom command to re-execute. +.. versionadded:: 4.4 + Depfile support for the :ref:`Visual Studio Generators` and + :generator:`Xcode`. Earlier versions used a depfile only for the + :generator:`Ninja` and :ref:`Makefile Generators`. + +.. note:: + When using the :ref:`Visual Studio Generators`, neither this target nor the + :ref:`_autogen <_autogen>` target is created in the common + case, see `Visual Studio Generators`_ below. + Visual Studio Generators ======================== -When using the :ref:`Visual Studio Generators`, CMake -generates a ``PRE_BUILD`` :command:`custom command ` -instead of the :ref:`_autogen <_autogen>` +When using the :ref:`Visual Studio Generators`, CMake adds the ``moc`` and +``uic`` step to the ```` project itself instead of creating the +:ref:`_autogen <_autogen>` :command:`custom target ` (for :prop_tgt:`AUTOMOC` and -:prop_tgt:`AUTOUIC`). This isn't always possible though and an +:prop_tgt:`AUTOUIC`). With Qt 5.15 or later, the step is a +:command:`custom command ` with a depfile, and therefore +runs only when one of its dependencies changed. With earlier Qt versions it is +a ``PRE_BUILD`` command that runs on every build of ````. + +.. versionchanged:: 4.4.3 + With Qt 5.15 or later, the ``moc`` and ``uic`` step is a custom command in + the ```` project. CMake 4.4.0 through 4.4.2 created the + :ref:`_autogen <_autogen>` and + :ref:`_autogen_timestamp_deps <_autogen_timestamp_deps>` + targets instead. + +This isn't always possible though and an :ref:`_autogen <_autogen>` :command:`custom target ` is used, when either @@ -266,6 +293,10 @@ instead of the :ref:`_autogen <_autogen>` - :prop_tgt:`AUTOGEN_TARGET_DEPENDS` lists a source file - :variable:`CMAKE_GLOBAL_AUTOGEN_TARGET` is enabled +With Qt 5.15 or later, the +:ref:`_autogen_timestamp_deps <_autogen_timestamp_deps>` target +is created along with it. + qtmain.lib on Windows ===================== diff --git a/Help/prop_tgt/AUTOGEN_ORIGIN_DEPENDS.rst b/Help/prop_tgt/AUTOGEN_ORIGIN_DEPENDS.rst index 4bf83eb678..bc8029cb3c 100644 --- a/Help/prop_tgt/AUTOGEN_ORIGIN_DEPENDS.rst +++ b/Help/prop_tgt/AUTOGEN_ORIGIN_DEPENDS.rst @@ -8,10 +8,13 @@ Switch for forwarding origin target dependencies to the corresponding .. note:: - If Qt 5.15 or later is used and the generator is either :generator:`Ninja` - or :ref:`Makefile Generators`, origin target dependencies are forwarded to - the :ref:`_autogen_timestamp_deps <_autogen_timestamp_deps>` - target instead of :ref:`_autogen <_autogen>`. + If the + :ref:`_autogen_timestamp_deps <_autogen_timestamp_deps>` + target is created, origin target dependencies are forwarded to it instead + of :ref:`_autogen <_autogen>`. When using the + :ref:`Visual Studio Generators`, the ``moc`` and ``uic`` step may be part + of the ```` target itself, in which case they need no forwarding. + See the :manual:`cmake-qt(7)` manual for details. Targets which have their :prop_tgt:`AUTOMOC` or :prop_tgt:`AUTOUIC` property ``ON`` have a corresponding :ref:`_autogen <_autogen>` target diff --git a/Help/prop_tgt/AUTOGEN_TARGET_DEPENDS.rst b/Help/prop_tgt/AUTOGEN_TARGET_DEPENDS.rst index 27f1fd3776..930ba33935 100644 --- a/Help/prop_tgt/AUTOGEN_TARGET_DEPENDS.rst +++ b/Help/prop_tgt/AUTOGEN_TARGET_DEPENDS.rst @@ -6,10 +6,13 @@ Additional target dependencies of the corresponding .. note:: - If Qt 5.15 or later is used and the generator is either :generator:`Ninja` - or :ref:`Makefile Generators`, additional target dependencies are added to - the :ref:`_autogen_timestamp_deps <_autogen_timestamp_deps>` - target instead of the :ref:`_autogen <_autogen>` target. + If the + :ref:`_autogen_timestamp_deps <_autogen_timestamp_deps>` + target is created, additional target dependencies are added to it instead + of the :ref:`_autogen <_autogen>` target. When using the + :ref:`Visual Studio Generators`, the ``moc`` and ``uic`` step may be part + of the ```` target itself, in which case the dependencies are added + to ````. See the :manual:`cmake-qt(7)` manual for details. Targets which have their :prop_tgt:`AUTOMOC` or :prop_tgt:`AUTOUIC` property diff --git a/Help/variable/CMAKE_AUTOGEN_ORIGIN_DEPENDS.rst b/Help/variable/CMAKE_AUTOGEN_ORIGIN_DEPENDS.rst index 139268b0c6..c2e7e82846 100644 --- a/Help/variable/CMAKE_AUTOGEN_ORIGIN_DEPENDS.rst +++ b/Help/variable/CMAKE_AUTOGEN_ORIGIN_DEPENDS.rst @@ -8,10 +8,13 @@ Switch for forwarding origin target dependencies to the corresponding .. note:: - If Qt 5.15 or later is used and the generator is either :generator:`Ninja` - or :ref:`Makefile Generators`, additional target dependencies are added to - the :ref:`_autogen_timestamp_deps <_autogen_timestamp_deps>` - target instead of the :ref:`_autogen <_autogen>` target. + If the + :ref:`_autogen_timestamp_deps <_autogen_timestamp_deps>` + target is created, additional target dependencies are added to it instead + of the :ref:`_autogen <_autogen>` target. When using the + :ref:`Visual Studio Generators`, the ``moc`` and ``uic`` step may be part + of the ```` target itself, in which case the dependencies are added + to ````. See the :manual:`cmake-qt(7)` manual for details. This variable is used to initialize the :prop_tgt:`AUTOGEN_ORIGIN_DEPENDS` property on all the targets. See that target property for additional diff --git a/Source/cmQtAutoGenInitializer.cxx b/Source/cmQtAutoGenInitializer.cxx index aafebaaad8..f02914d3f3 100644 --- a/Source/cmQtAutoGenInitializer.cxx +++ b/Source/cmQtAutoGenInitializer.cxx @@ -1374,6 +1374,16 @@ bool cmQtAutoGenInitializer::InitAutogenTarget() gen.find("Visual Studio") != std::string::npos || gen == "Xcode"); }(); + // Under VS, attach the autogen custom command to the origin target instead + // of creating separate targets, to reduce the number of targets loaded into + // the IDE. The dependencies of the origin target then provide the ordering + // that '_autogen_timestamp_deps' provides otherwise. The conditions match + // those of the PRE_BUILD event below, so that the '_autogen' target keeps + // existing wherever it did before depfiles were enabled for VS. + bool const attachToOrigin = useDepfile && + this->AutogenTarget.DependFiles.empty() && + !this->AutogenTarget.GlobalTarget && this->GlobalGen->IsVisualStudio(); + // Files provided by the autogen target std::vector autogenByproducts; std::vector timestampByproducts; @@ -1407,6 +1417,12 @@ bool cmQtAutoGenInitializer::InitAutogenTarget() } } + if (attachToOrigin) { + // Without an autogen target the timestamp command provides all byproducts. + cm::append(timestampByproducts, autogenByproducts); + autogenByproducts.clear(); + } + // Compose target comment std::string autogenComment; { @@ -1518,8 +1534,9 @@ bool cmQtAutoGenInitializer::InitAutogenTarget() } else { // Add link library target dependencies to the autogen target - // dependencies - if (this->AutogenTarget.DependOrigin) { + // dependencies. Not needed when attaching to the origin target, which + // already depends on its own link libraries. + if (this->AutogenTarget.DependOrigin && !attachToOrigin) { // add_dependencies/addUtility do not support generator expressions. // We depend only on the libraries found in all configs therefore. std::map targetsPartOfAllConfigs; @@ -1556,41 +1573,43 @@ bool cmQtAutoGenInitializer::InitAutogenTarget() if (useDepfile) { // Create a custom command that generates a timestamp file and // has a depfile assigned. The depfile is created by JobDepFilesMergeT. - // - // Also create an additional '_autogen_timestamp_deps' that the custom - // command will depend on. It will have no sources or commands to - // execute, but it will have dependencies that would originally be - // assigned to the pre-Qt 5.15 'autogen' target. These dependencies will - // serve as a list of order-only dependencies for the custom command, - // without forcing the custom command to re-execute. - // - // The dependency tree would then look like - // '_autogen_timestamp_deps (order-only)' <- '/timestamp' file <- - // '_autogen' target. - auto const timestampTargetName = - cmStrCat(this->GenTarget->GetName(), "_autogen_timestamp_deps"); + if (!attachToOrigin) { + // Also create an additional '_autogen_timestamp_deps' that the custom + // command will depend on. It will have no sources or commands to + // execute, but it will have dependencies that would originally be + // assigned to the pre-Qt 5.15 'autogen' target. These dependencies + // will serve as a list of order-only dependencies for the custom + // command, without forcing the custom command to re-execute. + // + // The dependency tree would then look like + // '_autogen_timestamp_deps (order-only)' <- '/timestamp' file <- + // '_autogen' target. + auto const timestampTargetName = + cmStrCat(this->GenTarget->GetName(), "_autogen_timestamp_deps"); - auto cc = cm::make_unique(); - cc->SetWorkingDirectory(this->Dir.Work.c_str()); - cc->SetDepends(dependencies); - cc->SetEscapeOldStyle(false); - timestampTarget = this->LocalGen->AddUtilityCommand(timestampTargetName, - true, std::move(cc)); + auto cc = cm::make_unique(); + cc->SetWorkingDirectory(this->Dir.Work.c_str()); + cc->SetDepends(dependencies); + cc->SetEscapeOldStyle(false); + timestampTarget = this->LocalGen->AddUtilityCommand( + timestampTargetName, true, std::move(cc)); - this->LocalGen->AddGeneratorTarget( - cm::make_unique(timestampTarget, this->LocalGen)); + this->LocalGen->AddGeneratorTarget( + cm::make_unique(timestampTarget, this->LocalGen)); - // Set FOLDER property on the timestamp target, so it appears in the - // appropriate folder in an IDE or in the file api. - if (!this->TargetsFolder.empty()) { - timestampTarget->SetProperty("FOLDER", this->TargetsFolder); + // Set FOLDER property on the timestamp target, so it appears in the + // appropriate folder in an IDE or in the file api. + if (!this->TargetsFolder.empty()) { + timestampTarget->SetProperty("FOLDER", this->TargetsFolder); + } + + // Make '/timestamp' file depend on '_autogen_timestamp_deps'. + dependencies.clear(); + dependencies.push_back(timestampTargetName); } - // Make '/timestamp' file depend on '_autogen_timestamp_deps' and on the - // moc and uic executables (whichever are enabled). - dependencies.clear(); - dependencies.push_back(timestampTargetName); - + // Make '/timestamp' file depend on the moc and uic executables + // (whichever are enabled). AddAutogenExecutableToDependencies(this->Moc, dependencies); AddAutogenExecutableToDependencies(this->Uic, dependencies); std::string outputFile; @@ -1616,7 +1635,8 @@ bool cmQtAutoGenInitializer::InitAutogenTarget() for (std::string const& config : this->ConfigsList) { auto tempTimestampFileName = timestampFileWithoutConfig + config; auto tempDepFile = depFileWithoutConfig + config; - outputFileWithConfig.Config[config] = tempTimestampFileName; + outputFileWithConfig.Config[config] = + cmStrCat(this->Dir.Build, '/', tempTimestampFileName); this->AutogenTarget.DepFileRuleName.Config[config] = cmStrCat(this->Dir.RelativeBuild, '/', tempTimestampFileName); this->AutogenTarget.DepFile.Config[config] = tempDepFile; @@ -1634,7 +1654,7 @@ bool cmQtAutoGenInitializer::InitAutogenTarget() { cmSystemTools::GetCMakeCommand(), "-E", "touch", outputFile })); this->AddGeneratedSource(outputFile, this->Moc); } - cc = cm::make_unique(); + auto cc = cm::make_unique(); cc->SetOutputs(outputFile); cc->SetByproducts(timestampByproducts); cc->SetDepends(dependencies); @@ -1651,48 +1671,57 @@ bool cmQtAutoGenInitializer::InitAutogenTarget() autogenComment.clear(); } - // Create autogen target - auto cc = cm::make_unique(); - cc->SetWorkingDirectory(this->Dir.Work.c_str()); - cc->SetByproducts(autogenByproducts); - cc->SetDepends(dependencies); - cc->SetCommandLines(commandLines); - cc->SetEscapeOldStyle(false); - cc->SetComment(autogenComment.c_str()); - cmTarget* autogenTarget = this->LocalGen->AddUtilityCommand( - this->AutogenTarget.Name, true, std::move(cc)); - // Create autogen generator target - this->LocalGen->AddGeneratorTarget( - cm::make_unique(autogenTarget, this->LocalGen)); - - // Order the autogen target(s) just before the original target. - cmTarget* orderTarget = timestampTarget ? timestampTarget : autogenTarget; - // Forward origin utilities to autogen target - if (this->AutogenTarget.DependOrigin) { - for (BT> const& depName : - this->GenTarget->GetUtilities()) { - orderTarget->AddUtility(depName.Value.first, false, this->Makefile); + if (attachToOrigin) { + // Add additional autogen target dependencies to the origin target + for (cmTarget const* depTarget : this->AutogenTarget.DependTargets) { + this->GenTarget->Target->AddUtility(depTarget->GetName(), false, + this->Makefile); } - } + } else { + // Create autogen target + auto cc = cm::make_unique(); + cc->SetWorkingDirectory(this->Dir.Work.c_str()); + cc->SetByproducts(autogenByproducts); + cc->SetDepends(dependencies); + cc->SetCommandLines(commandLines); + cc->SetEscapeOldStyle(false); + cc->SetComment(autogenComment.c_str()); + cmTarget* autogenTarget = this->LocalGen->AddUtilityCommand( + this->AutogenTarget.Name, true, std::move(cc)); + // Create autogen generator target + this->LocalGen->AddGeneratorTarget( + cm::make_unique(autogenTarget, this->LocalGen)); - // Add additional autogen target dependencies to autogen target - for (cmTarget const* depTarget : this->AutogenTarget.DependTargets) { - orderTarget->AddUtility(depTarget->GetName(), false, this->Makefile); - } + // Order the autogen target(s) just before the original target. + cmTarget* orderTarget = + timestampTarget ? timestampTarget : autogenTarget; + // Forward origin utilities to autogen target + if (this->AutogenTarget.DependOrigin) { + for (BT> const& depName : + this->GenTarget->GetUtilities()) { + orderTarget->AddUtility(depName.Value.first, false, this->Makefile); + } + } - // Set FOLDER property in autogen target - if (!this->TargetsFolder.empty()) { - autogenTarget->SetProperty("FOLDER", this->TargetsFolder); - } + // Add additional autogen target dependencies to autogen target + for (cmTarget const* depTarget : this->AutogenTarget.DependTargets) { + orderTarget->AddUtility(depTarget->GetName(), false, this->Makefile); + } - // Add autogen target to the origin target dependencies - this->GenTarget->Target->AddUtility(this->AutogenTarget.Name, false, - this->Makefile); + // Set FOLDER property in autogen target + if (!this->TargetsFolder.empty()) { + autogenTarget->SetProperty("FOLDER", this->TargetsFolder); + } - // Add autogen target to the global autogen target dependencies - if (this->AutogenTarget.GlobalTarget) { - this->GlobalInitializer->AddToGlobalAutoGen(this->LocalGen, - this->AutogenTarget.Name); + // Add autogen target to the origin target dependencies + this->GenTarget->Target->AddUtility(this->AutogenTarget.Name, false, + this->Makefile); + + // Add autogen target to the global autogen target dependencies + if (this->AutogenTarget.GlobalTarget) { + this->GlobalInitializer->AddToGlobalAutoGen(this->LocalGen, + this->AutogenTarget.Name); + } } } diff --git a/Tests/RunCMake/Autogen_2/QtAutoMocVsTargets.cmake b/Tests/RunCMake/Autogen_2/QtAutoMocVsTargets.cmake new file mode 100644 index 0000000000..c418b93e4d --- /dev/null +++ b/Tests/RunCMake/Autogen_2/QtAutoMocVsTargets.cmake @@ -0,0 +1,21 @@ +enable_language(CXX) + +find_package(Qt${with_qt_version} REQUIRED COMPONENTS Core) + +set(CMAKE_AUTOMOC ON) + +add_library(simple_lib STATIC simple_lib.cpp) + +# A plain AUTOMOC target gets the autogen custom command attached to itself. +add_library(plain_lib STATIC app_qt.cpp) +target_link_libraries(plain_lib PRIVATE simple_lib Qt${with_qt_version}::Core) + +# A target that depends on a GENERATED file keeps the separate autogen targets. +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated_dep.h + COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/generated_dep.h) +add_library(gen_dep_lib STATIC app_qt.cpp + ${CMAKE_CURRENT_BINARY_DIR}/generated_dep.h) +target_link_libraries(gen_dep_lib PRIVATE Qt${with_qt_version}::Core) +target_include_directories(gen_dep_lib PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +set_property(TARGET gen_dep_lib PROPERTY + AUTOGEN_TARGET_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/autogen_dep.txt) diff --git a/Tests/RunCMake/Autogen_2/RunCMakeTest.cmake b/Tests/RunCMake/Autogen_2/RunCMakeTest.cmake index e97e89617f..9286d96b98 100644 --- a/Tests/RunCMake/Autogen_2/RunCMakeTest.cmake +++ b/Tests/RunCMake/Autogen_2/RunCMakeTest.cmake @@ -84,4 +84,68 @@ Automatic MOC for target sub_exe_2") endif() endblock() endif() + + if(RunCMake_GENERATOR MATCHES "Visual Studio" + AND QtCore_VERSION VERSION_GREATER_EQUAL 5.15.0) + block() + macro(check_project_exists target expected) + if(EXISTS "${RunCMake_TEST_BINARY_DIR}/${target}.vcxproj") + set(actual TRUE) + else() + set(actual FALSE) + endif() + if("${actual}" STREQUAL "${expected}") + set(check_result "PASSED") + set(message_type "STATUS") + else() + set(check_result "FAILED") + set(message_type "FATAL_ERROR") + endif() + message(${message_type} + "QtAutoMocVsTargets-\"${target}.vcxproj\" exists is ${actual}, expected ${expected} - ${check_result}") + endmacro() + + set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/QtAutoMocVsTargets-build) + run_cmake_with_options(QtAutoMocVsTargets ${RunCMake_TEST_OPTIONS}) + set(RunCMake_TEST_NO_CLEAN 1) + + # A plain AUTOMOC target does not get extra projects. + check_project_exists(plain_lib TRUE) + check_project_exists(plain_lib_autogen FALSE) + check_project_exists(plain_lib_autogen_timestamp_deps FALSE) + # A target with a GENERATED dependency keeps them. + check_project_exists(gen_dep_lib_autogen TRUE) + check_project_exists(gen_dep_lib_autogen_timestamp_deps TRUE) + + # The first build must run AUTOMOC. Assert it, so that the checks below + # cannot pass just because the message is missing from the build output. + set(RunCMake_TEST_EXPECT_stdout "Automatic MOC for target plain_lib") + run_cmake_command(QtAutoMocVsTargets-build ${CMAKE_COMMAND} --build . --config Debug) + unset(RunCMake_TEST_EXPECT_stdout) + + set(RunCMake_TEST_NOT_EXPECT_stdout "Automatic MOC for target plain_lib|\ +Automatic MOC for target gen_dep_lib") + # Assert that the depfile prevents AUTOMOC from running again. + set(RunCMake_TEST_VARIANT_DESCRIPTION "-Don't execute AUTOMOC again") + run_cmake_command(QtAutoMocVsTargets-build ${CMAKE_COMMAND} --build . --config Debug) + # Touch the source file of a library that 'plain_lib' links. This must + # not cause a rerun of AUTOMOC. + file(TOUCH "${RunCMake_SOURCE_DIR}/simple_lib.cpp") + set(RunCMake_TEST_VARIANT_DESCRIPTION "-Don't execute AUTOMOC for a dependency change") + run_cmake_command(QtAutoMocVsTargets-build ${CMAKE_COMMAND} --build . --config Debug) + # Files in AUTOGEN_TARGET_DEPENDS are order-only dependencies. Touching + # one must not cause a rerun of AUTOMOC either. + file(TOUCH "${RunCMake_SOURCE_DIR}/autogen_dep.txt") + set(RunCMake_TEST_VARIANT_DESCRIPTION "-Don't execute AUTOMOC for an AUTOGEN_TARGET_DEPENDS file") + run_cmake_command(QtAutoMocVsTargets-build ${CMAKE_COMMAND} --build . --config Debug) + unset(RunCMake_TEST_NOT_EXPECT_stdout) + + # Another configuration has its own timestamp file, so it builds again. + set(RunCMake_TEST_EXPECT_stdout "Automatic MOC for target plain_lib") + set(RunCMake_TEST_VARIANT_DESCRIPTION "-Release") + run_cmake_command(QtAutoMocVsTargets-build ${CMAKE_COMMAND} --build . --config Release) + unset(RunCMake_TEST_EXPECT_stdout) + unset(RunCMake_TEST_VARIANT_DESCRIPTION) + endblock() + endif() endif () diff --git a/Tests/RunCMake/Autogen_2/autogen_dep.txt b/Tests/RunCMake/Autogen_2/autogen_dep.txt new file mode 100644 index 0000000000..7415b80db5 --- /dev/null +++ b/Tests/RunCMake/Autogen_2/autogen_dep.txt @@ -0,0 +1 @@ +A file listed in AUTOGEN_TARGET_DEPENDS.