From 33f617c1a2efcd178b8ab450f7a3a02dc86bae0a Mon Sep 17 00:00:00 2001 From: Konstantin Leontev Date: Mon, 12 Dec 2022 16:37:55 +0300 Subject: [PATCH] [bos #32522][EDF] SALOME on Demand. Added extension_remover module. --- bin/SalomeOnDemandTK/extension_remover.py | 199 ++++++++++++++++++++ bin/SalomeOnDemandTK/extension_utilities.py | 77 ++++++++ 2 files changed, 276 insertions(+) create mode 100644 bin/SalomeOnDemandTK/extension_remover.py diff --git a/bin/SalomeOnDemandTK/extension_remover.py b/bin/SalomeOnDemandTK/extension_remover.py new file mode 100644 index 000000000..af2291917 --- /dev/null +++ b/bin/SalomeOnDemandTK/extension_remover.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python3 +# -*- coding:utf-8 -*- +# Copyright (C) 2007-2022 CEA/DEN, EDF R&D, OPEN CASCADE +# +# Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +# CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See https://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +# File : extension_remover.py +# Author : Konstantin Leontev, Open Cascade +# +# @package SalomeOnDemandTK +# @brief Set of utility to unpack SALOME python extensions. + +"""Set of utility to remove SALOME python extensions. +""" + +import os +import sys +import shutil +from traceback import format_exc + +from .extension_utilities import logger, \ + DFILE_EXT, CFILE_EXT, SALOME_EXTDIR, ENVPYFILE_SUF, \ + isvalid_dirname, list_dependants, is_empty_dir + + +def remove_if_empty(top_dir, directory): + """ + Recursively remove empty directories from the given one to the top. + + Args: + top_dir - top parent directory that can be removed as well + directory - the given directory + + Returns: + None. + """ + + #logger.debug('Check if %s is empty...', directory) + if not is_empty_dir(directory): + return + + logger.debug('Directory %s is empty. Remove it.', directory) + os.rmdir(directory) + + # Don't go up than top root + if top_dir == directory: + return + + # Remove the parent dir as well + parent_dir = os.path.abspath(os.path.join(directory, os.pardir)) + remove_if_empty(top_dir, parent_dir) + + +def remove_bylist(root_dir, salomexc): + """ + Remove files and directories listed in the given salomexc file. + + Args: + root_dir - a root dir for listed files + salomexc - file that contents a list of files to remove. + + Returns: + True if all the files were deleted without critical errors. + """ + + logger.debug('Remove files from %s dir listed in %s...', + root_dir, salomexc) + + try: + with open(salomexc, 'r', encoding='UTF-8') as file: + for line in file: + path_to_remove = os.path.join(root_dir, line.strip()) + logger.debug('Remove file %s...', path_to_remove) + + if os.path.isfile(path_to_remove): + os.remove(path_to_remove) + + # Remove the parent folder if empty + parent_dir = os.path.dirname(path_to_remove) + remove_if_empty(root_dir, parent_dir) + + elif os.path.islink(path_to_remove): + os.unlink(path_to_remove) + + # Remove the parent folder if empty + parent_dir = os.path.dirname(path_to_remove) + remove_if_empty(root_dir, parent_dir) + + elif os.path.isdir(path_to_remove): + logger.warning('Directories are not expected to be listed in %s file! ' + 'Remove %s anyway.', + salomexc, path_to_remove) + # Use instead of rmdir here, because dir can be not empty + shutil.rmtree(path_to_remove) + + else: + logger.error('Unexpected path %s!' + 'It is not a file or directory. Skip.', + path_to_remove) + + except OSError: + logger.error(format_exc()) + return False + + return True + +def remove_salomex(install_dir, salomex_name): + """ + Remove a salome extension from SALOME install root. + + Args: + salome_root - path to SALOME install root directory. + salomex_name - a name of salome extension to remove. + + Returns: + None. + """ + + logger.debug('Starting remove a salome extension %s', salomex_name) + + # Check if provided dirname is valid + if not isvalid_dirname(install_dir): + return + + # Check if the given extension is installed + logger.debug('Check if an extension %s is installed:', salomex_name) + + logger.debug('Try to find %s file...', DFILE_EXT) + salomexd = os.path.join(install_dir, salomex_name + '.' + DFILE_EXT) + has_salomexd = os.path.isfile(salomexd) + if not has_salomexd: + logger.debug('Cannot find a description file %s for extension %s! ' + 'Extension has been already removed or %s file was deleted by mistake. ' + 'In the former case we can use %s file to clean up.', + salomexd, salomex_name, DFILE_EXT, CFILE_EXT) + + logger.debug('Try to find %s file...', CFILE_EXT) + salomexc = os.path.join(install_dir, salomex_name + '.' + CFILE_EXT) + if not os.path.isfile(salomexc): + logger.debug('Cannot find %s for extension %s! ' + 'Going to exit from extension removing process.', + salomexc, salomex_name) + return + + # Check if we cannot remove an extension because of dependencies + dependants = list_dependants(install_dir, salomex_name) + if len(dependants) > 0: + logger.error('Cannot remove an extension %s because followed extensions depend on it: %s! ' + 'Going to exit from extension removing process.', + salomex_name, dependants) + return + + # Try to remove all the files listed in the control file + if not remove_bylist(os.path.join(install_dir, SALOME_EXTDIR), salomexc): + return + + # Remove control file + os.remove(salomexc) + + # Remove env file + env_py = os.path.join(install_dir, salomex_name + ENVPYFILE_SUF) + if os.path.isfile(env_py): + os.remove(env_py) + else: + logger.error('Cannot find and remove %s file! ', env_py) + + # Remove description file + if has_salomexd: + os.remove(salomexd) + + logger.debug('An extension %s was removed from %s', + salomex_name, install_dir) + + +if __name__ == '__main__': + if len(sys.argv) == 3: + arg_1, arg_2 = sys.argv[1:] # pylint: disable=unbalanced-tuple-unpacking + remove_salomex(arg_1, arg_2) + else: + logger.error('You must provide all the arguments!') + logger.info(remove_salomex.__doc__) diff --git a/bin/SalomeOnDemandTK/extension_utilities.py b/bin/SalomeOnDemandTK/extension_utilities.py index 24183adb3..c0c367298 100644 --- a/bin/SalomeOnDemandTK/extension_utilities.py +++ b/bin/SalomeOnDemandTK/extension_utilities.py @@ -48,6 +48,7 @@ BFILE_EXT = 'salomexb' CFILE_EXT = 'salomexc' DFILE_EXT = 'salomexd' PYFILE_EXT = 'py' +ENVPYFILE_SUF = '_env.py' EXTNAME_KEY = 'name' EXTDESCR_KEY = 'descr' @@ -241,6 +242,25 @@ def list_files_filter(dir_path, filter_patterns): return files_abs, files_rel +def list_files_ext(dir_path, ext): + """ + Returns a list of abs paths to files with a given extension + in the dir_path directory. + + Args: + dir_path - the path to the directory where you search for files. + ext - a given extension. + + Returns: + A list of absolute paths to selected files. + """ + + logger.debug('Get list of files with extension %s...', ext) + + dot_ext = '.' + ext + return [os.path.join(dir_path, f) for f in os.listdir(dir_path) if f.endswith(dot_ext)] + + def list_tonewline_str(str_list): """ Converts the given list of strings to a newline separated string. @@ -323,3 +343,60 @@ def isvalid_dirname(dirname): logger.debug('Directory %s exists', dirname) return True + + +def list_dependants(install_dir, salomex_name): + """ + Checks if we have installed extensions those depend on a given extension. + + Args: + install_dir - path to SALOME install root directory. + salomex_name - a name of salome extension to check. + + Returns: + True if the given extension has dependants. + """ + + logger.debug('Check if there are other extensions that depends on %s...', salomex_name) + dependants = [] + salomexd_files = list_files_ext(install_dir, DFILE_EXT) + + for salomexd_file in salomexd_files: + logger.debug('Check dependencies for %s...', salomexd_file) + salomexd_content = read_salomexd(salomexd_file) + + if EXTDEPENDSON_KEY in salomexd_content and salomexd_content[EXTDEPENDSON_KEY]: + depends_on = salomexd_content[EXTDEPENDSON_KEY] + logger.debug('List of dependencies: %s', depends_on) + + if salomex_name in depends_on: + dependant_name = None + if EXTNAME_KEY in salomexd_content and salomexd_content[EXTNAME_KEY]: + dependant_name = salomexd_content[EXTNAME_KEY] + else: + logger.warning('%s file doesn\'t have %s key! ' + 'Get an extension name from the filename.', + salomexd_file, EXTNAME_KEY) + dependant_name, _ = os.path.splitext(os.path.basename(salomexd_file)) + + dependants.append(dependant_name) + + if len(dependants) > 0: + logger.debug('An extension %s has followed extensions those depend on it: %s', + salomex_name, dependants) + + return dependants + + +def is_empty_dir(directory): + """ + Checks if the given directory is empty. + + Args: + directory - path to directory to check. + + Returns: + True if the given directory is empty. + """ + + return not next(os.scandir(directory), None) -- 2.39.2