Rewrite translation file generation using cmake

This makes three big changes to how translation files are generated:

- use Qt5 cmake built-in commands to do the translations rather than
calling lrelease directly.  lrelease is often not in the path, while Qt5
cmake knows how to find and invoke it.

- Slam the resulting files into a C++ file using a cmake script rather
than needing to compile a .c file to generate C++ file.  This is
simpler, but more importantly avoids the mess needed when cross
compiling of having to import a cmake script from an external native
build.

- In the actual generated files, use an unordered_map rather than a
massive list of static variable pointers.
This commit is contained in:
Jason Rhinelander 2020-06-11 23:50:29 -03:00
parent 1c1a7e6f84
commit da400f6d66
10 changed files with 56 additions and 152 deletions

View file

@ -1,7 +1,7 @@
local default_deps_base='libsystemd-dev libboost-filesystem-dev libboost-thread-dev libboost-date-time-dev libboost-chrono-dev libgtest-dev ' +
'libboost-regex-dev libboost-serialization-dev libboost-program-options-dev libunbound-dev nettle-dev libevent-dev libminiupnpc-dev ' +
'libunwind8-dev libsodium-dev libssl-dev libreadline-dev libhidapi-dev libusb-1.0-0-dev libprotobuf-dev protobuf-compiler python3 ' +
'pkg-config libsqlite3-dev';
'pkg-config libsqlite3-dev qttools5-dev';
local default_deps='g++ ' + default_deps_base; // g++ sometimes needs replacement
local gtest_filter='-AddressFromURL.Failure:DNSResolver.DNSSEC*:is_hdd.linux_os_root';

View file

@ -433,8 +433,6 @@ add_definition_if_library_exists(c explicit_bzero "strings.h" HAVE_EXPLICIT_BZER
add_definition_if_function_found(strptime HAVE_STRPTIME)
# Generate header for embedded translations
# Generate header for embedded translations, use target toolchain if depends, otherwise use the
# lrelease and lupdate binaries from the host
add_subdirectory(translations)
add_library(miniupnpc INTERFACE)

View file

@ -59,7 +59,7 @@ library archives (`.a`).
| expat | 1.1 | NO | `libexpat1-dev` | `expat` | `expat-devel` | YES | XML parsing |
| Doxygen | any | NO | `doxygen` | `doxygen` | `doxygen` | YES | Documentation |
| Graphviz | any | NO | `graphviz` | `graphviz` | `graphviz` | YES | Documentation |
| lrelease | ? | NO | `qttools5-dev-tools` | `qt5-tools` | `qt5-linguist` | YES | Translations |
| Qt tools | 5.x | NO | `qttools5-dev` | `qt5-tools` | `qt5-linguist` | YES | Translations |
| libhidapi | ? | NO | `libhidapi-dev` | `hidapi` | `hidapi-devel` | YES | Hardware wallet |
| libusb | ? | NO | `libusb-dev` | `libusb` | `libusb-devel` | YES | Hardware wallet |
| libprotobuf | ? | NO | `libprotobuf-dev` | `protobuf` | `protobuf-devel` | YES | Hardware wallet |
@ -70,7 +70,7 @@ build the library binary manually. This can be done with the following command `
Install all dependencies at once on Debian/Ubuntu:
``` sudo apt update && sudo apt install build-essential cmake pkg-config libboost-all-dev libssl-dev libzmq3-dev libunbound-dev libsodium-dev libunwind8-dev liblzma-dev libreadline6-dev libldns-dev libexpat1-dev doxygen graphviz libpgm-dev libsqlite3-dev qttools5-dev-tools libhidapi-dev libusb-dev libprotobuf-dev protobuf-compiler ```
``` sudo apt update && sudo apt install build-essential cmake pkg-config libboost-all-dev libssl-dev libzmq3-dev libunbound-dev libsodium-dev libunwind8-dev liblzma-dev libreadline6-dev libldns-dev libexpat1-dev doxygen graphviz libpgm-dev libsqlite3-dev qttools5-dev libhidapi-dev libusb-dev libprotobuf-dev protobuf-compiler ```
Install all dependencies at once on macOS with the provided Brewfile:
``` brew update && brew bundle --file=contrib/brew/Brewfile ```

View file

@ -51,8 +51,13 @@ add_library(common
timings.cc
updates.cpp
util.cpp
${PROJECT_BINARY_DIR}/translations/translation_files.cpp
)
set_source_files_properties(${PROJECT_BINARY_DIR}/translations/translation_files.cpp PROPERTIES GENERATED 1)
add_dependencies(common generate_translation_data)
if (STACK_TRACE)
target_sources(common PRIVATE stack_trace.cpp)
if(WIN32 OR STATIC)
@ -64,8 +69,6 @@ if (BACKCOMPAT)
target_sources(common PRIVATE compat/glibc_compat.cpp)
endif()
add_dependencies(common generate_translations_header)
target_link_libraries(common
PUBLIC
cncrypto
@ -77,8 +80,6 @@ target_link_libraries(common
OpenSSL::Crypto
extra)
target_include_directories(common PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../../translations")
option(STACK_TRACE "Install a hook that dumps stack on exception" OFF)
if(STACK_TRACE)

View file

@ -34,7 +34,6 @@
#include <map>
#include "file_io_utils.h"
#include "common/i18n.h"
#include "translation_files.h"
#undef LOKI_DEFAULT_LOG_CATEGORY
#define LOKI_DEFAULT_LOG_CATEGORY "i18n"

View file

@ -34,5 +34,5 @@
std::string i18n_get_language();
int i18n_set_language(const char *directory, const char *base, std::string language = std::string());
const char *i18n_translate(const char *str, const std::string &context);
static inline std::string get_default_i18n_context() { return std::string(); }
static inline const char *tr(const char *str) { return i18n_translate(str,get_default_i18n_context()); }
inline const char *tr(const char *str) { return i18n_translate(str, std::string{}); }
bool find_embedded_file(const std::string &name, std::string &data); // In the generated translation_files.cpp

View file

@ -1,4 +1,4 @@
# Copyright (c) 2017-2018, The Monero Project
# Copyright (c) 2020, The Loki Project
#
# All rights reserved.
#
@ -26,58 +26,24 @@
# 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.
cmake_minimum_required(VERSION 3.0)
project(translations)
# when crosscompiling import the executable targets from a file
IF(CMAKE_CROSSCOMPILING)
message(WARNING "CrossCompiling")
SET(IMPORT_EXECUTABLES "${CMAKE_CURRENT_BINARY_DIR}/ImportExecutables.cmake" CACHE FILEPATH "Point it to the export file from a native build")
INCLUDE(${IMPORT_EXECUTABLES})
ENDIF(CMAKE_CROSSCOMPILING)
# only build the generator if not crosscompiling
IF(NOT CMAKE_CROSSCOMPILING)
add_executable(generate_translations_header generate_translations_header.c)
ENDIF(NOT CMAKE_CROSSCOMPILING)
if(NOT LRELEASE_PATH OR LRELEASE_PATH STREQUAL "")
find_program(LRELEASE lrelease)
find_package(Qt5 QUIET COMPONENTS Core LinguistTools)
if(NOT Qt5_FOUND OR NOT Qt5LinguistTools_FOUND)
set(qm_files "")
message(WARNING "Qt5 LingustTools not found, translation files not built")
else()
set(LRELEASE ${LRELEASE_PATH}/lrelease)
file(GLOB ts_files RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" *.ts)
qt5_add_translation(qm_files ${ts_files})
endif()
if(LRELEASE STREQUAL "LRELEASE-NOTFOUND")
set(ts_files "")
message(WARNING "lrelease program not found, translation files not built")
else()
execute_process(COMMAND ${LRELEASE} -version
RESULT_VARIABLE lrelease_ret)
if(NOT lrelease_ret EQUAL "0")
set(ts_files "")
message(WARNING "lrelease program not working, translation files not built")
else()
file(GLOB ts_files RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" *.ts)
foreach(ts_file ${ts_files})
string(REPLACE ".ts" ".qm" qm_file "${ts_file}")
add_custom_command(TARGET generate_translations_header
PRE_BUILD
COMMAND ${LRELEASE} "${CMAKE_CURRENT_SOURCE_DIR}/${ts_file}" -qm "${qm_file}"
WORKING_DIRECTORY "${CMAKE_CURRENT_BIN_DIR}")
endforeach()
endif()
endif()
string(REPLACE ".ts" ".qm" qm_files "${ts_files}")
add_custom_command(TARGET generate_translations_header
POST_BUILD
COMMAND ./generate_translations_header ${qm_files}
WORKING_DIRECTORY "${CMAKE_CURRENT_BIN_DIR}"
COMMENT "Generating embedded translations header")
# export the generator target to a file, so it can be imported (see above) by another build
IF(NOT CMAKE_CROSSCOMPILING)
EXPORT(TARGETS generate_translations_header FILE ${CMAKE_CURRENT_BINARY_DIR}/ImportExecutables.cmake )
ENDIF(NOT CMAKE_CROSSCOMPILING)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/translation_files.cpp
COMMAND ${CMAKE_COMMAND}
-D "qm_files=${qm_files}"
-D "base_dir=${CMAKE_CURRENT_BINARY_DIR}"
-D "in_file=${CMAKE_CURRENT_SOURCE_DIR}/translation_files.cpp.in"
-D "out_file=${CMAKE_CURRENT_BINARY_DIR}/translation_files.cpp"
-P ${CMAKE_CURRENT_SOURCE_DIR}/generate_translation_data.cmake
DEPENDS ${qm_files} ${CMAKE_CURRENT_SOURCE_DIR}/generate_translation_data.cmake
VERBATIM
)
add_custom_target(generate_translation_data DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/translation_files.cpp)

View file

@ -0,0 +1,10 @@
set(TRANSLATION_FILES "")
foreach(qm_file ${qm_files})
file(READ "${qm_file}" trans_data HEX)
file(RELATIVE_PATH basename "${base_dir}" "${qm_file}")
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "\\\\x\\1" trans_data "${trans_data}")
set(TRANSLATION_FILES "${TRANSLATION_FILES} {\"${basename}\"s, \"${trans_data}\"s},\n")
endforeach()
configure_file(${in_file} ${out_file} @ONLY)

View file

@ -1,87 +0,0 @@
// Copyright (c) 2013, Sergey Lyubka
// Copyright (c) 2017-2018, The Monero Project
// All rights reserved.
// Released under the MIT license.
// This program takes a list of files as an input, and produces C++ code that
// contains the contents of all these files as a collection of strings.
//
// Usage:
// 1. Compile this file:
// cc -o generate-translations-header generate-translations-header.c
//
// 2. Convert list of files into single header:
// ./generate-translations-header monero_fr.qm monero_it.qm > translations_files.h
//
// 3. In your application code, include translations_files.h, then you can
// access the files using this function:
// static bool find_embedded_file(const std::string &file_name, std::string &data);
// std::string data;
// find_embedded_file("monero_fr.qm", data);
#include <stdio.h>
#include <stdlib.h>
static const char *code =
"static bool find_embedded_file(const std::string &name, std::string &data) {\n"
" const struct embedded_file *p;\n"
" for (p = embedded_files; p->name != NULL; p++) {\n"
" if (*p->name == name) {\n"
" data = *p->data;\n"
" return true;\n"
" }\n"
" }\n"
" return false;\n"
"}\n";
int main(int argc, char *argv[]) {
FILE *fp, *foutput;
int i, j, ch;
if((foutput = fopen("translation_files.h", "w")) == NULL) {
exit(EXIT_FAILURE);
}
fprintf(foutput, "#ifndef TRANSLATION_FILES_H\n");
fprintf(foutput, "#define TRANSLATION_FILES_H\n\n");
fprintf(foutput, "#include <string>\n\n");
for (i = 1; i < argc; i++) {
if ((fp = fopen(argv[i], "rb")) == NULL) {
fclose(foutput);
exit(EXIT_FAILURE);
} else {
fprintf(foutput, "static const std::string translation_file_name_%d = \"%s\";\n", i, argv[i]);
fprintf(foutput, "static const std::string translation_file_data_%d = std::string(", i);
for (j = 0; (ch = fgetc(fp)) != EOF; j++) {
if ((j % 16) == 0) {
if (j > 0) {
fprintf(foutput, "%s", "\"");
}
fprintf(foutput, "%s", "\n \"");
}
fprintf(foutput, "\\x%02x", ch);
}
fprintf(foutput, "\",\n %d);\n\n", j);
fclose(fp);
}
}
fprintf(foutput, "%s", "static const struct embedded_file {\n");
fprintf(foutput, "%s", " const std::string *name;\n");
fprintf(foutput, "%s", " const std::string *data;\n");
fprintf(foutput, "%s", "} embedded_files[] = {\n");
for (i = 1; i < argc; i++) {
fprintf(foutput, " {&translation_file_name_%d, &translation_file_data_%d},\n", i, i);
}
fprintf(foutput, "%s", " {NULL, NULL}\n");
fprintf(foutput, "%s", "};\n\n");
fprintf(foutput, "%s\n", code);
fprintf(foutput, "#endif /* TRANSLATION_FILES_H */\n");
fclose(foutput);
return EXIT_SUCCESS;
}

View file

@ -0,0 +1,17 @@
#include <string>
#include <unordered_map>
using namespace std::literals;
static std::unordered_map<std::string, std::string> _translation_files{
@TRANSLATION_FILES@
};
bool find_embedded_file(const std::string &name, std::string &data) {
auto it = _translation_files.find(name);
if (it != _translation_files.end()) {
data = it->second;
return true;
}
return false;
}