Files
Joerg Bornemann d963b87ab4 Autogen: Reduce the number of targets under Visual Studio
Commit 680fbb112a (Autogen: Enable depfile support for Visual Studio and
Xcode generators) disabled the PRE_BUILD optimization when a depfile is
used, because a PRE_BUILD event cannot carry depfile dependencies. Since
then, every target with AUTOMOC or AUTOUIC enabled gains an
'<ORIGIN>_autogen' and an '<ORIGIN>_autogen_timestamp_deps' target,
which the Visual Studio generators turn into two extra projects per
target in the generated solution.

Attach the autogen custom command to the origin target instead. The
custom command still carries the depfile, and the dependencies of the
origin target provide the ordering that
'<ORIGIN>_autogen_timestamp_deps' provides otherwise, because MSBuild
builds all referenced projects before any step of the referencing
project.

Use the conditions of the PRE_BUILD event, so that the
'<ORIGIN>_autogen' target keeps existing wherever it existed before
depfiles were enabled for the Visual Studio generators. In particular,
projects that name the target in add_dependencies() keep working.

Ninja and the Makefile generators keep the separate targets. Extra
targets have no cost for them, and '<ORIGIN>_autogen' has always existed
there.

The Xcode generator gained the same two targets per origin target in
4.4, but attaching the custom command there needs some more thoughts on
the per-config timestamp file first: Xcode rejects targets whose sources
vary by configuration, and with AUTOGEN_BETTER_GRAPH_MULTI_CONFIG the
timestamp file is per configuration.

Fixes: #28034
Fixes: #28033
2026-08-14 13:14:54 +02:00

152 lines
7.4 KiB
CMake

include(RunCMake)
if (DEFINED with_qt_version)
set(RunCMake_TEST_OPTIONS
-Dwith_qt_version=${with_qt_version}
"-DQt${with_qt_version}_DIR:PATH=${Qt${with_qt_version}_DIR}"
"-DCMAKE_PREFIX_PATH:STRING=${CMAKE_PREFIX_PATH}"
)
if(RunCMake_GENERATOR MATCHES "Make|Ninja")
block()
if(QtCore_VERSION VERSION_GREATER_EQUAL 5.15.0)
if (RunCMake_GENERATOR MATCHES "Ninja Multi-Config")
set(config_list Debug Release RelWithDebInfo)
set(use_better_graph_list ON OFF)
else()
set(config_list single-config)
set(use_better_graph_list OFF)
endif()
foreach(use_better_graph IN ITEMS ${use_better_graph_list})
foreach(config IN ITEMS ${config_list})
block()
if (config STREQUAL "single-config")
set(config_suffix "")
else()
set(config_path "_${config}")
if (use_better_graph)
set(config_suffix "_${config}")
endif()
endif()
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/QtAutoMocDeps${config_path}-build)
run_cmake_with_options(QtAutoMocDeps ${RunCMake_TEST_OPTIONS} -DCMAKE_AUTOGEN_BETTER_GRAPH_MULTI_CONFIG=${use_better_graph})
set(RunCMake_TEST_NO_CLEAN 1)
# Build the project.
if (config STREQUAL "single-config")
set(config_param "")
else()
set(config_param "--config ${config}")
endif()
run_cmake_command(QtAutoMocDeps-build ${CMAKE_COMMAND} --build . --verbose ${config_param})
# Touch just the library source file, which shouldn't cause a rerun of AUTOMOC
# for app_with_qt target.
file(TOUCH "${RunCMake_SOURCE_DIR}/simple_lib.cpp")
set(RunCMake_TEST_NOT_EXPECT_stdout "Automatic MOC for target app_with_qt|\
Automatic MOC for target sub_exe_1|\
Automatic MOC for target sub_exe_2")
set(RunCMake_TEST_VARIANT_DESCRIPTION "-Don't execute AUTOMOC for 'app_with_qt', 'sub_exe_1' and 'sub_exe_2'")
# Build and assert that AUTOMOC was not run for app_with_qt, sub_exe_1 and sub_exe_2.
run_cmake_command(QtAutoMocDeps-build ${CMAKE_COMMAND} --build . --verbose ${config_param})
unset(RunCMake_TEST_VARIANT_DESCRIPTION)
unset(RunCMake_TEST_NOT_EXPECT_stdout)
macro(check_file_exists file)
if (EXISTS "${file}")
set(check_result "PASSED")
set(message_type "STATUS")
else()
set(check_result "FAILED")
set(message_type "FATAL_ERROR")
endif()
message(${message_type} "QtAutoMocDeps-build-\"${file}\" was generated - ${check_result}")
endmacro()
check_file_exists("${RunCMake_TEST_BINARY_DIR}/app_with_qt_autogen/deps${config_suffix}")
check_file_exists("${RunCMake_TEST_BINARY_DIR}/QtSubDir1/sub_exe_1_autogen/deps${config_suffix}")
check_file_exists("${RunCMake_TEST_BINARY_DIR}/QtSubDir2/sub_exe_2_autogen/deps${config_suffix}")
check_file_exists("${RunCMake_TEST_BINARY_DIR}/app_with_qt_autogen/timestamp${config_suffix}")
check_file_exists("${RunCMake_TEST_BINARY_DIR}/QtSubDir1/sub_exe_1_autogen/timestamp${config_suffix}")
check_file_exists("${RunCMake_TEST_BINARY_DIR}/QtSubDir2/sub_exe_2_autogen/timestamp${config_suffix}")
# Touch a header file to make sure an automoc dependency cycle is not introduced.
file(TOUCH "${RunCMake_SOURCE_DIR}/MyWindow.h")
set(RunCMake_TEST_VARIANT_DESCRIPTION "-First build after touch to detect dependency cycle")
run_cmake_command(QtAutoMocDeps-build ${CMAKE_COMMAND} --build . --verbose)
# Need to run a second time to hit the dependency cycle.
set(RunCMake_TEST_VARIANT_DESCRIPTION "-Don't hit dependency cycle")
run_cmake_command(QtAutoMocDeps-build ${CMAKE_COMMAND} --build . --verbose)
endblock()
endforeach()
endforeach()
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 ()