diff options
author | Joerg Bornemann <joerg.bornemann@qt.io> | 2024-04-18 12:28:35 +0200 |
---|---|---|
committer | Joerg Bornemann <joerg.bornemann@qt.io> | 2024-04-25 10:19:47 +0200 |
commit | d39e9f21c98eb4788f3434f139b828862b2d8823 (patch) | |
tree | f53f3aec5cacb6b0c3c448b92261a774ce4d6705 | |
parent | 5992c457de417dd32f7503db5f2dfa30cc54f494 (diff) |
CMake: Exclude all AUTOGENerated files from lupdate
To fix QTBUG-118808 we added code to filter ui_*.h files from lupdate's
input. This was done by creating a big regular expression.
Unfortunately, CMake cannot handle regular expressions that contain a
certain amount of sub-expressions. See QTBUG-123995.
We now fix the issue more generally by filtering out everything that's
generated by CMake's AUTOGEN facility. The data flow is like follows:
1. On project configuration, .lupdate/foo_project.cmake is generated.
This file contains the location of the AUTOGEN directory per target. The
AUTOGEN_BUILD_DIR target property is taken into account.
2. At build time, GenerateLUpdateProject.cmake reads
.lupdate/foo_project.cmake, and filters out source files that are below
the AUTOGEN directory for the currently handled target. The filtered
result is written to .lupdate/foo_project.json.
3. On 'update_translations', lupdate reads .lupdate/foo_project.json and
runs on the source files mentioned there.
Fixes: QTBUG-118808
Fixes: QTBUG-123995
Pick-to: 6.6 6.7
Change-Id: Id23b94c22b2eadeb7f96321bf631731b9f257bce
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
11 files changed, 235 insertions, 21 deletions
diff --git a/src/linguist/GenerateLUpdateProject.cmake b/src/linguist/GenerateLUpdateProject.cmake index 40854aa8d..e359708c5 100644 --- a/src/linguist/GenerateLUpdateProject.cmake +++ b/src/linguist/GenerateLUpdateProject.cmake @@ -44,34 +44,42 @@ function(filter_unsuitable_lupdate_input out_var) set("${out_var}" "${result}" PARENT_SCOPE) endfunction() -# Remove ui_foo.h for each foo.ui file found in the sources. -# filter_generated_ui_headers(existing_files .../src/foo.ui .../target_autogen/include/ui_foo.h) +# Remove files from SOURCES that are in EXCLUDED_DIR. +# filter_files_in_directory(existing_files +# EXCLUDED_DIR .../target_autogen +# SOURCES .../src/foo.ui .../target_autogen/include/ui_foo.h) # -> .../src/foo.ui -function(filter_generated_ui_headers out_var) - set(ui_file_paths ${ARGN}) - list(FILTER ui_file_paths INCLUDE REGEX "/[^/]+\\.ui$") +function(filter_files_in_directory out_var) + set(no_value_options "") + set(single_value_options EXCLUDED_DIR) + set(multi_value_options SOURCES) + cmake_parse_arguments(PARSE_ARGV 1 arg + "${no_value_options}" "${single_value_options}" "${multi_value_options}" + ) - set(filter_regex "") - foreach(file_path IN LISTS ui_file_paths) - get_filename_component(file_name "${file_path}" NAME_WLE) - if(NOT "${filter_regex}" STREQUAL "") - string(APPEND filter_regex "|") + set(result "") + foreach(source_file IN LISTS arg_SOURCES) + file(RELATIVE_PATH relative_path "${arg_EXCLUDED_DIR}" "${source_file}") + if(IS_ABSOLUTE "${relative_path}" OR (relative_path MATCHES "^\\.\\.")) + list(APPEND result "${source_file}") endif() - string(APPEND filter_regex "(/ui_${file_name}\\.h$)") endforeach() - - set(result ${ARGN}) - if(NOT "${filter_regex}" STREQUAL "") - list(FILTER result EXCLUDE REGEX ${filter_regex}) - endif() - set("${out_var}" "${result}" PARENT_SCOPE) endfunction() function(prepare_json_sources out_var) - filter_nonexistent_files(sources ${ARGN}) + set(no_value_options "") + set(single_value_options AUTOGEN_DIR) + set(multi_value_options SOURCES) + cmake_parse_arguments(PARSE_ARGV 1 arg + "${no_value_options}" "${single_value_options}" "${multi_value_options}" + ) + + filter_nonexistent_files(sources ${arg_SOURCES}) filter_unsuitable_lupdate_input(sources ${sources}) - filter_generated_ui_headers(sources ${sources}) + if(DEFINED arg_AUTOGEN_DIR) + filter_files_in_directory(sources EXCLUDED_DIR ${arg_AUTOGEN_DIR} SOURCES ${sources}) + endif() list_to_json_array("${sources}" result) set("${out_var}" "${result}" PARENT_SCOPE) endfunction() @@ -107,7 +115,7 @@ foreach(path_var IN LISTS path_variables) endforeach() endforeach() -prepare_json_sources(json_sources ${absolute_sources}) +prepare_json_sources(json_sources SOURCES ${absolute_sources}) list_to_json_array("${absolute_include_paths}" json_include_paths) list_to_json_array("${absolute_translations}" json_translations) @@ -120,7 +128,10 @@ set(content "{ ") if(lupdate_subproject_count GREATER 0) foreach(i RANGE 1 ${lupdate_subproject_count}) - prepare_json_sources(json_sources ${absolute_subproject${i}_sources}) + prepare_json_sources(json_sources + AUTOGEN_DIR ${lupdate_subproject${i}_autogen_dir} + SOURCES ${absolute_subproject${i}_sources} + ) list_to_json_array("${absolute_subproject${i}_include_paths}" json_include_paths) list_to_json_array("${absolute_subproject${i}_excluded}" json_sources_exclusions) string(APPEND content " { diff --git a/src/linguist/Qt6LinguistToolsMacros.cmake b/src/linguist/Qt6LinguistToolsMacros.cmake index 61700546b..b9c924c9c 100644 --- a/src/linguist/Qt6LinguistToolsMacros.cmake +++ b/src/linguist/Qt6LinguistToolsMacros.cmake @@ -286,11 +286,15 @@ set(lupdate_subproject_count ${targets_length}) 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() diff --git a/tests/auto/cmake/linguist/CMakeLists.txt b/tests/auto/cmake/linguist/CMakeLists.txt index 90be96016..e08d12f5e 100644 --- a/tests/auto/cmake/linguist/CMakeLists.txt +++ b/tests/auto/cmake/linguist/CMakeLists.txt @@ -63,4 +63,5 @@ _qt_internal_test_expect_pass(test_create_translation_same_base_names) _qt_internal_test_expect_pass(test_translation_api) _qt_internal_test_expect_pass(test_i18n_auto_ts_file_names) _qt_internal_test_expect_pass(test_i18n_exclusion) +_qt_internal_test_expect_pass(test_i18n_filter_autogen_files) _qt_internal_test_expect_pass(test_i18n_source_language) diff --git a/tests/auto/cmake/linguist/test_i18n_filter_autogen_files/CMakeLists.txt b/tests/auto/cmake/linguist/test_i18n_filter_autogen_files/CMakeLists.txt new file mode 100644 index 000000000..0e3871ef6 --- /dev/null +++ b/tests/auto/cmake/linguist/test_i18n_filter_autogen_files/CMakeLists.txt @@ -0,0 +1,30 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(test_i18n_filter_autogen_files) +set(CMAKE_CXX_STANDARD 20) +find_package(Qt6 COMPONENTS Widgets LinguistTools) +qt_standard_project_setup() +add_subdirectory(app1) +add_subdirectory(app2) + +set(ts_file "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}_de.ts") +qt_add_lupdate( + TS_FILES "${ts_file}" + SOURCE_TARGETS app1 app2 + LUPDATE_TARGET update_ts_files + NO_GLOBAL_TARGET +) + +# Make sure that we build the app before creating the .ts file. +# We want to have AUTOMOC and AUTOUIC files generated. +add_dependencies(update_ts_files app1 app2) + +# Update the .ts files by default and check the content of the updated .ts file. +add_custom_target(force_ts_update ALL + COMMENT "Checking .ts file contents..." + COMMAND "${CMAKE_COMMAND}" -DTS_FILE="${ts_file}" + -P "${CMAKE_CURRENT_SOURCE_DIR}/check_ts_file.cmake" +) +add_dependencies(force_ts_update update_ts_files) diff --git a/tests/auto/cmake/linguist/test_i18n_filter_autogen_files/app1/CMakeLists.txt b/tests/auto/cmake/linguist/test_i18n_filter_autogen_files/app1/CMakeLists.txt new file mode 100644 index 000000000..6ff3989e7 --- /dev/null +++ b/tests/auto/cmake/linguist/test_i18n_filter_autogen_files/app1/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +qt_add_executable(app1 + main.cpp + mainwindow.ui +) +target_link_libraries(app1 PRIVATE Qt6::Widgets) diff --git a/tests/auto/cmake/linguist/test_i18n_filter_autogen_files/app1/main.cpp b/tests/auto/cmake/linguist/test_i18n_filter_autogen_files/app1/main.cpp new file mode 100644 index 000000000..21db50503 --- /dev/null +++ b/tests/auto/cmake/linguist/test_i18n_filter_autogen_files/app1/main.cpp @@ -0,0 +1,27 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtCore/QCoreApplication> + +#include "ui_mainwindow.h" + +class MyObject : public QObject +{ + Q_OBJECT +public: + MyObject(QObject *parent = nullptr) + : QObject(parent) + { + qDebug() << tr("Hello from app1!"); + } +}; + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + MyObject obj; + Ui::MainWindow wnd; + app.exec(); +} + +#include "main.moc" diff --git a/tests/auto/cmake/linguist/test_i18n_filter_autogen_files/app1/mainwindow.ui b/tests/auto/cmake/linguist/test_i18n_filter_autogen_files/app1/mainwindow.ui new file mode 100644 index 000000000..2b9bac77c --- /dev/null +++ b/tests/auto/cmake/linguist/test_i18n_filter_autogen_files/app1/mainwindow.ui @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>800</width> + <height>600</height> + </rect> + </property> + <property name="windowTitle"> + <string>App1's Main Window</string> + </property> + <widget class="QWidget" name="centralwidget"/> + <widget class="QMenuBar" name="menubar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>800</width> + <height>22</height> + </rect> + </property> + </widget> + <widget class="QStatusBar" name="statusbar"/> + </widget> + <resources/> + <connections/> +</ui> diff --git a/tests/auto/cmake/linguist/test_i18n_filter_autogen_files/app2/CMakeLists.txt b/tests/auto/cmake/linguist/test_i18n_filter_autogen_files/app2/CMakeLists.txt new file mode 100644 index 000000000..d5c26b126 --- /dev/null +++ b/tests/auto/cmake/linguist/test_i18n_filter_autogen_files/app2/CMakeLists.txt @@ -0,0 +1,12 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +qt_add_executable(app2 + main.cpp + mainwindow.ui +) +target_link_libraries(app2 PRIVATE Qt6::Widgets) +set_target_properties(app2 PROPERTIES + AUTOGEN_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/wossname_autogen" +) diff --git a/tests/auto/cmake/linguist/test_i18n_filter_autogen_files/app2/main.cpp b/tests/auto/cmake/linguist/test_i18n_filter_autogen_files/app2/main.cpp new file mode 100644 index 000000000..3b432078f --- /dev/null +++ b/tests/auto/cmake/linguist/test_i18n_filter_autogen_files/app2/main.cpp @@ -0,0 +1,27 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtCore/QCoreApplication> + +#include "ui_mainwindow.h" + +class MyObject : public QObject +{ + Q_OBJECT +public: + MyObject(QObject *parent = nullptr) + : QObject(parent) + { + qDebug() << tr("Hello from app2!"); + } +}; + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + MyObject obj; + Ui::MainWindow wnd; + app.exec(); +} + +#include "main.moc" diff --git a/tests/auto/cmake/linguist/test_i18n_filter_autogen_files/app2/mainwindow.ui b/tests/auto/cmake/linguist/test_i18n_filter_autogen_files/app2/mainwindow.ui new file mode 100644 index 000000000..6b0b6f2de --- /dev/null +++ b/tests/auto/cmake/linguist/test_i18n_filter_autogen_files/app2/mainwindow.ui @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>800</width> + <height>600</height> + </rect> + </property> + <property name="windowTitle"> + <string>App2's Main Window</string> + </property> + <widget class="QWidget" name="centralwidget"/> + <widget class="QMenuBar" name="menubar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>800</width> + <height>22</height> + </rect> + </property> + </widget> + <widget class="QStatusBar" name="statusbar"/> + </widget> + <resources/> + <connections/> +</ui> diff --git a/tests/auto/cmake/linguist/test_i18n_filter_autogen_files/check_ts_file.cmake b/tests/auto/cmake/linguist/test_i18n_filter_autogen_files/check_ts_file.cmake new file mode 100644 index 000000000..0e0118180 --- /dev/null +++ b/tests/auto/cmake/linguist/test_i18n_filter_autogen_files/check_ts_file.cmake @@ -0,0 +1,31 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +file(READ "${TS_FILE}" ts_file_content) + +set(expected_strings + "<source>Hello from app1!</source>" + "<source>Hello from app2!</source>" +) +foreach(needle IN LISTS expected_strings) + string(FIND "${ts_file_content}" "${needle}" pos) + if(pos EQUAL "-1") + message(FATAL_ERROR + "Expected string '${needle}' was not found in '${TS_FILE}'. " + "The file content is:\n${ts_file_content}" + ) + endif() +endforeach() + +set(forbidden_strings + "/ui_mainwindow.h\"" +) +foreach(needle IN LISTS forbidden_strings) + string(FIND "${ts_file_content}" "${needle}" pos) + if(NOT pos EQUAL "-1") + message(FATAL_ERROR + "Excluded string '${needle}' was found in '${TS_FILE}'. " + "The file content is:\n${ts_file_content}" + ) + endif() +endforeach() |