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>
bb10
Orkun Tokdemir 2023-11-21 10:46:30 +01:00
parent 0678a8a00d
commit 545b84b93c
48 changed files with 2718 additions and 0 deletions

View File

@ -49,3 +49,293 @@ if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS)
set("${outfiles}" "${${outfiles}}" PARENT_SCOPE)
endfunction()
endif()
function(qt6_add_ui target)
if(NOT TARGET ${target})
message(FATAL_ERROR "Target \"${target}\" does not exist.")
endif()
set(options)
set(oneValueArgs INCLUDE_PREFIX)
set(multiValueArgs SOURCES OPTIONS)
cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}"
${ARGN})
set(sources ${arg_SOURCES})
set(ui_options ${arg_OPTIONS})
set(raw_include_prefix "${arg_INCLUDE_PREFIX}")
if("${sources}" STREQUAL "")
message(FATAL_ERROR "The \"SOURCES\" parameter is empty.")
endif()
# Disable AUTOUIC for the target explicitly to avoid the AUTOUIC
# error message when the AUTOUIC is enabled for the target.
set_target_properties(${target} PROPERTIES AUTOUIC OFF)
if(NOT "${raw_include_prefix}" STREQUAL "")
# generate dummy `_` folders from the given path
# e.g. if the given path is `../a/b/c`, then the generated path will be
# `_`. if the given path is ``../a/../b/c`, then the generated path will
# be `_/_`
function(_qt_internal_generate_dash_path_from_input input_path
out_generated_dash_path)
string(REGEX MATCHALL "\\.\\." upper_folder_list "${input_path}")
list(JOIN upper_folder_list "" upper_folder_list)
string(REGEX REPLACE "\\.\\." "_/" additional_path
"${upper_folder_list}")
# remove last / character
if(NOT "${additional_path}" STREQUAL "")
string(LENGTH "${additional_path}" additional_path_length)
math(EXPR additional_path_length "${additional_path_length} - 1")
string(SUBSTRING "${additional_path}" 0 ${additional_path_length}
additional_path)
endif()
set(${out_generated_dash_path} "${additional_path}" PARENT_SCOPE)
endfunction()
# NOTE: If previous folders are less than ../ folder count in
# ${raw_include_prefix}, relative path calculation will miscalculate,
# so we need to add dummy folders to calculate the relative path
# correctly
_qt_internal_generate_dash_path_from_input("${raw_include_prefix}"
dummy_path_for_relative_calculation)
if(NOT "${dummy_path_for_relative_calculation}" STREQUAL "")
set(dummy_path_for_relative_calculation
"/${dummy_path_for_relative_calculation}")
set(raw_include_prefix_to_compare
"${dummy_path_for_relative_calculation}/${raw_include_prefix}")
else()
set(dummy_path_for_relative_calculation "/")
set(raw_include_prefix_to_compare "/${raw_include_prefix}")
endif()
# NOTE: This relative path calculation could be done just by using the
# below code
# cmake_path(RELATIVE_PATH normalized_include_prefix "${CMAKE_CURRENT_SOURCE_DIR}"
# but due to the backward compatibility, we need to use
# file(RELATIVE_PATH) and ../ folder calculation the with
# _qt_internal_generate_dash_path_from_input() function
if(WIN32)
set(dummy_path_for_relative_calculation
"${CMAKE_CURRENT_SOURCE_DIR}/${dummy_path_for_relative_calculation}")
set(raw_include_prefix_to_compare
"${CMAKE_CURRENT_SOURCE_DIR}/${raw_include_prefix_to_compare}")
string(REGEX REPLACE "//" "/" dummy_path_for_relative_calculation
"${dummy_path_for_relative_calculation}")
string(REGEX REPLACE "//" "/" raw_include_prefix_to_compare
"${raw_include_prefix_to_compare}")
endif()
file(RELATIVE_PATH normalized_include_prefix
"${dummy_path_for_relative_calculation}"
"${raw_include_prefix_to_compare}")
# if normalized_include_prefix ends with `/` remove it
string(REGEX REPLACE "/$" "" normalized_include_prefix
"${normalized_include_prefix}")
_qt_internal_generate_dash_path_from_input("${normalized_include_prefix}"
additional_path)
endif()
if(ui_options MATCHES "\\$<CONFIG")
set(is_ui_options_contains_config true)
endif()
get_property(is_multi GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(is_multi AND is_ui_options_contains_config)
set(include_folder "$<CONFIG>")
endif()
set(include_folder_to_add "${include_folder}")
if(NOT "${additional_path}" STREQUAL "")
set(include_folder_to_add "${include_folder_to_add}/${additional_path}")
endif()
set(prefix_info_file_cmake
"${CMAKE_CURRENT_BINARY_DIR}/${target}_autogen/prefix_info.cmake")
if(EXISTS ${prefix_info_file_cmake})
include(${prefix_info_file_cmake})
set(prefix_info_file_cmake_exists true)
else()
set(prefix_info_file_cmake_exists false)
endif()
foreach(source_file ${sources})
get_filename_component(outfile "${source_file}" NAME_WE)
get_filename_component(infile "${source_file}" ABSOLUTE)
string(SHA1 infile_hash "${target}${infile}")
string(SUBSTRING "${infile_hash}" 0 6 short_hash)
set(file_ui_folder "${CMAKE_CURRENT_BINARY_DIR}/.qt/${short_hash}")
target_include_directories(${target} SYSTEM PRIVATE
"${file_ui_folder}/${include_folder_to_add}")
set(target_include_folder_with_add_path
"${file_ui_folder}/${include_folder}")
# Add additional path to include folder if it is not empty
if(NOT "${additional_path}" STREQUAL "")
set(target_include_folder_with_add_path
"${target_include_folder_with_add_path}/${additional_path}")
endif()
set(inc_dir_to_create "${target_include_folder_with_add_path}")
if(NOT "${raw_include_prefix}" STREQUAL "")
set(inc_dir_to_create
"${target_include_folder_with_add_path}/${raw_include_prefix}")
endif()
set(output_directory "${target_include_folder_with_add_path}")
if(NOT "${normalized_include_prefix}" STREQUAL "")
set(output_directory
"${output_directory}/${normalized_include_prefix}")
endif()
if(NOT EXISTS "${infile}")
message(FATAL_ERROR "${infile} does not exist.")
endif()
set_source_files_properties(${infile} PROPERTIES SKIP_AUTOUIC ON)
macro(_qt_internal_set_output_file_properties file)
set_source_files_properties(${file} PROPERTIES
SKIP_AUTOMOC TRUE
SKIP_AUTOUIC TRUE
SKIP_LINTING TRUE)
endmacro()
set(outfile "${output_directory}/ui_${outfile}.h")
# Before CMake 3.27, there is a bug for using $<CONFIG> in a generated
# file with Ninja Multi-Config generator. To avoid this issue, we need
# to add the generated file for each configuration.
# Explicitly set the output file properties for each configuration
# to avoid Policy CMP0071 warnings.
# See https://gitlab.kitware.com/cmake/cmake/-/issues/24848
# Xcode generator cannot handle $<CONFIG> in the output file name.
# it changes $<CONFIG> with NOCONFIG. That's why we need to add the
# generated file for each configuration for Xcode generator as well.
if(is_multi AND outfile MATCHES "\\$<CONFIG>"
AND CMAKE_VERSION VERSION_LESS "3.27"
OR CMAKE_GENERATOR MATCHES "Xcode")
foreach(config ${CMAKE_CONFIGURATION_TYPES})
string(REPLACE "$<CONFIG>" "${config}" outfile_with_config
"${outfile}")
_qt_internal_set_output_file_properties(
${outfile_with_config})
target_sources(${target} PRIVATE ${outfile_with_config})
endforeach()
else()
_qt_internal_set_output_file_properties(${outfile})
target_sources(${target} PRIVATE ${outfile})
endif()
# remove double slashes
string(REGEX REPLACE "//" "/" outfile "${outfile}")
# Note: 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. To avoid this issue, we need to
# remove the output file in configure step if the include_prefix is
# changed.
if(CMAKE_GENERATOR MATCHES "Ninja")
if(prefix_info_file_cmake_exists)
if(NOT "${${short_hash}_prefix}" STREQUAL
"${normalized_include_prefix}")
file(REMOVE_RECURSE "${file_ui_folder}")
list(APPEND prefix_info_list
"set(${short_hash}_prefix \"${normalized_include_prefix}\")")
elseif(NOT DEFINED ${short_hash}_prefix)
list(APPEND prefix_info_list
"set(${short_hash}_prefix \"${normalized_include_prefix}\")")
endif()
else()
set(prefix_info_list "")
list(APPEND prefix_info_list
"set(${short_hash}_prefix \"${normalized_include_prefix}\")")
endif()
file(MAKE_DIRECTORY ${file_ui_folder})
endif()
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.17")
set(remove_command rm -rf)
else()
set(remove_command "remove_directory")
endif()
add_custom_command(OUTPUT ${outfile}
DEPENDS ${QT_CMAKE_EXPORT_NAMESPACE}::uic
COMMAND ${CMAKE_COMMAND} -E ${remove_command} "${file_ui_folder}/${include_folder}"
COMMAND ${CMAKE_COMMAND} -E make_directory ${inc_dir_to_create}
COMMAND ${QT_CMAKE_EXPORT_NAMESPACE}::uic ${ui_options} -o
${outfile} ${infile}
COMMAND_EXPAND_LISTS
MAIN_DEPENDENCY ${infile} VERBATIM)
endforeach()
get_target_property(is_guard_on ${target} _qt_ui_property_check_guard)
if(NOT is_guard_on)
# Ninja fails when a newline is used. That's why message is
# divided into parts.
set(error_message_1
"Error: The target \"${target}\" has \"AUTOUIC\" enabled.")
set(error_message_2
"Error: Please disable \"AUTOUIC\" for the target \"${target}\".")
set(ui_property_check_dummy_file
"${CMAKE_CURRENT_BINARY_DIR}/${target}_autogen/ui_property_check_timestamp")
add_custom_command(OUTPUT ${ui_property_check_dummy_file}
COMMAND ${CMAKE_COMMAND} -E make_directory
"${CMAKE_CURRENT_BINARY_DIR}/${target}_autogen"
COMMAND ${CMAKE_COMMAND} -E touch ${ui_property_check_dummy_file}
COMMAND
"$<$<BOOL:$<TARGET_PROPERTY:${target},AUTOUIC>>:${CMAKE_COMMAND}\
;-E;echo;${error_message_1}>"
COMMAND
"$<$<BOOL:$<TARGET_PROPERTY:${target},AUTOUIC>>:${CMAKE_COMMAND}\
;-E;echo;${error_message_2}>"
# Remove the dummy file so that the error message is shown until
# the AUTOUIC is disabled. Otherwise, the error message is shown
# only once when the AUTOUIC is enabled with Visual Studio generator.
COMMAND
"$<$<BOOL:$<TARGET_PROPERTY:${target},AUTOUIC>>:${CMAKE_COMMAND}\
;-E;remove;${ui_property_check_dummy_file}>"
COMMAND
"$<$<BOOL:$<TARGET_PROPERTY:${target},AUTOUIC>>:${CMAKE_COMMAND}\
;-E;false>"
COMMAND_EXPAND_LISTS
VERBATIM)
# When AUTOUIC is enabled, AUTOUIC runs before ${target}_ui_property_check
# target. So we need to add ${target}_ui_property_check as a dependency
# to ${target} to make sure that AUTOUIC runs after ${target}_ui_property_check
if (NOT QT_NO_MIX_UI_AUTOUIC_CHECK)
set_target_properties(${target} PROPERTIES
_qt_ui_property_check_guard ON)
add_custom_target(${target}_ui_property_check
ALL DEPENDS ${ui_property_check_dummy_file})
_qt_internal_assign_to_internal_targets_folder(
${target}_ui_property_check)
add_dependencies(${target} ${target}_ui_property_check)
endif()
endif()
# write prefix info file
if(CMAKE_GENERATOR MATCHES "Ninja")
list(PREPEND prefix_info_list "include_guard()")
string(REPLACE ";" "\n" prefix_info_list "${prefix_info_list}")
file(WRITE "${prefix_info_file_cmake}" "${prefix_info_list}")
endif()
endfunction()
if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS)
function(qt_add_ui)
if(QT_DEFAULT_MAJOR_VERSION EQUAL 6)
qt6_add_ui(${ARGN})
else()
message(FATAL_ERROR "qt_add_ui() is only available in Qt 6.")
endif()
endfunction()
endif()

View File

@ -6,3 +6,30 @@ set(SOURCES mainwindow.cpp main.cpp)
qt_wrap_ui(SOURCES mainwindow.ui)
qt_add_executable(myapp ${SOURCES})
#! [qt_wrap_ui]
#! [qt6_add_ui_1]
qt_add_executable(myapp mainwindow.cpp main.cpp)
qt6_add_ui(myapp SOURCES mainwindow.ui)
#! [qt6_add_ui_1]
#! [qt6_add_ui_2]
qt_add_executable(myapp mainwindow.cpp main.cpp)
qt6_add_ui(myapp INCLUDE_PREFIX "src/files" SOURCES mainwindow.ui)
#! [qt6_add_ui_2]
#! [qt6_add_ui_3]
qt_add_executable(myapp widget1.cpp widget2.cpp main.cpp)
qt6_add_ui(myapp INCLUDE_PREFIX "src/files" SOURCES widget1.ui widget2.ui)
#! [qt6_add_ui_3]
#! [qt6_add_ui_4]
qt_add_executable(myapp widget1.cpp widget2.cpp main.cpp)
qt6_add_ui(myapp INCLUDE_PREFIX "src/files"
SOURCES "my_ui_files_1/widget1.ui" "my_ui_files_2/widget2.ui")
#! [qt6_add_ui_4]
#! [qt6_add_ui_5]
qt_add_executable(myapp widget1.cpp widget2.cpp main.cpp)
qt6_add_ui(myapp INCLUDE_PREFIX "src/files_1" SOURCES "my_ui_files/widget1.ui")
qt6_add_ui(myapp INCLUDE_PREFIX "src/files_2" SOURCES "my_ui_files/widget2.ui")
#! [qt6_add_ui_5]

View File

@ -0,0 +1,26 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [qt6_add_ui_1]
#include "mainwindow.h"
#include "ui_mainwindow.h"
//! [qt6_add_ui_1]
//! [qt6_add_ui_2]
#include "mainwindow.h"
#include "src/files/ui_mainwindow.h"
//! [qt6_add_ui_2]
//! [qt6_add_ui_3_1]
#include "src/files/ui_widget1.h"
//! [qt6_add_ui_3_1]
//! [qt6_add_ui_3_2]
#include "src/files/ui_widget2.h"
//! [qt6_add_ui_3_2]
//! [qt6_add_ui_5]
#include "src/files_1/ui_widget1.h"
#include "src/files_2/ui_widget2.h"
//! [qt6_add_ui_5]

View File

@ -30,6 +30,10 @@ directory. The paths of the generated header files are added to \c{<VAR>}.
\note This is a low-level macro. See the \l{CMake AUTOUIC Documentation} for a
more convenient way to process \c{.ui} files with \c{uic}.
Since 6.8:
\note \l{qt6_add_ui}{qt_add_ui} is the recommended way to process \c{.ui}
files.
\section1 Options
You can set additional \c{OPTIONS} that should be added to the \c{uic} calls.
@ -39,3 +43,132 @@ You can find possible options in the \l{uic}{uic documentation}.
\snippet cmake-macros/examples.cmake qt_wrap_ui
*/
/*!
\page qt-add-ui.html
\ingroup cmake-macros-qtwidgets
\title qt_add_ui
\keyword qt6_add_ui
\summary {Adds .ui files to a target.}
\include cmake-find-package-widgets.qdocinc
\section1 Synopsis
\badcode
qt_add_ui(<TARGET>
SOURCES file1.ui [file2.ui ...]
[INCLUDE_PREFIX <PREFIX>]
[OPTIONS ...])
\endcode
\versionlessCMakeCommandsNote qt6_add_ui()
\cmakecommandsince 6.8
\section1 Description
Creates rules for calling the \l{uic}{User Interface Compiler (uic)} on the
\c{.ui} files. For each input file, a header file is generated in the build
directory. The generated header files are added to sources of the target.
\section1 Arguments
\section2 TARGET
The \c{TARGET} argument specifies the CMake target to which the generated header
files will be added.
\section2 SOURCES
The \c{SOURCES} argument specifies the list of \c{.ui} files to process.
\section2 INCLUDE_PREFIX
\c INCLUDE_PREFIX specifies the include prefix for the generated header files.
Use the same include prefix as in the \c{#include} directive in the source
files. If \c{ui_<basename>.h} is included without a prefix, then this argument
can be omitted.
\section2 OPTIONS
You can set additional \c{OPTIONS} that should be added to the \c{uic} calls.
You can find possible options in the \l{uic}{uic documentation}.
\section1 Examples
\section2 Without INCLUDE_PREFIX
In the following snippet, the \c{mainwindow.cpp} file includes
\c{ui_mainwindow.h} and \c{mainwindow.h}.
\snippet cmake-macros/examples.cpp qt6_add_ui_1
\c{CMakeLists.txt} is implemented as follows and it calls
\l{qt6_add_ui}{qt_add_ui} to add \c{ui_mainwindow.h} to the \c{myapp} target.
\snippet cmake-macros/examples.cmake qt6_add_ui_1
In the above example, \c{ui_mainwindow.h} is included without a prefix. So the
\c{INCLUDE_PREFIX} argument is not specified.
\section2 With INCLUDE_PREFIX
\snippet cmake-macros/examples.cpp qt6_add_ui_2
In the above snippet, \c{mainwindow.cpp} includes \c{ui_mainwindow.h} with a
prefix.
\snippet cmake-macros/examples.cmake qt6_add_ui_2
Since \c{ui_mainwindow.h} is included with a prefix, the \c{INCLUDE_PREFIX}
argument is specified as \c{src/files} in the above example.
\section2 Multiple .ui files
In the following snippets, both \c{widget1.cpp} and \c{widget2.cpp} include
\c{ui_widget1.h} and \c{ui_widget2.h} respectively.
\c{widget1.cpp}:
\snippet cmake-macros/examples.cpp qt6_add_ui_3_1
\c{widget2.cpp}:
\snippet cmake-macros/examples.cpp qt6_add_ui_3_2
Both \c{ui_widget1.h} and \c{ui_widget2.h} are included with the same prefix
\snippet cmake-macros/examples.cmake qt6_add_ui_3
In this case, the \c{INCLUDE_PREFIX} argument can be specified as \c{src/files}
for both files in the above snippet.
\section1 When to prefer \l{qt6_add_ui}{qt_add_ui} over \c{AUTOUIC}?
\l{qt6_add_ui}{qt_add_ui} has some advantages over \c{AUTOUIC}:
\list
\li \l{qt6_add_ui}{qt_add_ui} ensures that the \c{.ui} files are generated
correctly during the first build, for the \c{Ninja} and \c{Ninja Multi-Config}
generators.
\li \l{qt6_add_ui}{qt_add_ui} guarantees that the generated \c{.h} files are
not leaked outside the build directory.
\li Since \l{qt6_add_ui}{qt_add_ui} does not scan source files, it provides a
faster build than \c{AUTOUIC}.
\endlist
\section1 When to prefer \l{qt6_add_ui}{qt_add_ui} over \c{qt_wrap_ui}?
\l{qt6_add_ui}{qt_add_ui} has the \c{INCLUDE_PREFIX} argument, which can be used to
specify the include prefix for the generated header files.
\note It is not recommended to use both \l{qt6_add_ui}{qt_add_ui} and
\c{AUTOUIC} for the same target.
*/

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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)

View File

@ -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()

View File

@ -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();
}

View File

@ -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

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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);
}

View File

@ -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

View File

@ -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>

View File

@ -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);
}

View File

@ -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

View File

@ -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>")

View File

@ -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>

View File

@ -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>")

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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

View File

@ -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>

View File

@ -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);
}

View File

@ -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

View File

@ -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>

View File

@ -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);
}

View File

@ -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

View File

@ -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>

View File

@ -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()

View File

@ -0,0 +1,4 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: BSD-3-Clause
int main() { return 0; }

View File

@ -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()

View File

@ -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;
}

View File

@ -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>

View File

@ -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>