| 1 | diff --git a/cmake/UraniumPluginInstall.cmake b/cmake/UraniumPluginInstall.cmake
|
| 2 | new file mode 100644
|
| 3 | index 000000000..8ba93c60b
|
| 4 | |
| 5 | |
| 6 | @@ -0,0 +1,86 @@
|
| 7 | +# Copyright (c) 2022 Ultimaker B.V.
|
| 8 | +# UraniumPluginInstall.cmake is released under the terms of the LGPLv3 or higher.
|
| 9 | +
|
| 10 | +#
|
| 11 | +# This module detects all plugins that need to be installed and adds them using the CMake install() command.
|
| 12 | +# It detects all plugin folder in the path "plugins/*" where there's a "plugin.json" in it.
|
| 13 | +#
|
| 14 | +# Plugins can be configured to NOT BE INSTALLED via the variable "UM_NO_INSTALL_PLUGINS" as a list of string in the
|
| 15 | +# form of "a;b;c" or "a,b,c". By default all plugins will be installed.
|
| 16 | +#
|
| 17 | +
|
| 18 | +# Options or configuration variables
|
| 19 | +set(UM_NO_INSTALL_PLUGINS "" CACHE STRING "A list of plugins that should not be installed, separated with ';' or ','.")
|
| 20 | +
|
| 21 | +file(GLOB_RECURSE _plugin_json_list ${CMAKE_SOURCE_DIR}/plugins/*/plugin.json)
|
| 22 | +list(LENGTH _plugin_json_list _plugin_json_list_len)
|
| 23 | +
|
| 24 | +# Sort the lists alphabetically so we can handle cases like this:
|
| 25 | +# - plugins/my_plugin/plugin.json
|
| 26 | +# - plugins/my_plugin/my_module/plugin.json
|
| 27 | +# In this case, only "plugins/my_plugin" should be added via install().
|
| 28 | +set(_no_install_plugin_list ${UM_NO_INSTALL_PLUGINS})
|
| 29 | +# Sanitize the string so the comparison will be case-insensitive.
|
| 30 | +string(STRIP "${_no_install_plugin_list}" _no_install_plugin_list)
|
| 31 | +string(TOLOWER "${_no_install_plugin_list}" _no_install_plugin_list)
|
| 32 | +
|
| 33 | +# WORKAROUND counterpart of what's in cura-build.
|
| 34 | +string(REPLACE "," ";" _no_install_plugin_list "${_no_install_plugin_list}")
|
| 35 | +
|
| 36 | +list(LENGTH _no_install_plugin_list _no_install_plugin_list_len)
|
| 37 | +
|
| 38 | +if(_no_install_plugin_list_len GREATER 0)
|
| 39 | + list(SORT _no_install_plugin_list)
|
| 40 | +endif()
|
| 41 | +if(_plugin_json_list_len GREATER 0)
|
| 42 | + list(SORT _plugin_json_list)
|
| 43 | +endif()
|
| 44 | +
|
| 45 | +# Check all plugin directories and add them via install() if needed.
|
| 46 | +set(_install_plugin_list "")
|
| 47 | +foreach(_plugin_json_path ${_plugin_json_list})
|
| 48 | + get_filename_component(_plugin_dir ${_plugin_json_path} DIRECTORY)
|
| 49 | + file(RELATIVE_PATH _rel_plugin_dir ${CMAKE_CURRENT_SOURCE_DIR} ${_plugin_dir})
|
| 50 | + get_filename_component(_plugin_dir_name ${_plugin_dir} NAME)
|
| 51 | +
|
| 52 | + # Make plugin name comparison case-insensitive
|
| 53 | + string(TOLOWER "${_plugin_dir_name}" _plugin_dir_name_lowercase)
|
| 54 | +
|
| 55 | + # Check if this plugin needs to be skipped for installation
|
| 56 | + set(_add_plugin ON) # Indicates if this plugin should be added to the build or not.
|
| 57 | + set(_is_no_install_plugin OFF) # If this plugin will not be added, this indicates if it's because the plugin is
|
| 58 | + # specified in the NO_INSTALL_PLUGINS list.
|
| 59 | + if(_no_install_plugin_list)
|
| 60 | + if("${_plugin_dir_name_lowercase}" IN_LIST _no_install_plugin_list)
|
| 61 | + set(_add_plugin OFF)
|
| 62 | + set(_is_no_install_plugin ON)
|
| 63 | + endif()
|
| 64 | + endif()
|
| 65 | +
|
| 66 | + # Make sure this is not a subdirectory in a plugin that's already in the install list
|
| 67 | + if(_add_plugin)
|
| 68 | + foreach(_known_install_plugin_dir ${_install_plugin_list})
|
| 69 | + if(_plugin_dir MATCHES "${_known_install_plugin_dir}.+")
|
| 70 | + set(_add_plugin OFF)
|
| 71 | + break()
|
| 72 | + endif()
|
| 73 | + endforeach()
|
| 74 | + endif()
|
| 75 | +
|
| 76 | + if(_add_plugin)
|
| 77 | + message(STATUS "[+] PLUGIN TO INSTALL: ${_rel_plugin_dir}")
|
| 78 | + get_filename_component(_rel_plugin_parent_dir ${_rel_plugin_dir} DIRECTORY)
|
| 79 | + install(DIRECTORY ${_rel_plugin_dir}
|
| 80 | + DESTINATION lib${LIB_SUFFIX}/uranium/${_rel_plugin_parent_dir}
|
| 81 | + PATTERN "__pycache__" EXCLUDE
|
| 82 | + PATTERN "*.qmlc" EXCLUDE
|
| 83 | + )
|
| 84 | + list(APPEND _install_plugin_list ${_plugin_dir})
|
| 85 | + elseif(_is_no_install_plugin)
|
| 86 | + message(STATUS "[-] PLUGIN TO REMOVE : ${_rel_plugin_dir}")
|
| 87 | + execute_process(COMMAND ${Python_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/mod_bundled_packages_json.py
|
| 88 | + -d ${CMAKE_CURRENT_SOURCE_DIR}/resources/bundled_packages
|
| 89 | + ${_plugin_dir_name}
|
| 90 | + RESULT_VARIABLE _mod_json_result)
|
| 91 | + endif()
|
| 92 | +endforeach()
|
| 93 | diff --git a/cmake/UraniumTranslationTools.cmake b/cmake/UraniumTranslationTools.cmake
|
| 94 | new file mode 100644
|
| 95 | index 000000000..91e65f5a7
|
| 96 | |
| 97 | |
| 98 | @@ -0,0 +1,114 @@
|
| 99 | +## 3rd party code:
|
| 100 | +
|
| 101 | +# Macro needed to list all sub-directory of a directory.
|
| 102 | +# There is no function in cmake as far as I know.
|
| 103 | +# Found at: http://stackoverflow.com/a/7788165
|
| 104 | +MACRO(SUBDIRLIST result curdir)
|
| 105 | + FILE(GLOB children RELATIVE ${curdir} ${curdir}/*)
|
| 106 | + SET(dirlist "")
|
| 107 | + FOREACH(child ${children})
|
| 108 | + IF(IS_DIRECTORY ${curdir}/${child})
|
| 109 | + STRING(REPLACE "/" "" child ${child})
|
| 110 | + LIST(APPEND dirlist ${child})
|
| 111 | + ENDIF()
|
| 112 | + ENDFOREACH()
|
| 113 | + SET(${result} ${dirlist})
|
| 114 | +ENDMACRO()
|
| 115 | +
|
| 116 | +
|
| 117 | +## Translation tools:
|
| 118 | +
|
| 119 | +SET(CURA_BINARY_DATA_DIRECTORY CACHE PATH "Directory to the cura-binary-data repository")
|
| 120 | +
|
| 121 | +if(NOT CURA_BINARY_DATA_DIRECTORY AND NOT DEFINED $ENV{CURA_BINARY_DATA_DIRECTORY})
|
| 122 | + message(STATUS "Using CURA_BINARY_DATA_DIRECTORY from set of environment variables...")
|
| 123 | + SET(CURA_BINARY_DATA_DIRECTORY $ENV{CURA_BINARY_DATA_DIRECTORY})
|
| 124 | +endif()
|
| 125 | +
|
| 126 | +# Dynamically creates targets for each language to create a *.po-file
|
| 127 | +MACRO(TARGETS_FOR_PO_FILES language)
|
| 128 | + if(DEFINED GETTEXT_MSGINIT_EXECUTABLE)
|
| 129 | + message(STATUS "Creating target i18n-create-po-${language}")
|
| 130 | + add_custom_target(i18n-create-po-${language})
|
| 131 | + add_dependencies(i18n-create-po i18n-create-po-${language})
|
| 132 | + endif()
|
| 133 | + message(STATUS "Creating target i18n-update-po-${language}")
|
| 134 | + add_custom_target(i18n-update-po-${language})
|
| 135 | + add_dependencies(i18n-update-po i18n-update-po-${language})
|
| 136 | + foreach(pot_file ${pot_files})
|
| 137 | + string(REGEX REPLACE ".*/(.*).pot" "${CMAKE_SOURCE_DIR}/resources/i18n/${language}/\\1.po" po_file ${pot_file})
|
| 138 | + if(DEFINED GETTEXT_MSGINIT_EXECUTABLE)
|
| 139 | + add_custom_command(TARGET i18n-create-po-${language} POST_BUILD
|
| 140 | + COMMAND ${GETTEXT_MSGINIT_EXECUTABLE} ARGS --no-wrap --no-translator -l ${language} -i ${pot_file} -o ${po_file})
|
| 141 | + endif()
|
| 142 | + add_custom_command(TARGET i18n-update-po-${language} POST_BUILD
|
| 143 | + COMMAND ${GETTEXT_MSGMERGE_EXECUTABLE} ARGS --no-wrap --no-fuzzy-matching -o ${po_file} ${po_file} ${pot_file})
|
| 144 | + endforeach()
|
| 145 | +ENDMACRO()
|
| 146 | +
|
| 147 | +# Dynamically creates targets for each language to create a *.mo-file
|
| 148 | +MACRO(TARGETS_FOR_MO_FILES language)
|
| 149 | + message(STATUS "Creating target i18n-create-mo-${language}")
|
| 150 | + add_custom_target(i18n-create-mo-${language})
|
| 151 | + add_dependencies(i18n-create-mo i18n-create-mo-${language})
|
| 152 | + if(TARGET i18n-copy-mo)
|
| 153 | + message(STATUS "Creating target i18n-copy-mo-${language}")
|
| 154 | + add_custom_target(i18n-copy-mo-${language})
|
| 155 | + add_dependencies(i18n-copy-mo i18n-copy-mo-${language})
|
| 156 | + endif()
|
| 157 | + file(GLOB po_files ${CMAKE_SOURCE_DIR}/resources/i18n/${language}/*.po)
|
| 158 | + foreach(po_file ${po_files})
|
| 159 | + string(REGEX REPLACE ".*/(.*).po" "${CMAKE_BINARY_DIR}/resources/i18n/${language}/LC_MESSAGES/\\1.mo" mo_file ${po_file})
|
| 160 | + add_custom_command(TARGET i18n-create-mo-${language} POST_BUILD
|
| 161 | + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/resources/i18n/${language}/LC_MESSAGES/
|
| 162 | + COMMAND ${GETTEXT_MSGFMT_EXECUTABLE} ARGS ${po_file} -o ${mo_file} -f)
|
| 163 | + if(TARGET i18n-copy-mo-${language})
|
| 164 | + string(REGEX REPLACE ".*/(.*).po" "${CURA_BINARY_DATA_DIRECTORY}/${PROJECT_NAME}/resources/i18n/${language}/LC_MESSAGES/\\1.mo" mo_file_binary_copy ${po_file})
|
| 165 | + add_custom_command(TARGET i18n-copy-mo-${language} POST_BUILD
|
| 166 | + COMMAND ${CMAKE_COMMAND} -E make_directory ${CURA_BINARY_DATA_DIRECTORY}/resources/i18n/${language}/LC_MESSAGES/
|
| 167 | + COMMAND ${CMAKE_COMMAND} -E copy ${mo_file} ${mo_file_binary_copy})
|
| 168 | + add_dependencies(i18n-copy-mo-${language} i18n-create-mo-${language})
|
| 169 | + endif()
|
| 170 | + endforeach()
|
| 171 | +ENDMACRO()
|
| 172 | +
|
| 173 | +# Checks for availability of gettext and when found creates all targets
|
| 174 | +# TODO: Adding option to set the PROJECT_NAME externally!
|
| 175 | +MACRO(CREATE_TRANSLATION_TARGETS)
|
| 176 | +find_package(Gettext)
|
| 177 | +
|
| 178 | +if(GETTEXT_FOUND)
|
| 179 | + # translations target will convert .po files into .mo and .qm as needed.
|
| 180 | + # The files are checked for a _qt suffix and if it is found, converted to
|
| 181 | + # qm, otherwise they are converted to .po.
|
| 182 | + if(DEFINED GETTEXT_MSGINIT_EXECUTABLE)
|
| 183 | + message(STATUS "Creating target i18n-create-po")
|
| 184 | + add_custom_target(i18n-create-po)
|
| 185 | + else()
|
| 186 | + message(WARNING "GETTEXT_MSGINIT_EXECUTABLE is undefined!\nSkipping to create i18n-create-po* targets...")
|
| 187 | + endif()
|
| 188 | + if(CURA_BINARY_DATA_DIRECTORY)
|
| 189 | + if(EXISTS ${CURA_BINARY_DATA_DIRECTORY})
|
| 190 | + message(STATUS "CURA_BINARY_DATA_DIRECTORY: ${CURA_BINARY_DATA_DIRECTORY}")
|
| 191 | + message(STATUS "Creating target i18n-copy-mo")
|
| 192 | + add_custom_target(i18n-copy-mo)
|
| 193 | + else()
|
| 194 | + message(WARNING "CURA_BINARY_DATA_DIRECTORY does not exist! (${CURA_BINARY_DATA_DIRECTORY})")
|
| 195 | + endif()
|
| 196 | + else()
|
| 197 | + message(WARNING "CURA_BINARY_DATA_DIRECTORY is not set!")
|
| 198 | + endif()
|
| 199 | + message(STATUS "Creating target i18n-update-po")
|
| 200 | + add_custom_target(i18n-update-po)
|
| 201 | + message(STATUS "Creating target i18n-create-mo")
|
| 202 | + add_custom_target(i18n-create-mo ALL)
|
| 203 | +
|
| 204 | + SUBDIRLIST(languages ${CMAKE_SOURCE_DIR}/resources/i18n/)
|
| 205 | + file(GLOB pot_files ${CMAKE_SOURCE_DIR}/resources/i18n/*.pot)
|
| 206 | + foreach(language ${languages})
|
| 207 | + TARGETS_FOR_PO_FILES(${language})
|
| 208 | + TARGETS_FOR_MO_FILES(${language})
|
| 209 | + endforeach()
|
| 210 | + install(DIRECTORY ${CMAKE_BINARY_DIR}/resources DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/)
|
| 211 | +endif()
|
| 212 | +ENDMACRO()
|
| 213 | diff --git a/cmake/mod_bundled_packages_json.py b/cmake/mod_bundled_packages_json.py
|
| 214 | new file mode 100644
|
| 215 | index 000000000..6423591f5
|
| 216 | |
| 217 | |
| 218 | @@ -0,0 +1,69 @@
|
| 219 | +#!/usr/bin/env python3
|
| 220 | +#
|
| 221 | +# This script removes the given package entries in the bundled_packages JSON files. This is used by the PluginInstall
|
| 222 | +# CMake module.
|
| 223 | +#
|
| 224 | +
|
| 225 | +import argparse
|
| 226 | +import collections
|
| 227 | +import json
|
| 228 | +import os
|
| 229 | +import sys
|
| 230 | +
|
| 231 | +
|
| 232 | +## Finds all JSON files in the given directory recursively and returns a list of those files in absolute paths.
|
| 233 | +#
|
| 234 | +# \param work_dir The directory to look for JSON files recursively.
|
| 235 | +# \return A list of JSON files in absolute paths that are found in the given directory.
|
| 236 | +def find_json_files(work_dir: str) -> list:
|
| 237 | + json_file_list = []
|
| 238 | + for root, dir_names, file_names in os.walk(work_dir):
|
| 239 | + for file_name in file_names:
|
| 240 | + abs_path = os.path.abspath(os.path.join(root, file_name))
|
| 241 | + json_file_list.append(abs_path)
|
| 242 | + return json_file_list
|
| 243 | +
|
| 244 | +
|
| 245 | +## Removes the given entries from the given JSON file. The file will modified in-place.
|
| 246 | +#
|
| 247 | +# \param file_path The JSON file to modify.
|
| 248 | +# \param entries A list of strings as entries to remove.
|
| 249 | +# \return None
|
| 250 | +def remove_entries_from_json_file(file_path: str, entries: list) -> None:
|
| 251 | + try:
|
| 252 | + with open(file_path, "r", encoding = "utf-8") as f:
|
| 253 | + package_dict = json.load(f, object_hook = collections.OrderedDict)
|
| 254 | + except Exception as e:
|
| 255 | + msg = "Failed to load '{file_path}' as a JSON file. This file will be ignored Exception: {e}"\
|
| 256 | + .format(file_path = file_path, e = e)
|
| 257 | + sys.stderr.write(msg + os.linesep)
|
| 258 | + return
|
| 259 | +
|
| 260 | + for entry in entries:
|
| 261 | + if entry in package_dict:
|
| 262 | + del package_dict[entry]
|
| 263 | + print("[INFO] Remove entry [{entry}] from [{file_path}]".format(file_path = file_path, entry = entry))
|
| 264 | +
|
| 265 | + try:
|
| 266 | + with open(file_path, "w", encoding = "utf-8", newline = "\n") as f:
|
| 267 | + json.dump(package_dict, f, indent = 4)
|
| 268 | + except Exception as e:
|
| 269 | + msg = "Failed to write '{file_path}' as a JSON file. Exception: {e}".format(file_path = file_path, e = e)
|
| 270 | + raise IOError(msg)
|
| 271 | +
|
| 272 | +
|
| 273 | +def main() -> None:
|
| 274 | + parser = argparse.ArgumentParser("mod_bundled_packages_json")
|
| 275 | + parser.add_argument("-d", "--dir", dest = "work_dir",
|
| 276 | + help = "The directory to look for bundled packages JSON files, recursively.")
|
| 277 | + parser.add_argument("entries", metavar = "ENTRIES", type = str, nargs = "+")
|
| 278 | +
|
| 279 | + args = parser.parse_args()
|
| 280 | +
|
| 281 | + json_file_list = find_json_files(args.work_dir)
|
| 282 | + for json_file_path in json_file_list:
|
| 283 | + remove_entries_from_json_file(json_file_path, args.entries)
|
| 284 | +
|
| 285 | +
|
| 286 | +if __name__ == "__main__":
|
| 287 | + main()
|