47b879aa0b
Take 2.
Re-land previously reverted commit, due to not handling resource names
that are not valid c++ identifiers. Now we sanitize the resource names
just like rcc does by replacing non-alphanumeric characters with
underscores.
Original commit message.
During the Qt 5 -> Qt 6 and qmake -> CMake porting time frame, it was
decided to keep resources in an object file (object library), rather
than putting them directly into a static library when doing a static
Qt build, so that the build system can take care of linking the
object file directly into the executable and thus not forcing
project developers to manually initialize resources with
the Q_INIT_RESOURCE() macro in project code.
This worked for most qmake and cmake projects, but it created
difficulties for other build systems, in the sense that these projects
would have to manually link to the resource object files, otherwise
they would get link time errors about undefined resource symbols,
assuming they kept the Q_INIT_RESOURCE() calls.
If the project code didn't contain Q_INIT_RESOURCE calls, the
situation would be even worse, the linker would not error out,
and the missing resources would only be discovered at runtime.
It's also an issue in CMake projects that try to link to the
library files directly instead of using the library target names,
which means the object files would not be automatically linked in.
Many projects try to do that because we don't yet offer a convenient
way to install libraries and reuse them in other projects (the SDK
case), so projects end up shipping only the libraries, without the
resource object files.
We can improve the situation by moving the resources back into their
associated static libraries, and only keeping a static initializer as
a separate object file / object library, which references the actual
resource initializer symbol, to ensure it does not get discarded
during linking.
This way, projects that link using targets get no behavior difference,
whereas projects linking to static libraries directly can still
successfully build as long as their sources have all the necessary
Q_INIT_RESOURCE calls.
To ensure the resource symbols do not get discarded, we use a few new
private macros. We declare the resource init symbols we want to keep as
extern symbols and then assign the symbol addresses to volatile
variables.
This prevents discarding the symbols with the compilers / linkers we
care about.
It comes at the cost of an additional static initializer per resource,
but we would get the same + a bigger performance hit if we just used
Q_INIT_RESOURCE twice (once in the object lib and once in project
code), which internally needs to traverse a linked list of all
resources to check if a resource was initialized or not.
For GHS / Integrity, we also need to use a GHS-specific pragma to keep
the symbols, which we currently use in qtdeclarative to ensure qml
plugin symbols are not discarded.
The same macros will be used in a qtdeclarative change to prevent
discarding of resources when linking to static qml plugins.
A cmake-based test case is added to verify that linking to static
libraries directly, without linking to the resource initializer
object libraries, works fine as long as the project code calls
Q_INIT_RESOURCE for the relevant resource.
This reverts commit bc88bb34ca
.
Fixes: QTBUG-91448
Task-number: QTBUG-110243
Change-Id: Idce69db0cf79d3e32916750bfa61774ced977a7e
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
392 lines
15 KiB
CMake
392 lines
15 KiB
CMake
# Copyright (C) 2022 The Qt Company Ltd.
|
|
# SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
# This is an automatic test for the CMake configuration files.
|
|
# To run it manually,
|
|
# 1) mkdir build # Create a build directory
|
|
# 2) cd build
|
|
# 3) # Run cmake on this directory
|
|
# `$qt_prefix/bin/qt-cmake ..` or `cmake -DCMAKE_PREFIX_PATH=/path/to/qt ..`
|
|
# 4) ctest # Run ctest
|
|
# 5) ctest -V -R test_wrap_cpp_options # Run single test
|
|
#
|
|
# The expected output is something like:
|
|
#
|
|
# Start 1: test_use_modules_function
|
|
# 1/11 Test #1: test_use_modules_function ........ Passed 3.36 sec
|
|
# Start 2: test_wrap_cpp_and_resources
|
|
# 2/11 Test #2: test_wrap_cpp_and_resources ...... Passed 1.41 sec
|
|
# Start 3: test_dependent_modules
|
|
# 3/11 Test #3: test_dependent_modules ........... Passed 2.22 sec
|
|
# Start 4: test_add_resource_options
|
|
# 4/11 Test #4: test_add_resource_options ........ Passed 0.16 sec
|
|
# Start 5: test_wrap_cpp_options
|
|
# 5/11 Test #5: test_wrap_cpp_options ............ Passed 0.36 sec
|
|
# Start 6: test_needsquoting_dirname
|
|
# 6/11 Test #6: test_needsquoting_dirname ........ Passed 2.20 sec
|
|
# Start 7: test_platform_defs_include
|
|
# 7/11 Test #7: test_platform_defs_include ....... Passed 0.28 sec
|
|
# Start 8: test_qtmainwin_library
|
|
# 8/11 Test #8: test_qtmainwin_library ........... Passed 1.27 sec
|
|
# Start 9: test_dbus_module
|
|
# 9/11 Test #9: test_dbus_module ................. Passed 3.46 sec
|
|
# Start 10: test_multiple_find_package
|
|
# 10/11 Test #10: test_multiple_find_package ....... Passed 0.07 sec
|
|
# Start 11: test_add_resources_delayed_file
|
|
# 11/11 Test #11: test_add_resources_delayed_file .. Passed 0.38 sec
|
|
#
|
|
#
|
|
# Note that if Qt is not installed, or if it is installed to a
|
|
# non-standard prefix, the environment variable CMAKE_PREFIX_PATH
|
|
# needs to be set to the installation prefix or build prefix of Qt
|
|
# before running these tests.
|
|
|
|
cmake_minimum_required(VERSION 3.16)
|
|
|
|
project(cmake_usage_tests)
|
|
include(GNUInstallDirs)
|
|
|
|
# Building the CMake tests as part of a Qt prefix build + in-tree tests, currently doesn't work.
|
|
# Each CMake test will fail with a message like
|
|
#
|
|
# CMake Error at qtbase/lib/cmake/Qt6/Qt6Config.cmake:33 (include):
|
|
# include could not find load file:
|
|
# qtbase/lib/cmake/Qt6/Qt6Targets.cmake
|
|
#
|
|
# That's because the Qt packages are not installed, and we try to load the Config files from the
|
|
# build dir, but they can't work in a prefix build without installation.
|
|
# Configuring the tests as standalone tests or as a separate project works fine.
|
|
# Configuring the tests in-tree also works fine in a non-prefix build.
|
|
if(QT_REPO_MODULE_VERSION AND NOT QT_BUILD_STANDALONE_TESTS AND QT_WILL_INSTALL)
|
|
message(WARNING
|
|
"Skipping building CMake build tests because they don't work in a prefix in-tree config")
|
|
endif()
|
|
|
|
enable_testing()
|
|
|
|
# Most of the tests fail to build on Boot2qt / qemu with undefined references to QtDBus because
|
|
# it's a private dependency of QtGui, and CMake for some reason doesn't generate an -rpath-link
|
|
# flag. Notably -rpath is specified which should implicitly enable -rpath-link, but that
|
|
# doesn't seem to be the case.
|
|
# Until this is figured out, disable the tests when cross-compiling to Linux.
|
|
if(UNIX AND NOT APPLE AND NOT WIN32 AND CMAKE_CROSSCOMPILING AND NOT QT_ENABLE_CMAKE_BOOT2QT_TESTS
|
|
AND NOT QT_BUILD_MINIMAL_ANDROID_MULTI_ABI_TESTS)
|
|
message(STATUS "Running CMake tests is disabled when cross-compiling to Linux / Boot2Qt.")
|
|
return()
|
|
endif()
|
|
|
|
set(required_packages Core Network Xml Sql Test)
|
|
set(optional_packages DBus Gui Widgets PrintSupport OpenGL Concurrent)
|
|
|
|
# Setup the test when called as a completely standalone project.
|
|
if(TARGET Qt6::Core)
|
|
# Tests are built as part of the qtbase build tree.
|
|
# Setup paths so that the Qt packages are found, similar to examples.
|
|
qt_internal_set_up_build_dir_package_paths()
|
|
endif()
|
|
find_package(Qt6 REQUIRED COMPONENTS ${required_packages})
|
|
find_package(Qt6 OPTIONAL_COMPONENTS ${optional_packages})
|
|
|
|
# Setup common test variables which were previously set by ctest_testcase_common.prf.
|
|
set(CMAKE_MODULES_UNDER_TEST "${required_packages}" ${optional_packages})
|
|
|
|
foreach(qt_package ${CMAKE_MODULES_UNDER_TEST})
|
|
set(package_name "${QT_CMAKE_EXPORT_NAMESPACE}${qt_package}")
|
|
if(${package_name}_FOUND)
|
|
set(CMAKE_${qt_package}_MODULE_MAJOR_VERSION "${${package_name}_VERSION_MAJOR}")
|
|
set(CMAKE_${qt_package}_MODULE_MINOR_VERSION "${${package_name}_VERSION_MINOR}")
|
|
set(CMAKE_${qt_package}_MODULE_PATCH_VERSION "${${package_name}_VERSION_PATCH}")
|
|
endif()
|
|
endforeach()
|
|
|
|
# Qt6CTestMacros.cmake also expects some of these variables to be set.
|
|
if(NOT TARGET Qt::Gui)
|
|
set(NO_GUI TRUE)
|
|
endif()
|
|
if(NOT TARGET Qt::DBus)
|
|
set(NO_DBUS TRUE)
|
|
endif()
|
|
if(NOT TARGET Qt::Widgets)
|
|
set(NO_WIDGETS TRUE)
|
|
endif()
|
|
|
|
include("${_Qt6CTestMacros}")
|
|
|
|
# Test only multi-abi specific functionality when QT_BUILD_MINIMAL_ANDROID_MULTI_ABI_TESTS is ON.
|
|
# Qt::Gui is the prerequisite for all Android tests.
|
|
if(QT_BUILD_MINIMAL_ANDROID_MULTI_ABI_TESTS AND NOT NO_GUI)
|
|
unset(multi_abi_vars)
|
|
foreach(abi IN LISTS QT_ANDROID_ABIS)
|
|
list(APPEND multi_abi_vars "-DQT_PATH_ANDROID_ABI_${abi}=${QT_PATH_ANDROID_ABI_${abi}}")
|
|
endforeach()
|
|
if(QT_ANDROID_BUILD_ALL_ABIS)
|
|
list(APPEND multi_abi_vars "-DQT_ANDROID_BUILD_ALL_ABIS=${QT_ANDROID_BUILD_ALL_ABIS}")
|
|
endif()
|
|
|
|
list(APPEND multi_abi_vars "-DQT_HOST_PATH=${QT_HOST_PATH}")
|
|
|
|
set(multi_abi_forward_vars
|
|
TEST_SINGLE_VALUE_ARG
|
|
TEST_SPACES_VALUE_ARG
|
|
TEST_LIST_VALUE_ARG
|
|
TEST_ESCAPING_VALUE_ARG
|
|
)
|
|
string(REPLACE ";" "[[;]]" multi_abi_forward_vars "${multi_abi_forward_vars}")
|
|
|
|
set(single_value "TestValue")
|
|
set(list_value "TestValue[[;]]TestValue2[[;]]TestValue3")
|
|
set(escaping_value "TestValue\\\\[[;]]TestValue2\\\\[[;]]TestValue3")
|
|
set(spaces_value "TestValue TestValue2 TestValue3")
|
|
_qt_internal_test_expect_pass(test_android_multi_abi_forward_vars
|
|
BUILD_OPTIONS
|
|
${multi_abi_vars}
|
|
"-DQT_ANDROID_MULTI_ABI_FORWARD_VARS=${multi_abi_forward_vars}"
|
|
"-DTEST_SINGLE_VALUE_ARG=${single_value}"
|
|
"-DTEST_LIST_VALUE_ARG=${list_value}"
|
|
"-DTEST_ESCAPING_VALUE_ARG=${escaping_value}"
|
|
"-DTEST_SPACES_VALUE_ARG=${spaces_value}"
|
|
)
|
|
return()
|
|
endif()
|
|
|
|
if(NOT NO_WIDGETS)
|
|
_qt_internal_test_expect_pass(test_build_simple_widget_app)
|
|
set(extra_widget_app_options "")
|
|
if(IOS)
|
|
list(APPEND extra_widget_app_options
|
|
QMAKE_OPTIONS CONFIG+=iossimulator
|
|
)
|
|
endif()
|
|
if(CMAKE_HOST_WIN32)
|
|
# Unset MAKEFLAGS environment variable when invoking build tool, it might
|
|
# have options incompatible with nmake.
|
|
list(APPEND extra_widget_app_options
|
|
BUILD_ENVIRONMENT MAKEFLAGS ""
|
|
)
|
|
endif()
|
|
|
|
_qt_internal_add_qmake_test(test_build_simple_widget_app
|
|
TESTNAME test_build_simple_widget_app_qmake
|
|
${extra_widget_app_options}
|
|
)
|
|
endif()
|
|
|
|
# We only support a limited subset of cmake tests when targeting iOS:
|
|
# - Only those that use qt_add_executable (but not add_executable)
|
|
# - and don't try to run the built binaries via BINARY_ARGS option
|
|
# - and don't use internal API like qt_internal_add_*
|
|
#
|
|
# So we can't run binaries in the simulator or on-device, but we at least
|
|
# want build coverage (app linking succeeds).
|
|
if(IOS)
|
|
return()
|
|
endif()
|
|
|
|
set(is_qt_build_platform TRUE)
|
|
# macOS versions less than 10.15 are not supported for building Qt.
|
|
if(CMAKE_HOST_APPLE AND CMAKE_HOST_SYSTEM_VERSION VERSION_LESS "19.0.0")
|
|
set(is_qt_build_platform FALSE)
|
|
endif()
|
|
|
|
_qt_internal_test_expect_pass(test_umbrella_config)
|
|
_qt_internal_test_expect_pass(test_wrap_cpp_and_resources)
|
|
if (NOT NO_WIDGETS)
|
|
_qt_internal_test_expect_pass(test_dependent_modules)
|
|
_qt_internal_test_expect_pass("test(needsquoting)dirname")
|
|
endif()
|
|
_qt_internal_test_expect_pass(test_add_resource_prefix BINARY test_add_resource_prefix)
|
|
_qt_internal_test_expect_build_fail(test_add_resource_options)
|
|
_qt_internal_test_expect_build_fail(test_wrap_cpp_options)
|
|
_qt_internal_test_expect_pass(test_platform_defs_include)
|
|
_qt_internal_test_expect_pass(test_qtmainwin_library)
|
|
|
|
if (CMAKE_GENERATOR STREQUAL Ninja AND UNIX AND NOT WIN32)
|
|
_qt_internal_test_expect_pass(test_QFINDTESTDATA
|
|
BINARY "tests/test_QFINDTESTDATA"
|
|
SIMULATE_IN_SOURCE
|
|
)
|
|
# TODO: Decide if there's a reason to keep this test. With CMake 3.21.0 which passes absolute
|
|
# source file paths to the compiler (instead of relative ones), specifying a custom
|
|
# QT_TESTCASE_BUILDDIR is a no-op, which fails the test's preconditions.
|
|
# See QTBUG-95268.
|
|
#_qt_internal_test_expect_pass(test_QT_TESTCASE_BUILDDIR
|
|
# BINARY "test_qt_testcase_builddir"
|
|
# SIMULATE_IN_SOURCE
|
|
#)
|
|
endif()
|
|
|
|
if (NOT NO_DBUS)
|
|
_qt_internal_test_expect_pass(test_dbus_module)
|
|
endif()
|
|
_qt_internal_test_expect_pass(test_multiple_find_package)
|
|
_qt_internal_test_expect_pass(test_add_resources_delayed_file)
|
|
_qt_internal_test_expect_pass(test_add_binary_resources_delayed_file BINARY test_add_binary_resources_delayed_file)
|
|
_qt_internal_test_expect_pass(test_qt_add_resources_rebuild)
|
|
_qt_internal_test_expect_pass(test_resource_without_obj_lib BINARY test_resource_without_obj_lib)
|
|
|
|
if(NOT NO_GUI)
|
|
_qt_internal_test_expect_pass(test_private_includes)
|
|
_qt_internal_test_expect_pass(test_private_targets)
|
|
endif()
|
|
|
|
_qt_internal_test_expect_pass(test_testlib_definitions)
|
|
_qt_internal_test_expect_pass(test_json_plugin_includes)
|
|
|
|
if(NOT NO_GUI)
|
|
_qt_internal_test_expect_build_fail(test_testlib_no_link_gui)
|
|
execute_process(COMMAND ${CMAKE_COMMAND} -E copy
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/test_testlib_definitions/main.cpp"
|
|
"${CMAKE_CURRENT_BINARY_DIR}/failbuild/test_testlib_no_link_gui/test_testlib_no_link_gui/"
|
|
)
|
|
endif()
|
|
|
|
if (NOT NO_WIDGETS)
|
|
_qt_internal_test_expect_build_fail(test_testlib_no_link_widgets)
|
|
execute_process(COMMAND ${CMAKE_COMMAND} -E copy
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/test_testlib_definitions/main.cpp"
|
|
"${CMAKE_CURRENT_BINARY_DIR}/failbuild/test_testlib_no_link_widgets/test_testlib_no_link_widgets/"
|
|
)
|
|
endif()
|
|
|
|
set(qt_module_includes
|
|
Core QObject
|
|
Network QHostInfo
|
|
Sql QSqlError
|
|
Test QTestEventList
|
|
Xml QDomDocument
|
|
)
|
|
|
|
if (NOT NO_GUI)
|
|
list(APPEND qt_module_includes
|
|
Gui QImage
|
|
)
|
|
endif()
|
|
|
|
if (NOT NO_WIDGETS)
|
|
list(APPEND qt_module_includes
|
|
Widgets QWidget
|
|
OpenGL QOpenGLBuffer
|
|
PrintSupport QPrinter
|
|
)
|
|
endif()
|
|
|
|
if (NOT NO_DBUS)
|
|
list(APPEND qt_module_includes
|
|
DBus QDBusMessage
|
|
)
|
|
endif()
|
|
|
|
_qt_internal_test_module_includes(
|
|
${qt_module_includes}
|
|
)
|
|
_qt_internal_test_expect_pass(test_concurrent_module)
|
|
|
|
if(NOT NO_GUI)
|
|
_qt_internal_test_expect_pass(test_opengl_lib)
|
|
endif()
|
|
|
|
if (NOT NO_WIDGETS)
|
|
_qt_internal_test_expect_pass(test_interface)
|
|
endif()
|
|
|
|
if(NOT NO_GUI)
|
|
_qt_internal_test_expect_pass(test_interface_link_libraries)
|
|
endif()
|
|
_qt_internal_test_expect_pass(test_moc_macro_target)
|
|
|
|
# The modification of TARGET_OBJECTS needs the following change in cmake
|
|
# https://gitlab.kitware.com/cmake/cmake/commit/93c89bc75ceee599ba7c08b8fe1ac5104942054f
|
|
_qt_internal_test_expect_pass(test_add_big_resource)
|
|
|
|
# With earlier CMake versions, this test would simply run moc multiple times and lead to:
|
|
# /usr/bin/ld: error: CMakeFiles/mywidget.dir/mywidget_automoc.cpp.o: multiple definition of 'MyWidget::qt_static_metacall(QObject*, QMetaObject::Call, int, void**)'
|
|
# /usr/bin/ld: CMakeFiles/mywidget.dir/moc_mywidget.cpp.o: previous definition here
|
|
# Reason: SKIP_* properties were added in CMake 3.8 only
|
|
if(NOT NO_WIDGETS)
|
|
_qt_internal_test_expect_pass(test_QTBUG-63422)
|
|
endif()
|
|
|
|
# Find main Qt installation location and bin dir.
|
|
if(QT_BUILD_INTERNALS_RELOCATABLE_INSTALL_PREFIX)
|
|
set(qt_install_prefix "${QT_BUILD_INTERNALS_RELOCATABLE_INSTALL_PREFIX}")
|
|
elseif(QT6_INSTALL_PREFIX)
|
|
set(qt_install_prefix "${QT6_INSTALL_PREFIX}")
|
|
endif()
|
|
|
|
if(INSTALL_LIBEXECDIR)
|
|
set(qt_install_libexec_dir "${INSTALL_LIBEXECDIR}")
|
|
elseif(QT6_INSTALL_LIBEXECS)
|
|
set(qt_install_libexec_dir "${QT6_INSTALL_LIBEXECS}")
|
|
endif()
|
|
|
|
# Test building and installing a few dummy Qt modules and plugins.
|
|
if(is_qt_build_platform)
|
|
set(mockplugins_test_args "")
|
|
if(NOT QT_FEATURE_no_prefix)
|
|
list(APPEND mockplugins_test_args
|
|
BINARY "${CMAKE_COMMAND}"
|
|
BINARY_ARGS
|
|
"-DQT_BUILD_DIR=${CMAKE_CURRENT_BINARY_DIR}/mockplugins"
|
|
-P "${qt_install_prefix}/${qt_install_libexec_dir}/qt-cmake-private-install.cmake"
|
|
)
|
|
endif()
|
|
_qt_internal_test_expect_pass(mockplugins ${mockplugins_test_args})
|
|
set_tests_properties(mockplugins PROPERTIES FIXTURES_SETUP build_mockplugins)
|
|
|
|
# Test importing the plugins built in the project above.
|
|
_qt_internal_test_expect_pass(test_import_plugins BINARY ${CMAKE_CTEST_COMMAND} BINARY_ARGS -V)
|
|
set_tests_properties(test_import_plugins PROPERTIES FIXTURES_REQUIRED build_mockplugins)
|
|
endif()
|
|
|
|
_qt_internal_test_expect_pass(test_versionless_targets)
|
|
|
|
if(NOT NO_GUI)
|
|
_qt_internal_test_expect_pass(test_global_promotion)
|
|
endif()
|
|
|
|
_qt_internal_test_expect_pass(test_add_resources_binary_generated
|
|
BINARY test_add_resources_binary_generated)
|
|
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.17")
|
|
_qt_internal_test_expect_pass(test_add_resources_big_resources
|
|
BINARY test_add_resources_big_resources)
|
|
endif()
|
|
|
|
include(test_plugin_shared_static_flavor.cmake)
|
|
_qt_internal_test_expect_pass(tst_qaddpreroutine
|
|
BINARY tst_qaddpreroutine)
|
|
|
|
if(is_qt_build_platform)
|
|
_qt_internal_test_expect_pass(test_static_resources
|
|
BINARY "${CMAKE_CTEST_COMMAND}"
|
|
BINARY_ARGS "-V")
|
|
|
|
_qt_internal_test_expect_pass(test_generating_cpp_exports)
|
|
endif()
|
|
|
|
_qt_internal_test_expect_pass(test_qt_extract_metatypes)
|
|
|
|
set(deploy_args
|
|
test_widgets_app_deployment
|
|
BINARY "${CMAKE_CTEST_COMMAND}"
|
|
BINARY_ARGS "-V"
|
|
# Need to explicitly specify a writable install prefix.
|
|
BUILD_OPTIONS
|
|
-DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/test_widgets_app_deployment_installed
|
|
NO_RUN_ENVIRONMENT_PLUGIN_PATH
|
|
)
|
|
|
|
set(is_desktop_linux FALSE)
|
|
if(UNIX AND NOT APPLE AND NOT ANDROID AND NOT CMAKE_CROSSCOMPILING)
|
|
set(is_desktop_linux TRUE)
|
|
endif()
|
|
|
|
# For now, the test should only pass on Windows, macOS and desktop Linux shared and static builds
|
|
# and fail on other platforms, because there is no support for runtime dependency deployment on
|
|
# those platforms.
|
|
# With static builds the runtime dependencies are just skipped, but the test should still pass.
|
|
if(WIN32 OR (APPLE AND NOT IOS) OR is_desktop_linux)
|
|
_qt_internal_test_expect_pass(${deploy_args})
|
|
else()
|
|
_qt_internal_test_expect_fail(${deploy_args})
|
|
endif()
|