Salome HOME
[bos #32522][EDF] SALOME on Demand. Added extension_remover module.
authorKonstantin Leontev <konstantin.leontev@opencascade.com>
Mon, 12 Dec 2022 13:37:55 +0000 (16:37 +0300)
committerKonstantin LEONTEV <konstantin.leontev@opencascade.com>
Wed, 8 Mar 2023 12:46:28 +0000 (13:46 +0100)
bin/SalomeOnDemandTK/extension_remover.py [new file with mode: 0644]
bin/SalomeOnDemandTK/extension_utilities.py

diff --git a/bin/SalomeOnDemandTK/extension_remover.py b/bin/SalomeOnDemandTK/extension_remover.py
new file mode 100644 (file)
index 0000000..af22919
--- /dev/null
@@ -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__)
index 24183adb3ad76c39dc7cad2c5e4bc47319fd7a14..c0c367298cb3d33166c82528d6e192b630f8b6ab 100644 (file)
@@ -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)