diff options
Diffstat (limited to 'src/linguist/Qt6LinguistToolsMacros.cmake')
-rw-r--r-- | src/linguist/Qt6LinguistToolsMacros.cmake | 596 |
1 files changed, 498 insertions, 98 deletions
diff --git a/src/linguist/Qt6LinguistToolsMacros.cmake b/src/linguist/Qt6LinguistToolsMacros.cmake index f0d99dd4d..5e3baecc2 100644 --- a/src/linguist/Qt6LinguistToolsMacros.cmake +++ b/src/linguist/Qt6LinguistToolsMacros.cmake @@ -1,35 +1,6 @@ -#============================================================================= # Copyright (C) 2020 The Qt Company Ltd. # 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. -#============================================================================= +# SPDX-License-Identifier: BSD-3-Clause include(CMakeParseArguments) @@ -64,11 +35,16 @@ function(qt6_create_translation _qm_files) list(APPEND _my_sources ${_abs_FILE}) endif() endforeach() + set(stamp_file_dir "${CMAKE_CURRENT_BINARY_DIR}/.lupdate") + if(NOT EXISTS "${stamp_file_dir}") + file(MAKE_DIRECTORY "${stamp_file_dir}") + endif() + set(stamp_files "") foreach(_ts_file ${_my_tsfiles}) + get_filename_component(_ts_name ${_ts_file} NAME) if(_my_sources) # make a list file to call lupdate on, so we don't make our commands too # long for some systems - get_filename_component(_ts_name ${_ts_file} NAME) set(_ts_lst_file "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${_ts_name}_lst_file") set(_lst_file_srcs) set(_dependencies) @@ -91,24 +67,34 @@ function(qt6_create_translation _qm_files) file(WRITE ${_ts_lst_file} "${_lst_file_srcs}") endif() - add_custom_command(OUTPUT ${_ts_file} + file(RELATIVE_PATH _ts_relative_path ${CMAKE_CURRENT_SOURCE_DIR} ${_ts_file}) + string(REPLACE "../" "__/" _ts_relative_path "${_ts_relative_path}") + set(stamp_file "${stamp_file_dir}/${_ts_relative_path}.stamp") + list(APPEND stamp_files ${stamp_file}) + get_filename_component(full_stamp_file_dir "${stamp_file}" DIRECTORY) + if(NOT EXISTS "${full_stamp_file_dir}") + file(MAKE_DIRECTORY "${full_stamp_file_dir}") + endif() + add_custom_command(OUTPUT ${stamp_file} COMMAND ${QT_CMAKE_EXPORT_NAMESPACE}::lupdate ARGS ${_lupdate_options} "@${_ts_lst_file}" -ts ${_ts_file} + COMMAND ${CMAKE_COMMAND} -E touch "${stamp_file}" DEPENDS ${_dependencies} VERBATIM) endforeach() - qt6_add_translation(${_qm_files} ${_my_tsfiles}) + qt6_add_translation(${_qm_files} ${_my_tsfiles} __QT_INTERNAL_TIMESTAMP_FILES ${stamp_files}) set(${_qm_files} ${${_qm_files}} PARENT_SCOPE) endfunction() function(qt6_add_translation _qm_files) set(options) set(oneValueArgs) - set(multiValueArgs OPTIONS) + set(multiValueArgs OPTIONS __QT_INTERNAL_TIMESTAMP_FILES) cmake_parse_arguments(_LRELEASE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) set(_lrelease_files ${_LRELEASE_UNPARSED_ARGUMENTS}) + set(idx 0) foreach(_current_FILE ${_lrelease_files}) get_filename_component(_abs_FILE ${_current_FILE} ABSOLUTE) get_filename_component(qm ${_abs_FILE} NAME) @@ -122,16 +108,66 @@ function(qt6_add_translation _qm_files) set(qm "${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}.qm") endif() + if(_LRELEASE___QT_INTERNAL_TIMESTAMP_FILES) + list(GET _LRELEASE___QT_INTERNAL_TIMESTAMP_FILES ${idx} qm_dep) + math(EXPR idx "${idx} + 1") + else() + set(qm_dep "${_abs_FILE}") + endif() + add_custom_command(OUTPUT ${qm} COMMAND ${QT_CMAKE_EXPORT_NAMESPACE}::lrelease ARGS ${_LRELEASE_OPTIONS} ${_abs_FILE} -qm ${qm} - DEPENDS ${_abs_FILE} VERBATIM + DEPENDS ${qm_dep} VERBATIM ) list(APPEND ${_qm_files} ${qm}) endforeach() set(${_qm_files} ${${_qm_files}} PARENT_SCOPE) endfunction() +function(_qt_internal_collect_translation_source_targets out_var dir) + set(result "") + get_property(excluded DIRECTORY "${dir}" PROPERTY QT_EXCLUDE_FROM_TRANSLATION) + if(NOT excluded) + get_property(subdirs DIRECTORY "${dir}" PROPERTY SUBDIRECTORIES) + foreach(subdir IN LISTS subdirs) + _qt_internal_collect_translation_source_targets(subresult "${subdir}") + list(APPEND result ${subresult}) + endforeach() + get_property(dir_targets DIRECTORY "${dir}" PROPERTY BUILDSYSTEM_TARGETS) + foreach(target IN LISTS dir_targets) + get_target_property(target_type ${target} TYPE) + if(CMAKE_VERSION VERSION_LESS "3.19" AND target_type STREQUAL "INTERFACE_LIBRARY") + # Skip INTERFACE libraries with CMake < 3.19 to avoid an error about + # QT_EXCLUDE_FROM_TRANSLATION not being whitelisted. + continue() + endif() + get_target_property(excluded ${target} QT_EXCLUDE_FROM_TRANSLATION) + if(NOT excluded AND NOT target_type STREQUAL "UTILITY") + list(APPEND result ${target}) + endif() + endforeach() + endif() + set("${out_var}" "${result}" PARENT_SCOPE) +endfunction() + +function(qt6_collect_translation_source_targets out_var) + set(no_value_options "") + set(single_value_options DIRECTORY) + set(multi_value_options "") + cmake_parse_arguments(PARSE_ARGV 1 arg + "${no_value_options}" "${single_value_options}" "${multi_value_options}" + ) + + set(dir "${arg_DIRECTORY}") + if(dir STREQUAL "") + set(dir "${CMAKE_CURRENT_SOURCE_DIR}") + endif() + + _qt_internal_collect_translation_source_targets(result "${dir}") + set("${out_var}" "${result}" PARENT_SCOPE) +endfunction() + # Makes the paths in the unparsed arguments absolute and stores them in out_var. function(qt_internal_make_paths_absolute out_var) set(result "") @@ -142,33 +178,95 @@ function(qt_internal_make_paths_absolute out_var) set("${out_var}" "${result}" PARENT_SCOPE) endfunction() +# If the given TS_FILE does not exist, write an initial .ts file that can be read by lrelease and +# updated by lupdate. +function(_qt_internal_ensure_ts_file) + set(no_value_options "") + set(single_value_options TS_FILE) + set(multi_value_options "") + cmake_parse_arguments(PARSE_ARGV 0 arg + "${no_value_options}" "${single_value_options}" "${multi_value_options}" + ) + + if(EXISTS "${arg_TS_FILE}") + return() + endif() + + file(WRITE "${arg_TS_FILE}" + [[<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS/> +]]) +endfunction() + # Needed to locate Qt6LupdateProject.json.in file inside functions set(_Qt6_LINGUIST_TOOLS_DIR ${CMAKE_CURRENT_LIST_DIR} CACHE INTERNAL "") -function(qt6_add_lupdate target) +function(qt6_add_lupdate) set(options NO_GLOBAL_TARGET) - set(oneValueArgs) + set(oneValueArgs + PLURALS_TS_FILE + LUPDATE_TARGET) set(multiValueArgs + SOURCE_TARGETS TS_FILES SOURCES INCLUDE_DIRECTORIES OPTIONS) cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + # Set up the list of targets. Support the old command signature that takes one target as first + # argument. + set(targets "${arg_SOURCE_TARGETS}") + if("${targets}" STREQUAL "") + list(POP_FRONT arg_UNPARSED_ARGUMENTS target) + if(TARGET "${target}") + list(APPEND targets ${target}) + endif() + unset(target) + endif() + + if("${targets}" STREQUAL "" AND "${arg_SOURCES}" STREQUAL "") + message(FATAL_ERROR "No SOURCE_TARGETS nor SOURCES were given.") + endif() + + # Set up the name of the custom target. + set(lupdate_target "${arg_LUPDATE_TARGET}") + if("${lupdate_target}" STREQUAL "") + set(lupdate_target "${PROJECT_NAME}_lupdate") + set(lupdate_target_orig "${lupdate_target}") + set(n 1) + while(TARGET "${lupdate_target}") + set(lupdate_target "${lupdate_target_orig}${n}") + math(EXPR n "${n} + 1") + endwhile() + endif() + + set(includePaths "") + set(sources "") + list(LENGTH targets targets_length) + if(arg_INCLUDE_DIRECTORIES) - qt_internal_make_paths_absolute(includePaths "${arg_INCLUDE_DIRECTORIES}") - else() - set(includePaths "$<TARGET_PROPERTY:${target},INCLUDE_DIRECTORIES>") + qt_internal_make_paths_absolute(additionalIncludePaths "${arg_INCLUDE_DIRECTORIES}") endif() if(arg_SOURCES) - qt_internal_make_paths_absolute(sources "${arg_SOURCES}") - else() - set(sources "$<TARGET_PROPERTY:${target},SOURCES>") + qt_internal_make_paths_absolute(additionalSources "${arg_SOURCES}") endif() + set(lupdate_work_dir "${CMAKE_CURRENT_BINARY_DIR}/.lupdate") qt_internal_make_paths_absolute(ts_files "${arg_TS_FILES}") + set(plurals_ts_file "") + set(raw_plurals_ts_file "") + if(NOT "${arg_PLURALS_TS_FILE}" STREQUAL "") + qt_internal_make_paths_absolute(plurals_ts_file "${arg_PLURALS_TS_FILE}") + _qt_internal_ensure_ts_file(TS_FILE "${plurals_ts_file}") + get_filename_component(raw_plurals_ts_file "${plurals_ts_file}" NAME) + string(PREPEND raw_plurals_ts_file "${lupdate_work_dir}/") + list(APPEND ts_files "${raw_plurals_ts_file}") + endif() - set(lupdate_project_base "${CMAKE_CURRENT_BINARY_DIR}/.lupdate/${target}_project") + set(lupdate_project_base "${lupdate_work_dir}/${lupdate_target}_project") set(lupdate_project_cmake "${lupdate_project_base}") get_property(multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(multi_config) @@ -176,19 +274,63 @@ function(qt6_add_lupdate target) endif() string(APPEND lupdate_project_cmake ".cmake") set(lupdate_project_json "${lupdate_project_base}.json") - file(GENERATE OUTPUT "${lupdate_project_cmake}" - CONTENT "set(lupdate_project_file \"${CMAKE_CURRENT_LIST_FILE}\") -set(lupdate_include_paths \"${includePaths}\") -set(lupdate_sources \"${sources}\") + set(content "set(lupdate_project_file \"${CMAKE_CURRENT_LIST_FILE}\") set(lupdate_translations \"${ts_files}\") +set(lupdate_include_paths \"${additionalIncludePaths}\") +set(lupdate_sources \"${additionalSources}\") +set(lupdate_subproject_count ${targets_length}) +") + set(exclude_ts "\\.ts$") + set(n 1) + foreach(target IN LISTS targets) + set(includePaths "$<TARGET_PROPERTY:${target},INCLUDE_DIRECTORIES>") + set(sources "$<FILTER:$<TARGET_PROPERTY:${target},SOURCES>,EXCLUDE,${exclude_ts}>") + set(excluded "$<TARGET_PROPERTY:${target},QT_EXCLUDE_SOURCES_FROM_TRANSLATION>") + set(autogen_build_dir_genex "$<TARGET_PROPERTY:${target},AUTOGEN_BUILD_DIR>") + set(default_autogen_build_dir "$<TARGET_PROPERTY:${target},BINARY_DIR>/${target}_autogen") + set(autogen_dir "$<IF:$<BOOL:${autogen_build_dir_genex}>,${autogen_build_dir_genex},${default_autogen_build_dir}>") + string(APPEND content " +set(lupdate_subproject${n}_source_dir \"$<TARGET_PROPERTY:${target},SOURCE_DIR>\") +set(lupdate_subproject${n}_include_paths \"${includePaths}\") +set(lupdate_subproject${n}_sources \"${sources}\") +set(lupdate_subproject${n}_excluded \"${excluded}\") +set(lupdate_subproject${n}_autogen_dir \"${autogen_dir}\") ") + math(EXPR n "${n} + 1") + endforeach() + file(GENERATE OUTPUT "${lupdate_project_cmake}" CONTENT "${content}") - add_custom_target(${target}_lupdate + _qt_internal_get_tool_wrapper_script_path(tool_wrapper) + set(lupdate_command + COMMAND + "${tool_wrapper}" + $<TARGET_FILE:${QT_CMAKE_EXPORT_NAMESPACE}::lupdate>) + set(prepare_native_ts_command "") + set(finish_native_ts_command "") + if(NOT plurals_ts_file STREQUAL "") + # Copy the existing .ts file to preserve already translated strings. + set(prepare_native_ts_command + COMMAND + "${CMAKE_COMMAND}" -E copy "${plurals_ts_file}" "${raw_plurals_ts_file}" + ) + + # Filter out the non-numerus forms with lconvert. + set(finish_native_ts_command + COMMAND + "${tool_wrapper}" + $<TARGET_FILE:${QT_CMAKE_EXPORT_NAMESPACE}::lconvert> + -pluralonly + -i "${raw_plurals_ts_file}" + -o "${plurals_ts_file}" + ) + endif() + add_custom_target(${lupdate_target} COMMAND "${CMAKE_COMMAND}" "-DIN_FILE=${lupdate_project_cmake}" "-DOUT_FILE=${lupdate_project_json}" -P "${_Qt6_LINGUIST_TOOLS_DIR}/GenerateLUpdateProject.cmake" - COMMAND ${QT_CMAKE_EXPORT_NAMESPACE}::lupdate -project "${lupdate_project_json}" - ${arg_OPTIONS} + ${prepare_native_ts_command} + ${lupdate_command} -project "${lupdate_project_json}" ${arg_OPTIONS} + ${finish_native_ts_command} DEPENDS ${QT_CMAKE_EXPORT_NAMESPACE}::lupdate VERBATIM) @@ -197,18 +339,62 @@ set(lupdate_translations \"${ts_files}\") endif() if(NOT arg_NO_GLOBAL_TARGET) - if(NOT TARGET ${QT_GLOBAL_LUPDATE_TARGET}) - add_custom_target(${QT_GLOBAL_LUPDATE_TARGET}) + _qt_internal_add_phony_target(${QT_GLOBAL_LUPDATE_TARGET} + WARNING_VARIABLE QT_NO_GLOBAL_LUPDATE_TARGET_CREATION_WARNING + ) + _qt_internal_add_phony_target_dependencies(${QT_GLOBAL_LUPDATE_TARGET} + ${lupdate_target} + ) + endif() +endfunction() + +function(_qt_internal_store_languages_from_ts_files_in_targets targets ts_files) + if(NOT APPLE) + return() + endif() + set(supported_languages "") + foreach(ts_file IN LISTS ts_files) + execute_process(COMMAND /usr/bin/xmllint --xpath "string(/TS/@language)" ${ts_file} + OUTPUT_STRIP_TRAILING_WHITESPACE + OUTPUT_VARIABLE language_code + ERROR_VARIABLE xmllint_error) + if(NOT language_code OR xmllint_error) + message(WARNING "Failed to resolve language code for ${ts_file}. " + "Please update CFBundleLocalizations in your Info.plist manually.") endif() - add_dependencies(${QT_GLOBAL_LUPDATE_TARGET} ${target}_lupdate) + endforeach() + foreach(target IN LISTS targets) + set_property(TARGET "${target}" APPEND PROPERTY + _qt_apple_supported_languages "${supported_languages}" + ) + endforeach() +endfunction() + +# Store in ${out_var} the file path to the .qm file that will be generated from the given .ts file. +function(_qt_internal_generated_qm_file_path out_var ts_file default_out_dir) + get_filename_component(qm ${ts_file} NAME_WLE) + string(APPEND qm ".qm") + get_source_file_property(output_location ${ts_file} OUTPUT_LOCATION) + if(output_location) + if(NOT IS_ABSOLUTE "${output_location}") + get_filename_component(output_location "${output_location}" ABSOLUTE + BASE_DIR "${default_out_dir}") + endif() + string(PREPEND qm "${output_location}/") + else() + string(PREPEND qm "${default_out_dir}/") endif() + set("${out_var}" "${qm}" PARENT_SCOPE) endfunction() -function(qt6_add_lrelease target) +function(qt6_add_lrelease) set(options - NO_TARGET_DEPENDENCY + NO_TARGET_DEPENDENCY ### Qt7: remove together with legacy signature + EXCLUDE_FROM_ALL NO_GLOBAL_TARGET) set(oneValueArgs + __QT_INTERNAL_DEFAULT_QM_OUT_DIR + LRELEASE_TARGET QM_FILES_OUTPUT_VARIABLE) set(multiValueArgs TS_FILES @@ -216,43 +402,88 @@ function(qt6_add_lrelease target) cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) qt_internal_make_paths_absolute(ts_files "${arg_TS_FILES}") + # Support the old command signature that takes one target as first argument. + set(legacy_signature_used FALSE) + set(legacy_target "") + list(LENGTH arg_UNPARSED_ARGUMENTS unparsed_arguments_count) + if(unparsed_arguments_count GREATER 0) + list(POP_FRONT arg_UNPARSED_ARGUMENTS legacy_target) + if(TARGET "${legacy_target}") + set(legacy_signature_used TRUE) + else() + set(legacy_target "") + endif() + endif() + + # Set up the driving target. + set(lrelease_target "${arg_LRELEASE_TARGET}") + if("${lrelease_target}" STREQUAL "") + set(lrelease_target "${PROJECT_NAME}_lrelease") + set(lrelease_target_orig "${lrelease_target}") + set(n 1) + while(TARGET "${lrelease_target}") + set(lrelease_target "${lrelease_target_orig}${n}") + math(EXPR n "${n} + 1") + endwhile() + endif() + + _qt_internal_get_tool_wrapper_script_path(tool_wrapper) + set(lrelease_command + COMMAND + "${tool_wrapper}" + $<TARGET_FILE:${QT_CMAKE_EXPORT_NAMESPACE}::lrelease>) + + set(default_qm_out_dir "${CMAKE_CURRENT_BINARY_DIR}") + if(NOT "${arg___QT_INTERNAL_DEFAULT_QM_OUT_DIR}" STREQUAL "") + set(default_qm_out_dir "${arg___QT_INTERNAL_DEFAULT_QM_OUT_DIR}") + endif() + set(qm_files "") foreach(ts_file ${ts_files}) if(NOT EXISTS "${ts_file}") message(WARNING "Translation file '${ts_file}' does not exist. " - "Consider building the target '${target}_lupdate' to create an initial " + "Consider building the target 'update_translations' to create an initial " "version of that file.") - - # Provide a command that creates an initial .ts file with the right language set. - # The language is guessed by lupdate from the file name. - add_custom_command(OUTPUT ${ts_file} - COMMAND ${QT_CMAKE_EXPORT_NAMESPACE}::lupdate -ts ${ts_file} - VERBATIM) - endif() - get_filename_component(qm ${ts_file} NAME_WLE) - string(APPEND qm ".qm") - get_source_file_property(output_location ${ts_file} OUTPUT_LOCATION) - if(output_location) - if(NOT IS_ABSOLUTE "${output_location}") - get_filename_component(output_location "${output_location}" ABSOLUTE - BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") - endif() - file(MAKE_DIRECTORY "${output_location}") - string(PREPEND qm "${output_location}/") - else() - string(PREPEND qm "${CMAKE_CURRENT_BINARY_DIR}/") + _qt_internal_ensure_ts_file(TS_FILE "${ts_file}") endif() + + _qt_internal_generated_qm_file_path(qm "${ts_file}" "${default_qm_out_dir}") + get_filename_component(qm_dir "${qm}" DIRECTORY) add_custom_command(OUTPUT ${qm} - COMMAND ${QT_CMAKE_EXPORT_NAMESPACE}::lrelease - ${arg_OPTIONS} ${ts_file} -qm ${qm} - DEPENDS ${QT_CMAKE_EXPORT_NAMESPACE}::lrelease ${ts_file} + COMMAND "${CMAKE_COMMAND}" -E make_directory "${qm_dir}" + ${lrelease_command} ${arg_OPTIONS} ${ts_file} -qm ${qm} + DEPENDS ${QT_CMAKE_EXPORT_NAMESPACE}::lrelease "${ts_file}" VERBATIM) list(APPEND qm_files "${qm}") + + # QTBUG-103470: Save the target responsible for driving the build of the custom command + # into an internal source file property. It will be added as a dependency for targets + # created by _qt_internal_process_resource, to avoid the Xcode issue of not allowing + # multiple targets depending on the output, without having a common target ancestor. + set(scope_args "") + if(legacy_signature_used AND CMAKE_VERSION VERSION_GREATER_EQUAL "3.18") + set(scope_args TARGET_DIRECTORY ${legacy_target}) + endif() + set_source_files_properties("${qm}" ${scope_args} PROPERTIES + _qt_resource_target_dependency "${lrelease_target}" + ) endforeach() - add_custom_target(${target}_lrelease DEPENDS ${qm_files}) - if(NOT arg_NO_TARGET_DEPENDENCY) - add_dependencies(${target} ${target}_lrelease) + if(legacy_signature_used) + _qt_internal_store_languages_from_ts_files_in_targets("${legacy_target}" "${ts_files}") + endif() + + if(legacy_signature_used) + add_custom_target(${lrelease_target} DEPENDS ${qm_files}) + if(NOT arg_NO_TARGET_DEPENDENCY) + add_dependencies(${legacy_target} ${lrelease_target}) + endif() + else() + set(maybe_all ALL) + if(arg_EXCLUDE_FROM_ALL) + set(maybe_all "") + endif() + add_custom_target(${lrelease_target} ${maybe_all} DEPENDS ${qm_files}) endif() if(NOT DEFINED QT_GLOBAL_LRELEASE_TARGET) @@ -263,7 +494,7 @@ function(qt6_add_lrelease target) if(NOT TARGET ${QT_GLOBAL_LRELEASE_TARGET}) add_custom_target(${QT_GLOBAL_LRELEASE_TARGET}) endif() - add_dependencies(${QT_GLOBAL_LRELEASE_TARGET} ${target}_lrelease) + add_dependencies(${QT_GLOBAL_LRELEASE_TARGET} ${lrelease_target}) endif() if(NOT "${arg_QM_FILES_OUTPUT_VARIABLE}" STREQUAL "") @@ -271,22 +502,40 @@ function(qt6_add_lrelease target) endif() endfunction() -# This function is currently in Technical Preview. -# It's signature and behavior might change. -function(qt6_add_translations target) - set(options) +function(qt6_add_translations) + set(options + IMMEDIATE_CALL + NO_GENERATE_PLURALS_TS_FILE) set(oneValueArgs + __QT_INTERNAL_DEFAULT_QM_OUT_DIR + LUPDATE_TARGET + LRELEASE_TARGET QM_FILES_OUTPUT_VARIABLE RESOURCE_PREFIX OUTPUT_TARGETS) set(multiValueArgs + TARGETS + SOURCE_TARGETS TS_FILES + TS_FILE_BASE + TS_FILE_DIR + PLURALS_TS_FILE SOURCES INCLUDE_DIRECTORIES LUPDATE_OPTIONS LRELEASE_OPTIONS) cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + set(targets "${arg_TARGETS}") + if(NOT "${arg_UNPARSED_ARGUMENTS}" STREQUAL "") + list(POP_FRONT arg_UNPARSED_ARGUMENTS target) + list(PREPEND targets "${target}") + unset(target) + set(arg_TARGETS ${targets}) # to forward this argument + endif() + if(targets STREQUAL "") + message(FATAL_ERROR "No targets provided.") + endif() if(DEFINED arg_RESOURCE_PREFIX AND DEFINED arg_QM_FILES_OUTPUT_VARIABLE) message(FATAL_ERROR "QM_FILES_OUTPUT_VARIABLE cannot be specified " "together with RESOURCE_PREFIX.") @@ -299,23 +548,166 @@ function(qt6_add_translations target) set(arg_RESOURCE_PREFIX "/i18n") endif() - qt6_add_lupdate(${target} + # Determine the .ts file paths if necessary. This must happen before function deferral. + if(NOT DEFINED arg_TS_FILES) + if(NOT DEFINED arg_TS_FILE_DIR) + set(arg_TS_FILE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") + endif() + if(NOT DEFINED arg_TS_FILE_BASE) + set(arg_TS_FILE_BASE "${PROJECT_NAME}") + string(REPLACE " " "-" arg_TS_FILE_BASE "${arg_TS_FILE_BASE}") + endif() + set(arg_TS_FILES "") + foreach(lang IN LISTS QT_I18N_TRANSLATED_LANGUAGES) + list(APPEND arg_TS_FILES "${arg_TS_FILE_DIR}/${arg_TS_FILE_BASE}_${lang}.ts") + endforeach() + + # Default the source language to "en" in case the user doesn't use + # qt_standard_project_setup. + set(source_lang en) + if(NOT "${QT_I18N_SOURCE_LANGUAGE}" STREQUAL "") + set(source_lang ${QT_I18N_SOURCE_LANGUAGE}) + endif() + + # Determine the path to the plurals-only .ts file if necessary. + if(NOT arg_NO_GENERATE_PLURALS_TS_FILE + AND NOT DEFINED arg_PLURALS_TS_FILE + AND NOT "${source_lang}" IN_LIST QT_I18N_TRANSLATED_LANGUAGES) + set(arg_PLURALS_TS_FILE + "${arg_TS_FILE_DIR}/${arg_TS_FILE_BASE}_${source_lang}.ts") + endif() + endif() + + # Defer the actual function call if SOURCE_TARGETS was not given and an immediate call was not + # requested. + set(source_targets "${arg_SOURCE_TARGETS}") + if(source_targets STREQUAL "" AND NOT arg_IMMEDIATE_CALL) + if(DEFINED arg_OUTPUT_TARGETS) + # We don't have the infrastructure to predict the resource target names. + message(FATAL_ERROR "Deferring qt6_add_translations is not supported with " + "OUTPUT_TARGETS. Pass IMMEDIATE_CALL or specify SOURCE_TARGETS.") + endif() + if(CMAKE_VERSION VERSION_LESS "3.19") + message(WARNING + "qt6_add_translations cannot defer function calls with this CMake version. " + "Only targets created prior to the qt6_add_translations call are used for i18n. " + "To avoid this warning, make sure this command is called at the end of the " + "top-level directory scope and pass the IMMEDIATE_CALL keyword. " + "Alternatively, upgrade to CMake 3.19 or newer." + ) + qt6_add_translations(IMMEDIATE_CALL ${ARGV}) + else() + # Predict the names of generated .qm files. + if(DEFINED arg_QM_FILES_OUTPUT_VARIABLE) + set(qm_files "") + foreach(ts_file IN LISTS arg_TS_FILES arg_PLURALS_TS_FILE) + _qt_internal_generated_qm_file_path(qm_file "${ts_file}" + "${CMAKE_CURRENT_BINARY_DIR}") + list(APPEND qm_files "${qm_file}") + endforeach() + set("${arg_QM_FILES_OUTPUT_VARIABLE}" "${qm_files}" PARENT_SCOPE) + endif() + + # Forward options. + set(forwarded_args + IMMEDIATE_CALL + __QT_INTERNAL_DEFAULT_QM_OUT_DIR "${CMAKE_CURRENT_BINARY_DIR}" + ) + foreach(keyword IN LISTS options) + if(arg_${keyword}) + list(APPEND forwarded_args ${keyword}) + endif() + endforeach() + + # Forward one-value and multi-value arguments. + # Filter path variables. We will forward those separately. + set(to_forward ${oneValueArgs} ${multiValueArgs}) + set(path_variables + TS_FILES + PLURALS_TS_FILE + SOURCES + INCLUDE_DIRECTORIES + ) + set(ignored_variables + TS_FILE_BASE + TS_FILE_DIR + ) + list(REMOVE_ITEM to_forward ${path_variables} ${ignored_variables}) + foreach(keyword IN LISTS to_forward) + if(DEFINED arg_${keyword}) + list(APPEND forwarded_args ${keyword} ${arg_${keyword}}) + endif() + endforeach() + + # Make variables that specify paths absolute. + # Relative paths are considered to be relative to the current source dir. + foreach(var IN LISTS path_variables) + qt_internal_make_paths_absolute(absolute_paths ${arg_${var}}) + if(NOT "${absolute_paths}" STREQUAL "") + list(APPEND forwarded_args ${var} ${absolute_paths}) + endif() + endforeach() + + # Schedule this command to be called at the end of the project's source dir. + cmake_language(EVAL CODE + "cmake_language(DEFER + DIRECTORY \"${PROJECT_SOURCE_DIR}\" + CALL qt6_add_translations ${forwarded_args})") + endif() + return() + endif() + + if(source_targets STREQUAL "") + qt6_collect_translation_source_targets(source_targets) + endif() + qt6_add_lupdate( + SOURCE_TARGETS "${source_targets}" + LUPDATE_TARGET "${arg_LUPDATE_TARGET}" TS_FILES "${arg_TS_FILES}" + PLURALS_TS_FILE "${arg_PLURALS_TS_FILE}" SOURCES "${arg_SOURCES}" INCLUDE_DIRECTORIES "${arg_INCLUDE_DIRECTORIES}" - OPTIONS "${arg_LUPDATE_OPTIONS}") - qt6_add_lrelease(${target} - TS_FILES "${arg_TS_FILES}" + OPTIONS "${arg_LUPDATE_OPTIONS}" + ) + qt6_add_lrelease( + LRELEASE_TARGET "${arg_LRELEASE_TARGET}" + TS_FILES "${arg_TS_FILES}" ${arg_PLURALS_TS_FILE} QM_FILES_OUTPUT_VARIABLE qm_files - OPTIONS "${arg_LRELEASE_OPTIONS}") + OPTIONS "${arg_LRELEASE_OPTIONS}" + __QT_INTERNAL_DEFAULT_QM_OUT_DIR "${arg___QT_INTERNAL_DEFAULT_QM_OUT_DIR}" + ) + + if("${QT_I18N_TRANSLATED_LANGUAGES}" STREQUAL "") + _qt_internal_store_languages_from_ts_files_in_targets("${targets}" "${arg_TS_FILES}") + endif() + + # Mark .qm files as GENERATED, so that calling _qt_internal_expose_deferred_files_to_ide + # doesn't cause an error at generation time saying "Cannot find source file:" when + # qt6_add_lrelease is called from a subdirectory different than the target. + # The issue happend when the user project called cmake_minimum_required(VERSION) + # with a version less than 3.20 or set the CMP0118 policy value to OLD. + set(scope_args "") + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.18") + set(scope_args TARGET_DIRECTORY ${targets}) + endif() + set_source_files_properties(${qm_files} + ${scope_args} + PROPERTIES GENERATED TRUE + ) + if(NOT "${arg_RESOURCE_PREFIX}" STREQUAL "") - qt6_add_resources(${target} "translations" - PREFIX "${arg_RESOURCE_PREFIX}" - BASE "${CMAKE_CURRENT_BINARY_DIR}" - OUTPUT_TARGETS out_targets - FILES ${qm_files}) + set(accumulated_out_targets "") + foreach(target IN LISTS targets) + get_target_property(target_binary_dir ${target} BINARY_DIR) + qt6_add_resources(${target} "${target}_translations" + PREFIX "${arg_RESOURCE_PREFIX}" + BASE "${target_binary_dir}" + OUTPUT_TARGETS out_targets + FILES ${qm_files}) + list(APPEND accumulated_out_targets ${out_targets}) + endforeach() if(DEFINED arg_OUTPUT_TARGETS) - set("${arg_OUTPUT_TARGETS}" "${out_targets}" PARENT_SCOPE) + set("${arg_OUTPUT_TARGETS}" "${accumulated_out_targets}" PARENT_SCOPE) endif() endif() if(NOT "${arg_QM_FILES_OUTPUT_VARIABLE}" STREQUAL "") @@ -340,6 +732,14 @@ if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) endif() set("${_qm_files}" "${${_qm_files}}" PARENT_SCOPE) endfunction() + function(qt_collect_translation_source_targets out_var) + if(QT_DEFAULT_MAJOR_VERSION EQUAL 6) + qt6_collect_translation_source_targets("${out_var}" ${ARGN}) + else() + message(FATAL_ERROR "qt_collect_translation_source_targets() is only available in Qt 6.") + endif() + set("${out_var}" "${${out_var}}" PARENT_SCOPE) + endfunction() function(qt_add_lupdate) if(QT_DEFAULT_MAJOR_VERSION EQUAL 6) qt6_add_lupdate(${ARGN}) |