diff options
Diffstat (limited to 'src/corelib/Qt6CoreMacros.cmake')
-rw-r--r-- | src/corelib/Qt6CoreMacros.cmake | 3021 |
1 files changed, 2652 insertions, 369 deletions
diff --git a/src/corelib/Qt6CoreMacros.cmake b/src/corelib/Qt6CoreMacros.cmake index fc8ae6a74f..f55e32a7cf 100644 --- a/src/corelib/Qt6CoreMacros.cmake +++ b/src/corelib/Qt6CoreMacros.cmake @@ -1,34 +1,6 @@ -#============================================================================= # Copyright 2005-2011 Kitware, Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of Kitware, Inc. nor the names of its -# contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#============================================================================= +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ###################################### # @@ -36,8 +8,6 @@ # ###################################### -include(CMakeParseArguments) - set(__qt_core_macros_module_base_dir "${CMAKE_CURRENT_LIST_DIR}") # macro used to create the names of output files preserving relative dirs @@ -104,7 +74,8 @@ macro(_qt_internal_get_moc_flags _moc_flags) endmacro() # helper macro to set up a moc rule -function(_qt_internal_create_moc_command infile outfile moc_flags moc_options moc_target moc_depends) +function(_qt_internal_create_moc_command infile outfile moc_flags moc_options + moc_target moc_depends out_json_file) # Pass the parameters in a file. Set the working directory to # be that containing the parameters file and reference it by # just the file name. This is necessary because the moc tool on @@ -117,36 +88,64 @@ function(_qt_internal_create_moc_command infile outfile moc_flags moc_options mo endif() set (_moc_parameters_file ${outfile}_parameters) set (_moc_parameters ${moc_flags} ${moc_options} -o "${outfile}" "${infile}") - string (REPLACE ";" "\n" _moc_parameters "${_moc_parameters}") + if(out_json_file) + list(APPEND _moc_parameters --output-json) + set(extra_output_files "${outfile}.json") + set(${out_json_file} "${extra_output_files}" PARENT_SCOPE) + endif() if(moc_target) - set(_moc_parameters_file ${_moc_parameters_file}$<$<BOOL:$<CONFIGURATION>>:_$<CONFIGURATION>>) + set(_moc_parameters_file ${_moc_parameters_file}$<$<BOOL:$<CONFIG>>:_$<CONFIG>>) set(targetincludes "$<TARGET_PROPERTY:${moc_target},INCLUDE_DIRECTORIES>") set(targetdefines "$<TARGET_PROPERTY:${moc_target},COMPILE_DEFINITIONS>") - set(targetincludes "$<$<BOOL:${targetincludes}>:-I$<JOIN:${targetincludes},\n-I>\n>") - set(targetdefines "$<$<BOOL:${targetdefines}>:-D$<JOIN:${targetdefines},\n-D>\n>") + set(targetincludes "$<$<BOOL:${targetincludes}>:-I$<JOIN:${targetincludes},;-I>>") + set(targetdefines "$<$<BOOL:${targetdefines}>:-D$<JOIN:${targetdefines},;-D>>") + set(_moc_parameters_list_without_genex) + set(_moc_parameters_list_with_genex) + foreach(_moc_parameter ${_moc_parameters}) + if(_moc_parameter MATCHES "\\\$<") + list(APPEND _moc_parameters_list_with_genex ${_moc_parameter}) + else() + list(APPEND _moc_parameters_list_without_genex ${_moc_parameter}) + endif() + endforeach() + + string(REPLACE ">" "$<ANGLE-R>" _moc_escaped_parameters "${_moc_parameters_list_without_genex}") + string(REPLACE "," "$<COMMA>" _moc_escaped_parameters "${_moc_escaped_parameters}") + string(REPLACE ";" "$<SEMICOLON>" _moc_escaped_parameters "${_moc_escaped_parameters}") + set(concatenated "$<$<BOOL:${targetincludes}>:${targetincludes};>$<$<BOOL:${targetdefines}>:${targetdefines};>$<$<BOOL:${_moc_escaped_parameters}>:${_moc_escaped_parameters};>") + + list(APPEND concatenated ${_moc_parameters_list_with_genex}) + set(concatenated "$<FILTER:$<REMOVE_DUPLICATES:${concatenated}>,EXCLUDE,^-[DI]$>") + set(concatenated "$<JOIN:${concatenated},\n>") file (GENERATE OUTPUT ${_moc_parameters_file} - CONTENT "${targetdefines}${targetincludes}${_moc_parameters}\n" + CONTENT "${concatenated}" ) + set(concatenated) set(targetincludes) set(targetdefines) else() + string (REPLACE ";" "\n" _moc_parameters "${_moc_parameters}") file(WRITE ${_moc_parameters_file} "${_moc_parameters}\n") endif() set(_moc_extra_parameters_file @${_moc_parameters_file}) - add_custom_command(OUTPUT ${outfile} + add_custom_command(OUTPUT ${outfile} ${extra_output_files} COMMAND ${QT_CMAKE_EXPORT_NAMESPACE}::moc ${_moc_extra_parameters_file} DEPENDS ${infile} ${moc_depends} ${_moc_working_dir} VERBATIM) set_source_files_properties(${infile} PROPERTIES SKIP_AUTOMOC ON) - set_source_files_properties(${outfile} PROPERTIES SKIP_AUTOMOC ON) - set_source_files_properties(${outfile} PROPERTIES SKIP_AUTOUIC ON) + set_source_files_properties(${outfile} PROPERTIES SKIP_AUTOMOC ON + SKIP_AUTOUIC ON + ) + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.27") + set_source_files_properties(${outfile} PROPERTIES SKIP_LINTING ON) + endif() endfunction() function(qt6_generate_moc infile outfile ) @@ -160,7 +159,10 @@ function(qt6_generate_moc infile outfile ) if ("x${ARGV2}" STREQUAL "xTARGET") set(moc_target ${ARGV3}) endif() - _qt_internal_create_moc_command(${abs_infile} ${_outfile} "${moc_flags}" "" "${moc_target}" "") + _qt_internal_create_moc_command(${abs_infile} ${_outfile} "${moc_flags}" "" "${moc_target}" + "" # moc_depends + "" # out_json_file + ) endfunction() if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) @@ -181,7 +183,10 @@ function(qt6_wrap_cpp outfiles ) _qt_internal_get_moc_flags(moc_flags) set(options) - set(oneValueArgs TARGET) + set(oneValueArgs + TARGET + __QT_INTERNAL_OUTPUT_MOC_JSON_FILES + ) set(multiValueArgs OPTIONS DEPENDS) cmake_parse_arguments(_WRAP_CPP "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) @@ -191,14 +196,76 @@ function(qt6_wrap_cpp outfiles ) set(moc_target ${_WRAP_CPP_TARGET}) set(moc_depends ${_WRAP_CPP_DEPENDS}) + set(metatypes_json_list "") + foreach(it ${moc_files}) get_filename_component(it ${it} ABSOLUTE) - _qt_internal_make_output_file(${it} moc_ cpp outfile) + get_filename_component(it_ext ${it} EXT) + # remove the dot + string(SUBSTRING ${it_ext} 1 -1 it_ext) + set(HEADER_REGEX "(h|hh|h\\+\\+|hm|hpp|hxx|in|txx|inl)$") + + if(it_ext MATCHES "${HEADER_REGEX}") + _qt_internal_make_output_file("${it}" moc_ cpp outfile) + set(is_header_file TRUE) + else() + set(found_source_extension FALSE) + foreach(LANG C CXX OBJC OBJCXX CUDA) + list(FIND CMAKE_${LANG}_SOURCE_FILE_EXTENSIONS "${it_ext}" + index) + if(${index} GREATER -1) + set(found_extension TRUE) + break() + endif() + endforeach() + if(found_extension) + if(TARGET ${moc_target}) + _qt_internal_make_output_file(${it} "" moc outfile) + target_sources(${moc_target} PRIVATE "${outfile}") + target_include_directories("${moc_target}" PRIVATE + "${CMAKE_CURRENT_BINARY_DIR}") + else() + if("${moc_target}" STREQUAL "") + string(JOIN "" err_msg + "qt6_wrap_cpp: TARGET parameter is empty. " + "Since the file ${it} is a source file, " + "the TARGET option must be specified.") + else() + string(JOIN "" err_msg + "qt6_wrap_cpp: TARGET \"${moc_target}\" " + "not found.") + endif() + message(FATAL_ERROR "${err_msg}") + endif() + else() + string(JOIN "" err_msg "qt6_wrap_cpp: Unknown file extension: " + "\"\.${it_ext}\".") + message(FATAL_ERROR "${err_msg}") + endif() + endif() + + set(out_json_file_var "") + if(_WRAP_CPP___QT_INTERNAL_OUTPUT_MOC_JSON_FILES) + set(out_json_file_var "out_json_file") + endif() + _qt_internal_create_moc_command( - ${it} ${outfile} "${moc_flags}" "${moc_options}" "${moc_target}" "${moc_depends}") + ${it} ${outfile} "${moc_flags}" "${moc_options}" "${moc_target}" "${moc_depends}" + "${out_json_file_var}") list(APPEND ${outfiles} ${outfile}) + if(_WRAP_CPP___QT_INTERNAL_OUTPUT_MOC_JSON_FILES) + list(APPEND metatypes_json_list "${${out_json_file_var}}") + endif() endforeach() - set(${outfiles} ${${outfiles}} PARENT_SCOPE) + + if(is_header_file) + set(${outfiles} "${${outfiles}}" PARENT_SCOPE) + endif() + + if(metatypes_json_list) + set(${_WRAP_CPP___QT_INTERNAL_OUTPUT_MOC_JSON_FILES} + "${metatypes_json_list}" PARENT_SCOPE) + endif() endfunction() # This will override the CMake upstream command, because that one is for Qt 3. @@ -262,6 +329,10 @@ function(qt6_add_binary_resources target ) set(rcc_options ${_RCC_OPTIONS}) set(rcc_destination ${_RCC_DESTINATION}) + if(NOT QT_FEATURE_zstd) + list(APPEND rcc_options "--no-zstd") + endif() + if(NOT rcc_destination) set(rcc_destination ${CMAKE_CURRENT_BINARY_DIR}/${target}.rcc) endif() @@ -326,6 +397,10 @@ function(qt6_add_resources outfiles ) message(WARNING "Use qt6_add_binary_resources for binary option") endif() + if(NOT QT_FEATURE_zstd) + list(APPEND rcc_options "--no-zstd") + endif() + foreach(it ${rcc_files}) get_filename_component(outfilename ${it} NAME_WE) get_filename_component(infile ${it} ABSOLUTE) @@ -340,8 +415,11 @@ function(qt6_add_resources outfiles ) MAIN_DEPENDENCY ${infile} DEPENDS ${_rc_depends} "${_out_depends}" ${QT_CMAKE_EXPORT_NAMESPACE}::rcc VERBATIM) - set_source_files_properties(${outfile} PROPERTIES SKIP_AUTOMOC ON) - set_source_files_properties(${outfile} PROPERTIES SKIP_AUTOUIC ON) + set_source_files_properties(${outfile} PROPERTIES SKIP_AUTOMOC ON + SKIP_AUTOUIC ON + SKIP_UNITY_BUILD_INCLUSION ON + SKIP_PRECOMPILE_HEADERS ON + ) list(APPEND ${outfiles} ${outfile}) endforeach() set(${outfiles} ${${outfiles}} PARENT_SCOPE) @@ -355,7 +433,12 @@ if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) elseif(QT_DEFAULT_MAJOR_VERSION EQUAL 6) qt6_add_resources("${outfiles}" ${ARGN}) endif() - if(NOT TARGET ${outfiles}) + if(TARGET ${outfiles}) + cmake_parse_arguments(PARSE_ARGV 1 arg "" "OUTPUT_TARGETS" "") + if (arg_OUTPUT_TARGETS) + set(${arg_OUTPUT_TARGETS} ${${arg_OUTPUT_TARGETS}} PARENT_SCOPE) + endif() + else() set("${outfiles}" "${${outfiles}}" PARENT_SCOPE) endif() endfunction() @@ -365,6 +448,18 @@ endif() # qt6_add_big_resources(outfiles inputfile ... ) function(qt6_add_big_resources outfiles ) + if(CMAKE_GENERATOR STREQUAL "Xcode" AND IOS) + message(WARNING + "Due to CMake limitations, qt6_add_big_resources can't be used when building for iOS. " + "See https://bugreports.qt.io/browse/QTBUG-103497 for details. " + "Falling back to using qt6_add_resources. " + "Consider using qt6_add_resources directly to silence this warning." + ) + qt6_add_resources(${ARGV}) + set(${outfiles} ${${outfiles}} PARENT_SCOPE) + return() + endif() + if (CMAKE_VERSION VERSION_LESS 3.9) message(FATAL_ERROR, "qt6_add_big_resources requires CMake 3.9 or newer") endif() @@ -382,36 +477,89 @@ function(qt6_add_big_resources outfiles ) message(WARNING "Use qt6_add_binary_resources for binary option") endif() + if(NOT QT_FEATURE_zstd) + list(APPEND rcc_options "--no-zstd") + endif() + foreach(it ${rcc_files}) get_filename_component(outfilename ${it} NAME_WE) + + # Provide unique targets and output file names + # in case we add multiple .qrc files with the same base name. + string(MAKE_C_IDENTIFIER "_qt_big_resource_count_${outfilename}" prop) + get_property(count GLOBAL PROPERTY ${prop}) + if(count) + string(APPEND outfilename "_${count}") + else() + set(count 0) + endif() + math(EXPR count "${count} + 1") + set_property(GLOBAL PROPERTY ${prop} ${count}) + get_filename_component(infile ${it} ABSOLUTE) set(tmpoutfile ${CMAKE_CURRENT_BINARY_DIR}/qrc_${outfilename}tmp.cpp) set(outfile ${CMAKE_CURRENT_BINARY_DIR}/qrc_${outfilename}.o) _qt6_parse_qrc_file(${infile} _out_depends _rc_depends) - set_source_files_properties(${infile} PROPERTIES SKIP_AUTORCC ON) + set_source_files_properties(${infile} PROPERTIES SKIP_AUTOGEN ON) + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.27") + set_source_files_properties(${tmpoutfile} PROPERTIES SKIP_LINTING ON) + endif() add_custom_command(OUTPUT ${tmpoutfile} COMMAND ${QT_CMAKE_EXPORT_NAMESPACE}::rcc ${rcc_options} --name ${outfilename} --pass 1 --output ${tmpoutfile} ${infile} DEPENDS ${infile} ${_rc_depends} "${out_depends}" ${QT_CMAKE_EXPORT_NAMESPACE}::rcc + COMMENT "Running rcc pass 1 for resource ${outfilename}" VERBATIM) add_custom_target(big_resources_${outfilename} ALL DEPENDS ${tmpoutfile}) - add_library(rcc_object_${outfilename} OBJECT ${tmpoutfile}) - target_compile_definitions(rcc_object_${outfilename} PUBLIC "$<TARGET_PROPERTY:Qt6::Core,INTERFACE_COMPILE_DEFINITIONS>") - set_target_properties(rcc_object_${outfilename} PROPERTIES AUTOMOC OFF) - set_target_properties(rcc_object_${outfilename} PROPERTIES AUTOUIC OFF) + _qt_internal_add_rcc_pass2( + RESOURCE_NAME ${outfilename} + RCC_OPTIONS ${rcc_options} + OBJECT_LIB rcc_object_${outfilename} + QRC_FILE ${infile} + PASS1_OUTPUT_FILE ${tmpoutfile} + OUT_OBJECT_FILE ${outfile} + ) add_dependencies(rcc_object_${outfilename} big_resources_${outfilename}) - # The modification of TARGET_OBJECTS needs the following change in cmake - # https://gitlab.kitware.com/cmake/cmake/commit/93c89bc75ceee599ba7c08b8fe1ac5104942054f - add_custom_command(OUTPUT ${outfile} - COMMAND ${QT_CMAKE_EXPORT_NAMESPACE}::rcc - ARGS ${rcc_options} --name ${outfilename} --pass 2 --temp $<TARGET_OBJECTS:rcc_object_${outfilename}> --output ${outfile} ${infile} - DEPENDS rcc_object_${outfilename} $<TARGET_OBJECTS:rcc_object_${outfilename}> ${QT_CMAKE_EXPORT_NAMESPACE}::rcc - VERBATIM) - list(APPEND ${outfiles} ${outfile}) + list(APPEND ${outfiles} ${outfile}) endforeach() set(${outfiles} ${${outfiles}} PARENT_SCOPE) endfunction() +function(_qt_internal_add_rcc_pass2) + set(options) + set(oneValueArgs + RESOURCE_NAME + OBJECT_LIB + QRC_FILE + PASS1_OUTPUT_FILE + OUT_OBJECT_FILE + ) + set(multiValueArgs + RCC_OPTIONS + ) + cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + add_library(${arg_OBJECT_LIB} OBJECT ${arg_PASS1_OUTPUT_FILE}) + _qt_internal_set_up_static_runtime_library(${arg_OBJECT_LIB}) + target_compile_definitions(${arg_OBJECT_LIB} PUBLIC + "$<TARGET_PROPERTY:Qt6::Core,INTERFACE_COMPILE_DEFINITIONS>") + set_target_properties(${arg_OBJECT_LIB} PROPERTIES + AUTOMOC OFF + AUTOUIC OFF) + # The modification of TARGET_OBJECTS needs the following change in cmake + # https://gitlab.kitware.com/cmake/cmake/commit/93c89bc75ceee599ba7c08b8fe1ac5104942054f + add_custom_command( + OUTPUT ${arg_OUT_OBJECT_FILE} + COMMAND ${QT_CMAKE_EXPORT_NAMESPACE}::rcc + ${arg_RCC_OPTIONS} --name ${arg_RESOURCE_NAME} --pass 2 + --temp $<TARGET_OBJECTS:${arg_OBJECT_LIB}> + --output ${arg_OUT_OBJECT_FILE} ${arg_QRC_FILE} + DEPENDS ${arg_OBJECT_LIB} $<TARGET_OBJECTS:${arg_OBJECT_LIB}> + ${QT_CMAKE_EXPORT_NAMESPACE}::rcc + COMMENT "Running rcc pass 2 for resource ${arg_RESOURCE_NAME}" + VERBATIM) +endfunction() + if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) function(qt_add_big_resources outfiles) if(QT_DEFAULT_MAJOR_VERSION EQUAL 5) @@ -423,11 +571,45 @@ if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) endfunction() endif() +function(_qt_internal_apply_win_prefix_and_suffix target) + if(WIN32) + # Table of prefix / suffixes for MSVC libraries as qmake expects them to be created. + # static - Qt6EdidSupport.lib (platform support libraries / or static QtCore, etc) + # shared - Qt6Core.dll + # shared import library - Qt6Core.lib + # module aka Qt plugin - qwindows.dll + # module import library - qwindows.lib + # + # The CMake defaults are fine for us. + + # Table of prefix / suffixes for MinGW libraries as qmake expects them to be created. + # static - libQt6EdidSupport.a (platform support libraries / or static QtCore, etc) + # shared - Qt6Core.dll + # shared import library - libQt6Core.a + # module aka Qt plugin - qwindows.dll + # module import library - libqwindows.a + # + # CMake for Windows-GNU platforms defaults the prefix to "lib". + # CMake for Windows-GNU platforms defaults the import suffix to ".dll.a". + # These CMake defaults are not ok for us. + + # This should cover both MINGW with GCC and CLANG. + if(NOT MSVC) + set_property(TARGET "${target}" PROPERTY IMPORT_SUFFIX ".a") + + get_target_property(target_type ${target} TYPE) + if(target_type STREQUAL "STATIC_LIBRARY") + set_property(TARGET "${target}" PROPERTY PREFIX "lib") + else() + set_property(TARGET "${target}" PROPERTY PREFIX "") + set_property(TARGET "${target}" PROPERTY IMPORT_PREFIX "lib") + endif() + endif() + endif() +endfunction() + set(_Qt6_COMPONENT_PATH "${CMAKE_CURRENT_LIST_DIR}/..") -# This function is currently in Technical Preview. -# It's signature and behavior might change. -# # Wrapper function that adds an executable with some Qt specific behavior. # Some scenarios require steps to be deferred to the end of the current # directory scope so that the caller has an opportunity to modify certain @@ -435,10 +617,14 @@ set(_Qt6_COMPONENT_PATH "${CMAKE_CURRENT_LIST_DIR}/..") function(qt6_add_executable target) cmake_parse_arguments(PARSE_ARGV 1 arg "MANUAL_FINALIZATION" "" "") + _qt_internal_warn_about_example_add_subdirectory() + _qt_internal_create_executable("${target}" ${arg_UNPARSED_ARGUMENTS}) + target_link_libraries("${target}" PRIVATE Qt6::Core) + set_property(TARGET ${target} PROPERTY _qt_expects_finalization TRUE) if(arg_MANUAL_FINALIZATION) - # Caller says they will call qt6_finalize_executable() themselves later + # Caller says they will call qt6_finalize_target() themselves later return() endif() @@ -448,9 +634,25 @@ function(qt6_add_executable target) if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.19) # Need to wrap in an EVAL CODE or else ${target} won't be evaluated # due to special behavior of cmake_language() argument handling - cmake_language(EVAL CODE "cmake_language(DEFER CALL qt6_finalize_executable ${target})") + cmake_language(EVAL CODE "cmake_language(DEFER CALL qt6_finalize_target ${target})") else() - qt6_finalize_executable("${target}") + set_target_properties("${target}" PROPERTIES _qt_is_immediately_finalized TRUE) + qt6_finalize_target("${target}") + endif() +endfunction() + +# Just like for qt_add_resources, we should disable zstd compression when cross-compiling to a +# target that doesn't support zstd decompression, even if the host tool supports it. +# Allow an opt out via a QT_NO_AUTORCC_ZSTD variable. +function(_qt_internal_disable_autorcc_zstd_when_not_supported target) + if(TARGET "${target}" + AND DEFINED QT_FEATURE_zstd + AND NOT QT_FEATURE_zstd + AND NOT QT_NO_AUTORCC_ZSTD) + get_target_property(target_type ${target} TYPE) + if(NOT target_type STREQUAL "INTERFACE_LIBRARY") + set_property(TARGET "${target}" APPEND PROPERTY AUTORCC_OPTIONS "--no-zstd") + endif() endif() endfunction() @@ -466,21 +668,24 @@ function(_qt_internal_create_executable target) set_property(TARGET "${target}" PROPERTY CXX_VISIBILITY_PRESET default) set_property(TARGET "${target}" PROPERTY OBJC_VISIBILITY_PRESET default) set_property(TARGET "${target}" PROPERTY OBJCXX_VISIBILITY_PRESET default) + set_property(TARGET "${target}" + PROPERTY _qt_android_apply_arch_suffix_called_from_qt_impl TRUE) qt6_android_apply_arch_suffix("${target}") + set_property(TARGET "${target}" PROPERTY _qt_is_android_executable TRUE) else() add_executable("${target}" ${ARGN}) endif() - target_link_libraries("${target}" PRIVATE Qt6::Core) + _qt_internal_disable_autorcc_zstd_when_not_supported("${target}") + _qt_internal_set_up_static_runtime_library("${target}") endfunction() -# This function is currently in Technical Preview. -# It's signature and behavior might change. -function(qt6_finalize_executable target) - +function(_qt_internal_finalize_executable target) # We can't evaluate generator expressions at configure time, so we can't # ask for any transitive properties or even the full library dependency - # chain. We can still look at the immediate dependencies though and query + # chain. + # We can still look at the immediate dependencies + # (and recursively their dependencies) and query # any that are not expressed as generator expressions. For any we can # identify as a CMake target known to the current scope, we can check if # that target has a finalizer to be called. This is expected to cover the @@ -489,12 +694,11 @@ function(qt6_finalize_executable target) # responsible for calling any relevant functions themselves instead of # relying on these automatic finalization calls. set(finalizers) - get_target_property(immediate_deps ${target} LINK_LIBRARIES) - if(immediate_deps) - foreach(dep IN LISTS immediate_deps) - if(NOT TARGET ${dep}) - continue() - endif() + + __qt_internal_collect_all_target_dependencies("${target}" dep_targets) + + if(dep_targets) + foreach(dep IN LISTS dep_targets) get_target_property(dep_finalizers ${dep} INTERFACE_QT_EXECUTABLE_FINALIZERS ) @@ -504,13 +708,18 @@ function(qt6_finalize_executable target) endforeach() list(REMOVE_DUPLICATES finalizers) endif() + if(finalizers) if(CMAKE_VERSION VERSION_LESS 3.18) - # cmake_language() not available - message(WARNING - "Skipping module-specific finalizers for target ${target} " - "(requires CMake 3.18 or later)" - ) + # cmake_language() not available, fall back to the slower method of + # writing a file and including it + set(contents "") + foreach(finalizer_func IN LISTS finalizers) + string(APPEND contents "${finalizer_func}(${target})\n") + endforeach() + set(finalizer_file "${CMAKE_CURRENT_BINARY_DIR}/.qt/finalize_${target}.cmake") + file(WRITE ${finalizer_file} "${contents}") + include(${finalizer_file}) else() foreach(finalizer_func IN LISTS finalizers) cmake_language(CALL ${finalizer_func} ${target}) @@ -518,27 +727,204 @@ function(qt6_finalize_executable target) endif() endif() - if(ANDROID) - qt6_android_generate_deployment_settings("${target}") - qt6_android_add_apk_target("${target}") + if(EMSCRIPTEN) + _qt_internal_wasm_add_target_helpers("${target}") + _qt_internal_add_wasm_extra_exported_methods("${target}") + _qt_internal_set_wasm_export_name("${target}") + endif() + if(IOS) + _qt_internal_finalize_ios_app("${target}") + elseif(APPLE) + _qt_internal_finalize_macos_app("${target}") + endif() + + # For finalizer mode of plugin importing to work safely, we need to know the list of Qt + # dependencies the target has, but those might be added later than the qt_add_executable call. + # Most of our examples are like that. Only enable finalizer mode when we are sure that the user + # manually called qt_finalize_target at the end of their CMake project, or it was automatically + # done via a deferred call. This is also applicable to the resource object finalizer. + get_target_property(is_immediately_finalized "${target}" _qt_is_immediately_finalized) + if(NOT is_immediately_finalized) + __qt_internal_apply_plugin_imports_finalizer_mode("${target}") + __qt_internal_process_dependency_object_libraries("${target}") endif() endfunction() +function(_cat IN_FILE OUT_FILE) + file(READ ${IN_FILE} CONTENTS) + file(APPEND ${OUT_FILE} "${CONTENTS}\n") +endfunction() + +function(_qt_internal_finalize_batch name) + find_package(Qt6 ${PROJECT_VERSION} CONFIG REQUIRED COMPONENTS Core) + + set(generated_blacklist_file "${CMAKE_CURRENT_BINARY_DIR}/BLACKLIST") + get_target_property(blacklist_files "${name}" _qt_blacklist_files) + file(WRITE "${generated_blacklist_file}" "") + foreach(blacklist_file ${blacklist_files}) + _cat("${blacklist_file}" "${generated_blacklist_file}") + endforeach() + qt_internal_add_resource(${name} "batch_blacklist" + PREFIX "/" + FILES "${CMAKE_CURRENT_BINARY_DIR}/BLACKLIST" + BASE ${CMAKE_CURRENT_BINARY_DIR}) +endfunction() + +# If a task needs to run before any targets are finalized in the current directory +# scope, call this function and pass the ID of that task as the argument. +function(_qt_internal_delay_finalization_until_after defer_id) + set_property(DIRECTORY APPEND PROPERTY qt_internal_finalizers_wait_for_ids "${defer_id}") +endfunction() + +function(qt6_finalize_target target) + set_property(TARGET ${target} PROPERTY _qt_expects_finalization FALSE) + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.19") + cmake_language(DEFER GET_CALL_IDS ids_queued) + get_directory_property(wait_for_ids qt_internal_finalizers_wait_for_ids) + while(wait_for_ids) + list(GET wait_for_ids 0 id_to_wait_for) + if(id_to_wait_for IN_LIST ids_queued) + # Something else needs to run before we finalize targets. + # Try again later by re-deferring ourselves, which effectively + # puts us at the end of the current list of deferred actions. + cmake_language(EVAL CODE "cmake_language(DEFER CALL ${CMAKE_CURRENT_FUNCTION} ${ARGV})") + set_directory_properties(PROPERTIES + qt_internal_finalizers_wait_for_ids "${wait_for_ids}" + ) + return() + endif() + list(POP_FRONT wait_for_ids) + endwhile() + # No other deferred tasks to wait for + set_directory_properties(PROPERTIES qt_internal_finalizers_wait_for_ids "") + endif() + + if(NOT TARGET "${target}") + message(FATAL_ERROR "No target '${target}' found in current scope.") + endif() + + get_target_property(is_finalized "${target}" _qt_is_finalized) + if(is_finalized) + message(AUTHOR_WARNING + "Tried to call qt6_finalize_target twice on target '${target}'. " + "Did you forget to specify MANUAL_FINALIZATION to qt6_add_executable, " + "qt6_add_library or qt6_add_plugin?") + return() + endif() + + _qt_internal_expose_deferred_files_to_ide(${target}) + _qt_internal_finalize_source_groups(${target}) + get_target_property(target_type ${target} TYPE) + get_target_property(is_android_executable "${target}" _qt_is_android_executable) + + if(target_type STREQUAL "EXECUTABLE" OR is_android_executable) + _qt_internal_finalize_executable(${ARGV}) + endif() + + if(APPLE) + # Tell CMake to generate run-scheme for the executable when generating + # Xcode projects. This avoids Xcode auto-generating default schemes for + # all targets, which includes internal and build-only targets. + get_target_property(generate_scheme "${target}" XCODE_GENERATE_SCHEME) + if(generate_scheme MATCHES "-NOTFOUND" AND ( + target_type STREQUAL "EXECUTABLE" OR + target_type STREQUAL "SHARED_LIBRARY" OR + target_type STREQUAL "STATIC_LIBRARY" OR + target_type STREQUAL "MODULE_LIBRARY")) + set_property(TARGET "${target}" PROPERTY XCODE_GENERATE_SCHEME TRUE) + endif() + endif() + + set_target_properties(${target} PROPERTIES _qt_is_finalized TRUE) +endfunction() + +function(_qt_internal_finalize_source_groups target) + if(NOT ("${CMAKE_GENERATOR}" STREQUAL "Xcode" + OR "${CMAKE_GENERATOR}" MATCHES "^Visual Studio")) + return() + endif() + + get_target_property(sources ${target} SOURCES) + if(NOT sources) + return() + endif() + + get_target_property(source_dir ${target} SOURCE_DIR) + get_target_property(binary_dir ${target} BINARY_DIR) + + get_property(generated_source_group GLOBAL PROPERTY AUTOGEN_SOURCE_GROUP) + if(NOT generated_source_group) + set(generated_source_group "Source Files/Generated") + endif() + + foreach(source IN LISTS sources) + string(GENEX_STRIP "${source}" source) + + if(IS_ABSOLUTE ${source}) + set(source_file_path "${source}") + else() + # Resolve absolute path. Can't use LOCATION, as that + # will error out if the file doesn't exist :( + get_filename_component(source_file_path "${source}" + ABSOLUTE BASE_DIR "${source_dir}") + if(NOT EXISTS ${source_file_path}) + # Likely generated file, will end up in build dir + get_filename_component(source_file_path "${source}" + ABSOLUTE BASE_DIR "${binary_dir}") + endif() + endif() + + # Include qml files in "Source Files". Can not be done via regex, + # due to https://gitlab.kitware.com/cmake/cmake/-/issues/25597 + if(${source_file_path} MATCHES "\\.qml$") + source_group("Source Files" FILES ${source_file_path}) + endif() + + get_source_file_property(is_generated "${source_file_path}" GENERATED) + if(${is_generated}) + source_group(${generated_source_group} FILES ${source_file_path}) + endif() + endforeach() +endfunction() + +function(_qt_internal_darwin_permission_finalizer target) + get_target_property(plist_file "${target}" MACOSX_BUNDLE_INFO_PLIST) + if(NOT plist_file) + return() + endif() + foreach(plugin_target IN LISTS QT_ALL_PLUGINS_FOUND_BY_FIND_PACKAGE_permissions) + set(versioned_plugin_target "${QT_CMAKE_EXPORT_NAMESPACE}::${plugin_target}") + get_target_property(usage_descriptions + ${versioned_plugin_target} + _qt_info_plist_usage_descriptions) + foreach(usage_description_key IN LISTS usage_descriptions) + execute_process(COMMAND "/usr/libexec/PlistBuddy" + -c "print ${usage_description_key}" "${plist_file}" + OUTPUT_VARIABLE usage_description + ERROR_VARIABLE plist_error) + if(usage_description AND NOT plist_error) + set_target_properties("${target}" + PROPERTIES "_qt_has_${plugin_target}_usage_description" TRUE) + qt6_import_plugins(${target} INCLUDE ${versioned_plugin_target}) + endif() + endforeach() + endforeach() +endfunction() + if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) function(qt_add_executable) qt6_add_executable(${ARGV}) endfunction() + function(qt_finalize_target) + qt6_finalize_target(${ARGV}) + endfunction() + + # Kept for compatibility with Qt Creator 4.15 wizards function(qt_finalize_executable) - qt6_finalize_executable(${ARGV}) + qt6_finalize_target(${ARGV}) endfunction() endif() -# Temporarily keep compatibility, until all repositories are migrated. -function(add_qt_gui_executable) - message(AUTHOR_WARNING "Please replace add_qt_gui_executable with qt_add_executable instead. The former will be removed shortly.") - qt6_add_executable(${ARGV}) -endfunction() - function(_qt_get_plugin_name_with_version target out_var) string(REGEX REPLACE "^Qt::(.+)" "Qt${QT_DEFAULT_MAJOR_VERSION}::\\1" qt_plugin_with_version "${target}") @@ -573,25 +959,6 @@ function(_qt_internal_disable_static_default_plugins target) set_target_properties(${target} PROPERTIES QT_DEFAULT_PLUGINS 0) endfunction() -# This function is used to indicate which plug-ins are going to be -# used by a given target. -# This allows static linking to a correct set of plugins. -# Options : -# NO_DEFAULT: disable linking against any plug-in by default for that target, e.g. no platform plug-in. -# INCLUDE <list of additional plug-ins to be linked against> -# EXCLUDE <list of plug-ins to be removed from the default set> -# INCLUDE_BY_TYPE <type> <included plugins> -# EXCLUDE_BY_TYPE <type to be excluded> -# -# Example : -# qt_import_plugins(myapp -# INCLUDE Qt::QCocoaIntegrationPlugin -# EXCLUDE Qt::QMinimalIntegrationPlugin -# INCLUDE_BY_TYPE imageformats Qt::QGifPlugin Qt::QJpegPlugin -# EXCLUDE_BY_TYPE sqldrivers -# ) - -# TODO : support qml plug-ins. function(qt6_import_plugins target) cmake_parse_arguments(arg "NO_DEFAULT" "" "INCLUDE;EXCLUDE;INCLUDE_BY_TYPE;EXCLUDE_BY_TYPE" ${ARGN}) @@ -625,10 +992,14 @@ function(qt6_import_plugins target) # Check if passed plugin target name is a version-less one, and make a version-full # one. + set_property(TARGET "${target}" APPEND PROPERTY "QT_PLUGINS_${_current_type}" "${_arg}") + set_property(TARGET "${target}" APPEND PROPERTY "_qt_plugins_by_type" "${_arg}") _qt_get_plugin_name_with_version("${_arg}" qt_plugin_with_version) - if(TARGET "${_arg}" OR TARGET "${qt_plugin_with_version}") - set_property(TARGET "${target}" APPEND PROPERTY "QT_PLUGINS_${_current_type}" "${_arg}") - else() + + # TODO: Do we really need this check? We didn't have it in Qt5, and plugin targets + # wrapped in genexes end up causing warnings, but we explicitly use GENEX_EVAL to + # support them. + if(NOT TARGET "${_arg}" AND NOT TARGET "${qt_plugin_with_version}") message("Warning: plug-in ${_arg} is not known to the current Qt installation.") endif() endif() @@ -651,19 +1022,196 @@ if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) endfunction() endif() +# This function is currently in Technical Preview. It's signature may change or be removed entirely. +function(qt6_set_finalizer_mode target) + cmake_parse_arguments(arg "ENABLE;DISABLE" "" "MODES" ${ARGN}) + if(NOT arg_ENABLE AND NOT arg_DISABLE) + message(FATAL_ERROR "No option was specified whether to enable or disable the modes.") + elseif(arg_ENABLE AND arg_DISABLE) + message(FATAL_ERROR "Both ENABLE and DISABLE options were specified.") + endif() + if(NOT arg_MODES) + message(FATAL_ERROR "No modes were specified in qt6_set_finalizer_mode() call.") + endif() + + if(arg_ENABLE) + set(value "TRUE") + elseif(arg_DISABLE) + set(value "FALSE") + endif() + + foreach(mode ${arg_MODES}) + __qt_internal_enable_finalizer_mode("${target}" "${mode}" "${value}") + endforeach() +endfunction() + +if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) + function(qt_set_finalizer_mode) + qt6_set_finalizer_mode(${ARGV}) + endfunction() +endif() + +function(_qt_internal_assign_to_internal_targets_folder target) + get_property(folder_name GLOBAL PROPERTY QT_TARGETS_FOLDER) + if(NOT "${folder_name}" STREQUAL "") + set_property(TARGET ${target} PROPERTY FOLDER "${folder_name}") + endif() +endfunction() + +function(_qt_internal_get_target_autogen_build_dir target out_var) + get_property(target_autogen_build_dir TARGET ${target} PROPERTY AUTOGEN_BUILD_DIR) + if(target_autogen_build_dir) + set(${out_var} "${target_autogen_build_dir}" PARENT_SCOPE) + else() + get_property(target_binary_dir TARGET ${target} PROPERTY BINARY_DIR) + set(${out_var} "${target_binary_dir}/${target}_autogen" PARENT_SCOPE) + endif() +endfunction() + +function(_qt_internal_should_install_metatypes target) + set(args_option + INTERNAL_INSTALL + ) + set(args_single + OUT_VAR + ) + set(args_multi + ) + + cmake_parse_arguments(arg + "${args_option}" + "${args_single}" + "${args_multi}" ${ARGN}) + + # Check whether the generated json file needs to be installed. + # Executable metatypes.json files should not be installed. Qt non-prefix builds should also + # not install the files. + set(should_install FALSE) + + get_target_property(target_type ${target} TYPE) + if(NOT target_type STREQUAL "EXECUTABLE" AND arg_INTERNAL_INSTALL) + set(should_install TRUE) + endif() + set(${arg_OUT_VAR} "${should_install}" PARENT_SCOPE) +endfunction() + +function(_qt_internal_get_metatypes_install_dir internal_install_dir arch_data_dir out_var) + # Automatically fill default install args when not specified. + if(NOT internal_install_dir) + # INSTALL_ARCHDATADIR is not set when QtBuildInternals is not loaded + # (when not doing a Qt build). Default to a hardcoded location for user + # projects (will likely be wrong). + if(arch_data_dir) + set(install_dir "${arch_data_dir}/metatypes") + else() + set(install_dir "lib/metatypes") + endif() + else() + set(install_dir "${internal_install_dir}") + endif() + set(${out_var} "${install_dir}" PARENT_SCOPE) +endfunction() + +# Propagates the build time metatypes file via INTERFACE_SOURCES (using $<BUILD_INTERFACE>) +# and saves the path and file name in properties, so that they can be queryied in the qml api +# implementation for the purpose of duplicating a qml module backing library's metatypes in its +# associated plugin. This is required for qmltyperegistrar to get the full set of foreign types +# when projects link to the plugin and not the backing library. +function(_qt_internal_assign_build_metatypes_files_and_properties target) + get_target_property(existing_meta_types_file ${target} INTERFACE_QT_META_TYPES_BUILD_FILE) + if (existing_meta_types_file) + return() + endif() + + set(args_option + ) + set(args_single + METATYPES_FILE_NAME + METATYPES_FILE_PATH + ) + set(args_multi + ) + + cmake_parse_arguments(arg + "${args_option}" + "${args_single}" + "${args_multi}" ${ARGN}) + + if(NOT arg_METATYPES_FILE_NAME) + message(FATAL_ERROR "METATYPES_FILE_NAME must be specified") + endif() + + if(NOT arg_METATYPES_FILE_PATH) + message(FATAL_ERROR "METATYPES_FILE_PATH must be specified") + endif() + + set(metatypes_file_name "${arg_METATYPES_FILE_NAME}") + set(metatypes_file_path "${arg_METATYPES_FILE_PATH}") + + # Set up consumption of files via INTERFACE_SOURCES. + set(consumes_metatypes "$<BOOL:$<TARGET_PROPERTY:QT_CONSUMES_METATYPES>>") + set(metatypes_file_genex_build + "$<BUILD_INTERFACE:$<${consumes_metatypes}:${metatypes_file_path}>>" + ) + target_sources(${target} INTERFACE ${metatypes_file_genex_build}) + + set_target_properties(${target} PROPERTIES + INTERFACE_QT_MODULE_HAS_META_TYPES YES + # The property name is a bit misleading, it's not wrapped in a genex. + INTERFACE_QT_META_TYPES_BUILD_FILE "${metatypes_file_path}" + INTERFACE_QT_META_TYPES_FILE_NAME "${metatypes_file_name}" + ) +endfunction() + +# Same as above, but with $<INSTALL_INTERFACE>. +function(_qt_internal_assign_install_metatypes_files_and_properties target) + get_target_property(existing_meta_types_file ${target} INTERFACE_QT_META_TYPES_INSTALL_FILE) + if (existing_meta_types_file) + return() + endif() + + set(args_option + ) + set(args_single + INSTALL_DIR + ) + set(args_multi + ) + + cmake_parse_arguments(arg + "${args_option}" + "${args_single}" + "${args_multi}" ${ARGN}) + + + get_target_property(metatypes_file_name "${target}" INTERFACE_QT_META_TYPES_FILE_NAME) + + if(NOT metatypes_file_name) + message(FATAL_ERROR "INTERFACE_QT_META_TYPES_FILE_NAME of target ${target} is empty") + endif() + + if(NOT arg_INSTALL_DIR) + message(FATAL_ERROR "INSTALL_DIR must be specified") + endif() + + # Set up consumption of files via INTERFACE_SOURCES. + set(consumes_metatypes "$<BOOL:$<TARGET_PROPERTY:QT_CONSUMES_METATYPES>>") + + set(install_dir "${arg_INSTALL_DIR}") + + set(metatypes_file_install_path "${install_dir}/${metatypes_file_name}") + set(metatypes_file_install_path_genex "$<INSTALL_PREFIX>/${metatypes_file_install_path}") + set(metatypes_file_genex_install + "$<INSTALL_INTERFACE:$<${consumes_metatypes}:${metatypes_file_install_path_genex}>>" + ) + target_sources(${target} INTERFACE ${metatypes_file_genex_install}) + + set_target_properties(${target} PROPERTIES + INTERFACE_QT_META_TYPES_INSTALL_FILE "${metatypes_file_install_path}" + ) +endfunction() + -# Extracts metatypes from a Qt target and generates a metatypes.json for it. -# By default we check whether AUTOMOC has been enabled and we extract the information from the -# target's AUTOMOC supporting files. -# Should you not wish to use automoc you need to pass in all the generated json files via the -# MANUAL_MOC_JSON_FILES parameter. The latter can be obtained by running moc with -# the --output-json parameter. -# Params: -# INSTALL_DIR: Location where to install the metatypes file. For public consumption, -# defaults to a ${CMAKE_INSTALL_PREFIX}/${INSTALL_LIBDIR}/metatypes directory. -# Executable metatypes files are never installed. -# COPY_OVER_INSTALL: (Qt Internal) When present will install the file via a post build step -# copy rather than using install. function(qt6_extract_metatypes target) get_target_property(existing_meta_types_file ${target} INTERFACE_QT_META_TYPES_BUILD_FILE) @@ -671,7 +1219,30 @@ function(qt6_extract_metatypes target) return() endif() - cmake_parse_arguments(arg "COPY_OVER_INSTALL" "INSTALL_DIR" "MANUAL_MOC_JSON_FILES" ${ARGN}) + set(args_option + # TODO: Move this into a separate internal function, so it doesn't pollute the public one. + # When given, metatypes files will be installed into the default Qt + # metatypes folder. Only to be used by the Qt build. + __QT_INTERNAL_INSTALL + ) + set(args_single + # TODO: Move this into a separate internal function, so it doesn't pollute the public one. + # Location where to install the metatypes file. Only used if + # __QT_INTERNAL_INSTALL is given. It defaults to the + # ${CMAKE_INSTALL_PREFIX}/${INSTALL_ARCHDATADIR}/metatypes directory. + # Executable metatypes files are never installed. + __QT_INTERNAL_INSTALL_DIR + + OUTPUT_FILES + ) + set(args_multi + MANUAL_MOC_JSON_FILES + ) + + cmake_parse_arguments(arg + "${args_option}" + "${args_single}" + "${args_multi}" ${ARGN}) get_target_property(target_type ${target} TYPE) if (target_type STREQUAL "INTERFACE_LIBRARY") @@ -684,28 +1255,13 @@ function(qt6_extract_metatypes target) return() endif() - # Whether the generated json file needs to be installed for prefix-builds, or copied for - # non-prefix builds. Regardless of the type of build, executable metatypes.json files should - # not be installed. Only library .json files should be installed. - set(should_install "TRUE") - if (target_type STREQUAL "EXECUTABLE") - set(should_install "FALSE") - endif() - - # Automatically fill default install args when not specified. - if (NOT arg_INSTALL_DIR) - # INSTALL_LIBDIR is not set when QtBuildInternals is not loaded (when not doing a Qt build). - if(INSTALL_LIBDIR) - set(arg_INSTALL_DIR "${INSTALL_LIBDIR}/metatypes") - else() - set(arg_INSTALL_DIR "lib/metatypes") - endif() - endif() - get_target_property(target_binary_dir ${target} BINARY_DIR) set(type_list_file "${target_binary_dir}/meta_types/${target}_json_file_list.txt") set(type_list_file_manual "${target_binary_dir}/meta_types/${target}_json_file_list_manual.txt") + set(target_autogen_build_dir "") + _qt_internal_get_target_autogen_build_dir(${target} target_autogen_build_dir) + get_target_property(uses_automoc ${target} AUTOMOC) set(automoc_args) set(automoc_dependencies) @@ -716,17 +1272,18 @@ function(qt6_extract_metatypes target) AUTOMOC_MOC_OPTIONS "--output-json" ) - if(NOT CMAKE_CONFIGURATION_TYPES) + get_property(is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + if(NOT is_multi_config) set(cmake_autogen_cache_file "${target_binary_dir}/CMakeFiles/${target}_autogen.dir/ParseCache.txt") - set(mutli_config_args - --cmake-autogen-include-dir-path "${target_binary_dir}/${target}_autogen/include" + set(multi_config_args + --cmake-autogen-include-dir-path "${target_autogen_build_dir}/include" ) else() set(cmake_autogen_cache_file "${target_binary_dir}/CMakeFiles/${target}_autogen.dir/ParseCache_$<CONFIG>.txt") - set(mutli_config_args - --cmake-autogen-include-dir-path "${target_binary_dir}/${target}_autogen/include_$<CONFIG>" + set(multi_config_args + --cmake-autogen-include-dir-path "${target_autogen_build_dir}/include_$<CONFIG>" "--cmake-multi-config") endif() @@ -735,41 +1292,92 @@ function(qt6_extract_metatypes target) set (use_dep_files FALSE) if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.17") # Requires automoc changes present only in 3.17 - if(CMAKE_GENERATOR STREQUAL "Ninja" OR CMAKE_GENERATOR STREQUAL "Ninja Multi-Config") - set(use_dep_files TRUE) + if(CMAKE_GENERATOR STREQUAL "Ninja" OR + CMAKE_GENERATOR STREQUAL "Ninja Multi-Config" OR + (CMAKE_GENERATOR MATCHES "Makefiles" AND + CMAKE_VERSION VERSION_GREATER_EQUAL "3.28")) + if(DEFINED QT_USE_CMAKE_DEPFILES) + set(use_dep_files ${QT_USE_CMAKE_DEPFILES}) + else() + set(use_dep_files TRUE) + endif() endif() endif() + set(cmake_automoc_parser_timestamp "${type_list_file}.timestamp") + if (NOT use_dep_files) + # When a project is configured with a Visual Studio generator, CMake's + # cmQtAutoGenInitializer::InitAutogenTarget() can take one of two code paths on how to + # handle AUTOMOC rules. + # It either creates a ${target}_autogen custom target or uses PRE_BUILD build events. + # + # The latter in considered an optimization and is used by CMake when possible. + # Unfortunately that breaks our add_dependency call because we expect on _autogen target + # to always exist. + # + # Ensure the PRE_BUILD path is not taken by generating a dummy header file and adding it + # as a source file to the target. This causes the file to be added to + # cmQtAutoGenInitializer::AutogenTarget.DependFiles, which disables the PRE_BUILD path. + if(CMAKE_GENERATOR MATCHES "Visual Studio") + # The file name should be target specific, but still short, so we don't hit path + # length issues. + string(MAKE_C_IDENTIFIER "ddf_${target}" dummy_dependency_file) + set(dummy_out_file "${CMAKE_CURRENT_BINARY_DIR}/${dummy_dependency_file}.h") + + # The content shouldn't be empty so we don't trigger AUTOMOC warnings about it. + file(GENERATE OUTPUT "${dummy_out_file}" CONTENT "//") + set_source_files_properties("${dummy_out_file}" PROPERTIES + GENERATED TRUE + SKIP_AUTOGEN OFF) + target_sources("${target}" PRIVATE "${dummy_out_file}") + endif() + add_custom_target(${target}_automoc_json_extraction DEPENDS ${QT_CMAKE_EXPORT_NAMESPACE}::cmake_automoc_parser - BYPRODUCTS ${type_list_file} + BYPRODUCTS + ${type_list_file} + "${cmake_automoc_parser_timestamp}" COMMAND ${QT_CMAKE_EXPORT_NAMESPACE}::cmake_automoc_parser --cmake-autogen-cache-file "${cmake_autogen_cache_file}" --cmake-autogen-info-file "${cmake_autogen_info_file}" --output-file-path "${type_list_file}" - ${mutli_config_args} - COMMENT "Running Automoc file extraction" + --timestamp-file-path "${cmake_automoc_parser_timestamp}" + ${multi_config_args} + COMMENT "Running AUTOMOC file extraction for target ${target}" COMMAND_EXPAND_LISTS ) add_dependencies(${target}_automoc_json_extraction ${target}_autogen) + _qt_internal_assign_to_internal_targets_folder(${target}_automoc_json_extraction) else() - set(cmake_autogen_timestamp_file - "${target_binary_dir}/${target}_autogen/timestamp" - ) + set(timestamp_file "${target_autogen_build_dir}/timestamp") + set(timestamp_file_with_config "${timestamp_file}_$<CONFIG>") + if (is_multi_config AND CMAKE_VERSION VERSION_GREATER_EQUAL "3.29" + AND NOT QT_INTERNAL_USE_OLD_AUTOGEN_GRAPH_MULTI_CONFIG_METATYPES) + string(JOIN "" timestamp_genex + "$<IF:$<BOOL:$<TARGET_PROPERTY:${target}," + "AUTOGEN_BETTER_GRAPH_MULTI_CONFIG>>," + "${timestamp_file_with_config},${timestamp_file}>") + set(cmake_autogen_timestamp_file "${timestamp_genex}") + else() + set(cmake_autogen_timestamp_file ${timestamp_file}) + endif() add_custom_command(OUTPUT ${type_list_file} DEPENDS ${QT_CMAKE_EXPORT_NAMESPACE}::cmake_automoc_parser ${cmake_autogen_timestamp_file} + BYPRODUCTS "${cmake_automoc_parser_timestamp}" COMMAND ${QT_CMAKE_EXPORT_NAMESPACE}::cmake_automoc_parser --cmake-autogen-cache-file "${cmake_autogen_cache_file}" --cmake-autogen-info-file "${cmake_autogen_info_file}" --output-file-path "${type_list_file}" - ${mutli_config_args} - COMMENT "Running Automoc file extraction" + --timestamp-file-path "${cmake_automoc_parser_timestamp}" + ${multi_config_args} + COMMENT "Running AUTOMOC file extraction for target ${target}" COMMAND_EXPAND_LISTS + VERBATIM ) endif() @@ -793,7 +1401,7 @@ function(qt6_extract_metatypes target) message(FATAL_ERROR "Metatype generation requires either the use of AUTOMOC or a manual list of generated json files") endif() - if (CMAKE_BUILD_TYPE) + if (CMAKE_BUILD_TYPE AND NOT is_multi_config) string(TOLOWER ${target}_${CMAKE_BUILD_TYPE} target_lowercase) else() string(TOLOWER ${target} target_lowercase) @@ -819,18 +1427,11 @@ function(qt6_extract_metatypes target) file(TOUCH ${metatypes_file}) endif() - # Need to make the path absolute during a Qt non-prefix build, otherwise files are written - # to the source dir because the paths are relative to the source dir when using file(TOUCH). - if(arg_COPY_OVER_INSTALL AND NOT IS_ABSOLUTE "${arg_INSTALL_DIR}/${metatypes_file_name}") - set(arg_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${arg_INSTALL_DIR}") - endif() - - if (should_install AND arg_COPY_OVER_INSTALL - AND NOT EXISTS ${arg_INSTALL_DIR}/${metatypes_file_name}) - file(MAKE_DIRECTORY "${arg_INSTALL_DIR}") - file(TOUCH "${arg_INSTALL_DIR}/${metatypes_file_name}") - endif() - add_custom_command(OUTPUT ${metatypes_file_gen} ${metatypes_file} + add_custom_command( + OUTPUT + ${metatypes_file_gen} + BYPRODUCTS + ${metatypes_file} DEPENDS ${QT_CMAKE_EXPORT_NAMESPACE}::moc ${automoc_dependencies} ${manual_dependencies} COMMAND ${QT_CMAKE_EXPORT_NAMESPACE}::moc -o ${metatypes_file_gen} @@ -838,85 +1439,87 @@ function(qt6_extract_metatypes target) COMMAND ${CMAKE_COMMAND} -E copy_if_different ${metatypes_file_gen} ${metatypes_file} - COMMENT "Running automoc with --collect-json" + COMMENT "Running moc --collect-json for target ${target}" + VERBATIM ) - # We still need to add this file as a source of Core, otherwise the file + if(CMAKE_GENERATOR MATCHES " Makefiles" OR CMAKE_GENERATOR MATCHES "^Visual Studio") + # Work around https://gitlab.kitware.com/cmake/cmake/-/issues/19005 to trigger the command + # that generates ${metatypes_file}. + add_custom_command( + OUTPUT ${metatypes_file} + DEPENDS ${metatypes_file_gen} + COMMAND ${CMAKE_COMMAND} -E true + VERBATIM + ) + endif() + + # We can't rely on policy CMP0118 since user project controls it + set(scope_args) + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.18") + set(scope_args TARGET_DIRECTORY ${target}) + endif() + set_source_files_properties(${metatypes_file_gen} ${metatypes_file} ${scope_args} + PROPERTIES GENERATED TRUE + ) + + # We still need to add this file as a source of the target, otherwise the file # rule above is not triggered. INTERFACE_SOURCES do not properly register # as dependencies to build the current target. + # TODO: Can we pass ${metatypes_file} instead of ${metatypes_file_gen} as a source? + # TODO: Do we still need the _gen variant at all? target_sources(${target} PRIVATE ${metatypes_file_gen}) - set(metatypes_file_genex_build) - set(metatypes_file_genex_install) - if (arg_COPY_OVER_INSTALL) - if(should_install) - set(metatypes_file_genex_build - "$<BUILD_INTERFACE:$<$<BOOL:$<TARGET_PROPERTY:QT_CONSUMES_METATYPES>>:${arg_INSTALL_DIR}/${metatypes_file_name}>>" - ) - endif() + set_source_files_properties(${metatypes_file} ${scope_args} + PROPERTIES HEADER_FILE_ONLY TRUE + ) + + if(arg_OUTPUT_FILES) + set(${arg_OUTPUT_FILES} "${metatypes_file}" PARENT_SCOPE) + endif() + + # Propagate the build time metatypes file. + _qt_internal_assign_build_metatypes_files_and_properties( + "${target}" + METATYPES_FILE_NAME "${metatypes_file_name}" + METATYPES_FILE_PATH "${metatypes_file}" + ) + + if(arg___QT_INTERNAL_INSTALL) + set(internal_install_option "INTERNAL_INSTALL") else() - set(metatypes_file_genex_build - "$<BUILD_INTERFACE:$<$<BOOL:$<TARGET_PROPERTY:QT_CONSUMES_METATYPES>>:${metatypes_file}>>" - ) - if(should_install) - set(metatypes_file_genex_install - "$<INSTALL_INTERFACE:$<$<BOOL:$<TARGET_PROPERTY:QT_CONSUMES_METATYPES>>:$<INSTALL_PREFIX>/${arg_INSTALL_DIR}/${metatypes_file_name}>>" - ) - endif() + set(internal_install_option "") endif() - set_source_files_properties(${metatypes_file} PROPERTIES HEADER_FILE_ONLY TRUE) - set_target_properties(${target} PROPERTIES - INTERFACE_QT_MODULE_HAS_META_TYPES YES - INTERFACE_QT_MODULE_META_TYPES_FROM_BUILD YES - INTERFACE_QT_META_TYPES_BUILD_FILE "${metatypes_file}" - QT_MODULE_META_TYPES_FILE_GENEX_BUILD "${metatypes_file_genex_build}" - QT_MODULE_META_TYPES_FILE_GENEX_INSTALL "${metatypes_file_genex_install}" + # TODO: Clean up Qt-specific installation not to happen in the public api. + # Check whether the metatype files should be installed. + _qt_internal_should_install_metatypes("${target}" + ${internal_install_option} + OUT_VAR should_install ) - target_sources(${target} INTERFACE ${metatypes_file_genex_build} ${metatypes_file_genex_install}) - - # Installation is complicated, because there are multiple combinations. - # In non-prefix builds (signaled by arg_COPY_OVER_INSTALL == TRUE), Qt modules are /copied/ - # into the qt_prefix/lib/metatypes. - # In prefix builds (signaled by arg_COPY_OVER_INSTALL == FALSE), Qt modules are /installed/ - # into the qt_prefix/lib/metatypes. - # Currently only the internal qt_add_module sets arg_COPY_OVER_INSTALL. - # - # Tests and examples are executables, and thus will not have their meta types installed, but - # they will have them generated (if this function is called). - # - # Regular libraries and plugins (which are not part of the Qt build), will be /installed/ - # into a lib/metatypes directory relative to their prefix, rather than the Qt prefix (only - # outside of a Qt build). - # We don't support non-prefix builds for libraries or plugins which are not part of the official - # Qt build. Aka everything non-prefix / COPY_OVER_INSTALL related are implementation details - # that users shouldn't use. + if(should_install) - if (arg_COPY_OVER_INSTALL) - set(command_args - COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${metatypes_file}" - "${arg_INSTALL_DIR}/${metatypes_file_name}" - ) - if (target_type STREQUAL "OBJECT_LIBRARY") - add_custom_target(${target}_metatypes_copy - DEPENDS "${metatypes_file}" - ${command_args} - ) - add_dependencies(${target} ${target}_metatypes_copy) - else() - add_custom_command(TARGET ${target} POST_BUILD - ${command_args} - ) - endif() - else() - install(FILES "${metatypes_file}" DESTINATION "${arg_INSTALL_DIR}") - endif() + _qt_internal_get_metatypes_install_dir( + "${arg___QT_INTERNAL_INSTALL_DIR}" + "${INSTALL_ARCHDATADIR}" + install_dir + ) + + # Propagate the install time metatypes file. + _qt_internal_assign_install_metatypes_files_and_properties( + "${target}" + INSTALL_DIR "${install_dir}" + ) + install(FILES "${metatypes_file}" DESTINATION "${install_dir}") endif() endfunction() if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) function(qt_extract_metatypes) qt6_extract_metatypes(${ARGV}) + cmake_parse_arguments(PARSE_ARGV 0 arg "" "OUTPUT_FILES" "") + if(arg_OUTPUT_FILES) + set(${arg_OUTPUT_FILES} "${${arg_OUTPUT_FILES}}" PARENT_SCOPE) + endif() endfunction() endif() @@ -953,9 +1556,16 @@ function(_qt_internal_generate_win32_rc_file target) return() endif() + if(MSVC) + set(extra_rc_flags "-c65001 -DWIN32 -nologo") + else() + set(extra_rc_flags) + endif() + if (target_rc_file) # Use the provided RC file target_sources(${target} PRIVATE "${target_rc_file}") + set_property(SOURCE ${target_rc_file} PROPERTY COMPILE_FLAGS "${extra_rc_flags}") else() # Generate RC File set(rc_file_output "${target_binary_dir}/") @@ -1111,10 +1721,29 @@ END # We would like to do the following: # target_sources(${target} PRIVATE "$<$<CONFIG:${cfg}>:${output}>") - # However, https://gitlab.kitware.com/cmake/cmake/-/issues/20682 doesn't let us. - # Work-around by compiling the resources in an object lib and linking that. - add_library(${target}_rc OBJECT "${output}") - target_link_libraries(${target} PRIVATE $<TARGET_OBJECTS:${target}_rc>) + # + # However, https://gitlab.kitware.com/cmake/cmake/-/issues/20682 doesn't let us do that + # in CMake 3.19 and earlier. + # We can do it in CMake 3.20 and later. + # And we have to do it with CMake 3.21.0 to avoid a different issue + # https://gitlab.kitware.com/cmake/cmake/-/issues/22436 + # + # So use the object lib work around for <= 3.19 and target_sources directly for later + # versions. + set(use_obj_lib FALSE) + set(end_target "${target}") + if(CMAKE_VERSION VERSION_LESS 3.20) + set(use_obj_lib TRUE) + set(end_target "${target}_rc") + add_library(${target}_rc OBJECT "${output}") + target_link_libraries(${target} PRIVATE $<TARGET_OBJECTS:${target}_rc>) + endif() + + set(scope_args) + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.18") + set(scope_args TARGET_DIRECTORY ${end_target}) + endif() + while(outputs) list(POP_FRONT cfgs cfg) list(POP_FRONT outputs output) @@ -1122,20 +1751,245 @@ END add_custom_command(OUTPUT "${output}" DEPENDS "${input}" COMMAND ${CMAKE_COMMAND} -E copy_if_different "${input}" "${output}" + VERBATIM + ) + # We can't rely on policy CMP0118 since user project controls it + set_source_files_properties(${output} ${scope_args} PROPERTIES + GENERATED TRUE + COMPILE_FLAGS "${extra_rc_flags}" ) - target_sources(${target}_rc PRIVATE "$<$<CONFIG:${cfg}>:${output}>") + target_sources(${end_target} PRIVATE "$<$<CONFIG:${cfg}>:${output}>") endwhile() endif() endfunction() +# Generate Win32 longPathAware RC and Manifest files for a target. +# MSVC needs the manifest file as part of target_sources. MinGW the RC file. +# +function(_qt_internal_generate_longpath_win32_rc_file_and_manifest target) + set(prohibited_target_types INTERFACE_LIBRARY STATIC_LIBRARY OBJECT_LIBRARY) + get_target_property(target_type ${target} TYPE) + if(target_type IN_LIST prohibited_target_types) + return() + endif() + + get_target_property(target_binary_dir ${target} BINARY_DIR) + + # Generate manifest + set(target_mn_filename "${target}_longpath.manifest") + set(mn_file_output "${target_binary_dir}/${target_mn_filename}") + + set(mn_contents [=[<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> +<application xmlns="urn:schemas-microsoft-com:asm.v3"> + <windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings"> + <ws2:longPathAware>true</ws2:longPathAware> + </windowsSettings> +</application> +</assembly>]=]) + file(GENERATE OUTPUT "${mn_file_output}" CONTENT "${mn_contents}") + + # Generate RC File + set(rc_file_output "${target_binary_dir}/${target}_longpath.rc") + set(rc_contents "1 /* CREATEPROCESS_MANIFEST_RESOURCE_ID */ 24 /* RT_MANIFEST */ ${target_mn_filename}") + file(GENERATE OUTPUT "${rc_file_output}" CONTENT "${rc_contents}") + + if (MINGW) + set(outputs "${rc_file_output}") + endif() + list(APPEND outputs "${mn_file_output}") + + foreach(output IN LISTS outputs) + # Needed for CMake versions < 3.19 + set_source_files_properties(${output} PROPERTIES GENERATED TRUE) + target_sources(${target} PRIVATE "${output}") + endforeach() +endfunction() + function(__qt_get_relative_resource_path_for_file output_alias file) get_property(alias SOURCE ${file} PROPERTY QT_RESOURCE_ALIAS) if (NOT alias) set(alias "${file}") + if(IS_ABSOLUTE "${file}") + message(FATAL_ERROR + "The source file '${file}' was specified with an absolute path and is used in a Qt " + "resource. Please set the QT_RESOURCE_ALIAS property on that source file to a " + "relative path to make the file properly accessible via the resource system." + ) + endif() endif() set(${output_alias} ${alias} PARENT_SCOPE) endfunction() +# Performs linking and propagation of the specified objects via the target's usage requirements. +# The objects may be given as generator expression. +# +# Arguments: +# EXTRA_CONDITIONS +# Conditions to be checked before linking the object files to the end-point executable. +# EXTRA_TARGET_LINK_LIBRARIES_CONDITIONS +# Conditions for the target_link_libraries call. +# EXTRA_TARGET_SOURCES_CONDITIONS +# Conditions for the target_sources call. +function(__qt_internal_propagate_object_files target objects) + set(options "") + set(single_args "") + set(extra_conditions_args + EXTRA_CONDITIONS + EXTRA_TARGET_LINK_LIBRARIES_CONDITIONS + EXTRA_TARGET_SOURCES_CONDITIONS + ) + set(multi_args ${extra_conditions_args}) + cmake_parse_arguments(arg "${options}" "${single_args}" "${multi_args}" ${ARGN}) + + # Collect additional conditions. + foreach(arg IN LISTS extra_conditions_args) + string(TOLOWER "${arg}" lcvar) + if(arg_${arg}) + list(JOIN arg_${arg} "," ${lcvar}) + else() + set(${lcvar} "$<BOOL:TRUE>") + endif() + endforeach() + + # Do not litter the static libraries + set(not_static_condition + "$<NOT:$<STREQUAL:$<TARGET_PROPERTY:TYPE>,STATIC_LIBRARY>>" + ) + + # Check if link order matters for the Platform. + set(platform_link_order_property + "$<TARGET_PROPERTY:${QT_CMAKE_EXPORT_NAMESPACE}::Platform,_qt_link_order_matters>" + ) + set(platform_link_order_condition + "$<BOOL:${platform_link_order_property}>" + ) + + # Check if link options are propagated according to CMP0099 + # In user builds the _qt_cmp0099_policy_check is set to FALSE or $<TARGET_POLICY:CMP0099> + # depending on the used CMake version. + # See __qt_internal_check_cmp0099_available for details. + set(cmp0099_policy_check_property + "$<TARGET_PROPERTY:${QT_CMAKE_EXPORT_NAMESPACE}::Platform,_qt_cmp0099_policy_check>" + ) + set(link_objects_using_link_options_condition + "$<BOOL:$<GENEX_EVAL:${cmp0099_policy_check_property}>>" + ) + + # Collect link conditions for the target_sources call. + string(JOIN "" target_sources_genex + "$<" + "$<AND:" + "${not_static_condition}," + "${platform_link_order_condition}," + "$<NOT:${link_objects_using_link_options_condition}>," + "${extra_target_sources_conditions}," + "${extra_conditions}" + ">" + ":${objects}>" + ) + target_sources(${target} INTERFACE + "${target_sources_genex}" + ) + + # Collect link conditions for the target_link_options call. + string(JOIN "" target_link_options_genex + "$<" + "$<AND:" + "${not_static_condition}," + "${platform_link_order_condition}," + "${link_objects_using_link_options_condition}," + "${extra_conditions}" + ">" + ":${objects}>" + ) + # target_link_options works well since CMake 3.17 which has policy CMP0099 set to NEW for the + # minimum required CMake version greater than or equal to 3.17. The default is OLD. See + # https://cmake.org/cmake/help/git-master/policy/CMP0099.html for details. + # This provides yet another way of linking object libraries if user sets the policy to NEW + # before calling find_package(Qt...). + target_link_options(${target} INTERFACE + "${target_link_options_genex}" + ) + + # Collect link conditions for the target_link_libraries call. + string(JOIN "" target_link_libraries_genex + "$<" + "$<AND:" + "${not_static_condition}," + "$<NOT:${platform_link_order_condition}>," + "${extra_target_link_libraries_conditions}," + "${extra_conditions}" + ">" + ":${objects}>" + ) + target_link_libraries(${target} INTERFACE + "${target_link_libraries_genex}" + ) +endfunction() + +# Performs linking and propagation of the object library via the target's usage requirements. +# Arguments: +# NO_LINK_OBJECT_LIBRARY_REQUIREMENTS_TO_TARGET skip linking of ${object_library} to ${target}, only +# propagate $<TARGET_OBJECTS:${object_library}> by linking it to ${target}. It's useful in case +# if ${object_library} depends on the ${target}. E.g. resource libraries depend on the Core +# library so linking them back to Core will cause a CMake error. +# +# EXTRA_CONDITIONS object library specific conditions to be checked before link the object library +# to the end-point executable. +function(__qt_internal_propagate_object_library target object_library) + set(options NO_LINK_OBJECT_LIBRARY_REQUIREMENTS_TO_TARGET) + set(single_args "") + set(multi_args EXTRA_CONDITIONS) + cmake_parse_arguments(arg "${options}" "${single_args}" "${multi_args}" ${ARGN}) + + get_target_property(is_imported ${object_library} IMPORTED) + if(NOT is_imported) + target_link_libraries(${object_library} PRIVATE ${QT_CMAKE_EXPORT_NAMESPACE}::Platform) + _qt_internal_copy_dependency_properties(${object_library} ${target} PRIVATE_ONLY) + endif() + + # After internal discussion we decided to not rely on the linker order that CMake + # offers, until CMake provides the guaranteed linking order that suites our needs in a + # future CMake version. + # All object libraries mark themselves with the _is_qt_propagated_object_library property. + # Using a finalizer approach we walk through the target dependencies and look for libraries + # using the _is_qt_propagated_object_library property. Then, objects of the collected libraries + # are moved to the beginning of the linker line using target_sources. + # + # Note: target_link_libraries works well with linkers other than ld. If user didn't enforce + # a finalizer we rely on linker to resolve circular dependencies between objects and static + # libraries. + set_property(TARGET ${object_library} PROPERTY _is_qt_propagated_object_library TRUE) + if(NOT is_imported) + set_property(TARGET ${object_library} APPEND PROPERTY + EXPORT_PROPERTIES _is_qt_propagated_object_library + ) + endif() + + # Keep the implicit linking if finalizers are not used. + set(not_finalizer_mode_condition + "$<NOT:$<BOOL:$<TARGET_PROPERTY:_qt_object_libraries_finalizer_mode>>>" + ) + + # Use TARGET_NAME to have the correct namespaced name in the exports. + set(objects "$<TARGET_OBJECTS:$<TARGET_NAME:${object_library}>>") + + __qt_internal_propagate_object_files(${target} ${objects} + EXTRA_CONDITIONS ${arg_EXTRA_CONDITIONS} + EXTRA_TARGET_SOURCES_CONDITIONS ${not_finalizer_mode_condition} + EXTRA_TARGET_LINK_LIBRARIES_CONDITIONS ${not_finalizer_mode_condition} + ) + + if(NOT arg_NO_LINK_OBJECT_LIBRARY_REQUIREMENTS_TO_TARGET) + # It's necessary to link the object library target, since we want to pass the object library + # dependencies to the 'target'. Interface linking doesn't add the objects of the library to + # the end-point linker line but propagates all the dependencies of the object_library added + # before or AFTER the line below. + target_link_libraries(${target} INTERFACE ${object_library}) + endif() +endfunction() + function(__qt_propagate_generated_resource target resource_name generated_source_code output_generated_target) get_target_property(type ${target} TYPE) if(type STREQUAL STATIC_LIBRARY) @@ -1146,8 +2000,38 @@ function(__qt_propagate_generated_resource target resource_name generated_source math(EXPR resource_count "${resource_count} + 1") set_target_properties(${target} PROPERTIES _qt_generated_resource_target_count ${resource_count}) + __qt_internal_generate_init_resource_source_file( + resource_init_file ${target} ${resource_name}) + set(resource_target "${target}_resources_${resource_count}") - add_library("${resource_target}" OBJECT "${generated_source_code}") + add_library("${resource_target}" OBJECT "${resource_init_file}") + set_target_properties(${resource_target} PROPERTIES + AUTOMOC FALSE + AUTOUIC FALSE + AUTORCC FALSE + ) + # Needed so that qtsymbolmacros.h and its dependent headers are already created / syncqt'ed. + if(TARGET Core_sync_headers) + set(headers_available_target "Core_sync_headers") + else() + set(headers_available_target "${QT_CMAKE_EXPORT_NAMESPACE}::Core") + endif() + add_dependencies(${resource_target} ${headers_available_target}) + target_compile_definitions("${resource_target}" PRIVATE + "$<TARGET_PROPERTY:${QT_CMAKE_EXPORT_NAMESPACE}::Core,INTERFACE_COMPILE_DEFINITIONS>" + ) + target_include_directories("${resource_target}" PRIVATE + "$<TARGET_PROPERTY:${QT_CMAKE_EXPORT_NAMESPACE}::Core,INTERFACE_INCLUDE_DIRECTORIES>" + ) + _qt_internal_set_up_static_runtime_library("${resource_target}") + + # Special handling is required for the Core library resources. The linking of the Core + # library to the resources adds a circular dependency. This leads to the wrong + # objects/library order in the linker command line, since the Core library target is + # resolved first. + if(NOT target STREQUAL "Core") + target_link_libraries(${resource_target} INTERFACE ${QT_CMAKE_EXPORT_NAMESPACE}::Core) + endif() set_property(TARGET ${resource_target} APPEND PROPERTY _qt_resource_name ${resource_name}) # Save the path to the generated source file, relative to the the current build dir. @@ -1157,153 +2041,286 @@ function(__qt_propagate_generated_resource target resource_name generated_source # .rcc/qrc_qprintdialog.cpp file(RELATIVE_PATH generated_cpp_file_relative_path "${CMAKE_CURRENT_BINARY_DIR}" - "${generated_source_code}") + "${resource_init_file}") set_property(TARGET ${resource_target} APPEND PROPERTY _qt_resource_generated_cpp_relative_path "${generated_cpp_file_relative_path}") - # Use TARGET_NAME genex to map to the correct prefixed target name when it is exported - # via qt_install(EXPORT), so that the consumers of the target can find the object library - # as well. - target_link_libraries(${target} INTERFACE - "$<TARGET_OBJECTS:$<TARGET_NAME:${resource_target}>>") - set(${output_generated_target} "${resource_target}" PARENT_SCOPE) + if(target STREQUAL "Core") + set(skip_direct_linking NO_LINK_OBJECT_LIBRARY_REQUIREMENTS_TO_TARGET) + endif() + __qt_internal_propagate_object_library(${target} ${resource_target} + ${skip_direct_linking} + ) - # No need to compile Q_IMPORT_PLUGIN-containing files for non-executables. - _qt_internal_disable_static_default_plugins("${resource_target}") + set(${output_generated_target} "${resource_target}" PARENT_SCOPE) else() set(${output_generated_target} "" PARENT_SCOPE) - target_sources(${target} PRIVATE ${generated_source_code}) endif() + + target_sources(${target} PRIVATE ${generated_source_code}) +endfunction() + +function(__qt_internal_sanitize_resource_name out_var name) + # The sanitized output should match RCCResourceLibrary::writeInitializer()'s + # isAsciiLetterOrNumber-based substituion. + # MAKE_C_IDENTIFIER matches that, it replaces non-alphanumeric chars with underscores. + string(MAKE_C_IDENTIFIER "${name}" sanitized_resource_name) + set(${out_var} "${sanitized_resource_name}" PARENT_SCOPE) +endfunction() + +function(__qt_internal_generate_init_resource_source_file out_var target resource_name) + set(template_file "${__qt_core_macros_module_base_dir}/Qt6CoreResourceInit.in.cpp") + + # Gets replaced in the template + __qt_internal_sanitize_resource_name(RESOURCE_NAME "${resource_name}") + set(resource_init_path "${CMAKE_CURRENT_BINARY_DIR}/.qt/rcc/qrc_${resource_name}_init.cpp") + + configure_file("${template_file}" "${resource_init_path}" @ONLY) + + set(scope_args "") + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.18") + set(scope_args TARGET_DIRECTORY ${target}) + endif() + set_source_files_properties(${resource_init_path} ${scope_args} PROPERTIES + SKIP_AUTOGEN TRUE + SKIP_UNITY_BUILD_INCLUSION TRUE + SKIP_PRECOMPILE_HEADERS TRUE + ) + + set(${out_var} "${resource_init_path}" PARENT_SCOPE) endfunction() -# Creates fake targets and adds resource files to IDE's tree +# Make file visible in IDEs. +# Targets that are finalized add the file as HEADER_FILE_ONLY in the finalizer. +# Targets that are not finalized add the file under a ${target}_other_files target. function(_qt_internal_expose_source_file_to_ide target file) - set(ide_target_extension "other_files") - set(qml_extensions ".qml" ".js") - get_filename_component(resource_extension "${file}" LAST_EXT) - if(resource_extension IN_LIST qml_extensions) - set(ide_target_extension "qml_files") + get_target_property(target_expects_finalization ${target} _qt_expects_finalization) + if(target_expects_finalization AND CMAKE_VERSION VERSION_GREATER_EQUAL "3.19") + set_property(TARGET ${target} APPEND PROPERTY _qt_deferred_files ${file}) + return() endif() - set(ide_target ${target}_${ide_target_extension}) + # Fallback for targets that are not finalized: Create fake target under which the file is added. + set(ide_target ${target}_other_files) if(NOT TARGET ${ide_target}) add_custom_target(${ide_target} SOURCES "${file}") + + # The new Xcode build system requires a common target to drive the generation of files, + # otherwise project configuration fails. + # By adding ${target} as a dependency of ${target}_other_files, + # it becomes the common target, so project configuration succeeds. + if(CMAKE_GENERATOR STREQUAL "Xcode") + add_dependencies(${ide_target} ${target}) + endif() else() set_property(TARGET ${ide_target} APPEND PROPERTY SOURCES "${file}") endif() + + set(scope_args) + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.18") + set(scope_args TARGET_DIRECTORY "${target}") + endif() + get_source_file_property( + target_dependency "${file}" ${scope_args} _qt_resource_target_dependency) + if(target_dependency) + if(NOT TARGET "${target_dependency}") + message(FATAL_ERROR "Target dependency on source file ${file} is not a cmake target.") + endif() + add_dependencies(${ide_target} ${target_dependency}) + endif() +endfunction() + +# Called by the target finalizer. +# Adds the files that were added to _qt_deferred_files to SOURCES. +# Sets HEADER_FILES_ONLY if they did not exist yet in SOURCES. +function(_qt_internal_expose_deferred_files_to_ide target) + get_target_property(new_sources ${target} _qt_deferred_files) + if(NOT new_sources) + return() + endif() + set(new_sources_real "") + foreach(f IN LISTS new_sources) + get_filename_component(realf "${f}" REALPATH) + list(APPEND new_sources_real ${realf}) + endforeach() + + set(filtered_new_sources "") + get_target_property(target_source_dir ${target} SOURCE_DIR) + get_filename_component(target_source_dir "${target_source_dir}" REALPATH) + get_target_property(existing_sources ${target} SOURCES) + if(existing_sources) + set(existing_sources_real "") + set(realf "") + foreach(f IN LISTS existing_sources) + get_filename_component(realf "${f}" REALPATH BASE_DIR ${target_source_dir}) + list(APPEND existing_sources_real ${realf}) + endforeach() + + list(LENGTH new_sources max_i) + math(EXPR max_i "${max_i} - 1") + foreach(i RANGE 0 ${max_i}) + list(GET new_sources_real ${i} realf) + if(NOT realf IN_LIST existing_sources_real) + list(GET new_sources ${i} f) + list(APPEND filtered_new_sources ${f}) + endif() + endforeach() + endif() + if("${filtered_new_sources}" STREQUAL "") + return() + endif() + + target_sources(${target} PRIVATE ${filtered_new_sources}) + set(scope_args) + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.18") + set(scope_args TARGET_DIRECTORY "${target}") + endif() + set_source_files_properties(${filtered_new_sources} + ${scope_args} PROPERTIES HEADER_FILE_ONLY ON) endfunction() # # Process resources via file path instead of QRC files. Behind the -# scnenes, it will generate a qrc file and apply post processing steps -# when applicable. (e.g.: QtQuickCompiler) +# scenes, it will generate a qrc file. # # The QRC Prefix is set via the PREFIX parameter. # # Alias settings for files need to be set via the QT_RESOURCE_ALIAS property -# via the set_soure_files_properties() command. +# via the set_source_files_properties() command. # # When using this command with static libraries, one or more special targets # will be generated. Should you wish to perform additional processing on these # targets pass a value to the OUTPUT_TARGETS parameter. # function(_qt_internal_process_resource target resourceName) - - cmake_parse_arguments(rcc "" "PREFIX;LANG;BASE;OUTPUT_TARGETS;DESTINATION" "FILES;OPTIONS" ${ARGN}) + cmake_parse_arguments(rcc "BIG_RESOURCES" + "PREFIX;LANG;BASE;OUTPUT_TARGETS;DESTINATION" "FILES;OPTIONS" ${ARGN}) if("${rcc_OPTIONS}" MATCHES "-binary") set(isBinary TRUE) + if(rcc_BIG_RESOURCES) + message(FATAL_ERROR "BIG_RESOURCES cannot be used together with the -binary option.") + endif() + endif() + + if(rcc_BIG_RESOURCES AND CMAKE_GENERATOR STREQUAL "Xcode" AND IOS) + message(WARNING + "Due to CMake limitations, the BIG_RESOURCES option can't be used when building " + "for iOS. " + "See https://bugreports.qt.io/browse/QTBUG-103497 for details. " + "Falling back to using regular resources. " + ) + set(rcc_BIG_RESOURCES OFF) + endif() + + if(rcc_BIG_RESOURCES AND CMAKE_VERSION VERSION_LESS "3.17") + message(WARNING + "The BIG_RESOURCES option does not work reliably with CMake < 3.17. " + "Consider upgrading to a more recent CMake version or disable the BIG_RESOURCES " + "option for older CMake versions." + ) endif() string(REPLACE "/" "_" resourceName ${resourceName}) string(REPLACE "." "_" resourceName ${resourceName}) - set(output_targets "") - # Apply base to all files - if (rcc_BASE) - foreach(file IN LISTS rcc_FILES) - set(resource_file "${rcc_BASE}/${file}") - __qt_get_relative_resource_path_for_file(alias ${resource_file}) - # Handle case where resources were generated from a directory - # different than the one where the main .pro file resides. - # Unless otherwise specified, we should use the original file path - # as alias. - if (alias STREQUAL resource_file) - set_source_files_properties(${resource_file} PROPERTIES QT_RESOURCE_ALIAS ${file}) + set(resource_files "") + # Strip the ending slashes from the file_path. If paths contain slashes in the end + # set/get source properties works incorrect and may have the same QT_RESOURCE_ALIAS + # for two different paths. See https://gitlab.kitware.com/cmake/cmake/-/issues/23212 + # for details. + foreach(file_path IN LISTS rcc_FILES) + if(file_path MATCHES "(.+)/$") + set(file_path "${CMAKE_MATCH_1}") + endif() + list(APPEND resource_files ${file_path}) + endforeach() + + if(NOT "${rcc_BASE}" STREQUAL "") + get_filename_component(abs_base "${rcc_BASE}" ABSOLUTE) + foreach(file_path IN LISTS resource_files) + get_source_file_property(alias "${file_path}" QT_RESOURCE_ALIAS) + if(alias STREQUAL "NOTFOUND") + get_filename_component(abs_file "${file_path}" ABSOLUTE) + file(RELATIVE_PATH rel_file "${abs_base}" "${abs_file}") + set_property(SOURCE "${file_path}" PROPERTY QT_RESOURCE_ALIAS "${rel_file}") endif() - file(TO_CMAKE_PATH ${resource_file} resource_file) - list(APPEND resource_files ${resource_file}) endforeach() - else() - set(resource_files ${rcc_FILES}) + endif() + + if(ANDROID) + if(COMMAND _qt_internal_collect_qml_root_paths) + _qt_internal_collect_qml_root_paths(${target} ${resource_files}) + endif() endif() if(NOT rcc_PREFIX) get_target_property(rcc_PREFIX ${target} QT_RESOURCE_PREFIX) if (NOT rcc_PREFIX) - message(FATAL_ERROR "_qt_internal_process_resource() was called without a PREFIX and the target does not provide QT_RESOURCE_PREFIX. Please either add a PREFIX or make the target ${target} provide a default.") + set(rcc_PREFIX "/") endif() endif() - # Apply quick compiler pass. This is only enabled when Qt6QmlMacros is - # parsed. - if (QT6_ADD_RESOURCE_DECLARATIVE_EXTENSIONS) - _qt_internal_quick_compiler_process_resources(${target} ${resourceName} - FILES ${resource_files} - PREFIX ${rcc_PREFIX} - OUTPUT_REMAINING_RESOURCES resources - OUTPUT_RESOURCE_NAME newResourceName - OUTPUT_GENERATED_TARGET output_target_quick - ) - else() - set(newResourceName ${resourceName}) - set(resources ${resource_files}) - endif() - - if (NOT resources) + if (NOT resource_files) if (rcc_OUTPUT_TARGETS) - set(${rcc_OUTPUT_TARGETS} "${output_target_quick}" PARENT_SCOPE) + set(${rcc_OUTPUT_TARGETS} "" PARENT_SCOPE) endif() return() endif() - list(APPEND output_targets ${output_target_quick}) - set(generatedBaseName "${newResourceName}") - set(generatedResourceFile "${CMAKE_CURRENT_BINARY_DIR}/.rcc/${generatedBaseName}.qrc") + set(generatedResourceFile "${CMAKE_CURRENT_BINARY_DIR}/.qt/rcc/${resourceName}.qrc") + _qt_internal_expose_source_file_to_ide(${target} ${generatedResourceFile}) + set_source_files_properties(${generatedResourceFile} PROPERTIES GENERATED TRUE) # Generate .qrc file: # <RCC><qresource ...> set(qrcContents "<RCC>\n <qresource") - if (rcc_PREFIX) - string(APPEND qrcContents " prefix=\"${rcc_PREFIX}\"") - endif() + string(APPEND qrcContents " prefix=\"${rcc_PREFIX}\"") + if (rcc_LANG) string(APPEND qrcContents " lang=\"${rcc_LANG}\"") endif() string(APPEND qrcContents ">\n") set(resource_dependencies) - foreach(file IN LISTS resources) + foreach(file IN LISTS resource_files) __qt_get_relative_resource_path_for_file(file_resource_path ${file}) if (NOT IS_ABSOLUTE ${file}) set(file "${CMAKE_CURRENT_SOURCE_DIR}/${file}") endif() + get_property(is_empty SOURCE ${file} PROPERTY QT_DISCARD_FILE_CONTENTS) + ### FIXME: escape file paths to be XML conform # <file ...>...</file> - string(APPEND qrcContents " <file alias=\"${file_resource_path}\">") - string(APPEND qrcContents "${file}</file>\n") + string(APPEND qrcContents " <file alias=\"${file_resource_path}\"") + if(is_empty) + string(APPEND qrcContents " empty=\"true\"") + endif() + string(APPEND qrcContents ">${file}</file>\n") list(APPEND files "${file}") - get_source_file_property(target_dependency ${file} QT_RESOURCE_TARGET_DEPENDENCY) - if (NOT target_dependency) - list(APPEND resource_dependencies ${file}) - else() - if (NOT TARGET ${target_dependency}) - message(FATAL_ERROR "Target dependency on resource file ${file} is not a cmake target.") + set(scope_args) + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.18") + set(scope_args TARGET_DIRECTORY ${target}) + endif() + get_source_file_property( + target_dependency ${file} ${scope_args} _qt_resource_target_dependency) + + # The target dependency code path does not take care of rebuilds when ${file} + # is touched. Limit its usage to the Xcode generator to avoid the Xcode common + # dependency issue. + # TODO: Figure out how to avoid the issue on Xcode, while also enabling proper + # dependency tracking when ${file} is touched. + if(target_dependency AND CMAKE_GENERATOR STREQUAL "Xcode") + if(NOT TARGET ${target_dependency}) + message(FATAL_ERROR + "Target dependency on resource file ${file} is not a cmake target.") endif() list(APPEND resource_dependencies ${target_dependency}) + else() + list(APPEND resource_dependencies ${file}) endif() _qt_internal_expose_source_file_to_ide(${target} "${file}") endforeach() @@ -1315,10 +2332,11 @@ function(_qt_internal_process_resource target resourceName) set(qt_core_configure_file_contents "${qrcContents}") configure_file("${template_file}" "${generatedResourceFile}") - set(rccArgs --name "${newResourceName}" "${generatedResourceFile}") + set(rccArgs --name "${resourceName}" "${generatedResourceFile}") + set(rccArgsAllPasses "") if(rcc_OPTIONS) - list(APPEND rccArgs ${rcc_OPTIONS}) + list(APPEND rccArgsAllPasses ${rcc_OPTIONS}) endif() # When cross-building, we use host tools to generate target code. If the host rcc was compiled @@ -1328,14 +2346,14 @@ function(_qt_internal_process_resource target resourceName) # If the target does not support zstd (feature is disabled), tell rcc not to generate # zstd related code. if(NOT QT_FEATURE_zstd) - list(APPEND rccArgs "--no-zstd") + list(APPEND rccArgsAllPasses "--no-zstd") endif() set_property(SOURCE "${generatedResourceFile}" PROPERTY SKIP_AUTOGEN ON) # Set output file name for rcc command if(isBinary) - set(generatedOutfile "${CMAKE_CURRENT_BINARY_DIR}/${generatedBaseName}.rcc") + set(generatedOutfile "${CMAKE_CURRENT_BINARY_DIR}/${resourceName}.rcc") if(rcc_DESTINATION) # Add .rcc suffix if it's not specified by user get_filename_component(destinationRccExt "${rcc_DESTINATION}" LAST_EXT) @@ -1345,68 +2363,155 @@ function(_qt_internal_process_resource target resourceName) set(generatedOutfile "${rcc_DESTINATION}.rcc") endif() endif() + elseif(rcc_BIG_RESOURCES) + set(generatedOutfile "${CMAKE_CURRENT_BINARY_DIR}/.qt/rcc/qrc_${resourceName}_tmp.cpp") else() - set(generatedOutfile "${CMAKE_CURRENT_BINARY_DIR}/.rcc/qrc_${newResourceName}.cpp") + set(generatedOutfile "${CMAKE_CURRENT_BINARY_DIR}/.qt/rcc/qrc_${resourceName}.cpp") + endif() + + set(pass_msg) + if(rcc_BIG_RESOURCES) + list(PREPEND rccArgs --pass 1) + set(pass_msg " pass 1") endif() list(PREPEND rccArgs --output "${generatedOutfile}") # Process .qrc file: add_custom_command(OUTPUT "${generatedOutfile}" - COMMAND "${QT_CMAKE_EXPORT_NAMESPACE}::rcc" ${rccArgs} + COMMAND "${QT_CMAKE_EXPORT_NAMESPACE}::rcc" ${rccArgs} ${rccArgsAllPasses} DEPENDS ${resource_dependencies} ${generatedResourceFile} "${QT_CMAKE_EXPORT_NAMESPACE}::rcc" - COMMENT "RCC ${newResourceName}" + COMMENT "Running rcc${pass_msg} for resource ${resourceName}" VERBATIM) if(isBinary) # Add generated .rcc target to 'all' set - add_custom_target(binary_resource_${generatedBaseName} ALL DEPENDS "${generatedOutfile}") - else() - set_property(SOURCE "${generatedOutfile}" PROPERTY SKIP_AUTOGEN ON) - set_property(TARGET ${target} APPEND PROPERTY _qt_generated_qrc_files "${generatedResourceFile}") - - # Only do this if newResourceName is the same as resourceName, since - # the resource will be chainloaded by the qt quickcompiler - # qml cache loader - if(newResourceName STREQUAL resourceName) - __qt_propagate_generated_resource(${target} ${resourceName} "${generatedOutfile}" output_target) - list(APPEND output_targets ${output_target}) + add_custom_target(binary_resource_${resourceName} ALL DEPENDS "${generatedOutfile}") + return() + endif() + + # We can't rely on policy CMP0118 since user project controls it. + # We also want SKIP_AUTOGEN known in the target's scope, where we can. + set(scope_args) + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.18") + set(scope_args TARGET_DIRECTORY ${target}) + endif() + set_source_files_properties(${generatedOutfile} ${scope_args} PROPERTIES + SKIP_AUTOGEN TRUE + GENERATED TRUE + SKIP_UNITY_BUILD_INCLUSION TRUE + SKIP_PRECOMPILE_HEADERS TRUE + ) + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.27") + set_source_files_properties(${generatedOutfile} ${scope_args} PROPERTIES SKIP_LINTING ON) + endif() + + get_target_property(target_source_dir ${target} SOURCE_DIR) + if(NOT target_source_dir STREQUAL CMAKE_CURRENT_SOURCE_DIR) + # We have to create a separate target in this scope that depends on + # the generated file, otherwise the original target won't have the + # required dependencies in place to ensure correct build order. + add_custom_target(${target}_${resourceName} DEPENDS ${generatedOutfile}) + add_dependencies(${target} ${target}_${resourceName}) + endif() + + if(rcc_BIG_RESOURCES) + set(pass1OutputFile ${generatedOutfile}) + set(generatedOutfile + "${CMAKE_CURRENT_BINARY_DIR}/.qt/rcc/qrc_${resourceName}${CMAKE_CXX_OUTPUT_EXTENSION}") + _qt_internal_add_rcc_pass2( + RESOURCE_NAME ${resourceName} + RCC_OPTIONS ${rccArgsAllPasses} + OBJECT_LIB ${target}_${resourceName}_obj + QRC_FILE ${generatedResourceFile} + PASS1_OUTPUT_FILE ${pass1OutputFile} + OUT_OBJECT_FILE ${generatedOutfile} + ) + get_target_property(type ${target} TYPE) + if(type STREQUAL STATIC_LIBRARY) + # Create a custom target to trigger the generation of ${generatedOutfile} + set(pass2_target ${target}_${resourceName}_pass2) + add_custom_target(${pass2_target} DEPENDS ${generatedOutfile}) + add_dependencies(${target} ${pass2_target}) + + # Propagate the object files to the target. + __qt_internal_propagate_object_files(${target} "${generatedOutfile}") else() - target_sources(${target} PRIVATE "${generatedOutfile}") - endif() - if (rcc_OUTPUT_TARGETS) - set(${rcc_OUTPUT_TARGETS} "${output_targets}" PARENT_SCOPE) + target_sources(${target} PRIVATE ${generatedOutfile}) endif() + else() + __qt_propagate_generated_resource(${target} ${resourceName} "${generatedOutfile}" + output_targets) + endif() + + set_property(TARGET ${target} + APPEND PROPERTY _qt_generated_qrc_files "${generatedResourceFile}") + + if (rcc_OUTPUT_TARGETS) + set(${rcc_OUTPUT_TARGETS} "${output_targets}" PARENT_SCOPE) endif() endfunction() -# This function is currently in Technical Preview. -# It's signature and behavior might change. -function(qt6_add_plugin target) - cmake_parse_arguments(arg - "STATIC" - "OUTPUT_NAME;CLASS_NAME;TYPE" - "" - ${ARGN} +macro(_qt_internal_get_add_plugin_keywords option_args single_args multi_args) + set(${option_args} + STATIC + SHARED + __QT_INTERNAL_NO_PROPAGATE_PLUGIN_INITIALIZER + ) + set(${single_args} + PLUGIN_TYPE # Internal use only, may be changed or removed + CLASS_NAME + OUTPUT_NAME # Internal use only, may be changed or removed + OUTPUT_TARGETS ) - if (arg_STATIC) - add_library(${target} STATIC) + set(${multi_args}) +endmacro() + +function(qt6_add_plugin target) + _qt_internal_get_add_plugin_keywords(opt_args single_args multi_args) + list(APPEND opt_args MANUAL_FINALIZATION) + + cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}") + + if(arg_STATIC AND arg_SHARED) + message(FATAL_ERROR + "Both STATIC and SHARED options were given. Only one of the two should be used." + ) + endif() + + # Explicit option takes priority over the computed default. + if(arg_STATIC) + set(create_static_plugin TRUE) + elseif(arg_SHARED) + set(create_static_plugin FALSE) else() - add_library(${target} MODULE) - if(APPLE) - # CMake defaults to using .so extensions for loadable modules, aka plugins, - # but Qt plugins are actually suffixed with .dylib. - set_property(TARGET "${target}" PROPERTY SUFFIX ".dylib") - endif() - if(WIN32) - # CMake sets for Windows-GNU platforms the suffix "lib" - set_property(TARGET "${target}" PROPERTY PREFIX "") + # If no explicit STATIC/SHARED option is set, default to the flavor of the Qt build. + if(QT6_IS_SHARED_LIBS_BUILD) + set(create_static_plugin FALSE) + else() + set(create_static_plugin TRUE) endif() endif() + # The default of _qt_internal_add_library creates SHARED in a shared Qt build, so we need to + # be explicit about the MODULE. + if(create_static_plugin) + set(type_to_create STATIC) + else() + set(type_to_create MODULE) + endif() + + _qt_internal_add_library(${target} ${type_to_create} ${arg_UNPARSED_ARGUMENTS}) + set_property(TARGET ${target} PROPERTY _qt_expects_finalization TRUE) + + get_target_property(target_type "${target}" TYPE) + if (target_type STREQUAL "STATIC_LIBRARY") + target_compile_definitions(${target} PRIVATE QT_STATICPLUGIN) + endif() + set(output_name ${target}) if (arg_OUTPUT_NAME) set(output_name ${arg_OUTPUT_NAME}) @@ -1414,42 +2519,237 @@ function(qt6_add_plugin target) set_property(TARGET "${target}" PROPERTY OUTPUT_NAME "${output_name}") if (ANDROID) - qt6_android_apply_arch_suffix("${target}") set_target_properties(${target} PROPERTIES - LIBRARY_OUTPUT_NAME "plugins_${arg_TYPE}_${output_name}" + LIBRARY_OUTPUT_NAME "plugins_${arg_PLUGIN_TYPE}_${output_name}" ) endif() # Derive the class name from the target name if it's not explicitly specified. set(plugin_class_name "") - if (NOT arg_CLASS_NAME) - set(plugin_class_name "${target}") + if (NOT "${arg_PLUGIN_TYPE}" STREQUAL "qml_plugin") + if (NOT arg_CLASS_NAME) + set(plugin_class_name "${target}") + else() + set(plugin_class_name "${arg_CLASS_NAME}") + endif() else() - set(plugin_class_name "${arg_CLASS_NAME}") + # Make sure to set any passed-in class name for qml plugins as well, because it's used for + # building the qml plugin foo_init object libraries. + if(arg_CLASS_NAME) + set(plugin_class_name "${arg_CLASS_NAME}") + else() + message(FATAL_ERROR "Qml plugin target has no CLASS_NAME specified: '${target}'") + endif() endif() + set_target_properties(${target} PROPERTIES QT_PLUGIN_CLASS_NAME "${plugin_class_name}") - set(static_plugin_define "") - if (arg_STATIC) - set(static_plugin_define "QT_STATICPLUGIN") + # Create a plugin initializer object library for static plugins. + # It contains a Q_IMPORT_PLUGIN(QT_PLUGIN_CLASS_NAME) call. + # Project targets will automatically link to the plugin initializer whenever they link to the + # plugin target. + # The plugin init target name is stored in OUTPUT_TARGETS, so projects may install them. + # Qml plugin inits are handled in Qt6QmlMacros. + if(NOT "${arg_PLUGIN_TYPE}" STREQUAL "qml_plugin" + AND target_type STREQUAL "STATIC_LIBRARY") + __qt_internal_add_static_plugin_init_object_library("${target}" plugin_init_target) + + if(arg_OUTPUT_TARGETS) + set(${arg_OUTPUT_TARGETS} ${plugin_init_target} PARENT_SCOPE) + endif() + + # We don't automatically propagate the plugin init library for Qt provided plugins, because + # there are 2 other code paths that take care of that, one involving finalizers and the + # other regular usage requirements. + if(NOT arg___QT_INTERNAL_NO_PROPAGATE_PLUGIN_INITIALIZER) + __qt_internal_propagate_object_library("${target}" "${plugin_init_target}") + endif() + else() + if(arg_OUTPUT_TARGETS) + set(${arg_OUTPUT_TARGETS} "" PARENT_SCOPE) + endif() endif() + target_compile_definitions(${target} PRIVATE QT_PLUGIN QT_DEPRECATED_WARNINGS - ${static_plugin_define} ) + + if(target_type STREQUAL "MODULE_LIBRARY") + if(NOT TARGET qt_internal_plugins) + add_custom_target(qt_internal_plugins) + _qt_internal_assign_to_internal_targets_folder(qt_internal_plugins) + endif() + add_dependencies(qt_internal_plugins ${target}) + endif() + + if(arg_MANUAL_FINALIZATION) + # Caller says they will call qt6_finalize_target() themselves later + return() + endif() + + # Defer the finalization if we can. When the caller's project requires + # CMake 3.19 or later, this makes the calls to this function concise while + # still allowing target property modification before finalization. + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.19) + # Need to wrap in an EVAL CODE or else ${target} won't be evaluated + # due to special behavior of cmake_language() argument handling + cmake_language(EVAL CODE "cmake_language(DEFER CALL qt6_finalize_target ${target})") + else() + set_target_properties("${target}" PROPERTIES _qt_is_immediately_finalized TRUE) + qt6_finalize_target("${target}") + endif() endfunction() if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) function(qt_add_plugin) qt6_add_plugin(${ARGV}) + cmake_parse_arguments(PARSE_ARGV 1 arg "" "OUTPUT_TARGETS" "") + if(arg_OUTPUT_TARGETS) + set(${arg_OUTPUT_TARGETS} ${${arg_OUTPUT_TARGETS}} PARENT_SCOPE) + endif() endfunction() endif() -# By default Qt6 forces usage of utf8 sources for consumers of Qt. -# Users can opt out of utf8 sources by calling this function with the target name of their -# application or library. +# Creates a library by forwarding arguments to add_library, applies some Qt naming file name naming +# conventions and ensures the execution of Qt specific finalizers. +function(qt6_add_library target) + cmake_parse_arguments(PARSE_ARGV 1 arg "MANUAL_FINALIZATION" "" "") + + _qt_internal_add_library("${target}" ${arg_UNPARSED_ARGUMENTS}) + set_property(TARGET ${target} PROPERTY _qt_expects_finalization TRUE) + + if(arg_MANUAL_FINALIZATION) + # Caller says they will call qt6_finalize_target() themselves later + return() + endif() + + # Defer the finalization if we can. When the caller's project requires + # CMake 3.19 or later, this makes the calls to this function concise while + # still allowing target property modification before finalization. + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.19) + # Need to wrap in an EVAL CODE or else ${target} won't be evaluated + # due to special behavior of cmake_language() argument handling + cmake_language(EVAL CODE "cmake_language(DEFER CALL qt6_finalize_target ${target})") + else() + set_target_properties("${target}" PROPERTIES _qt_is_immediately_finalized TRUE) + qt6_finalize_target("${target}") + endif() +endfunction() + +# Creates a library target by forwarding the arguments to add_library. +# +# Applies some Qt specific behaviors: +# - If no type option is specified, rather than defaulting to STATIC it defaults to STATIC or SHARED +# depending on the Qt configuration. +# - Applies Qt specific prefixes and suffixes to file names depending on platform. +function(_qt_internal_add_library target) + set(opt_args + STATIC + SHARED + MODULE + INTERFACE + OBJECT + ) + set(single_args "") + set(multi_args "") + cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}") + + set(option_type_count 0) + if(arg_STATIC) + set(type_to_create STATIC) + math(EXPR option_type_count "${option_type_count}+1") + elseif(arg_SHARED) + set(type_to_create SHARED) + math(EXPR option_type_count "${option_type_count}+1") + elseif(arg_MODULE) + set(type_to_create MODULE) + math(EXPR option_type_count "${option_type_count}+1") + elseif(arg_INTERFACE) + set(type_to_create INTERFACE) + math(EXPR option_type_count "${option_type_count}+1") + elseif(arg_OBJECT) + set(type_to_create OBJECT) + math(EXPR option_type_count "${option_type_count}+1") + endif() + + if(option_type_count GREATER 1) + message(FATAL_ERROR + "Multiple type options were given. Only one should be used." + ) + endif() + + # If no explicit type option is set, default to the flavor of the Qt build. + # This in contrast to CMake which defaults to STATIC. + if(NOT arg_STATIC AND NOT arg_SHARED AND NOT arg_MODULE AND NOT arg_INTERFACE + AND NOT arg_OBJECT) + if(DEFINED BUILD_SHARED_LIBS AND NOT QT_BUILDING_QT AND NOT QT_BUILD_STANDALONE_TESTS) + __qt_internal_setup_policy(QTP0003 "6.7.0" + "BUILD_SHARED_LIBS is set to ${BUILD_SHARED_LIBS} but it has no effect on\ + default library type created by Qt CMake API commands. The default library type\ + is set to the Qt build type.\ + This behavior can be changed by setting QTP0003 to NEW.\ + Check https://doc.qt.io/qt-6/qt-cmake-policy-qtp0003.html for policy details." + ) + qt6_policy(GET QTP0003 build_shared_libs_policy) + else() + set(build_shared_libs_policy "") + endif() + + if(build_shared_libs_policy STREQUAL "NEW" OR QT_BUILDING_QT OR QT_BUILD_STANDALONE_TESTS) + if(BUILD_SHARED_LIBS OR (NOT DEFINED BUILD_SHARED_LIBS AND QT6_IS_SHARED_LIBS_BUILD)) + set(type_to_create SHARED) + else() + set(type_to_create STATIC) + endif() + else() + if(QT6_IS_SHARED_LIBS_BUILD) + set(type_to_create SHARED) + else() + set(type_to_create STATIC) + endif() + endif() + endif() + + add_library(${target} ${type_to_create} ${arg_UNPARSED_ARGUMENTS}) + _qt_internal_disable_autorcc_zstd_when_not_supported("${target}") + _qt_internal_set_up_static_runtime_library(${target}) + + if(NOT type_to_create STREQUAL "INTERFACE" AND NOT type_to_create STREQUAL "OBJECT") + _qt_internal_apply_win_prefix_and_suffix("${target}") + endif() + + if(arg_MODULE AND APPLE) + # CMake defaults to using .so extensions for loadable modules, aka plugins, + # but Qt plugins are actually suffixed with .dylib. + set_property(TARGET "${target}" PROPERTY SUFFIX ".dylib") + endif() + + if(ANDROID) + set_property(TARGET "${target}" + PROPERTY _qt_android_apply_arch_suffix_called_from_qt_impl TRUE) + qt6_android_apply_arch_suffix("${target}") + endif() +endfunction() + +if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) + function(qt_add_library) + qt6_add_library(${ARGV}) + endfunction() +endif() + +# TODO: Remove once all repositories use qt_internal_add_example instead of add_subdirectory. +macro(_qt_internal_override_example_install_dir_to_dot) + # Set INSTALL_EXAMPLEDIR to ".". + # This overrides the install destination of unclean Qt example projects to install directly + # to CMAKE_INSTALL_PREFIX. + if(QT_INTERNAL_SET_EXAMPLE_INSTALL_DIR_TO_DOT) + set(INSTALL_EXAMPLEDIR ".") + set(_qt_internal_example_dir_set_to_dot TRUE) + endif() +endmacro() + function(qt6_allow_non_utf8_sources target) set_target_properties("${target}" PROPERTIES QT_NO_UTF8_SOURCE TRUE) endfunction() @@ -1475,3 +2775,986 @@ function(_qt_internal_apply_strict_cpp target) endif() endif() endfunction() + +# Copies properties of the dependency to the target. +# Arguments: +# PROPERTIES list of properties to copy. If not specified the following properties are copied +# by default: INCLUDE_DIRECTORIES SYSTEM_INCLUDE_DIRECTORIES COMPILE_DEFINITIONS +# COMPILE_OPTIONS COMPILE_FEATURES +# PRIVATE_ONLY copy only private properties (without INTERFACE analogues). Optional. +# INTERFACE_ONLY copy only interface properties (without non-prefixed analogues). Optional. +# Note: Not all properties have INTERFACE properties analogues. +# See https://cmake.org/cmake/help/latest/prop_tgt/EXPORT_PROPERTIES.html for details. +# +# PRIVATE_ONLY and INTERFACE_ONLY in the same call are not allowed. Omit these options to copy +# both sets. +function(_qt_internal_copy_dependency_properties target dependency) + cmake_parse_arguments(arg "INTERFACE_ONLY;PRIVATE_ONLY" "" "PROPERTIES" ${ARGN}) + if(arg_PRIVATE_ONLY AND arg_INTERFACE_ONLY) + message("Both PRIVATE_ONLY and INTERFACE_ONLY options are set.\ +Please use _qt_internal_copy_dependency_properties without these options to copy a set of +properties of both types." + ) + endif() + + if(arg_PROPERTIES) + set(common_props_to_set ${arg_PROPERTIES}) + else() + set(common_props_to_set + INCLUDE_DIRECTORIES SYSTEM_INCLUDE_DIRECTORIES + COMPILE_DEFINITIONS COMPILE_OPTIONS + COMPILE_FEATURES + ) + endif() + + set(props_to_set "") + if(NOT arg_INTERFACE_ONLY) + set(props_to_set ${common_props_to_set}) + endif() + if(NOT arg_PRIVATE_ONLY) + list(TRANSFORM common_props_to_set PREPEND INTERFACE_ + OUTPUT_VARIABLE interface_properties) + list(APPEND props_to_set ${interface_properties}) + endif() + + foreach(prop ${props_to_set}) + set_property(TARGET + "${target}" APPEND PROPERTY + ${prop} "$<TARGET_PROPERTY:${dependency},${prop}>" + ) + endforeach() +endfunction() + +function(qt6_disable_unicode_defines target) + set_target_properties(${target} PROPERTIES QT_NO_UNICODE_DEFINES TRUE) +endfunction() + +# Finalizer function for the top-level user projects. +# +# This function is currently in Technical Preview. +# Its signature and behavior might change. +function(qt6_finalize_project) + if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + message("qt6_finalize_project is called not in the top-level CMakeLists.txt.") + endif() + if(ANDROID) + _qt_internal_collect_apk_dependencies() + endif() +endfunction() + +if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) + function(qt_finalize_project) + if(QT_DEFAULT_MAJOR_VERSION EQUAL 6) + qt6_finalize_project() + else() + message(FATAL_ERROR "qt_finalize_project() is only available in Qt 6.") + endif() + endfunction() + + function(qt_disable_unicode_defines) + qt6_disable_unicode_defines(${ARGV}) + endfunction() +endif() + +function(_qt_internal_get_deploy_impl_dir var) + set(${var} "${CMAKE_BINARY_DIR}/.qt" PARENT_SCOPE) +endfunction() + +function(_qt_internal_add_deploy_support deploy_support_file) + get_filename_component(deploy_support_file "${deploy_support_file}" REALPATH) + + set(target ${QT_CMAKE_EXPORT_NAMESPACE}::Core) + get_target_property(aliased_target ${target} ALIASED_TARGET) + if(aliased_target) + set(target ${aliased_target}) + endif() + + get_property(scripts TARGET ${target} PROPERTY _qt_deploy_support_files) + if(NOT "${deploy_support_file}" IN_LIST scripts) + set_property(TARGET ${target} APPEND PROPERTY + _qt_deploy_support_files "${deploy_support_file}" + ) + endif() +endfunction() + +# Sets up the commands for use at install/deploy time +function(_qt_internal_setup_deploy_support) + if(QT_SKIP_SETUP_DEPLOYMENT) + return() + endif() + + get_property(cmake_role GLOBAL PROPERTY CMAKE_ROLE) + if(NOT cmake_role STREQUAL "PROJECT") + return() + endif() + + # Always set QT_DEPLOY_SUPPORT in the caller's scope, even if we've generated + # the deploy support file in a previous call. The project may be calling + # find_package() from sibling directories with separate variable scopes. + _qt_internal_get_deploy_impl_dir(deploy_impl_dir) + + get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG) + if(is_multi_config) + set(QT_DEPLOY_SUPPORT "${deploy_impl_dir}/QtDeploySupport-$<CONFIG>.cmake") + else() + set(QT_DEPLOY_SUPPORT "${deploy_impl_dir}/QtDeploySupport.cmake") + endif() + set(QT_DEPLOY_SUPPORT "${QT_DEPLOY_SUPPORT}" PARENT_SCOPE) + + get_property(have_generated_file GLOBAL PROPERTY _qt_have_generated_deploy_support) + if(have_generated_file) + return() + endif() + set_property(GLOBAL PROPERTY _qt_have_generated_deploy_support TRUE) + + include(GNUInstallDirs) + set(target ${QT_CMAKE_EXPORT_NAMESPACE}::Core) + get_target_property(aliased_target ${target} ALIASED_TARGET) + if(aliased_target) + set(target ${aliased_target}) + endif() + + # Generate deployment information for each target if the CMake version is recent enough. + # The call is deferred to have all targets of the projects available. + if(CMAKE_VERSION GREATER_EQUAL "3.19.0") + if(is_multi_config) + set(targets_file "${deploy_impl_dir}/QtDeployTargets-$<CONFIG>.cmake") + else() + set(targets_file "${deploy_impl_dir}/QtDeployTargets.cmake") + endif() + cmake_language(EVAL CODE + "cmake_language(DEFER + DIRECTORY [[${CMAKE_SOURCE_DIR}]] + CALL _qt_internal_write_target_deploy_info [[${targets_file}]])" + ) + _qt_internal_add_deploy_support("${targets_file}") + endif() + + # Make sure to look under the Qt bin dir with find_program, rather than randomly picking up + # a deployqt tool in the system. + # QT6_INSTALL_PREFIX is not set during Qt build, so add the hints conditionally. + set(find_program_hints) + if(QT6_INSTALL_PREFIX) + set(find_program_hints HINTS ${QT6_INSTALL_PREFIX}/${QT6_INSTALL_BINS}) + endif() + + # In the generator expression logic below, we need safe_target_file because + # CMake evaluates expressions in both the TRUE and FALSE branches of $<IF:...>. + # We still need a target to give to $<TARGET_FILE:...> when we have no deploy + # tool, so we cannot use something like $<TARGET_FILE:macdeployqt> directly. + if(APPLE AND NOT IOS) + find_program(MACDEPLOYQT_EXECUTABLE macdeployqt + ${find_program_hints}) + set(fallback "$<$<BOOL:${MACDEPLOYQT_EXECUTABLE}>:${MACDEPLOYQT_EXECUTABLE}>") + set(target_if_exists "$<TARGET_NAME_IF_EXISTS:${QT_CMAKE_EXPORT_NAMESPACE}::macdeployqt>") + set(have_deploy_tool "$<BOOL:${target_if_exists}>") + set(safe_target_file + "$<TARGET_FILE:$<IF:${have_deploy_tool},${target_if_exists},${target}>>") + set(__QT_DEPLOY_TOOL "$<IF:${have_deploy_tool},${safe_target_file},${fallback}>") + elseif(WIN32) + find_program(WINDEPLOYQT_EXECUTABLE windeployqt + ${find_program_hints}) + set(fallback "$<$<BOOL:${WINDEPLOYQT_EXECUTABLE}>:${WINDEPLOYQT_EXECUTABLE}>") + set(target_if_exists "$<TARGET_NAME_IF_EXISTS:${QT_CMAKE_EXPORT_NAMESPACE}::windeployqt>") + set(have_deploy_tool "$<BOOL:${target_if_exists}>") + set(safe_target_file + "$<TARGET_FILE:$<IF:${have_deploy_tool},${target_if_exists},${target}>>") + set(__QT_DEPLOY_TOOL "$<IF:${have_deploy_tool},${safe_target_file},${fallback}>") + elseif(UNIX AND NOT APPLE AND NOT ANDROID AND NOT CMAKE_CROSSCOMPILING) + set(__QT_DEPLOY_TOOL "GRD") + else() + # Android is handled as a build target, not via this install-based approach. + # Therefore, we don't consider androiddeployqt here. + set(__QT_DEPLOY_TOOL "") + endif() + + # Determine whether this is a multi-config build with a Debug configuration. + set(is_multi_config_build_with_debug_config FALSE) + get_target_property(target_is_imported ${target} IMPORTED) + if(target_is_imported) + get_target_property(target_imported_configs ${target} IMPORTED_CONFIGURATIONS) + list(LENGTH target_imported_configs target_imported_configs_length) + if(target_imported_configs_length GREATER "1" + AND "DEBUG" IN_LIST target_imported_configs) + set(is_multi_config_build_with_debug_config TRUE) + endif() + endif() + + _qt_internal_add_deploy_support("${CMAKE_CURRENT_LIST_DIR}/Qt6CoreDeploySupport.cmake") + + set(deploy_ignored_lib_dirs "") + if(__QT_DEPLOY_TOOL STREQUAL "GRD" AND NOT "${QT6_INSTALL_PREFIX}" STREQUAL "") + # Set up the directories we want to ignore when running file(GET_RUNTIME_DEPENDENCIES). + # If the Qt prefix is the root of one of those directories, don't ignore that directory. + # For example, if Qt's installation prefix is /usr, then we don't want to ignore /usr/lib. + foreach(link_dir IN LISTS CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES) + file(RELATIVE_PATH relative_dir "${QT6_INSTALL_PREFIX}" "${link_dir}") + if(relative_dir STREQUAL "") + # The Qt prefix is exactly ${link_dir}. + continue() + endif() + if(IS_ABSOLUTE "${relative_dir}" OR relative_dir MATCHES "^\\.\\./") + # The Qt prefix is outside of ${link_dir}. + list(APPEND deploy_ignored_lib_dirs "${link_dir}") + endif() + endforeach() + endif() + + # Check whether we will have to adjust the RPATH of plugins. + if("${QT_DEPLOY_FORCE_ADJUST_RPATHS}" STREQUAL "") + set(must_adjust_plugins_rpath "") + if(NOT CMAKE_SYSTEM_NAME STREQUAL "Windows" + AND NOT CMAKE_INSTALL_LIBDIR STREQUAL QT6_INSTALL_LIBS) + set(must_adjust_plugins_rpath ON) + endif() + else() + set(must_adjust_plugins_rpath "${QT_DEPLOY_FORCE_ADJUST_RPATHS}") + endif() + + # Find the patchelf executable if necessary. + if(must_adjust_plugins_rpath) + if(CMAKE_VERSION VERSION_LESS "3.21") + set(QT_DEPLOY_USE_PATCHELF ON) + endif() + if(QT_DEPLOY_USE_PATCHELF) + find_program(QT_DEPLOY_PATCHELF_EXECUTABLE patchelf) + if(NOT QT_DEPLOY_PATCHELF_EXECUTABLE) + set(QT_DEPLOY_PATCHELF_EXECUTABLE "patchelf") + message(WARNING "The patchelf executable could not be located. " + "To use Qt's CMake deployment API, install patchelf or upgrade CMake to 3.21 " + "or newer.") + endif() + endif() + endif() + + # Generate path to the target (not host) qtpaths file. Needed for windeployqt when + # cross-compiling from an x86_64 host to an arm64 target, so it knows which architecture + # libraries should be deployed. + if(CMAKE_HOST_WIN32) + if(CMAKE_CROSSCOMPILING) + set(qt_paths_ext ".bat") + else() + set(qt_paths_ext ".exe") + endif() + else() + set(qt_paths_ext "") + endif() + + + + set(target_qtpaths_path "") + set(qtpaths_prefix "${QT6_INSTALL_PREFIX}/${QT6_INSTALL_BINS}") + get_property(qt_major_version TARGET "${target}" PROPERTY INTERFACE_QT_MAJOR_VERSION) + if(qt_major_version) + set(target_qtpaths_with_major_version_path + "${qtpaths_prefix}/qtpaths${qt_major_version}${qt_paths_ext}") + if(EXISTS "${target_qtpaths_with_major_version_path}") + set(target_qtpaths_path "${target_qtpaths_with_major_version_path}") + endif() + endif() + + if(NOT target_qtpaths_path) + set(target_qtpaths_path_without_version "${qtpaths_prefix}/qtpaths${qt_paths_ext}") + if(EXISTS "${target_qtpaths_path_without_version}") + set(target_qtpaths_path "${target_qtpaths_path_without_version}") + endif() + endif() + + if(NOT target_qtpaths_path) + message(DEBUG "No qtpaths executable found for deployment purposes.") + endif() + + file(GENERATE OUTPUT "${QT_DEPLOY_SUPPORT}" CONTENT +"cmake_minimum_required(VERSION 3.16...3.21) + +# These are part of the public API. Projects should use them to provide a +# consistent set of prefix-relative destinations. +if(NOT QT_DEPLOY_BIN_DIR) + set(QT_DEPLOY_BIN_DIR \"${CMAKE_INSTALL_BINDIR}\") +endif() +if(NOT QT_DEPLOY_LIBEXEC_DIR) + set(QT_DEPLOY_LIBEXEC_DIR \"${CMAKE_INSTALL_LIBEXECDIR}\") +endif() +if(NOT QT_DEPLOY_LIB_DIR) + set(QT_DEPLOY_LIB_DIR \"${CMAKE_INSTALL_LIBDIR}\") +endif() +if(NOT QT_DEPLOY_PLUGINS_DIR) + set(QT_DEPLOY_PLUGINS_DIR \"plugins\") +endif() +if(NOT QT_DEPLOY_QML_DIR) + set(QT_DEPLOY_QML_DIR \"qml\") +endif() +if(NOT QT_DEPLOY_TRANSLATIONS_DIR) + set(QT_DEPLOY_TRANSLATIONS_DIR \"translations\") +endif() +if(NOT QT_DEPLOY_PREFIX) + set(QT_DEPLOY_PREFIX \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}\") +endif() +if(QT_DEPLOY_PREFIX STREQUAL \"\") + set(QT_DEPLOY_PREFIX .) +endif() +if(NOT QT_DEPLOY_IGNORED_LIB_DIRS) + set(QT_DEPLOY_IGNORED_LIB_DIRS \"${deploy_ignored_lib_dirs}\") +endif() + +# These are internal implementation details. They may be removed at any time. +set(__QT_DEPLOY_SYSTEM_NAME \"${CMAKE_SYSTEM_NAME}\") +set(__QT_DEPLOY_IS_SHARED_LIBS_BUILD \"${QT6_IS_SHARED_LIBS_BUILD}\") +set(__QT_DEPLOY_TOOL \"${__QT_DEPLOY_TOOL}\") +set(__QT_DEPLOY_IMPL_DIR \"${deploy_impl_dir}\") +set(__QT_DEPLOY_VERBOSE \"${QT_ENABLE_VERBOSE_DEPLOYMENT}\") +set(__QT_CMAKE_EXPORT_NAMESPACE \"${QT_CMAKE_EXPORT_NAMESPACE}\") +set(__QT_DEPLOY_GENERATOR_IS_MULTI_CONFIG \"${is_multi_config}\") +set(__QT_DEPLOY_ACTIVE_CONFIG \"$<CONFIG>\") +set(__QT_NO_CREATE_VERSIONLESS_FUNCTIONS \"${QT_NO_CREATE_VERSIONLESS_FUNCTIONS}\") +set(__QT_DEFAULT_MAJOR_VERSION \"${QT_DEFAULT_MAJOR_VERSION}\") +set(__QT_DEPLOY_QT_ADDITIONAL_PACKAGES_PREFIX_PATH \"${QT_ADDITIONAL_PACKAGES_PREFIX_PATH}\") +set(__QT_DEPLOY_QT_INSTALL_PREFIX \"${QT6_INSTALL_PREFIX}\") +set(__QT_DEPLOY_QT_INSTALL_BINS \"${QT6_INSTALL_BINS}\") +set(__QT_DEPLOY_QT_INSTALL_DATA \"${QT6_INSTALL_DATA}\") +set(__QT_DEPLOY_QT_INSTALL_LIBEXECS \"${QT6_INSTALL_LIBEXECS}\") +set(__QT_DEPLOY_QT_INSTALL_PLUGINS \"${QT6_INSTALL_PLUGINS}\") +set(__QT_DEPLOY_QT_INSTALL_TRANSLATIONS \"${QT6_INSTALL_TRANSLATIONS}\") +set(__QT_DEPLOY_TARGET_QT_PATHS_PATH \"${target_qtpaths_path}\") +set(__QT_DEPLOY_PLUGINS \"\") +set(__QT_DEPLOY_MUST_ADJUST_PLUGINS_RPATH \"${must_adjust_plugins_rpath}\") +set(__QT_DEPLOY_USE_PATCHELF \"${QT_DEPLOY_USE_PATCHELF}\") +set(__QT_DEPLOY_PATCHELF_EXECUTABLE \"${QT_DEPLOY_PATCHELF_EXECUTABLE}\") +set(__QT_DEPLOY_QT_IS_MULTI_CONFIG_BUILD_WITH_DEBUG \"${is_multi_config_build_with_debug_config}\") +set(__QT_DEPLOY_QT_DEBUG_POSTFIX \"${QT6_DEBUG_POSTFIX}\") + +# Define the CMake commands to be made available during deployment. +set(__qt_deploy_support_files + \"$<JOIN:$<TARGET_GENEX_EVAL:${target},$<TARGET_PROPERTY:${target},_qt_deploy_support_files>>,\" + \">\" +) +foreach(__qt_deploy_support_file IN LISTS __qt_deploy_support_files) + include(\"\${__qt_deploy_support_file}\") +endforeach() + +unset(__qt_deploy_support_file) +unset(__qt_deploy_support_files) +") +endfunction() + +# Write deployment information for the targets of the project. +function(_qt_internal_write_target_deploy_info out_file) + set(targets "") + _qt_internal_collect_buildsystem_targets(targets + "${CMAKE_SOURCE_DIR}" INCLUDE EXECUTABLE SHARED_LIBRARY MODULE_LIBRARY) + set(content "") + foreach(target IN LISTS targets) + set(var_prefix "__QT_DEPLOY_TARGET_${target}") + string(APPEND content "set(${var_prefix}_FILE $<TARGET_FILE:${target}>)\n") + if(WIN32 AND CMAKE_VERSION GREATER_EQUAL "3.21") + string(APPEND content + "set(${var_prefix}_RUNTIME_DLLS $<TARGET_RUNTIME_DLLS:${target}>)\n") + endif() + endforeach() + file(GENERATE OUTPUT "${out_file}" CONTENT "${content}") +endfunction() + +function(_qt_internal_is_examples_deployment_supported_in_current_config out_var out_var_reason) + # Deployment API doesn't work when examples / tests are built in-tree of a prefix qt build. + if(QT_BUILDING_QT AND QT_WILL_INSTALL AND NOT QT_INTERNAL_BUILD_STANDALONE_PARTS) + set(deployment_supported FALSE) + set(not_supported_reason "PREFIX_BUILD") + else() + set(deployment_supported TRUE) + set(not_supported_reason "") + endif() + + set(${out_var} "${deployment_supported}" PARENT_SCOPE) + set(${out_var_reason} "${not_supported_reason}" PARENT_SCOPE) +endfunction() + +function(_qt_internal_should_skip_deployment_api out_var out_var_reason) + set(skip_deployment FALSE) + _qt_internal_is_examples_deployment_supported_in_current_config( + deployment_supported + not_supported_reason + ) + + # Allow opting out of deployment, so that we can add deployment api to all our examples, + # but only run it in the CI for a select few, to avoid the overhead of deploying all examples. + if(QT_INTERNAL_SKIP_DEPLOYMENT OR (NOT deployment_supported)) + set(skip_deployment TRUE) + endif() + + set(reason "") + if(NOT deployment_supported) + set(reason "${not_supported_reason}") + elseif(QT_INTERNAL_SKIP_DEPLOYMENT) + set(reason "SKIP_REQUESTED") + endif() + + set(${out_var} "${skip_deployment}" PARENT_SCOPE) + set(${out_var_reason} "${reason}" PARENT_SCOPE) +endfunction() + +function(_qt_internal_should_skip_post_build_deployment_api out_var out_var_reason) + set(skip_deployment FALSE) + set(deployment_supported TRUE) + + # Allow opting out of deployment, so that we can add deployment api to all our examples, + # but only run it in the CI for a select few, to avoid the overhead of deploying all examples. + if(QT_INTERNAL_SKIP_DEPLOYMENT OR (NOT deployment_supported)) + set(skip_deployment TRUE) + endif() + + set(reason "") + if(NOT deployment_supported) + set(reason "REASON_UNSPECIFIED") + elseif(QT_INTERNAL_SKIP_DEPLOYMENT) + set(reason "SKIP_REQUESTED") + endif() + + set(${out_var} "${skip_deployment}" PARENT_SCOPE) + set(${out_var_reason} "${reason}" PARENT_SCOPE) +endfunction() + +# Generate a deploy script that does nothing aside from showing a warning message. +# The warning can be hidden by setting the QT_INTERNAL_HIDE_NO_OP_DEPLOYMENT_WARNING variable. +function(_qt_internal_generate_no_op_deploy_script) + set(no_value_options + ) + set(single_value_options + FUNCTION_NAME + NAME + OUTPUT_SCRIPT + SKIP_REASON + TARGET + ) + set(multi_value_options + ) + + cmake_parse_arguments(PARSE_ARGV 0 arg + "${no_value_options}" "${single_value_options}" "${multi_value_options}" + ) + + if(NOT arg_OUTPUT_SCRIPT) + message(FATAL_ERROR "No OUTPUT_SCRIPT option specified") + endif() + if(NOT arg_FUNCTION_NAME) + message(FATAL_ERROR "No FUNCTION_NAME option specified") + endif() + + set(generate_args "") + if(arg_NAME) + list(APPEND generate_args NAME "${arg_NAME}") + endif() + if(arg_TARGET) + list(APPEND generate_args TARGET "${arg_TARGET}") + endif() + + set(function_name "${arg_FUNCTION_NAME}") + + # The empty space is required, otherwise + set(content " +message(DEBUG \"Running no-op deployment script because QT_INTERNAL_SKIP_DEPLOYMENT was ON.\") +") + if(NOT QT_INTERNAL_HIDE_NO_OP_DEPLOYMENT_WARNING AND arg_SKIP_REASON STREQUAL "PREFIX_BUILD") + set(content " +message(STATUS \"${function_name}(TARGET ${arg_TARGET}) is a no-op for prefix \" +\"non-standalone builds due to various issues. Consider using a -no-prefix build \" +\"or qt-internal-configure-tests or qt-internal-configure-examples if you want deployment to run.\") +") + endif() + + qt6_generate_deploy_script( + ${generate_args} + OUTPUT_SCRIPT deploy_script + CONTENT "${content}") + + set("${arg_OUTPUT_SCRIPT}" "${deploy_script}" PARENT_SCOPE) +endfunction() + +# We basically mirror CMake's policy setup +# A policy can be set to OLD, set to NEW or unset +# unset is the default state +# +function(qt6_policy mode policy behaviorOrVariable) + # When building Qt, tests and examples might expect a policy to be known, but they won't be + # known depending on which scope or when a find_package(Module) with the respective policy + # is called. Check the global list of known policies to accommodate that. + if(QT_BUILDING_QT AND NOT DEFINED QT_KNOWN_POLICY_${policy}) + get_property(global_known_policies GLOBAL PROPERTY _qt_global_known_policies) + if(policy IN_LIST global_known_policies) + set(QT_KNOWN_POLICY_${policy} TRUE) + endif() + endif() + + if (NOT DEFINED QT_KNOWN_POLICY_${policy}) + message(FATAL_ERROR + "${policy} is not a known Qt policy. Did you include the necessary Qt module?" + ) + endif() + if (${mode} STREQUAL "SET") + set(behavior ${behaviorOrVariable}) + if (${behavior} STREQUAL "NEW" OR ${behavior} STREQUAL "OLD") + set(__QT_INTERNAL_POLICY_${policy} ${behavior} PARENT_SCOPE) + else() + message(FATAL_ERROR "Qt policies must be either set to NEW or OLD, but got ${behavior}") + endif() + else(${mode} STREQUAL "GET") + set(variable "${behaviorOrVariable}") + set("${variable}" "${__QT_INTERNAL_POLICY_${policy}}" PARENT_SCOPE) + endif() +endfunction() + +# Internal helper function; can be used in any module before doing a policy check +function(__qt_internal_setup_policy policy sinceversion policyexplanation) + if(DEFINED __QT_INTERNAL_POLICY_${policy}) + if (__QT_INTERNAL_POLICY_${policy} STREQUAL "OLD") + # policy is explicitly disabled + message(DEPRECATION + "Qt policy ${policy} is set to OLD. " + "Support for the old behavior will be removed in a future major version of Qt." + ) + endif() + #else: policy is already enabled, nothing to do + elseif (${sinceversion} VERSION_LESS_EQUAL __qt_policy_check_version) + # we cannot use the public function here as we want to set it in parent scope + set(__QT_INTERNAL_POLICY_${policy} "NEW" PARENT_SCOPE) + elseif(NOT "${QT_NO_SHOW_OLD_POLICY_WARNINGS}") + message(AUTHOR_WARNING + "Qt policy ${policy} is not set: " + "${policyexplanation} " + "Use the qt_policy command to set the policy and suppress this warning.\n" + ) + endif() +endfunction() + +# Note this needs to be a macro because it sets variables intended for the +# calling scope. +macro(qt6_standard_project_setup) + # A parent project might want to prevent child projects pulled in with + # add_subdirectory() from changing the parent's preferred arrangement. + # They can set this variable to true to effectively disable this function. + if(NOT QT_NO_STANDARD_PROJECT_SETUP) + + set(__qt_sps_args_option) + set(__qt_sps_args_single + REQUIRES + SUPPORTS_UP_TO + I18N_SOURCE_LANGUAGE + I18N_NATIVE_LANGUAGE # intermediate, remove after dependencies trickled through + ) + set(__qt_sps_args_multi + I18N_TRANSLATED_LANGUAGES + I18N_LANGUAGES # intermediate, remove after dependencies trickled through + ) + cmake_parse_arguments(__qt_sps_arg + "${__qt_sps_args_option}" + "${__qt_sps_args_single}" + "${__qt_sps_args_multi}" + ${ARGN} + ) + + # intermediate, remove after dependencies trickled through + if(DEFINED arg_I18N_NATIVE_LANGUAGE) + set(arg_I18N_SOURCE_LANGUAGE ${arg_I18N_NATIVE_LANGUAGE}) + endif() + if(DEFINED arg_I18N_LANGUAGES) + set(arg_I18N_TRANSLATED_LANGUAGES ${arg_I18N_LANGUAGES}) + endif() + + if(__qt_sps_arg_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unexpected arguments: ${arg_UNPARSED_ARGUMENTS}") + endif() + + # Set the Qt CMake policy based on the requested version(s) + set(__qt_policy_check_version "6.0.0") + if(Qt6_VERSION_MAJOR) + set(__qt_current_version + "${Qt6_VERSION_MAJOR}.${Qt6_VERSION_MINOR}.${Qt6_VERSION_PATCH}") + elseif(QT_BUILDING_QT) + set(__qt_current_version + "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") + else() + message(FATAL_ERROR "Can not determine Qt version.") + endif() + if(__qt_sps_arg_REQUIRES) + if("${__qt_current_version}" VERSION_LESS "${__qt_sps_arg_REQUIRES}") + message(FATAL_ERROR + "Project required a Qt minimum version of ${__qt_sps_arg_REQUIRES}, " + "but current version is only ${__qt_current_version}.") + endif() + set(__qt_policy_check_version "${__qt_sps_arg_REQUIRES}") + endif() + if(__qt_sps_arg_SUPPORTS_UP_TO) + if(__qt_sps_arg_REQUIRES) + if(${__qt_sps_arg_SUPPORTS_UP_TO} VERSION_LESS ${__qt_sps_arg_REQUIRES}) + message(FATAL_ERROR "SUPPORTS_UP_TO must be larger than or equal to REQUIRES.") + endif() + set(__qt_policy_check_version "${__qt_sps_arg_SUPPORTS_UP_TO}") + else() + message(FATAL_ERROR "Please specify the REQUIRES as well.") + endif() + endif() + + # All changes below this point should not result in a change to an + # existing value, except for CMAKE_INSTALL_RPATH which may append new + # values (but no duplicates). + + # Use standard install locations, provided by GNUInstallDirs. All + # platforms should have this included so that we know the + # CMAKE_INSTALL_xxxDIR variables will be set. + include(GNUInstallDirs) + if(WIN32) + # Windows has no RPATH support, so we need all non-plugin DLLs in + # the same directory as application executables if we want to be + # able to run them without having to augment the PATH environment + # variable. Don't discard an existing value in case the project has + # already set this to somewhere else. Our setting is somewhat + # opinionated, so make it easy for projects to choose something else. + if(NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + endif() + elseif(NOT APPLE) + # Apart from Windows and Apple, most other platforms support RPATH + # and $ORIGIN. Make executables and non-static libraries use an + # install RPATH that allows them to find library dependencies if the + # project installs things to the directories defined by the + # CMAKE_INSTALL_xxxDIR variables (which is what CMake's defaults + # are based on). + file(RELATIVE_PATH __qt_relDir + ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR} + ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR} + ) + list(APPEND CMAKE_INSTALL_RPATH $ORIGIN $ORIGIN/${__qt_relDir}) + list(REMOVE_DUPLICATES CMAKE_INSTALL_RPATH) + unset(__qt_reldir) + endif() + + # Turn these on by default, unless they are already set. Projects can + # always turn off any they really don't want after we return. + foreach(auto_set IN ITEMS MOC UIC) + if(NOT DEFINED CMAKE_AUTO${auto_set}) + set(CMAKE_AUTO${auto_set} TRUE) + endif() + endforeach() + + # Enable folder support for IDEs. CMake >= 3.26 enables USE_FOLDERS by default but this is + # guarded by CMake policy CMP0143. + get_property(__qt_use_folders GLOBAL PROPERTY USE_FOLDERS) + if(__qt_use_folders OR "${__qt_use_folders}" STREQUAL "") + set_property(GLOBAL PROPERTY USE_FOLDERS ON) + get_property(__qt_qt_targets_folder GLOBAL PROPERTY QT_TARGETS_FOLDER) + if("${__qt_qt_targets_folder}" STREQUAL "") + set(__qt_qt_targets_folder QtInternalTargets) + set_property(GLOBAL PROPERTY QT_TARGETS_FOLDER ${__qt_qt_targets_folder}) + endif() + get_property(__qt_autogen_targets_folder GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER) + if("${__qt_autogen_targets_folder}" STREQUAL "") + set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER ${__qt_qt_targets_folder}) + endif() + endif() + + # Hide generated files in dedicated folder. Unfortunately we can't use a + # top level "Generated Files" folder for this, as CMake will then put the + # folder first in the list of folders, whereas we want to keep Sources and + # Headers front and center. See also _qt_internal_finalize_source_groups + set_property(GLOBAL PROPERTY AUTOGEN_SOURCE_GROUP "Source Files/Generated") + + # Treat metatypes JSON files as generated. We propagate these INTERFACE_SOURCES, + # due to CMake's lack of a generic mechanism for property inheritance (see + # https://gitlab.kitware.com/cmake/cmake/-/issues/20416), but we don't want + # them to clutter up the user's project. + source_group("Source Files/Generated" REGULAR_EXPRESSION "(_metatypes\\.json)$") + + # I18N support. + if(DEFINED __qt_sps_arg_I18N_TRANSLATED_LANGUAGES + AND NOT DEFINED QT_I18N_TRANSLATED_LANGUAGES) + set(QT_I18N_TRANSLATED_LANGUAGES ${__qt_sps_arg_I18N_TRANSLATED_LANGUAGES}) + endif() + if(NOT DEFINED __qt_sps_arg_I18N_SOURCE_LANGUAGE) + set(__qt_sps_arg_I18N_SOURCE_LANGUAGE en) + endif() + if(NOT DEFINED QT_I18N_SOURCE_LANGUAGE) + set(QT_I18N_SOURCE_LANGUAGE ${__qt_sps_arg_I18N_SOURCE_LANGUAGE}) + endif() + endif() +endmacro() + +if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) + macro(qt_standard_project_setup) + qt6_standard_project_setup(${ARGV}) + endmacro() + macro(qt_policy) + qt6_policy(${ARGV}) + endmacro() +endif() + +function(qt6_generate_deploy_script) + set(no_value_options "") + set(single_value_options + CONTENT + OUTPUT_SCRIPT + NAME + TARGET + + # TODO: For backward compatibility / transitional use only, + # remove at some point + FILENAME_VARIABLE + ) + set(multi_value_options "") + cmake_parse_arguments(PARSE_ARGV 0 arg + "${no_value_options}" "${single_value_options}" "${multi_value_options}" + ) + if(arg_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unexpected arguments: ${arg_UNPARSED_ARGUMENTS}") + endif() + + # TODO: Remove when FILENAME_VARIABLE is fully removed + # Handle the slow deprecation of FILENAME_VARIABLE + if(arg_FILENAME_VARIABLE) + if(arg_OUTPUT_SCRIPT AND NOT arg_FILENAME_VARIABLE STREQUAL arg_OUTPUT_SCRIPT) + message(FATAL_ERROR + "Both FILENAME_VARIABLE and OUTPUT_SCRIPT were given and were different. " + "Only one of the two should be used." + ) + endif() + message(AUTHOR_WARNING + "The FILENAME_VARIABLE keyword is deprecated and will be removed soon. Please use OUTPUT_SCRIPT instead.") + set(arg_OUTPUT_SCRIPT "${arg_FILENAME_VARIABLE}") + unset(arg_FILENAME_VARIABLE) + endif() + + if(NOT arg_OUTPUT_SCRIPT) + message(FATAL_ERROR "OUTPUT_SCRIPT must be specified") + endif() + + if("${arg_CONTENT}" STREQUAL "") + message(FATAL_ERROR "CONTENT must be specified") + endif() + + # Check whether manual finalization is needed. + if(CMAKE_VERSION VERSION_LESS "3.19") + get_target_property(is_immediately_finalized ${arg_TARGET} _qt_is_immediately_finalized) + if(is_immediately_finalized) + message(WARNING + "Deployment of plugins for target '${arg_TARGET}' will not work. " + "Either, upgrade CMake to version 3.19 or newer, or call " + "qt_finalize_target(${arg_TARGET}) after generating the deployment script." + ) + endif() + endif() + + # Mark the target as "to be deployed". + set_property(TARGET ${arg_TARGET} PROPERTY _qt_marked_for_deployment ON) + + # If the target already was finalized, maybe because it was defined in a subdirectory, generate + # the plugin deployment information here. + get_target_property(is_finalized "${arg_TARGET}" _qt_is_finalized) + if(is_finalized) + __qt_internal_generate_plugin_deployment_info(${arg_TARGET}) + endif() + + # Create a file name that will be unique for this target and the combination + # of arguments passed to this command. This allows the project to call us + # multiple times with different arguments for the same target (e.g. to + # create deployment scripts for different scenarios). + set(file_base_name "custom") + if(NOT "${arg_NAME}" STREQUAL "") + set(file_base_name "${arg_NAME}") + elseif(NOT "${arg_TARGET}" STREQUAL "") + set(file_base_name "${arg_TARGET}") + endif() + string(MAKE_C_IDENTIFIER "${file_base_name}" target_id) + string(SHA1 args_hash "${ARGV}") + string(SUBSTRING "${args_hash}" 0 10 short_hash) + _qt_internal_get_deploy_impl_dir(deploy_impl_dir) + set(deploy_script "${deploy_impl_dir}/deploy_${target_id}_${short_hash}") + get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG) + if(is_multi_config) + set(config_infix "-$<CONFIG>") + else() + set(config_infix "") + endif() + string(APPEND deploy_script "${config_infix}.cmake") + set(${arg_OUTPUT_SCRIPT} "${deploy_script}" PARENT_SCOPE) + + set(boiler_plate "include(${QT_DEPLOY_SUPPORT}) +include(\"\${CMAKE_CURRENT_LIST_DIR}/${arg_TARGET}-plugins${config_infix}.cmake\" OPTIONAL) +set(__QT_DEPLOY_ALL_MODULES_FOUND_VIA_FIND_PACKAGE \"${QT_ALL_MODULES_FOUND_VIA_FIND_PACKAGE}\") +") + list(TRANSFORM arg_CONTENT REPLACE "\\$" "\$") + file(GENERATE OUTPUT ${deploy_script} CONTENT "${boiler_plate}${arg_CONTENT}") +endfunction() + +if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) + macro(qt_generate_deploy_script) + if(QT_DEFAULT_MAJOR_VERSION EQUAL 6) + qt6_generate_deploy_script(${ARGV}) + else() + message(FATAL_ERROR "qt_generate_deploy_script() is only available in Qt 6.") + endif() + endmacro() +endif() + +function(qt6_generate_deploy_app_script) + # We use a TARGET keyword option instead of taking the target as the first + # positional argument. This is to keep open the possibility of deploying + # an app for which we don't have a target (e.g. an application from a + # third party project that the caller may want to include in their own + # package). We would add an EXECUTABLE keyword for that, which would be + # mutually exclusive with the TARGET keyword. + set(no_value_options + NO_TRANSLATIONS + NO_COMPILER_RUNTIME + NO_UNSUPPORTED_PLATFORM_ERROR + ) + set(single_value_options + TARGET + OUTPUT_SCRIPT + + # TODO: For backward compatibility / transitional use only, + # remove at some point + FILENAME_VARIABLE + ) + set(qt_deploy_runtime_dependencies_options + # These options are forwarded as is to qt_deploy_runtime_dependencies. + DEPLOY_TOOL_OPTIONS + PRE_INCLUDE_REGEXES + PRE_EXCLUDE_REGEXES + POST_INCLUDE_REGEXES + POST_EXCLUDE_REGEXES + POST_INCLUDE_FILES + POST_EXCLUDE_FILES + ) + set(multi_value_options + ${qt_deploy_runtime_dependencies_options} + ) + cmake_parse_arguments(PARSE_ARGV 0 arg + "${no_value_options}" "${single_value_options}" "${multi_value_options}" + ) + if(arg_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unexpected arguments: ${arg_UNPARSED_ARGUMENTS}") + endif() + if(NOT arg_TARGET) + message(FATAL_ERROR "TARGET must be specified") + endif() + + # TODO: Remove when FILENAME_VARIABLE is fully removed + # Handle the slow deprecation of FILENAME_VARIABLE + if(arg_FILENAME_VARIABLE) + if(arg_OUTPUT_SCRIPT AND NOT arg_FILENAME_VARIABLE STREQUAL arg_OUTPUT_SCRIPT) + message(FATAL_ERROR + "Both FILENAME_VARIABLE and OUTPUT_SCRIPT were given and were different. " + "Only one of the two should be used." + ) + endif() + message(AUTHOR_WARNING + "The FILENAME_VARIABLE keyword is deprecated and will be removed soon. Please use OUTPUT_SCRIPT instead.") + set(arg_OUTPUT_SCRIPT "${arg_FILENAME_VARIABLE}") + unset(arg_FILENAME_VARIABLE) + endif() + + if(NOT arg_OUTPUT_SCRIPT) + message(FATAL_ERROR "OUTPUT_SCRIPT must be specified") + endif() + + get_target_property(is_bundle ${arg_TARGET} MACOSX_BUNDLE) + + set(unsupported_platform_extra_message "") + if(QT6_IS_SHARED_LIBS_BUILD) + set(qt_build_type_string "shared Qt libs") + else() + set(qt_build_type_string "static Qt libs") + endif() + + if(CMAKE_CROSSCOMPILING) + string(APPEND qt_build_type_string ", cross-compiled") + endif() + + if(NOT is_bundle) + string(APPEND qt_build_type_string ", non-bundle app") + set(unsupported_platform_extra_message + "Executable targets have to be app bundles to use this command on Apple platforms.") + endif() + + set(generate_args + TARGET ${arg_TARGET} + OUTPUT_SCRIPT deploy_script + ) + + set(common_deploy_args "") + if(arg_NO_TRANSLATIONS) + string(APPEND common_deploy_args " NO_TRANSLATIONS\n") + endif() + if(arg_NO_COMPILER_RUNTIME) + string(APPEND common_deploy_args " NO_COMPILER_RUNTIME\n") + endif() + + # Forward the arguments that are exactly the same for qt_deploy_runtime_dependencies. + foreach(var IN LISTS qt_deploy_runtime_dependencies_options) + if(NOT "${arg_${var}}" STREQUAL "") + list(APPEND common_deploy_args ${var} ${arg_${var}}) + endif() + endforeach() + + _qt_internal_should_skip_deployment_api(skip_deployment skip_reason) + if(skip_deployment) + _qt_internal_generate_no_op_deploy_script( + FUNCTION_NAME "qt6_generate_deploy_app_script" + SKIP_REASON "${skip_reason}" + ${generate_args} + ) + elseif(APPLE AND NOT IOS AND QT6_IS_SHARED_LIBS_BUILD AND is_bundle) + # TODO: Consider handling non-bundle applications in the future using the generic cmake + # runtime dependency feature. + qt6_generate_deploy_script(${generate_args} + CONTENT " +qt6_deploy_runtime_dependencies( + EXECUTABLE $<TARGET_FILE_NAME:${arg_TARGET}>.app +${common_deploy_args}) +") + + elseif(WIN32 AND QT6_IS_SHARED_LIBS_BUILD) + qt6_generate_deploy_script(${generate_args} + CONTENT " +qt6_deploy_runtime_dependencies( + EXECUTABLE $<TARGET_FILE:${arg_TARGET}> + GENERATE_QT_CONF +${common_deploy_args}) +") + + elseif(UNIX AND NOT APPLE AND NOT ANDROID AND QT6_IS_SHARED_LIBS_BUILD + AND NOT CMAKE_CROSSCOMPILING) + qt6_generate_deploy_script(${generate_args} + CONTENT " +qt6_deploy_runtime_dependencies( + EXECUTABLE $<TARGET_FILE:${arg_TARGET}> + GENERATE_QT_CONF +${common_deploy_args}) +") + + elseif(NOT arg_NO_UNSUPPORTED_PLATFORM_ERROR AND NOT QT_INTERNAL_NO_UNSUPPORTED_PLATFORM_ERROR) + # Currently we don't deploy runtime dependencies if cross-compiling or using a static Qt. + # Error out by default unless the project opted out of the error. + # This provides us a migration path in the future without breaking compatibility promises. + message(FATAL_ERROR + "Support for installing runtime dependencies is not implemented for " + "this target platform (${CMAKE_SYSTEM_NAME}, ${qt_build_type_string}). " + ${unsupported_platform_extra_message} + ) + else() + set(skip_message + "_qt_internal_show_skip_runtime_deploy_message(\"${qt_build_type_string}\"") + if(unsupported_platform_extra_message) + string(APPEND skip_message + "\n EXTRA_MESSAGE \"${unsupported_platform_extra_message}\"") + endif() + string(APPEND skip_message "\n)") + qt6_generate_deploy_script(${generate_args} CONTENT "${skip_message}") + endif() + + set(${arg_OUTPUT_SCRIPT} "${deploy_script}" PARENT_SCOPE) +endfunction() + +if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) + macro(qt_generate_deploy_app_script) + qt6_generate_deploy_app_script(${ARGV}) + endmacro() +endif() |