cmake: Introduce translate.cmake script for translate target
What changed, and why it matters
This commit is a routine build-system cleanup for Bitcoin Core's Qt GUI translation tooling. It moves the logic for finding translatable source files out of the CMake configuration step and into a separate script that runs only when developers explicitly build the 'translate' target. There is no change to how Bitcoin handles money, network messages, wallets, or private keys, and no security issue is evident.
No security action required. Review as normal build-system refactoring if desired.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch refactors the CMake ‘translate’ target. Previously, file globbing and source-list extraction happened at CMake configure time in src/qt/CMakeLists.txt. Now, an add_custom_target invokes a new share/qt/translate.cmake script with -P at build time, passing tool paths as -D variables. The script performs GLOB_RECURSE, filters subtrees and test directories, then runs extract_strings_qt.py, lupdate, lconvert, and sed to regenerate locale files. The old get_translatable_sources() CMake function is removed. No runtime code, consensus logic, cryptography, or input parsing is modified.
Changed components
share/qt/translate.cmake (new)src/qt/CMakeLists.txtInspect captured patch +94 / −53
diff --git a/share/qt/translate.cmake b/share/qt/translate.cmake
new file mode 100644
index 00000000..cc503526
--- /dev/null
+++ b/share/qt/translate.cmake
@@ -0,0 +1,85 @@
+# Copyright (c) 2025 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+set(input_variables
+ PROJECT_SOURCE_DIR
+ COPYRIGHT_HOLDERS
+ LCONVERT_EXECUTABLE
+ LUPDATE_EXECUTABLE
+ PYTHON_EXECUTABLE
+ SED_EXECUTABLE
+ XGETTEXT_EXECUTABLE
+)
+
+foreach(var IN LISTS input_variables)
+ if(NOT DEFINED ${var})
+ message(FATAL_ERROR "Variable '${var}' is not defined!")
+ endif()
+endforeach()
+
+file(GLOB_RECURSE translatable_sources
+ "${PROJECT_SOURCE_DIR}/src/*.h"
+ "${PROJECT_SOURCE_DIR}/src/*.cpp"
+ "${PROJECT_SOURCE_DIR}/src/*.mm"
+)
+
+file(GLOB_RECURSE qt_translatable_sources
+ "${PROJECT_SOURCE_DIR}/src/qt/*.h"
+ "${PROJECT_SOURCE_DIR}/src/qt/*.cpp"
+ "${PROJECT_SOURCE_DIR}/src/qt/*.mm"
+)
+
+file(GLOB ui_files
+ "${PROJECT_SOURCE_DIR}/src/qt/forms/*.ui"
+)
+
+set(subtrees crc32c crypto/ctaes leveldb minisketch secp256k1)
+set(exclude_dirs bench compat crypto support test univalue)
+foreach(directory IN LISTS subtrees exclude_dirs)
+ list(FILTER translatable_sources
+ EXCLUDE REGEX "${PROJECT_SOURCE_DIR}/src/${directory}/.*"
+ )
+endforeach()
+
+execute_process(
+ COMMAND ${CMAKE_COMMAND} -E env
+ XGETTEXT=${XGETTEXT_EXECUTABLE}
+ COPYRIGHT_HOLDERS=${COPYRIGHT_HOLDERS}
+ ${PYTHON_EXECUTABLE}
+ ${CMAKE_CURRENT_LIST_DIR}/extract_strings_qt.py
+ ${translatable_sources}
+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src
+ COMMAND_ERROR_IS_FATAL ANY
+)
+
+execute_process(
+ COMMAND ${LUPDATE_EXECUTABLE}
+ -no-obsolete
+ -I ${PROJECT_SOURCE_DIR}/src
+ -locations relative
+ ${ui_files}
+ ${qt_translatable_sources}
+ ${PROJECT_SOURCE_DIR}/src/qt/bitcoinstrings.cpp
+ -ts ${PROJECT_SOURCE_DIR}/src/qt/locale/bitcoin_en.ts
+ COMMAND_ERROR_IS_FATAL ANY
+)
+
+execute_process(
+ COMMAND ${LCONVERT_EXECUTABLE}
+ -drop-translations
+ -o ${PROJECT_SOURCE_DIR}/src/qt/locale/bitcoin_en.xlf
+ -i ${PROJECT_SOURCE_DIR}/src/qt/locale/bitcoin_en.ts
+ COMMAND_ERROR_IS_FATAL ANY
+)
+
+execute_process(
+ COMMAND ${SED_EXECUTABLE}
+ -i.old
+ -e "s|source-language=\"en\" target-language=\"en\"|source-language=\"en\"|"
+ -e "/<target xml:space=\"preserve\"><\\/target>/d"
+ ${PROJECT_SOURCE_DIR}/src/qt/locale/bitcoin_en.xlf
+ COMMAND_ERROR_IS_FATAL ANY
+)
+
+file(REMOVE "${PROJECT_SOURCE_DIR}/src/qt/locale/bitcoin_en.xlf.old")
diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt
index 73645283..c3113067 100644
--- a/src/qt/CMakeLists.txt
+++ b/src/qt/CMakeLists.txt
@@ -289,44 +289,6 @@ if(BUILD_GUI_TESTS)
add_subdirectory(test)
endif()
-
-# Gets sources to be parsed to gather translatable strings.
-function(get_translatable_sources var)
- set(result)
- set(targets)
- foreach(dir IN ITEMS ${ARGN})
- get_directory_property(dir_targets DIRECTORY ${PROJECT_SOURCE_DIR}/${dir} BUILDSYSTEM_TARGETS)
- list(APPEND targets ${dir_targets})
- endforeach()
- foreach(target IN LISTS targets)
- get_target_property(target_sources ${target} SOURCES)
- if(target_sources)
- foreach(source IN LISTS target_sources)
- # Get an expression from the generator expression, if any.
- if(source MATCHES ":([^>]+)>$")
- set(source ${CMAKE_MATCH_1})
- endif()
- cmake_path(GET source EXTENSION LAST_ONLY ext)
- if(ext STREQUAL ".qrc")
- continue()
- endif()
- if(NOT IS_ABSOLUTE source)
- get_target_property(target_source_dir ${target} SOURCE_DIR)
- cmake_path(APPEND target_source_dir ${source} OUTPUT_VARIABLE source)
- endif()
- get_property(is_generated
- SOURCE ${source} TARGET_DIRECTORY ${target}
- PROPERTY GENERATED
- )
- if(NOT is_generated)
- list(APPEND result ${source})
- endif()
- endforeach()
- endif()
- endforeach()
- set(${var} ${result} PARENT_SCOPE)
-endfunction()
-
find_program(XGETTEXT_EXECUTABLE xgettext)
find_program(SED_EXECUTABLE sed)
if(NOT XGETTEXT_EXECUTABLE)
@@ -338,20 +300,14 @@ elseif(NOT SED_EXECUTABLE)
COMMAND ${CMAKE_COMMAND} -E echo "Error: GNU sed not found"
)
else()
- set(translatable_sources_directories src src/qt src/util)
- if(ENABLE_WALLET)
- list(APPEND translatable_sources_directories src/wallet)
- endif()
- get_translatable_sources(translatable_sources ${translatable_sources_directories})
- get_translatable_sources(qt_translatable_sources src/qt)
- file(GLOB ui_files ${CMAKE_CURRENT_SOURCE_DIR}/forms/*.ui)
- add_custom_target(translate
- COMMAND ${CMAKE_COMMAND} -E env XGETTEXT=${XGETTEXT_EXECUTABLE} COPYRIGHT_HOLDERS=${COPYRIGHT_HOLDERS} $<TARGET_FILE:Python3::Interpreter> ${PROJECT_SOURCE_DIR}/share/qt/extract_strings_qt.py ${translatable_sources}
- COMMAND Qt6::lupdate -no-obsolete -I ${PROJECT_SOURCE_DIR}/src -locations relative ${CMAKE_CURRENT_SOURCE_DIR}/bitcoinstrings.cpp ${ui_files} ${qt_translatable_sources} -ts ${CMAKE_CURRENT_SOURCE_DIR}/locale/bitcoin_en.ts
- COMMAND Qt6::lconvert -drop-translations -o ${CMAKE_CURRENT_SOURCE_DIR}/locale/bitcoin_en.xlf -i ${CMAKE_CURRENT_SOURCE_DIR}/locale/bitcoin_en.ts
- COMMAND ${SED_EXECUTABLE} -i.old -e "s|source-language=\"en\" target-language=\"en\"|source-language=\"en\"|" -e "/<target xml:space=\"preserve\"><\\/target>/d" ${CMAKE_CURRENT_SOURCE_DIR}/locale/bitcoin_en.xlf
- COMMAND ${CMAKE_COMMAND} -E rm ${CMAKE_CURRENT_SOURCE_DIR}/locale/bitcoin_en.xlf.old
- WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src
- VERBATIM
+ add_custom_target(translate COMMAND ${CMAKE_COMMAND}
+ -D "PROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR}"
+ -D "COPYRIGHT_HOLDERS=${COPYRIGHT_HOLDERS}"
+ -D "LCONVERT_EXECUTABLE=$<TARGET_FILE:Qt6::lconvert>"
+ -D "LUPDATE_EXECUTABLE=$<TARGET_FILE:Qt6::lupdate>"
+ -D "PYTHON_EXECUTABLE=$<TARGET_FILE:Python3::Interpreter>"
+ -D "SED_EXECUTABLE=${SED_EXECUTABLE}"
+ -D "XGETTEXT_EXECUTABLE=${XGETTEXT_EXECUTABLE}"
+ -P ${PROJECT_SOURCE_DIR}/share/qt/translate.cmake
)
endif()
Why this scored 14/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.