From 9e6207794b682df4e275410866971ec93734cc7d Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 24 Oct 2019 13:19:06 +0200 Subject: [PATCH 1/3] Add CMake options to exclude plugins for installation CURA-6557 --- CMakeLists.txt | 6 +- cmake/CuraPluginInstall.cmake | 98 ++++++++++++++++++++++++++++++ cmake/mod_bundled_packages_json.py | 71 ++++++++++++++++++++++ 3 files changed, 172 insertions(+), 3 deletions(-) create mode 100644 cmake/CuraPluginInstall.cmake create mode 100755 cmake/mod_bundled_packages_json.py diff --git a/CMakeLists.txt b/CMakeLists.txt index b516de6b63..4954ac46dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,7 +49,7 @@ endif() if(NOT ${URANIUM_DIR} STREQUAL "") - set(CMAKE_MODULE_PATH "${URANIUM_DIR}/cmake") + set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${URANIUM_DIR}/cmake") endif() if(NOT ${URANIUM_SCRIPTS_DIR} STREQUAL "") list(APPEND CMAKE_MODULE_PATH ${URANIUM_DIR}/cmake) @@ -63,8 +63,8 @@ endif() install(DIRECTORY resources DESTINATION ${CMAKE_INSTALL_DATADIR}/cura) -install(DIRECTORY plugins - DESTINATION lib${LIB_SUFFIX}/cura) + +include(CuraPluginInstall) if(NOT APPLE AND NOT WIN32) install(FILES cura_app.py diff --git a/cmake/CuraPluginInstall.cmake b/cmake/CuraPluginInstall.cmake new file mode 100644 index 0000000000..2eadb6c18f --- /dev/null +++ b/cmake/CuraPluginInstall.cmake @@ -0,0 +1,98 @@ +# Copyright (c) 2019 Ultimaker B.V. +# CuraPluginInstall.cmake is released under the terms of the LGPLv3 or higher. + +# +# This module detects all plugins that need to be installed and adds them using the CMake install() command. +# It detects all plugin folder in the path "plugins/*" where there's a "plugin.json" in it. +# +# Plugins can be configured to NOT BE INSTALLED via the variable "CURA_NO_INSTALL_PLUGINS" as a list of string in the +# form of "a;b;c" or "a,b,c". By default all plugins will be installed. +# + +# FIXME: Remove the code for CMake <3.12 once we have switched over completely. +# FindPython3 is a new module since CMake 3.12. It deprecates FindPythonInterp and FindPythonLibs. The FindPython3 +# module is copied from the CMake repository here so in CMake <3.12 we can still use it. +if(${CMAKE_VERSION} VERSION_LESS 3.12) + # Use FindPythonInterp and FindPythonLibs for CMake <3.12 + find_package(PythonInterp 3 REQUIRED) + + set(Python3_EXECUTABLE ${PYTHON_EXECUTABLE}) +else() + # Use FindPython3 for CMake >=3.12 + find_package(Python3 REQUIRED COMPONENTS Interpreter) +endif() + +# Options or configuration variables +set(CURA_NO_INSTALL_PLUGINS "" CACHE STRING "A list of plugins that should not be installed, separated with ';' or ','.") + +file(GLOB_RECURSE _plugin_json_list ${CMAKE_SOURCE_DIR}/plugins/*/plugin.json) +list(LENGTH _plugin_json_list _plugin_json_list_len) + +# Sort the lists alphabetically so we can handle cases like this: +# - plugins/my_plugin/plugin.json +# - plugins/my_plugin/my_module/plugin.json +# In this case, only "plugins/my_plugin" should be added via install(). +set(_no_install_plugin_list ${CURA_NO_INSTALL_PLUGINS}) +# Sanitize the string so the comparison will be case-insensitive. +string(STRIP "${_no_install_plugin_list}" _no_install_plugin_list) +string(TOLOWER "${_no_install_plugin_list}" _no_install_plugin_list) + +# WORKAROUND counterpart of what's in cura-build. +string(REPLACE "," ";" _no_install_plugin_list "${_no_install_plugin_list}") + +list(LENGTH _no_install_plugin_list _no_install_plugin_list_len) + +if(_no_install_plugin_list_len GREATER 0) + list(SORT _no_install_plugin_list) +endif() +if(_plugin_json_list_len GREATER 0) + list(SORT _plugin_json_list) +endif() + +# Check all plugin directories and add them via install() if needed. +set(_install_plugin_list "") +foreach(_plugin_json_path ${_plugin_json_list}) + get_filename_component(_plugin_dir ${_plugin_json_path} DIRECTORY) + file(RELATIVE_PATH _rel_plugin_dir ${CMAKE_CURRENT_SOURCE_DIR} ${_plugin_dir}) + get_filename_component(_plugin_dir_name ${_plugin_dir} NAME) + + # Make plugin name comparison case-insensitive + string(TOLOWER "${_plugin_dir_name}" _plugin_dir_name_lowercase) + + # Check if this plugin needs to be skipped for installation + set(_add_plugin ON) + set(_is_no_install_plugin OFF) + if(_no_install_plugin_list) + if("${_plugin_dir_name_lowercase}" IN_LIST _no_install_plugin_list) + set(_add_plugin OFF) + set(_is_no_install_plugin ON) + endif() + endif() + + # Make sure this is not a subdirectory in a plugin that's already in the install list + if(_add_plugin) + foreach(_known_install_plugin_dir ${_install_plugin_list}) + if(_plugin_dir MATCHES "${_known_install_plugin_dir}.+") + set(_add_plugin OFF) + break() + endif() + endforeach() + endif() + + if(_add_plugin) + message(STATUS "[+] PLUGIN TO INSTALL: ${_rel_plugin_dir}") + get_filename_component(_rel_plugin_parent_dir ${_rel_plugin_dir} DIRECTORY) + install(DIRECTORY ${_rel_plugin_dir} + DESTINATION lib${LIB_SUFFIX}/cura/${_rel_plugin_parent_dir} + PATTERN "__pycache__" EXCLUDE + PATTERN "*.qmlc" EXCLUDE + ) + list(APPEND _install_plugin_list ${_plugin_dir}) + elseif(_is_no_install_plugin) + message(STATUS "[-] PLUGIN TO REMOVE : ${_rel_plugin_dir}") + execute_process(COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/mod_bundled_packages_json.py + -d ${CMAKE_CURRENT_SOURCE_DIR}/resources/bundled_packages + ${_plugin_dir_name} + RESULT_VARIABLE _mod_json_result) + endif() +endforeach() diff --git a/cmake/mod_bundled_packages_json.py b/cmake/mod_bundled_packages_json.py new file mode 100755 index 0000000000..8a33f88a5a --- /dev/null +++ b/cmake/mod_bundled_packages_json.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +# +# This script removes the given package entries in the bundled_packages JSON files. This is used by the PluginInstall +# CMake module. +# + +import argparse +import collections +import json +import os +import sys + + +def find_json_files(work_dir: str) -> list: + """ + Finds all JSON files in the given directory recursively and returns a list of those files in absolute paths. + :param work_dir: The directory to look for JSON files recursively. + :return: A list of JSON files in absolute paths that are found in the given directory. + """ + json_file_list = [] + for root, dir_names, file_names in os.walk(work_dir): + for file_name in file_names: + abs_path = os.path.abspath(os.path.join(root, file_name)) + json_file_list.append(abs_path) + return json_file_list + + +def remove_entries_from_json_file(file_path: str, entries: list) -> None: + """ + Removes the given entries from the given JSON file. The file will modified in-place. + :param file_path: The JSON file to modify. + :param entries: A list of strings as entries to remove. + :return: None + """ + try: + with open(file_path, "r", encoding = "utf-8") as f: + package_dict = json.load(f, object_hook = collections.OrderedDict) + except Exception as e: + msg = "Failed to load '{file_path}' as a JSON file. This file will be ignored Exception: {e}"\ + .format(file_path = file_path, e = e) + sys.stderr.write(msg + os.linesep) + return + + for entry in entries: + if entry in package_dict: + del package_dict[entry] + print("[INFO] Remove entry [{entry}] from [{file_path}]".format(file_path = file_path, entry = entry)) + + try: + with open(file_path, "w", encoding = "utf-8", newline = "\n") as f: + json.dump(package_dict, f, indent = 4) + except Exception as e: + msg = "Failed to write '{file_path}' as a JSON file. Exception: {e}".format(file_path = file_path, e = e) + raise IOError(msg) + + +def main() -> None: + parser = argparse.ArgumentParser("mod_bundled_packages_json") + parser.add_argument("-d", "--dir", dest = "work_dir", + help = "The directory to look for bundled packages JSON files, recursively.") + parser.add_argument("entries", metavar = "ENTRIES", type = str, nargs = "+") + + args = parser.parse_args() + + json_file_list = find_json_files(args.work_dir) + for json_file_path in json_file_list: + remove_entries_from_json_file(json_file_path, args.entries) + + +if __name__ == "__main__": + main() From 3f1a3d76eab136f818bfef8513cd42ad8f477265 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Mon, 28 Oct 2019 14:37:07 +0100 Subject: [PATCH 2/3] Add more docs CURA-6557 --- cmake/CuraPluginInstall.cmake | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmake/CuraPluginInstall.cmake b/cmake/CuraPluginInstall.cmake index 2eadb6c18f..d35e74acb8 100644 --- a/cmake/CuraPluginInstall.cmake +++ b/cmake/CuraPluginInstall.cmake @@ -60,8 +60,9 @@ foreach(_plugin_json_path ${_plugin_json_list}) string(TOLOWER "${_plugin_dir_name}" _plugin_dir_name_lowercase) # Check if this plugin needs to be skipped for installation - set(_add_plugin ON) - set(_is_no_install_plugin OFF) + set(_add_plugin ON) # Indicates if this plugin should be added to the build or not. + set(_is_no_install_plugin OFF) # If this plugin will not be added, this indicates if it's because the plugin is + # specified in the NO_INSTALL_PLUGINS list. if(_no_install_plugin_list) if("${_plugin_dir_name_lowercase}" IN_LIST _no_install_plugin_list) set(_add_plugin OFF) From 7b9ababc11394091f727656725fae3de75a9978d Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 29 Oct 2019 11:14:49 +0100 Subject: [PATCH 3/3] Use Doxygen-style docs CURA-6557 --- cmake/mod_bundled_packages_json.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/cmake/mod_bundled_packages_json.py b/cmake/mod_bundled_packages_json.py index 8a33f88a5a..6423591f57 100755 --- a/cmake/mod_bundled_packages_json.py +++ b/cmake/mod_bundled_packages_json.py @@ -11,12 +11,11 @@ import os import sys +## Finds all JSON files in the given directory recursively and returns a list of those files in absolute paths. +# +# \param work_dir The directory to look for JSON files recursively. +# \return A list of JSON files in absolute paths that are found in the given directory. def find_json_files(work_dir: str) -> list: - """ - Finds all JSON files in the given directory recursively and returns a list of those files in absolute paths. - :param work_dir: The directory to look for JSON files recursively. - :return: A list of JSON files in absolute paths that are found in the given directory. - """ json_file_list = [] for root, dir_names, file_names in os.walk(work_dir): for file_name in file_names: @@ -25,13 +24,12 @@ def find_json_files(work_dir: str) -> list: return json_file_list +## Removes the given entries from the given JSON file. The file will modified in-place. +# +# \param file_path The JSON file to modify. +# \param entries A list of strings as entries to remove. +# \return None def remove_entries_from_json_file(file_path: str, entries: list) -> None: - """ - Removes the given entries from the given JSON file. The file will modified in-place. - :param file_path: The JSON file to modify. - :param entries: A list of strings as entries to remove. - :return: None - """ try: with open(file_path, "r", encoding = "utf-8") as f: package_dict = json.load(f, object_hook = collections.OrderedDict)