cmake: Drop python dependency for translate
What changed, and why it matters
This commit removes a Python helper script used during the build process and replaces it with equivalent logic written directly in CMake. It is a build-system cleanup that drops a Python dependency for generating translation string files. There is no indication this change fixes or introduces a security vulnerability.
No security action required. Treat as a routine build-system refactoring.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change deletes share/qt/extract_strings_qt.py and reimplements its xgettext output parsing and bitcoinstrings.cpp generation inside share/qt/translate.cmake using CMake commands. src/qt/CMakeLists.txt is updated to stop passing PYTHON_EXECUTABLE to translate.cmake. The generated output is intended to be functionally equivalent. No runtime code, cryptographic logic, or network handling is modified.
Changed components
share/qt/translate.cmakesrc/qt/CMakeLists.txtshare/qt/extract_strings_qt.pyInspect captured patch +58 / −96
diff --git a/share/qt/extract_strings_qt.py b/share/qt/extract_strings_qt.py
deleted file mode 100755
index 42971430..00000000
--- a/share/qt/extract_strings_qt.py
+++ /dev/null
@@ -1,85 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2012-2021 The Bitcoin Core developers
-# Distributed under the MIT software license, see the accompanying
-# file COPYING or http://www.opensource.org/licenses/mit-license.php.
-'''
-Extract _("...") strings for translation and convert to Qt stringdefs so that
-they can be picked up by Qt linguist.
-'''
-from subprocess import Popen, PIPE
-import operator
-import os
-import sys
-
-OUT_CPP="qt/bitcoinstrings.cpp"
-EMPTY=['""']
-
-def parse_po(text):
- """
- Parse 'po' format produced by xgettext.
- Return a list of (msgid,msgstr) tuples.
- """
- messages = []
- msgid = []
- msgstr = []
- in_msgid = False
- in_msgstr = False
-
- for line in text.split('\n'):
- line = line.rstrip('\r')
- if line.startswith('msgid '):
- if in_msgstr:
- messages.append((msgid, msgstr))
- in_msgstr = False
- # message start
- in_msgid = True
-
- msgid = [line[6:]]
- elif line.startswith('msgstr '):
- in_msgid = False
- in_msgstr = True
- msgstr = [line[7:]]
- elif line.startswith('"'):
- if in_msgid:
- msgid.append(line)
- if in_msgstr:
- msgstr.append(line)
-
- if in_msgstr:
- messages.append((msgid, msgstr))
-
- return messages
-
-files = sys.argv[1:]
-
-# xgettext -n --keyword=_ $FILES
-XGETTEXT=os.getenv('XGETTEXT', 'xgettext')
-if not XGETTEXT:
- print('Cannot extract strings: xgettext utility is not installed or not configured.',file=sys.stderr)
- print('Please install package "gettext" and re-run \'cmake -B build\'.',file=sys.stderr)
- sys.exit(1)
-child = Popen([XGETTEXT,'--output=-','--from-code=utf-8','-n','--keyword=_'] + files, stdout=PIPE)
-(out, err) = child.communicate()
-
-messages = parse_po(out.decode('utf-8'))
-
-f = open(OUT_CPP, 'w', encoding="utf8")
-f.write("""
-
-#include <QtGlobal>
-
-// Automatically generated by extract_strings_qt.py
-#ifdef __GNUC__
-#define UNUSED __attribute__((unused))
-#else
-#define UNUSED
-#endif
-""")
-f.write('static const char UNUSED *bitcoin_strings[] = {\n')
-f.write('QT_TRANSLATE_NOOP("bitcoin-core", "%s"),\n' % (os.getenv('COPYRIGHT_HOLDERS'),))
-messages.sort(key=operator.itemgetter(0))
-for (msgid, msgstr) in messages:
- if msgid != EMPTY:
- f.write('QT_TRANSLATE_NOOP("bitcoin-core", %s),\n' % ('\n'.join(msgid)))
-f.write('};\n')
-f.close()
diff --git a/share/qt/translate.cmake b/share/qt/translate.cmake
index a4eeef96..ed32c6dd 100644
--- a/share/qt/translate.cmake
+++ b/share/qt/translate.cmake
@@ -2,12 +2,13 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+cmake_minimum_required(VERSION 3.22)
+
set(input_variables
PROJECT_SOURCE_DIR
COPYRIGHT_HOLDERS
LCONVERT_EXECUTABLE
LUPDATE_EXECUTABLE
- PYTHON_EXECUTABLE
XGETTEXT_EXECUTABLE
)
@@ -17,6 +18,60 @@ foreach(var IN LISTS input_variables)
endif()
endforeach()
+# Extract _("...") strings for translation and convert to Qt stringdefs so that
+# they can be picked up by Qt linguist.
+function(extract_strings output)
+ execute_process(
+ COMMAND ${XGETTEXT_EXECUTABLE}
+ --output=bitcoinstrings.po
+ --no-location
+ --from-code=utf-8
+ --keyword=_
+ ${ARGN}
+ COMMAND_ERROR_IS_FATAL ANY
+ )
+
+ file(STRINGS "bitcoinstrings.po" text ENCODING "UTF-8")
+
+ set(messages "${COPYRIGHT_HOLDERS}")
+ foreach(line IN LISTS text)
+ if(line MATCHES "^msgid \"(.*)\"$")
+ set(msgid "${CMAKE_MATCH_1}")
+ elseif(line MATCHES "^\"(.*)\"$")
+ string(APPEND msgid "${CMAKE_MATCH_1}")
+ elseif(line MATCHES "^msgstr .*$" AND NOT msgid STREQUAL "")
+ # CMake uses ';' as a list separator.
+ # We need to temporarily replace that in order to keep strings intact.
+ string(REPLACE ";" "<cmake-semicolon>" msgid "${msgid}")
+ list(APPEND messages "${msgid}")
+ endif()
+ endforeach()
+
+ set(content [[// Automatically generated by translate.cmake
+
+#include <QtGlobal>
+
+#ifdef __GNUC__
+#define UNUSED __attribute__((unused))
+#else
+#define UNUSED
+#endif
+
+static const char UNUSED *bitcoin_strings[] = {
+]])
+
+ set(prefix "QT_TRANSLATE_NOOP(\"bitcoin-core\", \"")
+ set(suffix "\"),\n")
+
+ list(SORT messages)
+ list(JOIN messages "${suffix}${prefix}" messages_str)
+ string(APPEND content "${prefix}${messages_str}${suffix}")
+ string(APPEND content "};\n")
+ string(REPLACE "<cmake-semicolon>" ";" content "${content}")
+
+ file(WRITE "${output}" "${content}")
+endfunction()
+
file(GLOB_RECURSE translatable_sources
"${PROJECT_SOURCE_DIR}/src/*.h"
"${PROJECT_SOURCE_DIR}/src/*.cpp"
@@ -41,15 +96,8 @@ foreach(directory IN LISTS subtrees exclude_dirs)
)
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
+extract_strings("${PROJECT_SOURCE_DIR}/src/qt/bitcoinstrings.cpp"
+ ${translatable_sources}
)
execute_process(
diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt
index 5a3f9873..c23a7ec6 100644
--- a/src/qt/CMakeLists.txt
+++ b/src/qt/CMakeLists.txt
@@ -300,7 +300,6 @@ else()
-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 "XGETTEXT_EXECUTABLE=${XGETTEXT_EXECUTABLE}"
-P ${PROJECT_SOURCE_DIR}/share/qt/translate.cmake
)
Why this scored 15/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.