summaryrefslogtreecommitdiffstats
path: root/tests/auto/cmake
diff options
context:
space:
mode:
authorOrkun Tokdemir <orkun.tokdemir@qt.io>2023-11-21 10:46:30 +0100
committerOrkun Tokdemir <orkun.tokdemir@qt.io>2024-05-07 21:12:29 +0200
commit545b84b93c0b84a08c037e7b70deec3400d1f827 (patch)
tree0d3deef864f3df8294822f82d9e66106329695ec /tests/auto/cmake
parent0678a8a00da586e78d464608e83e89b4df589485 (diff)
CMake: Add `qt_add_ui` API
This commit introduces a new `qt_add_ui` CMake API to be used instead of AUTOUIC, due to several hard-to-fix bugs it has. * Resolves the issue where Ninja required two passes to correctly build `.ui` files. * Avoids build folder leakage, in contrast to `AUTOUIC` * Prevents unnecessary recompilation of unchanged source files when a `.ui` file is modified with Ninja. * Since `qt_add_ui` does not scan source files, it provides a faster build than `AUTOUIC`. These changes aim to streamline the build process and improve efficiency when working with `.ui` files. This addresses the following issues: https://gitlab.kitware.com/cmake/cmake/-/issues/16776 https://gitlab.kitware.com/cmake/cmake/-/issues/25436 https://gitlab.kitware.com/cmake/cmake/-/issues/25195 Task-number: QTBUG-110369 Fixes: QTBUG-114563 Change-Id: I34cd3df70542becf6e08502f0cbcd1c11eeeadd6 Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'tests/auto/cmake')
-rw-r--r--tests/auto/cmake/CMakeLists.txt36
-rw-r--r--tests/auto/cmake/test_qt_add_ui_1/CMakeLists.txt62
-rw-r--r--tests/auto/cmake/test_qt_add_ui_10/CMakeLists.txt62
-rw-r--r--tests/auto/cmake/test_qt_add_ui_2/CMakeLists.txt60
-rw-r--r--tests/auto/cmake/test_qt_add_ui_3/CMakeLists.txt199
-rw-r--r--tests/auto/cmake/test_qt_add_ui_4/CMakeLists.txt65
-rw-r--r--tests/auto/cmake/test_qt_add_ui_5/CMakeLists.txt164
-rw-r--r--tests/auto/cmake/test_qt_add_ui_6/CMakeLists.txt64
-rw-r--r--tests/auto/cmake/test_qt_add_ui_7/CMakeLists.txt62
-rw-r--r--tests/auto/cmake/test_qt_add_ui_8/CMakeLists.txt67
-rw-r--r--tests/auto/cmake/test_qt_add_ui_9/CMakeLists.txt66
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/CMakeLists.txt7
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/RunCMake.cmake157
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/UicBuildFolderLeakageCommon/main.cpp14
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/UicBuildFolderLeakageCommon/mainwindow.h26
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/main.cpp14
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/mainwindow.cpp28
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/mainwindow.h25
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/ui_files/mainwindow.ui33
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/ui_files/widget1.ui52
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/widget1.cpp25
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/widget1.h28
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/widget1.ui52
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/widget2.cpp25
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/widget2.h29
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/sub1/sub2/sub3/sub4/CMakeLists.txt39
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/widget1.ui45
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/CMakeLists.txt33
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/main.cpp14
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/mainwindow.cpp28
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/mainwindow.h25
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/mainwindow.ui33
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/widget1.cpp25
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/widget1.h28
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/widget1.ui52
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/widget2.cpp25
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/widget2.h29
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/widget2.ui45
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/functions.cmake223
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/main.cpp4
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/uic_test/CMakeLists.txt65
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/uic_test/mainwindow.cpp17
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/uic_test/mainwindow.ui45
-rw-r--r--tests/auto/cmake/test_qt_add_ui_common/uic_test/subdir/mainwindow.ui45
44 files changed, 2242 insertions, 0 deletions
diff --git a/tests/auto/cmake/CMakeLists.txt b/tests/auto/cmake/CMakeLists.txt
index 3331ad260d..b159de7c9c 100644
--- a/tests/auto/cmake/CMakeLists.txt
+++ b/tests/auto/cmake/CMakeLists.txt
@@ -399,8 +399,44 @@ endif()
_qt_internal_test_expect_pass(test_config_expressions)
_qt_internal_test_expect_pass(test_QTP0003)
+
if(NOT NO_GUI)
_qt_internal_test_expect_pass(test_collecting_plugins)
endif()
_qt_internal_test_expect_pass(test_qt_manual_moc)
+
+# check if the os is opensuse. If it is, we need to skip tests due to CI problems
+set(is_opensuse FALSE)
+if(UNIX)
+ if(EXISTS "/etc/os-release")
+ file(STRINGS "/etc/os-release" os_release_content)
+ foreach(line IN LISTS os_release_content)
+ if(line MATCHES "openSUSE" OR line MATCHES "opensuse")
+ set(is_opensuse TRUE)
+ break()
+ endif()
+ endforeach()
+ endif()
+endif()
+
+if(NOT QNX AND NOT WASM AND NOT (WIN32 AND QT_BUILD_MINIMAL_STATIC_TESTS)
+ AND NOT is_opensuse)
+ # Since our CI machines are slow, ctest --build-and-test buffers the output
+ # of the configure step of a test, and the fact that we run all the test
+ # logic in the configure step, we need to divide the tests into smaller
+ # chunks to avoid CI stdout timeout errors.
+ # See https://gitlab.kitware.com/cmake/cmake/-/issues/25790
+ _qt_internal_test_expect_pass(test_qt_add_ui_common)
+ _qt_internal_test_expect_pass(test_qt_add_ui_1)
+ _qt_internal_test_expect_pass(test_qt_add_ui_2)
+ _qt_internal_test_expect_pass(test_qt_add_ui_3)
+ _qt_internal_test_expect_pass(test_qt_add_ui_4)
+ _qt_internal_test_expect_pass(test_qt_add_ui_5)
+ _qt_internal_test_expect_pass(test_qt_add_ui_6)
+ _qt_internal_test_expect_pass(test_qt_add_ui_7)
+ _qt_internal_test_expect_pass(test_qt_add_ui_8)
+ _qt_internal_test_expect_pass(test_qt_add_ui_9)
+ _qt_internal_test_expect_pass(test_qt_add_ui_10)
+endif()
+
diff --git a/tests/auto/cmake/test_qt_add_ui_1/CMakeLists.txt b/tests/auto/cmake/test_qt_add_ui_1/CMakeLists.txt
new file mode 100644
index 0000000000..882abecc0b
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_1/CMakeLists.txt
@@ -0,0 +1,62 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(test)
+
+include(../test_qt_add_ui_common/RunCMake.cmake)
+include(../test_qt_add_ui_common/functions.cmake)
+
+get_generators(generators)
+
+foreach(generator IN ITEMS ${generators})
+ message(STATUS "Running tests for generator: ${generator}")
+ is_multi_config(${generator} multi_config_out)
+ if(multi_config_out)
+ set(configs "Debug" "Release")
+ else()
+ set(configs "single_config")
+ endif()
+
+ foreach(config IN ITEMS ${configs})
+ if("${config}" STREQUAL "single_config")
+ set(config_path "")
+ set(config_arg "")
+ else()
+ set(config_path "_${config}")
+ set(config_arg "${config}")
+ endif()
+
+ # Test case: ui_mainwindow.h is included as
+ # "sub1/sub2/sub3/sub4/../../../../src/ui_files/ui_mainwindow.h".
+ # Expect 1: Successful build without the double build issue.
+ # Expect 2: No build folder leakage and generation of the
+ # sub1/sub2/sub3/sub4 folder in ${hash_folder}/include
+ string(CONCAT test_ui_file "${CMAKE_CURRENT_SOURCE_DIR}/../"
+ "test_qt_add_ui_common/uic_test/mainwindow.ui")
+ generate_hash_folder( "example" "${test_ui_file}" hash_folder)
+ string(CONCAT test_build_dir
+ "${CMAKE_CURRENT_BINARY_DIR}/UicBuildLeak_incPathGen"
+ "${config_path}-build")
+ string(CONCAT test_source_dir "${CMAKE_CURRENT_SOURCE_DIR}/../"
+ "test_qt_add_ui_common/uic_test")
+ string(CONCAT test_additional_args "-DMAINWINDOW_UI_PATH=sub1/sub2/"
+ "sub3/sub4/../../../../src/ui_files/")
+ string(CONCAT test_file_to_check "${test_build_dir}/.qt/${hash_folder}/"
+ "${config_arg}/src/ui_files/ui_mainwindow.h")
+ string(CONCAT test_folder_to_check "${test_build_dir}/.qt/"
+ "${hash_folder}/${config_arg}/sub1/sub2/sub3/sub4")
+ incremental_build_test(
+ TEST_NAME UicBuildLeak_incPathGen
+ SOURCE_DIR "${test_source_dir}"
+ BUILD_DIR "${test_build_dir}"
+ CONFIG "${config_arg}"
+ GENERATOR "${generator}"
+ ADDITIONAL_ARGS "${test_additional_args}"
+ FILE_TO_TOUCH "${test_ui_file}"
+ FILE_TO_CHECK "${test_file_to_check}"
+ FOLDER_TO_CHECK "${test_folder_to_check}"
+ )
+ endforeach()
+endforeach()
+
diff --git a/tests/auto/cmake/test_qt_add_ui_10/CMakeLists.txt b/tests/auto/cmake/test_qt_add_ui_10/CMakeLists.txt
new file mode 100644
index 0000000000..6b223fb281
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_10/CMakeLists.txt
@@ -0,0 +1,62 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(test)
+
+include(../test_qt_add_ui_common/RunCMake.cmake)
+include(../test_qt_add_ui_common/functions.cmake)
+
+get_generators(generators)
+
+foreach(generator IN ITEMS ${generators})
+ message(STATUS "Running tests for generator: ${generator}")
+ is_multi_config(${generator} multi_config_out)
+ # A CI test fails with the below condition. So, we are running the test
+ # only for the Debug configuration.
+ if ("${generator}" MATCHES "Xcode" AND
+ CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")
+ set(configs "Debug")
+ elseif(multi_config_out)
+ set(configs "Debug" "Release")
+ else()
+ set(configs "single_config")
+ endif()
+
+ foreach(config IN ITEMS ${configs})
+ if("${config}" STREQUAL "single_config")
+ set(config_path "")
+ set(config_arg "")
+ else()
+ set(config_path "_${config}")
+ set(config_arg "${config}")
+ endif()
+
+ # Test case: mainwindow.ui file is touched after the first build. The
+ # ui_mainwindow.h should be generated without the ${config_arg} folder.
+ # Expect 1: Successful build without the double build issue.
+ string(CONCAT test_build_dir "${CMAKE_CURRENT_BINARY_DIR}/"
+ "qt_add_ui_simple_no_config${config_path}-build")
+ string(CONCAT test_ui_file "${CMAKE_CURRENT_SOURCE_DIR}/../"
+ "test_qt_add_ui_common/uic_test/mainwindow.ui")
+ string(CONCAT test_source_dir "${CMAKE_CURRENT_SOURCE_DIR}/../"
+ "test_qt_add_ui_common/uic_test")
+ string(CONCAT test_file_to_touch "${CMAKE_CURRENT_SOURCE_DIR}/../"
+ "test_qt_add_ui_common/uic_test/mainwindow.ui")
+ generate_hash_folder(
+ "example"
+ "${test_ui_file}"
+ hash_folder)
+ incremental_build_test(
+ TEST_NAME qt6_add_ui_simple_no_config
+ SOURCE_DIR "${test_source_dir}"
+ BUILD_DIR "${test_build_dir}"
+ GENERATOR "${generator}"
+ CONFIG "${config_arg}"
+ ADDITIONAL_ARGS "-DUI_NO_CONFIG_OPTIONS=ON"
+ FILE_TO_TOUCH "${test_file_to_touch}"
+ FILE_TO_CHECK
+ "${test_build_dir}/.qt/${hash_folder}/ui_mainwindow.h"
+ )
+ endforeach()
+endforeach()
diff --git a/tests/auto/cmake/test_qt_add_ui_2/CMakeLists.txt b/tests/auto/cmake/test_qt_add_ui_2/CMakeLists.txt
new file mode 100644
index 0000000000..dceb0c7a2e
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_2/CMakeLists.txt
@@ -0,0 +1,60 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(test)
+
+include(../test_qt_add_ui_common/RunCMake.cmake)
+include(../test_qt_add_ui_common/functions.cmake)
+
+get_generators(generators)
+
+foreach(generator IN ITEMS ${generators})
+ message(STATUS "Running tests for generator: ${generator}")
+ is_multi_config(${generator} multi_config_out)
+ # A CI test fails with the below condition. So, we are running the test
+ # only for the Debug configuration.
+ if ("${generator}" MATCHES "Xcode" AND
+ CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")
+ set(configs "Debug")
+ elseif(multi_config_out)
+ set(configs "Debug" "Release")
+ else()
+ set(configs "single_config")
+ endif()
+
+ foreach(config IN ITEMS ${configs})
+ if("${config}" STREQUAL "single_config")
+ set(config_path "")
+ set(config_arg "")
+ else()
+ set(config_path "_${config}")
+ set(config_arg "${config}")
+ endif()
+
+ # Test case: mainwindow.ui file is touched after the first build.
+ # Expect 1: Successful build without the double build issue.
+ set(test_build_dir
+ "${CMAKE_CURRENT_BINARY_DIR}/qt_add_ui_simple${config_path}-build")
+ string(CONCAT test_ui_file "${CMAKE_CURRENT_SOURCE_DIR}/../"
+ "test_qt_add_ui_common/uic_test/mainwindow.ui")
+ string(CONCAT test_source_dir "${CMAKE_CURRENT_SOURCE_DIR}/../"
+ "test_qt_add_ui_common/uic_test")
+ string(CONCAT test_file_to_touch "${CMAKE_CURRENT_SOURCE_DIR}/../"
+ "test_qt_add_ui_common/uic_test/mainwindow.ui")
+ generate_hash_folder(
+ "example"
+ "${test_ui_file}"
+ hash_folder)
+ incremental_build_test(
+ TEST_NAME qt6_add_ui_simple
+ SOURCE_DIR "${test_source_dir}"
+ BUILD_DIR "${test_build_dir}"
+ GENERATOR "${generator}"
+ CONFIG "${config_arg}"
+ FILE_TO_TOUCH "${test_file_to_touch}"
+ FILE_TO_CHECK
+ "${test_build_dir}/.qt/${hash_folder}/${config_arg}/ui_mainwindow.h"
+ )
+ endforeach()
+endforeach()
diff --git a/tests/auto/cmake/test_qt_add_ui_3/CMakeLists.txt b/tests/auto/cmake/test_qt_add_ui_3/CMakeLists.txt
new file mode 100644
index 0000000000..81a9a38431
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_3/CMakeLists.txt
@@ -0,0 +1,199 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(test)
+
+include(../test_qt_add_ui_common/RunCMake.cmake)
+include(../test_qt_add_ui_common/functions.cmake)
+
+get_generators(generators)
+
+foreach(generator IN ITEMS ${generators})
+ message(STATUS "Running tests for generator: ${generator}")
+ is_multi_config(${generator} multi_config_out)
+ if(multi_config_out)
+ set(configs "Debug" "Release")
+ else()
+ set(configs "single_config")
+ endif()
+
+ foreach(config IN ITEMS ${configs})
+ if("${config}" STREQUAL "single_config")
+ set(config_path "")
+ set(config_arg "")
+ else()
+ set(config_path "_${config}")
+ set(config_arg "${config}")
+ endif()
+
+ # Test case: Check whether api sets warning against AUTOUIC activation.
+ # Expect 1: Printing of the error message and failure of the build.
+ string(CONCAT test_source_dir "${CMAKE_CURRENT_SOURCE_DIR}/../"
+ "test_qt_add_ui_common/uic_test")
+ string(CONCAT test_build_dir "${CMAKE_CURRENT_BINARY_DIR}/"
+ "uic_warning_test${config_path}-build")
+ run_cmake_configure(SOURCE_DIR "${test_source_dir}"
+ BUILD_DIR "${test_build_dir}"
+ GENERATOR "${generator}"
+ CLEAN_FIRST
+ ADDITIONAL_ARGS "-DCMAKE_AUTOUIC=ON"
+ OUTPUT_VARIABLE cmake_output
+ ERROR_VARIABLE cmake_error
+ RESULT_VARIABLE cmake_result)
+ if(NOT cmake_result EQUAL 0)
+ message(FATAL_ERROR "cmake_output: ${cmake_output}\ncmake_error: "
+ "${cmake_error}\nFAIL: \"uic_warning_test\" test in "
+ "${CMAKE_CURRENT_BINARY_DIR}/uic_warning_test${config_path}-"
+ "build failed to configure")
+ else()
+ message(STATUS "PASS: \"uic_warning_test\" test in "
+ "${CMAKE_CURRENT_BINARY_DIR}/uic_warning_test${config_path}-"
+ "build was configured successfully")
+ endif()
+
+ string(CONCAT test_build_dir "${CMAKE_CURRENT_BINARY_DIR}/"
+ "uic_warning_test${config_path}-build")
+ run_cmake_build(
+ BUILD_DIR "${test_build_dir}"
+ VERBOSE ON
+ CONFIG "${config_arg}"
+ OUTPUT_VARIABLE cmake_build_output
+ RESULT_VARIABLE cmake_build_result)
+ if(NOT cmake_build_result EQUAL 0)
+ message(STATUS "PASS: \"uic_warning_test\" test in "
+ "${CMAKE_CURRENT_BINARY_DIR}/uic_warning_test${config_path}"
+ "-build failed to build")
+ else()
+ message(FATAL_ERROR "FAIL: \"uic_warning_test\" test in "
+ "${CMAKE_CURRENT_BINARY_DIR}/uic_warning_test${config_path}-"
+ "build was built successfully")
+ endif()
+ expect_string_contains("${cmake_build_output}" "has \"AUTOUIC\" enabled"
+ SUCCESS_MESSAGE "\"uic_warning_test\" test in \
+${CMAKE_CURRENT_BINARY_DIR}/uic_warning_test${config_path}-build \
+has \"has AUTOUIC enabled\""
+ FAILURE_MESSAGE "\"uic_warning_test\" test in \
+${CMAKE_CURRENT_BINARY_DIR}/uic_warning_test${config_path}-build \
+does not have \"has AUTOUIC enabled\"")
+
+
+ if("${generator}" MATCHES "Ninja")
+ # Test case: If INCLUDE_PREFIX is changed without changing the
+ # corresponding include path in the source file and Ninja generator
+ # is used, this casues the double build issue.
+ # Note: Only happens in Ninja generator.
+ # Expect 1: Failure of the build in the first build.
+ string(CONCAT test_source_dir "${CMAKE_CURRENT_SOURCE_DIR}/../"
+ "test_qt_add_ui_common/uic_test")
+ string(CONCAT test_build_dir "${CMAKE_CURRENT_BINARY_DIR}/"
+ "uic_double_build_test${config_path}-build")
+ run_cmake_configure(SOURCE_DIR "${test_source_dir}"
+ BUILD_DIR "${test_build_dir}"
+ GENERATOR "${generator}"
+ CLEAN_FIRST
+ ADDITIONAL_ARGS "-DMAINWINDOW_UI_PATH=sub1/sub2/sub3/"
+ "-DDO_NOT_GENERATE_FILE=ON"
+ OUTPUT_VARIABLE cmake_output
+ ERROR_VARIABLE cmake_error
+ RESULT_VARIABLE cmake_result)
+
+ if(NOT cmake_result EQUAL 0)
+ message(FATAL_ERROR "cmake_output: ${cmake_output}\ncmake_error"
+ ": ${cmake_error}\nFAIL: \"uic_double_build_test\" test in "
+ "${CMAKE_CURRENT_BINARY_DIR}/uic_double_build_test"
+ "${config_path}-build failed to configure")
+ else()
+ message(STATUS "PASS: \"uic_double_build_test\" test in "
+ "${CMAKE_CURRENT_BINARY_DIR}/uic_double_build_test"
+ "${config_path}-build was configured successfully")
+ endif()
+
+ string(CONCAT test_build_dir "${CMAKE_CURRENT_BINARY_DIR}/"
+ "uic_double_build_test${config_path}-build")
+ run_cmake_build(
+ BUILD_DIR "${test_build_dir}"
+ VERBOSE ON
+ CONFIG "${config_arg}"
+ OUTPUT_VARIABLE cmake_build_output
+ RESULT_VARIABLE cmake_build_result)
+
+ if(NOT cmake_build_result EQUAL 0)
+ message(FATAL_ERROR "cmake_build_output: ${cmake_build_output}"
+ "FAIL: \"uic_double_build_test\" test in "
+ "${CMAKE_CURRENT_BINARY_DIR}/uic_double_build_test"
+ "${config_path}-build failed to build in the first build")
+ else()
+ message(STATUS "PASS: \"uic_double_build_test\" test in "
+ "${CMAKE_CURRENT_BINARY_DIR}/uic_double_build_test"
+ "${config_path}-build was built successfully in the first "
+ "build")
+ endif()
+
+ string(CONCAT test_source_dir "${CMAKE_CURRENT_SOURCE_DIR}/../"
+ "test_qt_add_ui_common/uic_test")
+ string(CONCAT test_build_dir "${CMAKE_CURRENT_BINARY_DIR}/"
+ "uic_double_build_test${config_path}-build")
+ run_cmake_configure(SOURCE_DIR "${test_source_dir}"
+ BUILD_DIR "${test_build_dir}"
+ GENERATOR "${generator}"
+ # We change the INCLUDE_PREFIX here.
+ ADDITIONAL_ARGS "-DMAINWINDOW_UI_PATH=sub1/sub2/sub/"
+ "-DDO_NOT_GENERATE_FILE=ON"
+ OUTPUT_VARIABLE cmake_output
+ ERROR_VARIABLE cmake_error
+ RESULT_VARIABLE cmake_result)
+
+ if(NOT cmake_result EQUAL 0)
+ message(FATAL_ERROR "cmake_output: ${cmake_output}\ncmake_error"
+ ":${cmake_error}\nFAIL: \"uic_double_build_test\" test in "
+ "${CMAKE_CURRENT_BINARY_DIR}/uic_double_build_test"
+ "${config_path}-build failed to configure in the second "
+ "build")
+ else()
+ message(STATUS "PASS: \"uic_double_build_test\" test in "
+ "${CMAKE_CURRENT_BINARY_DIR}/uic_double_build_test"
+ "${config_path}-build was configured successfully in the "
+ "second build")
+ endif()
+
+ string(CONCAT test_build_dir "${CMAKE_CURRENT_BINARY_DIR}/"
+ "uic_double_build_test${config_path}-build")
+ run_cmake_build(
+ BUILD_DIR "${test_build_dir}"
+ VERBOSE ON
+ CONFIG "${config_arg}"
+ OUTPUT_VARIABLE cmake_build_output
+ ERROR_VARIABLE cmake_build_error
+ RESULT_VARIABLE cmake_build_result)
+
+ if(NOT cmake_build_result EQUAL 0)
+ message(STATUS "PASS: \"uic_double_build_test\" test in"
+ "${CMAKE_CURRENT_BINARY_DIR}/uic_double_build_test"
+ "${config_path}-build failed to build in the first build "
+ "after changing INCLUDE_PREFIX")
+ else()
+ message(FATAL_ERROR "cmake_build_output: ${cmake_build_output}"
+ "\ncmake_build_error: ${cmake_build_error}\n"
+ "FAIL: \"uic_double_build_test\" test in "
+ "${CMAKE_CURRENT_BINARY_DIR}/uic_double_build_test"
+ "${config_path}-build was built successfully in the first "
+ "build after changing INCLUDE_PREFIX")
+ endif()
+
+ set(expected_fail_string "No such file or directory|file not found")
+ expect_string_contains(${cmake_build_output}
+ "${expected_fail_string}"
+ SUCCESS_MESSAGE "\"uic_double_build_test\" test in \
+${CMAKE_CURRENT_BINARY_DIR}/uic_double_build_test${config_path}\
+-build has \"ui_mainwindow.h: No such file or directory\" in \
+the first build after changing INCLUDE_PREFIX"
+ FAILURE_MESSAGE "\"uic_double_build_test\" test in \
+${CMAKE_CURRENT_BINARY_DIR}/uic_double_build_test${config_path}\
+-build does not have \"ui_mainwindow.h: No such file or \
+directory\" in the first build after changing INCLUDE_PREFIX")
+
+ endif()
+ endforeach()
+endforeach()
+
diff --git a/tests/auto/cmake/test_qt_add_ui_4/CMakeLists.txt b/tests/auto/cmake/test_qt_add_ui_4/CMakeLists.txt
new file mode 100644
index 0000000000..51c74352d6
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_4/CMakeLists.txt
@@ -0,0 +1,65 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(test)
+
+include(../test_qt_add_ui_common/RunCMake.cmake)
+include(../test_qt_add_ui_common/functions.cmake)
+
+get_generators(generators)
+
+foreach(generator IN ITEMS ${generators})
+ message(STATUS "Running tests for generator: ${generator}")
+ is_multi_config(${generator} multi_config_out)
+ # A CI test fails with the below condition. So, we are running the test
+ # only for the Debug configuration.
+ if ("${generator}" MATCHES "Xcode" AND
+ CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")
+ set(configs "Debug")
+ elseif(multi_config_out)
+ set(configs "Debug" "Release")
+ else()
+ set(configs "single_config")
+ endif()
+
+ foreach(config IN ITEMS ${configs})
+ if("${config}" STREQUAL "single_config")
+ set(config_path "")
+ set(config_arg "")
+ else()
+ set(config_path "_${config}")
+ set(config_arg "${config}")
+ endif()
+
+ # Test case: widget1.ui file is touched after the first build.
+ # Expect 1: Successful build without the double build issue.
+ # Expect 2: Only touched files to be built
+ set(test_build_dir
+ "${CMAKE_CURRENT_BINARY_DIR}/UicIncrementalBuild${config_path}-build")
+ string(CONCAT ui_file "${CMAKE_CURRENT_SOURCE_DIR}/../"
+ "test_qt_add_ui_common/UicIncrementalBuild/src/widget1.ui")
+ string(CONCAT test_source_dir "${CMAKE_CURRENT_SOURCE_DIR}/../"
+ "test_qt_add_ui_common/UicIncrementalBuild")
+
+ generate_hash_folder(
+ "example"
+ "${ui_file}"
+ hash_folder)
+ string(CONCAT test_file_to_check "${test_build_dir}/.qt/${hash_folder}/"
+ "${config_arg}/src/ui_widget1.h")
+ incremental_build_test(
+ TEST_NAME UicIncrementalBuild
+ SOURCE_DIR "${test_source_dir}"
+ BUILD_DIR "${test_build_dir}"
+ CONFIG "${config_arg}"
+ GENERATOR "${generator}"
+ FILE_TO_TOUCH "${ui_file}"
+ FILE_TO_CHECK
+ "${test_file_to_check}"
+ FOLDER_TO_CHECK
+ "${test_build_dir}/.qt/${hash_folder}/${config_arg}/src"
+ CHECK_UNWANTED_BUILDS
+ )
+ endforeach()
+endforeach()
diff --git a/tests/auto/cmake/test_qt_add_ui_5/CMakeLists.txt b/tests/auto/cmake/test_qt_add_ui_5/CMakeLists.txt
new file mode 100644
index 0000000000..7b29bcf52b
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_5/CMakeLists.txt
@@ -0,0 +1,164 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(test)
+
+include(../test_qt_add_ui_common/RunCMake.cmake)
+include(../test_qt_add_ui_common/functions.cmake)
+
+get_generators(generators)
+
+foreach(generator IN ITEMS ${generators})
+ message(STATUS "Running tests for generator: ${generator}")
+ is_multi_config(${generator} multi_config_out)
+ if(multi_config_out)
+ set(configs "Debug" "Release")
+ else()
+ set(configs "single_config")
+ endif()
+
+ foreach(config IN ITEMS ${configs})
+ if("${config}" STREQUAL "single_config")
+ set(config_path "")
+ set(config_arg "")
+ else()
+ set(config_path "_${config}")
+ set(config_arg "${config}")
+ endif()
+
+
+ if("${generator}" MATCHES "Ninja")
+ # Test case: Add a new ui file to CMakeLists.txt after the first
+ # build.
+ # Expect 1: Expect ${target}_autogen/prefix_info.cmake to be
+ # generated as expected.
+ string(CONCAT test_source_dir "${CMAKE_CURRENT_SOURCE_DIR}/../"
+ "test_qt_add_ui_common/uic_test")
+ string(CONCAT test_build_dir "${CMAKE_CURRENT_BINARY_DIR}/"
+ "uic_prefix_info_cmake_test${config_path}-build")
+ run_cmake_configure(SOURCE_DIR "${test_source_dir}"
+ BUILD_DIR "${test_build_dir}"
+ GENERATOR "${generator}"
+ CLEAN_FIRST
+ ADDITIONAL_ARGS "-DMAINWINDOW_UI_PATH=sub1/sub2/sub3/"
+ OUTPUT_VARIABLE cmake_output
+ ERROR_VARIABLE cmake_error
+ RESULT_VARIABLE cmake_result)
+
+ if(NOT cmake_result EQUAL 0)
+ message(FATAL_ERROR "cmake_output: ${cmake_output}\ncmake_error"
+ ":${cmake_error}\nFAIL: \"uic_prefix_info_cmake_test\" test"
+ " in ${CMAKE_CURRENT_BINARY_DIR}/uic_prefix_info_cmake_test"
+ "${config_path}-build failed to configure")
+ else()
+ message(STATUS "PASS: \"uic_prefix_info_cmake_test\" test in"
+ "${CMAKE_CURRENT_BINARY_DIR}/uic_prefix_info_cmake_test"
+ "${config_path}-build was configured successfully")
+ endif()
+
+ string(CONCAT test_ui_file "${CMAKE_CURRENT_SOURCE_DIR}/../"
+ "test_qt_add_ui_common/uic_test/mainwindow.ui")
+ generate_hash_folder("example" "${test_ui_file}"
+ hash_folder_mainwindow)
+ string(CONCAT expected_prefix_info_cmake_content
+ "include_guard()\nset(${hash_folder_mainwindow}_prefix \""
+ "sub1/sub2/sub3\")")
+ # read the content of the prefix_info.cmake file
+ string(CONCAT "${CMAKE_CURRENT_BINARY_DIR}/"
+ "uic_prefix_info_cmake_test${config_path}-build")
+ file(READ
+ "${test_build_dir}/example_autogen/prefix_info.cmake"
+ prefix_info_cmake_content)
+
+ if("${prefix_info_cmake_content}" STREQUAL
+ "${expected_prefix_info_cmake_content}")
+ message(STATUS "PASS: \"uic_prefix_info_cmake_test\" test in "
+ "${CMAKE_CURRENT_BINARY_DIR}/uic_prefix_info_cmake_test"
+ "${config_path}-build has the expected content in "
+ "prefix_info.cmake")
+ else()
+ message(FATAL_ERROR "FAIL: \"uic_prefix_info_cmake_test\" test "
+ "in ${CMAKE_CURRENT_BINARY_DIR}/uic_prefix_info_cmake_test"
+ "${config_path}-build does not have the expected content in"
+ " prefix_info.cmake")
+ endif()
+
+ string(CONCAT test_build_dir "${CMAKE_CURRENT_BINARY_DIR}/"
+ "uic_prefix_info_cmake_test${config_path}-build")
+ run_cmake_build(
+ BUILD_DIR "${test_build_dir}"
+ VERBOSE ON
+ CONFIG "${config_arg}"
+ OUTPUT_VARIABLE cmake_build_output
+ ERROR_VARIABLE cmake_build_error
+ RESULT_VARIABLE cmake_build_result)
+
+ if(NOT cmake_build_result EQUAL 0)
+ message(FATAL_ERROR "cmake_build_output: ${cmake_build_output}"
+ "\ncmake_build_error: ${cmake_build_error}\n"
+ "FAIL: \"uic_prefix_info_cmake_test\" test in "
+ "${CMAKE_CURRENT_BINARY_DIR}/uic_prefix_info_cmake_test"
+ "${config_path}-build failed to build in the first build")
+ else()
+ message(STATUS "PASS: \"uic_prefix_info_cmake_test\" test in "
+ "${CMAKE_CURRENT_BINARY_DIR}/uic_prefix_info_cmake_test"
+ "${config_path}-build was built successfully in the first "
+ "build")
+ endif()
+
+ string(CONCAT test_source_dir "${CMAKE_CURRENT_SOURCE_DIR}/../"
+ "test_qt_add_ui_common/uic_test")
+ string(CONCAT test_build_dir "${CMAKE_CURRENT_BINARY_DIR}/"
+ "uic_prefix_info_cmake_test${config_path}-build")
+ run_cmake_configure(SOURCE_DIR "${test_source_dir}"
+ BUILD_DIR "${test_build_dir}"
+ GENERATOR "${generator}"
+ ADDITIONAL_ARGS "-DMAINWINDOW_UI_PATH=sub1/sub2/sub3/"
+ "-DNEW_UI_PATH=sub5/sub6/sub7/ " "-DADD_NEW_UI=ON"
+ OUTPUT_VARIABLE cmake_output
+ ERROR_VARIABLE cmake_error
+ RESULT_VARIABLE cmake_result)
+
+ if(NOT cmake_result EQUAL 0)
+ message(FATAL_ERROR "cmake_output: ${cmake_output}\ncmake_error"
+ ": ${cmake_error}\nFAIL: \"uic_prefix_info_cmake_test\" "
+ "test in ${CMAKE_CURRENT_BINARY_DIR}/uic_prefix_info_cmake_"
+ "test ${config_path}-build failed to configure in the"
+ "second build")
+ else()
+ message(STATUS "PASS: \"uic_prefix_info_cmake_test\" test in"
+ " ${CMAKE_CURRENT_BINARY_DIR}/uic_prefix_info_cmake_test"
+ "${config_path}-build was configured successfully in the "
+ "second build")
+ endif()
+
+ string(CONCAT test_ui_file "${CMAKE_CURRENT_SOURCE_DIR}/../"
+ "test_qt_add_ui_common/uic_test/subdir/mainwindow.ui")
+ generate_hash_folder("example" "${test_ui_file}"
+ hash_folder_subdir_mainwindow)
+ set(expected_prefix_info_cmake_content
+ "include_guard()\nset(${hash_folder_subdir_mainwindow}_prefix \
+\"sub5/sub6/sub7\")")
+ string(CONCAT test_build_dir "${CMAKE_CURRENT_BINARY_DIR}/"
+ "uic_prefix_info_cmake_test${config_path}-build")
+ file(READ
+ "${test_build_dir}/example_autogen/prefix_info.cmake"
+ prefix_info_cmake_content)
+
+ if("${prefix_info_cmake_content}" STREQUAL
+ "${expected_prefix_info_cmake_content}")
+ message(STATUS "PASS: \"uic_prefix_info_cmake_test\" test in "
+ "${CMAKE_CURRENT_BINARY_DIR}/uic_prefix_info_cmake_test"
+ "${config_path}-build has the expected content in "
+ "prefix_info.cmake")
+ else()
+ message(FATAL_ERROR "FAIL: \"uic_prefix_info_cmake_test\" test "
+ "in ${CMAKE_CURRENT_BINARY_DIR}/uic_prefix_info_cmake_test"
+ "${config_path}-build does not have the expected content "
+ "in prefix_info.cmake")
+ endif()
+ endif()
+ endforeach()
+endforeach()
+
diff --git a/tests/auto/cmake/test_qt_add_ui_6/CMakeLists.txt b/tests/auto/cmake/test_qt_add_ui_6/CMakeLists.txt
new file mode 100644
index 0000000000..2d57c6067d
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_6/CMakeLists.txt
@@ -0,0 +1,64 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(test)
+
+include(../test_qt_add_ui_common/RunCMake.cmake)
+include(../test_qt_add_ui_common/functions.cmake)
+
+get_generators(generators)
+
+foreach(generator IN ITEMS ${generators})
+ message(STATUS "Running tests for generator: ${generator}")
+ is_multi_config(${generator} multi_config_out)
+ # A CI test fails with the below condition. So, we are running the test
+ # only for the Debug configuration.
+ if ("${generator}" MATCHES "Xcode" AND
+ CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")
+ set(configs "Debug")
+ elseif(multi_config_out)
+ set(configs "Debug" "Release")
+ else()
+ set(configs "single_config")
+ endif()
+
+ foreach(config IN ITEMS ${configs})
+ if("${config}" STREQUAL "single_config")
+ set(config_path "")
+ set(config_arg "")
+ else()
+ set(config_path "_${config}")
+ set(config_arg "${config}")
+ endif()
+
+ # Test case: ui_mainwindow.h is included as
+ # "../../../../src/ui_files/ui_mainwindow.h".
+ # Test case: mainwindow1.ui file is touched after the first build.
+ # Expect 1: Successful build without the double build issue.
+ # Expect 2: No build folder leakage and generation of the "_/_/_/_/"
+ # folder in ${hash_folder}/include.
+ string(CONCAT test_ui_file "${CMAKE_CURRENT_SOURCE_DIR}/../test_qt_add_ui_"
+ "common/uic_test/mainwindow.ui")
+ generate_hash_folder("example" "${test_ui_file}" hash_folder)
+ string(CONCAT test_build_dir "${CMAKE_CURRENT_BINARY_DIR}/UicBuildLeak_"
+ "subFolderGen${config_path}-build")
+ string(CONCAT test_file_to_touch "${CMAKE_CURRENT_SOURCE_DIR}/../"
+ "test_qt_add_ui_common/uic_test/mainwindow.ui")
+ string(CONCAT test_file_to_check "${test_build_dir}/.qt/${hash_folder}/"
+ "${config_arg}/src/ui_files/ui_mainwindow.h")
+ incremental_build_test(
+ TEST_NAME UicBuildLeak_subFolderGen
+ SOURCE_DIR
+ "${CMAKE_CURRENT_SOURCE_DIR}/../test_qt_add_ui_common/uic_test"
+ BUILD_DIR "${test_build_dir}"
+ CONFIG "${config_arg}"
+ GENERATOR "${generator}"
+ ADDITIONAL_ARGS "-DMAINWINDOW_UI_PATH=../../../../src/ui_files/"
+ FILE_TO_TOUCH "${test_file_to_touch}"
+ FILE_TO_CHECK "${test_file_to_check}"
+ FOLDER_TO_CHECK
+ "${test_build_dir}/.qt/${hash_folder}/${config_arg}/_/_/_/_"
+ )
+ endforeach()
+endforeach()
diff --git a/tests/auto/cmake/test_qt_add_ui_7/CMakeLists.txt b/tests/auto/cmake/test_qt_add_ui_7/CMakeLists.txt
new file mode 100644
index 0000000000..8278f13a72
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_7/CMakeLists.txt
@@ -0,0 +1,62 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(test)
+
+include(../test_qt_add_ui_common/RunCMake.cmake)
+include(../test_qt_add_ui_common/functions.cmake)
+
+get_generators(generators)
+
+foreach(generator IN ITEMS ${generators})
+ message(STATUS "Running tests for generator: ${generator}")
+ is_multi_config(${generator} multi_config_out)
+ # A CI test fails with the below condition. So, we are running the test
+ # only for the Debug configuration.
+ if ("${generator}" MATCHES "Xcode" AND
+ CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")
+ set(configs "Debug")
+ elseif(multi_config_out)
+ set(configs "Debug" "Release")
+ else()
+ set(configs "single_config")
+ endif()
+
+ foreach(config IN ITEMS ${configs})
+ if("${config}" STREQUAL "single_config")
+ set(config_path "")
+ set(config_arg "")
+ else()
+ set(config_path "_${config}")
+ set(config_arg "${config}")
+ endif()
+
+ # Test case 1: There are two widget1.ui files in different folders.
+ # Expect 1: Successful build without the double build issue.
+ # Expect 2: Only touched files to be built.
+ string(CONCAT ui_file "${CMAKE_CURRENT_SOURCE_DIR}/../"
+ "test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/widget1.ui")
+ string(CONCAT test_source_dir "${CMAKE_CURRENT_SOURCE_DIR}/../"
+ "test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/sub1/sub2/sub3/"
+ "sub4")
+ string(CONCAT test_build_dir "${CMAKE_CURRENT_BINARY_DIR}/"
+ "UicIncBuild_sameFileDiffFolder${config_path}-build")
+ string(CONCAT test_file_to_touch "${CMAKE_CURRENT_SOURCE_DIR}/../"
+ "test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/widget1.ui")
+ generate_hash_folder(
+ "example"
+ "${ui_file}"
+ hash_folder)
+ incremental_build_test(
+ TEST_NAME UicIncBuild_sameFileDiffFolder
+ SOURCE_DIR "${test_source_dir}"
+ BUILD_DIR "${test_build_dir}"
+ CONFIG "${config_arg}"
+ GENERATOR "${generator}"
+ FILE_TO_TOUCH "${test_file_to_touch}"
+ CHECK_UNWANTED_BUILDS
+ )
+
+ endforeach()
+endforeach()
diff --git a/tests/auto/cmake/test_qt_add_ui_8/CMakeLists.txt b/tests/auto/cmake/test_qt_add_ui_8/CMakeLists.txt
new file mode 100644
index 0000000000..f8d0763d35
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_8/CMakeLists.txt
@@ -0,0 +1,67 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(test)
+
+include(../test_qt_add_ui_common/RunCMake.cmake)
+include(../test_qt_add_ui_common/functions.cmake)
+
+get_generators(generators)
+
+foreach(generator IN ITEMS ${generators})
+ message(STATUS "Running tests for generator: ${generator}")
+ is_multi_config(${generator} multi_config_out)
+ # A CI test fails with the below condition. So, we are running the test
+ # only for the Debug configuration.
+ if ("${generator}" MATCHES "Xcode" AND
+ CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")
+ set(configs "Debug")
+ elseif(multi_config_out)
+ set(configs "Debug" "Release")
+ else()
+ set(configs "single_config")
+ endif()
+
+ foreach(config IN ITEMS ${configs})
+ if("${config}" STREQUAL "single_config")
+ set(config_path "")
+ set(config_arg "")
+ else()
+ set(config_path "_${config}")
+ set(config_arg "${config}")
+ endif()
+
+ # Test case: ui_mainwindow.h is included as
+ # "sub1/sub2/sub3/../../../../../../../../../../../../../ui_mainwindow.h".
+ # Test case: mainwindow1.ui file is touched after the first build.
+ # Expect 1: Successful build without the double build issue.
+ # Expect 2: No build folder leakage and generation of the
+ # "_/_/_/_/_/_/_/_/_/_/sub1/sub2/sub3" folder in ${hash_folder}/include.
+ string(CONCAT test_ui_file "${CMAKE_CURRENT_SOURCE_DIR}/../"
+ "test_qt_add_ui_common/uic_test/mainwindow.ui")
+ generate_hash_folder("example" "${test_ui_file}" hash_folder)
+ string(CONCAT test_build_dir "${CMAKE_CURRENT_BINARY_DIR}/"
+ "UicBuildLeak_subFolderGen_complex${config_path}-build")
+ string(CONCAT test_additional_args "-DMAINWINDOW_UI_PATH=sub1/sub2/sub3"
+ "/../../../../../../../../../../../../../")
+ string(CONCAT test_file_to_touch "${CMAKE_CURRENT_SOURCE_DIR}/../"
+ "test_qt_add_ui_common/uic_test/mainwindow.ui")
+ string(CONCAT test_file_to_check "${test_build_dir}/.qt/${hash_folder}/"
+ "${config_arg}/ui_mainwindow.h")
+ string(CONCAT test_folder_to_check "${test_build_dir}/.qt/${hash_folder}/"
+ "${config_arg}/_/_/_/_/_/_/_/_/_/_/sub1/sub2/sub3")
+ incremental_build_test(
+ TEST_NAME UicBuildLeak_subFolderGen_complex
+ SOURCE_DIR
+ "${CMAKE_CURRENT_SOURCE_DIR}/../test_qt_add_ui_common/uic_test"
+ BUILD_DIR "${test_build_dir}"
+ CONFIG "${config_arg}"
+ GENERATOR "${generator}"
+ ADDITIONAL_ARGS "${test_additional_args}"
+ FILE_TO_TOUCH "${test_file_to_touch}"
+ FILE_TO_CHECK "${test_file_to_check}"
+ FOLDER_TO_CHECK "${test_folder_to_check}"
+ )
+ endforeach()
+endforeach()
diff --git a/tests/auto/cmake/test_qt_add_ui_9/CMakeLists.txt b/tests/auto/cmake/test_qt_add_ui_9/CMakeLists.txt
new file mode 100644
index 0000000000..9e28ddc927
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_9/CMakeLists.txt
@@ -0,0 +1,66 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(test)
+
+include(../test_qt_add_ui_common/RunCMake.cmake)
+include(../test_qt_add_ui_common/functions.cmake)
+
+get_generators(generators)
+
+foreach(generator IN ITEMS ${generators})
+ message(STATUS "Running tests for generator: ${generator}")
+ is_multi_config(${generator} multi_config_out)
+ if(multi_config_out)
+ # Since our CI machines are slow, ctest --build-and-test buffers the
+ # output of the configure step of a test, and the fact that we run all
+ # the test logic in the configure step, we need to exclude Release
+ # to avoid CI stdout timeout errors.
+ # See https://gitlab.kitware.com/cmake/cmake/-/issues/25790
+ set(configs "Debug" "Release")
+ else()
+ set(configs "single_config")
+ endif()
+
+ foreach(config IN ITEMS ${configs})
+ if("${config}" STREQUAL "single_config")
+ set(config_path "")
+ set(config_arg "")
+ else()
+ set(config_path "_${config}")
+ set(config_arg "${config}")
+ endif()
+
+ # Test case: ui_mainwindow.h is included as
+ # "sub2/sub3/../../../src/ui_files/ui_mainwindow.h".
+ # Expect 1: Successful build without the double build issue.
+ # Expect 2: No build folder leakage and generation of
+ # _/sub2/sub3 in ${hash_folder}/include.
+ # Note: This test case is a mix of previous two test cases.
+ string(CONCAT test_ui_file "${CMAKE_CURRENT_SOURCE_DIR}/../"
+ "test_qt_add_ui_common/uic_test/mainwindow.ui")
+ generate_hash_folder("example" "${test_ui_file}" hash_folder)
+ set(test_build_dir
+ "${CMAKE_CURRENT_BINARY_DIR}/UicBuildLeak_mix${config_path}-build")
+ string(CONCAT test_additional_args "-DMAINWINDOW_UI_PATH=sub2/sub3/"
+ "../../../src/ui_files/")
+ string(CONCAT test_file_to_check "${test_build_dir}/.qt/${hash_folder}/"
+ "${config_arg}/src/ui_files/ui_mainwindow.h")
+ string(CONCAT test_source_dir "${CMAKE_CURRENT_SOURCE_DIR}/../"
+ "test_qt_add_ui_common/uic_test")
+ incremental_build_test(
+ TEST_NAME UicBuildLeak_mix
+ SOURCE_DIR "${test_source_dir}"
+ BUILD_DIR "${test_build_dir}"
+ CONFIG "${config_arg}"
+ GENERATOR "${generator}"
+ ADDITIONAL_ARGS "${test_additional_args}"
+ FILE_TO_TOUCH "${test_ui_file}"
+ FILE_TO_CHECK "${test_file_to_check}"
+ FOLDER_TO_CHECK
+ "${test_build_dir}/.qt/${hash_folder}/${config_arg}/_/sub2/sub3"
+ )
+ endforeach()
+endforeach()
+
diff --git a/tests/auto/cmake/test_qt_add_ui_common/CMakeLists.txt b/tests/auto/cmake/test_qt_add_ui_common/CMakeLists.txt
new file mode 100644
index 0000000000..628dc9373a
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/CMakeLists.txt
@@ -0,0 +1,7 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(test)
+
+add_executable(test main.cpp)
diff --git a/tests/auto/cmake/test_qt_add_ui_common/RunCMake.cmake b/tests/auto/cmake/test_qt_add_ui_common/RunCMake.cmake
new file mode 100644
index 0000000000..6b39fe3398
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/RunCMake.cmake
@@ -0,0 +1,157 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+function(run_cmake_configure)
+ set(options CLEAN_FIRST)
+ set(oneValueArgs SOURCE_DIR BUILD_DIR RESULT_VARIABLE OUTPUT_VARIABLE
+ ERROR_VARIABLE GENERATOR)
+ set(multiValueArgs ADDITIONAL_ARGS)
+
+ cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}"
+ ${ARGN})
+
+ if(NOT arg_SOURCE_DIR)
+ message(FATAL_ERROR "SOURCE_DIR not specified")
+ endif()
+
+ if(NOT arg_BUILD_DIR)
+ message(FATAL_ERROR "BUILD_DIR not specified")
+ endif()
+
+ is_multi_config(arg_GENERATOR multi_config_out)
+ if (NOT ${multi_config_out})
+ set(run_arg_config_arg -Darg_TYPE=Debug)
+ endif()
+
+ set(test_project_source_dir ${arg_SOURCE_DIR})
+ set(test_project_build_dir ${arg_BUILD_DIR})
+
+ # Make sure that file paths are 'real' paths
+ get_filename_component(test_project_source_dir "${test_project_source_dir}"
+ REALPATH)
+ get_filename_component(test_project_build_dir "${test_project_build_dir}"
+ REALPATH)
+
+ if(arg_CLEAN_FIRST)
+ file(REMOVE_RECURSE "${test_project_build_dir}")
+ endif()
+ file(MAKE_DIRECTORY "${test_project_build_dir}")
+
+ execute_process(COMMAND
+ "${CMAKE_COMMAND}"
+ -S "${test_project_source_dir}"
+ -B "${test_project_build_dir}"
+ -G "${arg_GENERATOR}"
+ "-DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}"
+ ${run_arg_config_arg}
+ ${arg_ADDITIONAL_ARGS}
+ RESULT_VARIABLE cmake_result
+ OUTPUT_VARIABLE cmake_output
+ ERROR_VARIABLE cmake_error
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ ERROR_STRIP_TRAILING_WHITESPACE
+ ECHO_OUTPUT_VARIABLE
+ ECHO_ERROR_VARIABLE
+ )
+
+ # set output variables
+ set(${arg_RESULT_VARIABLE} ${cmake_result} PARENT_SCOPE)
+ set(${arg_OUTPUT_VARIABLE} ${cmake_output} PARENT_SCOPE)
+ set(${arg_ERROR_VARIABLE} ${cmake_error} PARENT_SCOPE)
+endfunction()
+
+function(run_cmake_build)
+ set(options VERBOSE)
+ set(oneValueArgs CONFIG BUILD_DIR RESULT_VARIABLE OUTPUT_VARIABLE
+ ERROR_VARIABLE)
+ cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}"
+ ${ARGN})
+
+ if(NOT arg_BUILD_DIR)
+ message(FATAL_ERROR "BUILD_DIR not specified")
+ endif()
+
+ if (arg_VERBOSE OR arg_VERBOSE STREQUAL "")
+ set(arg_VERBOSE_ARG --verbose)
+ endif()
+
+ if(arg_CONFIG)
+ set(arg_BUILD_CONFIG_ARG --config ${arg_CONFIG})
+ endif()
+
+ execute_process(COMMAND ${CMAKE_COMMAND}
+ --build ${arg_BUILD_DIR}
+ ${arg_VERBOSE_ARG}
+ ${arg_BUILD_CONFIG_ARG}
+ RESULT_VARIABLE cmake_result
+ OUTPUT_VARIABLE cmake_output
+ ERROR_VARIABLE cmake_error
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ ERROR_STRIP_TRAILING_WHITESPACE
+ ECHO_OUTPUT_VARIABLE
+ ECHO_ERROR_VARIABLE)
+
+ set(${arg_RESULT_VARIABLE} ${cmake_result} PARENT_SCOPE)
+ set(${arg_OUTPUT_VARIABLE} ${cmake_output} PARENT_SCOPE)
+ set(${arg_ERROR_VARIABLE} ${cmake_error} PARENT_SCOPE)
+endfunction()
+
+function(is_multi_config generator output)
+ if ("${generator}" MATCHES "Visual Studio" OR "${generator}" MATCHES "Xcode"
+ OR "${generator}" MATCHES "Ninja Multi-Config")
+ set(${output} TRUE PARENT_SCOPE)
+ else()
+ set(${output} FALSE PARENT_SCOPE)
+ endif()
+endfunction()
+
+# check if string includes substring
+function(_internal_string_contains output string substring)
+ if("${string}" MATCHES "${substring}")
+ set(${output} TRUE PARENT_SCOPE)
+ else()
+ set(${output} FALSE PARENT_SCOPE)
+ endif()
+endfunction()
+
+function(expect_string_contains string substring)
+ set(oneValueArgs SUCCESS_MESSAGE FAILURE_MESSAGE)
+ cmake_parse_arguments(expect_string_contains "${options}" "${oneValueArgs}"
+ "${multiValueArgs}" ${ARGN})
+ _internal_string_contains(result "${string}" "${substring}")
+ if("${result}" STREQUAL TRUE)
+ if (expect_string_contains_SUCCESS_MESSAGE)
+ message(STATUS "PASS: ${expect_string_contains_SUCCESS_MESSAGE}")
+ else()
+ message(STATUS "PASS: \"${string}\" contains \"${substring}\"")
+ endif()
+ else()
+ if (expect_string_contains_FAILURE_MESSAGE)
+ message(FATAL_ERROR
+ "FAIL: ${expect_string_contains_FAILURE_MESSAGE}")
+ else()
+ message(FATAL_ERROR "FAIL: \"${string}\" contains \"${substring}\"")
+ endif()
+ endif()
+endfunction()
+
+function(expect_string_not_contains string substring)
+ set(oneValueArgs SUCCESS_MESSAGE FAILURE_MESSAGE)
+ cmake_parse_arguments(expect_string_not_contains
+ "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
+ _internal_string_contains(result ${string} ${substring})
+ if(${result} STREQUAL FALSE)
+ if (expect_string_not_contains_SUCCESS_MESSAGE)
+ message(STATUS "PASS: ${expect_string_not_contains_SUCCESS_MESSAGE}")
+ else()
+ message(STATUS "PASS: \"${string}\" not contains \"${substring}\"")
+ endif()
+ else()
+ if (expect_string_not_contains_FAILURE_MESSAGE)
+ message(FATAL_ERROR
+ "FAIL: ${expect_string_not_contains_FAILURE_MESSAGE}")
+ else()
+ message(FATAL_ERROR "FAIL: \"${string}\" contains \"${substring}\"")
+ endif()
+ endif()
+endfunction()
diff --git a/tests/auto/cmake/test_qt_add_ui_common/UicBuildFolderLeakageCommon/main.cpp b/tests/auto/cmake/test_qt_add_ui_common/UicBuildFolderLeakageCommon/main.cpp
new file mode 100644
index 0000000000..ed46eb370d
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/UicBuildFolderLeakageCommon/main.cpp
@@ -0,0 +1,14 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <QApplication>
+
+#include "mainwindow.h"
+
+int main(int argc, char* argv[])
+{
+ QApplication a(argc, argv);
+ MainWindow w;
+ w.show();
+ return a.exec();
+}
diff --git a/tests/auto/cmake/test_qt_add_ui_common/UicBuildFolderLeakageCommon/mainwindow.h b/tests/auto/cmake/test_qt_add_ui_common/UicBuildFolderLeakageCommon/mainwindow.h
new file mode 100644
index 0000000000..21b7824f8a
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/UicBuildFolderLeakageCommon/mainwindow.h
@@ -0,0 +1,26 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: BSD-3-Clause
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QMainWindow>
+
+QT_BEGIN_NAMESPACE
+namespace Ui {
+class MainWindow;
+}
+QT_END_NAMESPACE
+
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ MainWindow(QWidget* parent = nullptr);
+ ~MainWindow();
+
+private:
+ Ui::MainWindow* ui;
+};
+#endif // MAINWINDOW_H
diff --git a/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/main.cpp b/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/main.cpp
new file mode 100644
index 0000000000..ed46eb370d
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/main.cpp
@@ -0,0 +1,14 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <QApplication>
+
+#include "mainwindow.h"
+
+int main(int argc, char* argv[])
+{
+ QApplication a(argc, argv);
+ MainWindow w;
+ w.show();
+ return a.exec();
+}
diff --git a/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/mainwindow.cpp b/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/mainwindow.cpp
new file mode 100644
index 0000000000..1503e504b4
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/mainwindow.cpp
@@ -0,0 +1,28 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "mainwindow.h"
+
+#include <QVBoxLayout>
+
+#include "../../../../src/ui_files/ui_mainwindow.h"
+#include "widget1.h"
+
+MainWindow::MainWindow(QWidget* parent)
+ : QMainWindow(parent)
+ , ui(new Ui::MainWindow)
+{
+ ui->setupUi(this);
+ auto layout = new QVBoxLayout;
+ layout->addWidget(new Widget1);
+
+ QWidget* w = new QWidget(this);
+ w->setLayout(layout);
+
+ setCentralWidget(w);
+}
+
+MainWindow::~MainWindow()
+{
+ delete ui;
+}
diff --git a/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/mainwindow.h b/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/mainwindow.h
new file mode 100644
index 0000000000..47f1cdce0f
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/mainwindow.h
@@ -0,0 +1,25 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: BSD-3-Clause
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QMainWindow>
+
+QT_BEGIN_NAMESPACE
+namespace Ui {
+class MainWindow;
+}
+QT_END_NAMESPACE
+
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+public:
+ MainWindow(QWidget* parent = nullptr);
+ ~MainWindow();
+
+private:
+ Ui::MainWindow* ui;
+};
+#endif // MAINWINDOW_H
diff --git a/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/ui_files/mainwindow.ui b/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/ui_files/mainwindow.ui
new file mode 100644
index 0000000000..828d7c1782
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/ui_files/mainwindow.ui
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>600</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>MainWindow</string>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QGridLayout" name="gridLayout"/>
+ </widget>
+ <widget class="QMenuBar" name="menubar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>22</height>
+ </rect>
+ </property>
+ </widget>
+ <widget class="QStatusBar" name="statusbar"/>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/ui_files/widget1.ui b/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/ui_files/widget1.ui
new file mode 100644
index 0000000000..db0c58d5df
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/ui_files/widget1.ui
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Widget1</class>
+ <widget class="QWidget" name="Widget1">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Input:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="lineEdit"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>OnTextChanged:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="OnTextChanged">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>TextLabel</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/widget1.cpp b/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/widget1.cpp
new file mode 100644
index 0000000000..ba53958a75
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/widget1.cpp
@@ -0,0 +1,25 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "widget1.h"
+
+#include "../../../../src/ui_files/ui_widget1.h"
+
+Widget1::Widget1(QWidget* parent)
+ : QWidget(parent)
+ , ui(new Ui::Widget1)
+{
+ ui->setupUi(this);
+ connect(ui->lineEdit, SIGNAL(textChanged(const QString&)), this,
+ SLOT(onTextChanged(const QString&)));
+}
+
+Widget1::~Widget1()
+{
+ delete ui;
+}
+
+void Widget1::onTextChanged(const QString& text)
+{
+ ui->OnTextChanged->setText(text);
+}
diff --git a/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/widget1.h b/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/widget1.h
new file mode 100644
index 0000000000..327949b4d8
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/widget1.h
@@ -0,0 +1,28 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: BSD-3-Clause
+
+#ifndef WIDGET1_H
+#define WIDGET1_H
+
+#include <QWidget>
+
+QT_BEGIN_NAMESPACE
+namespace Ui {
+class Widget1;
+}
+QT_END_NAMESPACE
+
+class Widget1 : public QWidget
+{
+ Q_OBJECT
+public:
+ explicit Widget1(QWidget* parent = nullptr);
+ ~Widget1();
+public slots:
+ void onTextChanged(const QString& text);
+
+private:
+ Ui::Widget1* ui;
+};
+
+#endif // WIDGET1_H
diff --git a/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/widget1.ui b/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/widget1.ui
new file mode 100644
index 0000000000..db0c58d5df
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/widget1.ui
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Widget1</class>
+ <widget class="QWidget" name="Widget1">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Input:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="lineEdit"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>OnTextChanged:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="OnTextChanged">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>TextLabel</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/widget2.cpp b/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/widget2.cpp
new file mode 100644
index 0000000000..ca84756a7f
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/widget2.cpp
@@ -0,0 +1,25 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "widget2.h"
+
+#include "../../../../ui_widget1.h"
+
+Widget2::Widget2(QWidget* parent)
+ : QWidget(parent)
+ , ui(new Ui::Widget2)
+{
+ ui->setupUi(this);
+ connect(ui->lineEdit, SIGNAL(textChanged(const QString&)), this,
+ SLOT(onTextChanged(const QString&)));
+}
+
+Widget2::~Widget2()
+{
+ delete ui;
+}
+
+void Widget2::onTextChanged(const QString& text)
+{
+ ui->OnTextChanged->setText(text);
+}
diff --git a/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/widget2.h b/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/widget2.h
new file mode 100644
index 0000000000..db1c3b7157
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/src/widget2.h
@@ -0,0 +1,29 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: BSD-3-Clause
+
+#ifndef WIDGET2_H
+#define WIDGET2_H
+
+#include <QWidget>
+
+QT_BEGIN_NAMESPACE
+namespace Ui {
+class Widget2;
+}
+QT_END_NAMESPACE
+
+class Widget2 : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit Widget2(QWidget* parent = nullptr);
+ ~Widget2();
+public slots:
+ void onTextChanged(const QString& text);
+
+private:
+ Ui::Widget2* ui;
+};
+
+#endif // WIDGET2_H
diff --git a/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/sub1/sub2/sub3/sub4/CMakeLists.txt b/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/sub1/sub2/sub3/sub4/CMakeLists.txt
new file mode 100644
index 0000000000..b8443c370d
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/sub1/sub2/sub3/sub4/CMakeLists.txt
@@ -0,0 +1,39 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+
+project(UicIncrementalBuild_sameFileDifferentFolder LANGUAGES CXX)
+
+find_package(Qt6 REQUIRED COMPONENTS Core Widgets Gui)
+
+set(CMAKE_AUTOMOC ON)
+
+qt_add_executable(example
+ ../../../../src/ui_files/mainwindow.ui
+ ../../../../src/ui_files/widget1.ui
+ ../../../../widget1.ui
+ ../../../../src/mainwindow.h
+ ../../../../src/widget1.h
+ ../../../../src/widget2.h
+ ../../../../src/main.cpp
+ ../../../../src/mainwindow.cpp
+ ../../../../src/widget1.cpp
+ ../../../../src/widget2.cpp
+)
+
+target_link_libraries(example PRIVATE Qt6::Widgets
+ Qt6::Core
+ Qt6::Gui)
+
+qt6_add_ui(example
+ INCLUDE_PREFIX "../../../../src/ui_files"
+ SOURCES "../../../../src/ui_files/mainwindow.ui"
+ "../../../../src/ui_files/widget1.ui"
+ OPTIONS "$<$<CONFIG:Debug>:-a>")
+
+qt6_add_ui(example
+ INCLUDE_PREFIX "../../../../"
+ SOURCES "../../../../widget1.ui"
+ OPTIONS "$<$<CONFIG:Debug>:-a>")
+
diff --git a/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/widget1.ui b/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/widget1.ui
new file mode 100644
index 0000000000..facf4678f2
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/UicIncBuild_sameFileDiffFolder/widget1.ui
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Widget2</class>
+ <widget class="QWidget" name="Widget2">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Input:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="lineEdit"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>OnTextChanged:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="OnTextChanged">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/CMakeLists.txt b/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/CMakeLists.txt
new file mode 100644
index 0000000000..81023c3382
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/CMakeLists.txt
@@ -0,0 +1,33 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+
+project(UicIncrementalBuild LANGUAGES CXX)
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
+
+set(CMAKE_AUTOMOC ON)
+
+qt_add_executable(example
+ src/mainwindow.ui
+ src/widget1.ui
+ src/widget2.ui
+ src/mainwindow.h
+ src/widget1.h
+ src/widget2.h
+ src/main.cpp
+ src/mainwindow.cpp
+ src/widget1.cpp
+ src/widget2.cpp
+)
+
+target_link_libraries(example PRIVATE Qt6::Widgets
+ Qt6::Core
+ Qt6::Gui)
+
+qt6_add_ui(example
+ INCLUDE_PREFIX "src"
+ SOURCES "src/mainwindow.ui" "src/widget1.ui" "src/widget2.ui"
+ OPTIONS "$<$<CONFIG:Debug>:-a>")
+
diff --git a/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/main.cpp b/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/main.cpp
new file mode 100644
index 0000000000..ed46eb370d
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/main.cpp
@@ -0,0 +1,14 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <QApplication>
+
+#include "mainwindow.h"
+
+int main(int argc, char* argv[])
+{
+ QApplication a(argc, argv);
+ MainWindow w;
+ w.show();
+ return a.exec();
+}
diff --git a/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/mainwindow.cpp b/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/mainwindow.cpp
new file mode 100644
index 0000000000..aa913763b0
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/mainwindow.cpp
@@ -0,0 +1,28 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "mainwindow.h"
+
+#include <QVBoxLayout>
+
+#include "src/ui_mainwindow.h"
+#include "widget1.h"
+
+MainWindow::MainWindow(QWidget* parent)
+ : QMainWindow(parent)
+ , ui(new Ui::MainWindow)
+{
+ ui->setupUi(this);
+ auto layout = new QVBoxLayout;
+ layout->addWidget(new Widget1);
+
+ QWidget* w = new QWidget(this);
+ w->setLayout(layout);
+
+ setCentralWidget(w);
+}
+
+MainWindow::~MainWindow()
+{
+ delete ui;
+}
diff --git a/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/mainwindow.h b/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/mainwindow.h
new file mode 100644
index 0000000000..47f1cdce0f
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/mainwindow.h
@@ -0,0 +1,25 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: BSD-3-Clause
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QMainWindow>
+
+QT_BEGIN_NAMESPACE
+namespace Ui {
+class MainWindow;
+}
+QT_END_NAMESPACE
+
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+public:
+ MainWindow(QWidget* parent = nullptr);
+ ~MainWindow();
+
+private:
+ Ui::MainWindow* ui;
+};
+#endif // MAINWINDOW_H
diff --git a/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/mainwindow.ui b/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/mainwindow.ui
new file mode 100644
index 0000000000..828d7c1782
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/mainwindow.ui
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>600</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>MainWindow</string>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QGridLayout" name="gridLayout"/>
+ </widget>
+ <widget class="QMenuBar" name="menubar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>22</height>
+ </rect>
+ </property>
+ </widget>
+ <widget class="QStatusBar" name="statusbar"/>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/widget1.cpp b/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/widget1.cpp
new file mode 100644
index 0000000000..5ba773c529
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/widget1.cpp
@@ -0,0 +1,25 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "widget1.h"
+
+#include "src/ui_widget1.h"
+
+Widget1::Widget1(QWidget* parent)
+ : QWidget(parent)
+ , ui(new Ui::Widget1)
+{
+ ui->setupUi(this);
+ connect(ui->lineEdit, SIGNAL(textChanged(const QString&)), this,
+ SLOT(onTextChanged(const QString&)));
+}
+
+Widget1::~Widget1()
+{
+ delete ui;
+}
+
+void Widget1::onTextChanged(const QString& text)
+{
+ ui->OnTextChanged->setText(text);
+}
diff --git a/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/widget1.h b/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/widget1.h
new file mode 100644
index 0000000000..327949b4d8
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/widget1.h
@@ -0,0 +1,28 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: BSD-3-Clause
+
+#ifndef WIDGET1_H
+#define WIDGET1_H
+
+#include <QWidget>
+
+QT_BEGIN_NAMESPACE
+namespace Ui {
+class Widget1;
+}
+QT_END_NAMESPACE
+
+class Widget1 : public QWidget
+{
+ Q_OBJECT
+public:
+ explicit Widget1(QWidget* parent = nullptr);
+ ~Widget1();
+public slots:
+ void onTextChanged(const QString& text);
+
+private:
+ Ui::Widget1* ui;
+};
+
+#endif // WIDGET1_H
diff --git a/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/widget1.ui b/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/widget1.ui
new file mode 100644
index 0000000000..db0c58d5df
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/widget1.ui
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Widget1</class>
+ <widget class="QWidget" name="Widget1">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Input:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="lineEdit"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>OnTextChanged:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="OnTextChanged">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>TextLabel</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/widget2.cpp b/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/widget2.cpp
new file mode 100644
index 0000000000..bb8983c8b5
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/widget2.cpp
@@ -0,0 +1,25 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "widget2.h"
+
+#include "src/ui_widget2.h"
+
+Widget2::Widget2(QWidget* parent)
+ : QWidget(parent)
+ , ui(new Ui::Widget2)
+{
+ ui->setupUi(this);
+ connect(ui->lineEdit, SIGNAL(textChanged(const QString&)), this,
+ SLOT(onTextChanged(const QString&)));
+}
+
+Widget2::~Widget2()
+{
+ delete ui;
+}
+
+void Widget2::onTextChanged(const QString& text)
+{
+ ui->OnTextChanged->setText(text);
+}
diff --git a/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/widget2.h b/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/widget2.h
new file mode 100644
index 0000000000..db1c3b7157
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/widget2.h
@@ -0,0 +1,29 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: BSD-3-Clause
+
+#ifndef WIDGET2_H
+#define WIDGET2_H
+
+#include <QWidget>
+
+QT_BEGIN_NAMESPACE
+namespace Ui {
+class Widget2;
+}
+QT_END_NAMESPACE
+
+class Widget2 : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit Widget2(QWidget* parent = nullptr);
+ ~Widget2();
+public slots:
+ void onTextChanged(const QString& text);
+
+private:
+ Ui::Widget2* ui;
+};
+
+#endif // WIDGET2_H
diff --git a/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/widget2.ui b/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/widget2.ui
new file mode 100644
index 0000000000..facf4678f2
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/UicIncrementalBuild/src/widget2.ui
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Widget2</class>
+ <widget class="QWidget" name="Widget2">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Input:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="lineEdit"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>OnTextChanged:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="OnTextChanged">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/tests/auto/cmake/test_qt_add_ui_common/functions.cmake b/tests/auto/cmake/test_qt_add_ui_common/functions.cmake
new file mode 100644
index 0000000000..efb8b09774
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/functions.cmake
@@ -0,0 +1,223 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+function(generate_hash_folder target_name infile out_folder)
+ get_filename_component(infile_abs "${infile}" ABSOLUTE)
+ string(SHA1 infile_hash "${target_name}${infile_abs}")
+ string(SUBSTRING "${infile_hash}" 0 6 short_hash)
+ set(${out_folder} "${short_hash}" PARENT_SCOPE)
+endfunction()
+
+function(get_latest_vs_generator output)
+ execute_process(COMMAND ${CMAKE_COMMAND} -G
+ ERROR_VARIABLE CMAKE_GENERATORS_ERROR
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+ string(REGEX MATCHALL "Visual Studio [0-9]+ [0-9]+" vs_generators
+ "${CMAKE_GENERATORS_ERROR}")
+
+ if(NOT vs_generators)
+ message(FATAL_ERROR "No visual studio generators found")
+ endif()
+
+ set(last_version "0")
+ set(last_generator "")
+ foreach(generator IN LISTS vs_generators)
+ string(REGEX MATCH "Visual Studio ([0-9]+) [0-9]+" unused "${generator}")
+ if("${CMAKE_MATCH_1}" VERSION_GREATER "${last_version}")
+ set(last_version "${CMAKE_MATCH_1}")
+ set(last_generator "${CMAKE_MATCH_0}")
+ endif()
+ endforeach()
+ set(${output} "${last_generator}" PARENT_SCOPE)
+endfunction()
+
+function(check_unwanted_builds_after_first_build cmake_output test_name test_dir
+ generator)
+ set(unwanted_builds_success_message
+ "\"${test_name}\" in \"${test_dir}\" -> No unwanted builds")
+ set(unwanted_builds_failure_message
+ "\"${test_name}\" in \"${test_dir}\" -> Unwanted builds found")
+ if(${generator} MATCHES "Ninja")
+ expect_string_not_contains(${cmake_output}
+ "widget2.cpp.o.d|mainwindow.cpp.o.d"
+ SUCCESS_MESSAGE ${unwanted_builds_success_message}
+ FAILURE_MESSAGE ${unwanted_builds_failure_message})
+ elseif(${generator} MATCHES "Make")
+ string(CONCAT not_expect_string
+ "Building CXX object UicIncrementalBuild/CMakeFiles"
+ "/example.dir/src/widget2.cpp.o|Building CXX object UicIncremental"
+ "Build/CMakeFiles/example.dir/src/mainwindow.cpp.o")
+ expect_string_not_contains(${cmake_output} "${not_expect_string}"
+ SUCCESS_MESSAGE ${unwanted_builds_success_message}
+ FAILURE_MESSAGE ${unwanted_builds_failure_message})
+ elseif(${generator} MATCHES "Visual Studio")
+ expect_string_not_contains(${cmake_output} "widget2.cpp|mainwindow.cpp"
+ SUCCESS_MESSAGE ${unwanted_builds_success_message}
+ FAILURE_MESSAGE ${unwanted_builds_failure_message})
+ elseif(${generator} MATCHES "Xcode")
+ expect_string_not_contains(${cmake_output} "widget2.cpp|mainwindow.cpp"
+ SUCCESS_MESSAGE ${unwanted_builds_success_message}
+ FAILURE_MESSAGE ${unwanted_builds_failure_message})
+ endif()
+endfunction()
+
+function(check_output_after_second_build cmake_output test_name
+ test_dir generator)
+ set(second_build_success_message
+ "\"${test_name}\" in \"${test_dir}\" -> Generation of UI files were not \
+triggered in the second build")
+ set(second_build_failure_message
+ "\"${test_name}\" in \"${test_dir}\" -> Generation of UI files were \
+triggered in the second build")
+
+ if(${generator} MATCHES "Ninja")
+ expect_string_contains(${cmake_output} "ninja: no work to do."
+ SUCCESS_MESSAGE ${second_build_success_message}
+ FAILURE_MESSAGE ${second_build_failure_message})
+ elseif(${generator} MATCHES "Visual Studio" OR ${generator} MATCHES "Xcode")
+ expect_string_not_contains(${cmake_output} "mainwindow"
+ SUCCESS_MESSAGE
+ ${second_build_success_message}
+ FAILURE_MESSAGE
+ ${second_build_failure_message})
+ elseif(${generator} MATCHES "Makefiles")
+ expect_string_not_contains(${cmake_output} "mainwindow.cpp.o.d -o"
+ SUCCESS_MESSAGE ${second_build_success_message}
+ FAILURE_MESSAGE ${second_build_failure_message})
+ endif()
+endfunction()
+
+function(incremental_build_test)
+ set(options CHECK_UNWANTED_BUILDS)
+ set(oneValueArgs CONFIG TEST_NAME SOURCE_DIR BUILD_DIR FILE_TO_TOUCH
+ FILE_TO_CHECK FOLDER_TO_CHECK GENERATOR)
+ set(multiValueArgs ADDITIONAL_ARGS)
+
+ cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}"
+ ${ARGN})
+
+ string(REPLACE ";" " " arg_ADDITIONAL_ARGS "${arg_ADDITIONAL_ARGS}")
+ if ("${arg_SOURCE_DIR}" STREQUAL "")
+ message(FATAL_ERROR "FAIL: \"${arg_TEST_NAME}\" test failed because "
+ "SOURCE_DIR is empty")
+ endif()
+
+ if ("${arg_BUILD_DIR}" STREQUAL "")
+ message(FATAL_ERROR "FAIL: \"${arg_TEST_NAME}\" test failed because "
+ "BUILD_DIR is empty")
+ endif()
+
+ if ("${arg_GENERATOR}" STREQUAL "")
+ message(FATAL_ERROR "FAIL: \"${arg_TEST_NAME}\" test failed because "
+ "GENERATOR is empty")
+ endif()
+
+ run_cmake_configure(SOURCE_DIR "${arg_SOURCE_DIR}"
+ BUILD_DIR "${arg_BUILD_DIR}"
+ GENERATOR "${arg_GENERATOR}"
+ CLEAN_FIRST
+ ADDITIONAL_ARGS ${arg_ADDITIONAL_ARGS}
+ OUTPUT_VARIABLE cmake_output
+ ERROR_VARIABLE cmake_error
+ RESULT_VARIABLE cmake_result)
+
+ if(${cmake_result} EQUAL 0)
+ message(STATUS
+ "PASS: \"${arg_TEST_NAME}\" test in ${arg_BUILD_DIR} was configured "
+ "successfully")
+ else()
+ message(FATAL_ERROR "cmake_output: ${cmake_output}\ncmake_error: "
+ "${cmake_error}\nFAIL: \"${arg_TEST_NAME}\" test in ${arg_BUILD_DIR}"
+ " failed to configure")
+ endif()
+
+ if(NOT "${arg_CONFIG}" STREQUAL "single_config")
+ set(config_arg "${arg_CONFIG}")
+ endif()
+
+ run_cmake_build(BUILD_DIR ${arg_BUILD_DIR}
+ VERBOSE ON
+ CONFIG ${config_arg}
+ OUTPUT_VARIABLE cmake_build_output
+ ERROR_VARIABLE cmake_build_error
+ RESULT_VARIABLE cmake_build_result)
+
+ if(${cmake_build_result} EQUAL 0)
+ message(STATUS
+ "PASS: \"${arg_TEST_NAME}\" test in ${arg_BUILD_DIR} was built "
+ "successfully")
+ else()
+ message(FATAL_ERROR
+ "cmake_build_output: ${cmake_build_output}\ncmake_build_error: "
+ "${cmake_build_error}\nFAIL: \"${arg_TEST_NAME}\" test in "
+ "${arg_BUILD_DIR} failed to build")
+ endif()
+
+ if(NOT "${arg_FILE_TO_CHECK}" STREQUAL "")
+ if(NOT EXISTS "${arg_FILE_TO_CHECK}")
+ message(FATAL_ERROR "FAIL: \"${arg_TEST_NAME}\" ${arg_FILE_TO_CHECK}"
+ " could not be found")
+ else()
+ message(STATUS "PASS: \"${arg_TEST_NAME}\" \"${arg_FILE_TO_CHECK}\" "
+ "was generated successfully")
+ endif()
+ endif()
+
+ if(NOT "${arg_FOLDER_TO_CHECK}" STREQUAL "" AND NOT WIN32)
+ if(NOT EXISTS "${arg_FOLDER_TO_CHECK}")
+ message(FATAL_ERROR
+ "FAIL: \"${arg_TEST_NAME}\" Folder \"${arg_FOLDER_TO_CHECK}\" "
+ "does not exist")
+ else()
+ message(STATUS
+ "PASS: \"${arg_TEST_NAME}\" Folder \"${arg_FOLDER_TO_CHECK}\" "
+ "exists")
+ endif()
+ endif()
+
+ if(NOT "${arg_FILE_TO_TOUCH}" STREQUAL "")
+ file(TOUCH "${arg_FILE_TO_TOUCH}")
+ endif()
+
+ run_cmake_build(BUILD_DIR ${arg_BUILD_DIR}
+ VERBOSE ON
+ CONFIG ${arg_CONFIG}
+ OUTPUT_VARIABLE cmake_build_output)
+ if(${arg_CHECK_UNWANTED_BUILDS})
+ check_unwanted_builds_after_first_build(${cmake_build_output}
+ ${arg_TEST_NAME} ${arg_BUILD_DIR} ${arg_GENERATOR})
+ endif()
+
+ run_cmake_build(BUILD_DIR ${arg_BUILD_DIR}
+ VERBOSE ON
+ CONFIG ${arg_CONFIG}
+ OUTPUT_VARIABLE cmake_build_output)
+ check_output_after_second_build(${cmake_build_output}
+ ${arg_TEST_NAME} ${arg_BUILD_DIR} ${arg_GENERATOR})
+endfunction()
+
+function(get_generators output)
+ if(${CMAKE_HOST_SYSTEM_NAME} MATCHES "Linux")
+ set(generators "Unix Makefiles" "Ninja" "Ninja Multi-Config")
+ elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "Windows")
+ # CI fails with Clang and Visual Studio generators.
+ # That's why discard that combination.
+ if (NOT "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" AND
+ NOT MINGW)
+ get_latest_vs_generator(latest_vs)
+ endif()
+ set(generators "Ninja" "Ninja Multi-Config" "${latest_vs}")
+ elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "Darwin")
+ # TODO: Add Xcode generator when
+ # https://gitlab.kitware.com/cmake/cmake/-/issues/25790 is fixed.
+ # Otherwise, adding Xcode generator might fail CI due to the timeout
+ # issue.
+ set(generators "Unix Makefiles" "Ninja" "Ninja Multi-Config")
+ else()
+ string(JOIN "" ERROR_MESSAGE
+ "FAIL: host OS not supported for this test."
+ "host : ${CMAKE_HOST_SYSTEM_NAME}")
+ message(FATAL_ERROR "${ERROR_MESSAGE}")
+ endif()
+ set(${output} "${generators}" PARENT_SCOPE)
+endfunction()
diff --git a/tests/auto/cmake/test_qt_add_ui_common/main.cpp b/tests/auto/cmake/test_qt_add_ui_common/main.cpp
new file mode 100644
index 0000000000..d3aff6a3c5
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/main.cpp
@@ -0,0 +1,4 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: BSD-3-Clause
+
+int main() { return 0; }
diff --git a/tests/auto/cmake/test_qt_add_ui_common/uic_test/CMakeLists.txt b/tests/auto/cmake/test_qt_add_ui_common/uic_test/CMakeLists.txt
new file mode 100644
index 0000000000..b05efd5e4d
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/uic_test/CMakeLists.txt
@@ -0,0 +1,65 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+
+project(UicTest LANGUAGES CXX)
+
+find_package(Qt6 REQUIRED COMPONENTS Core Widgets Gui)
+
+set(CMAKE_AUTOMOC ON)
+
+if (NOT DO_NOT_GENERATE_FILE)
+ file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/mainwindow.cpp"
+ CONTENT " \
+#include \"${CMAKE_CURRENT_SOURCE_DIR}/../UicBuildFolderLeakageCommon/mainwindow.h\" \n \
+#include \"${MAINWINDOW_UI_PATH}ui_mainwindow.h\" \n \
+MainWindow::MainWindow(QWidget* parent) \n \
+ : QMainWindow(parent) \n \
+ , ui(new Ui::MainWindow) \n \
+{ \n \
+ ui->setupUi(this); \n \
+} \n \
+ \n \
+MainWindow::~MainWindow() \n \
+{ \n \
+ delete ui; \n \
+} \n \
+")
+endif()
+
+qt_add_executable(example
+ ../UicBuildFolderLeakageCommon/main.cpp
+ ../UicBuildFolderLeakageCommon/mainwindow.h
+ mainwindow.ui
+)
+
+if (${DO_NOT_GENERATE_FILE})
+ target_sources(example PRIVATE mainwindow.cpp)
+else()
+ target_sources(example PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/mainwindow.cpp")
+endif()
+
+target_link_libraries(example PRIVATE Qt6::Widgets
+ Qt6::Core
+ Qt6::Gui)
+
+if (NOT UI_NO_CONFIG_OPTIONS)
+ set(uic_options "$<$<CONFIG:Debug>:-a>")
+endif()
+qt6_add_ui(example
+ INCLUDE_PREFIX "${MAINWINDOW_UI_PATH}"
+ SOURCES "mainwindow.ui"
+ OPTIONS "${uic_options}")
+
+if(ADD_NEW_UI)
+ qt6_add_ui(example INCLUDE_PREFIX "${NEW_UI_PATH}"
+ SOURCES "subdir/mainwindow.ui"
+ OPTIONS "${uic_options}")
+endif()
+
+# Enable AUTOUIC after qt6_add_ui() has been called
+if (CMAKE_AUTOUIC)
+ set_property(TARGET example PROPERTY AUTOUIC ON)
+endif()
+
diff --git a/tests/auto/cmake/test_qt_add_ui_common/uic_test/mainwindow.cpp b/tests/auto/cmake/test_qt_add_ui_common/uic_test/mainwindow.cpp
new file mode 100644
index 0000000000..de6cfbaa31
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/uic_test/mainwindow.cpp
@@ -0,0 +1,17 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "../UicBuildFolderLeakageCommon/mainwindow.h"
+#include "sub1/sub2/sub3/ui_mainwindow.h"
+
+MainWindow::MainWindow(QWidget* parent)
+ : QMainWindow(parent)
+ , ui(new Ui::MainWindow)
+{
+ ui->setupUi(this);
+}
+
+MainWindow::~MainWindow()
+{
+ delete ui;
+}
diff --git a/tests/auto/cmake/test_qt_add_ui_common/uic_test/mainwindow.ui b/tests/auto/cmake/test_qt_add_ui_common/uic_test/mainwindow.ui
new file mode 100644
index 0000000000..4e57b05eac
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/uic_test/mainwindow.ui
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>600</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>MainWindow</string>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QPushButton" name="pushButton">
+ <property name="text">
+ <string>PushButton</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="menubar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>23</height>
+ </rect>
+ </property>
+ </widget>
+ <widget class="QStatusBar" name="statusbar"/>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/tests/auto/cmake/test_qt_add_ui_common/uic_test/subdir/mainwindow.ui b/tests/auto/cmake/test_qt_add_ui_common/uic_test/subdir/mainwindow.ui
new file mode 100644
index 0000000000..4e57b05eac
--- /dev/null
+++ b/tests/auto/cmake/test_qt_add_ui_common/uic_test/subdir/mainwindow.ui
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>600</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>MainWindow</string>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QPushButton" name="pushButton">
+ <property name="text">
+ <string>PushButton</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="menubar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>23</height>
+ </rect>
+ </property>
+ </widget>
+ <widget class="QStatusBar" name="statusbar"/>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>