3 # Copyright (C) 2007-2022 CEA/DEN, EDF R&D, OPEN CASCADE
5 # Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
6 # CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
8 # This library is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU Lesser General Public
10 # License as published by the Free Software Foundation; either
11 # version 2.1 of the License, or (at your option) any later version.
13 # This library is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 # Lesser General Public License for more details.
18 # You should have received a copy of the GNU Lesser General Public
19 # License along with this library; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 # See https://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
25 # File : extension_utilities.py
26 # Author : Konstantin Leontev, Open Cascade
28 # @package SalomeOnDemandTK
29 # @brief Utilities and constants those help to deal with salome extension files.
32 Utilities and constants those help to deal with salome extension files.
39 from traceback import format_exc
40 from pathlib import Path
45 # Usually logging verbosity is set inside bin/runSalomeCommon.py when salome is starting.
46 # Here we do just the same for a case if we call this package stand alone.
47 FORMAT = '%(levelname)s : %(asctime)s : [%(filename)s:%(funcName)s:%(lineno)s] : %(message)s'
48 logging.basicConfig(format=FORMAT, level=logging.DEBUG, force=False)
49 logger = logging.getLogger()
51 # SalomeContext sets the logging verbosity level on its own,
52 # and we put it here, so it doesn't override the local format above.
53 #pylint: disable=wrong-import-position
55 #pylint: enable=wrong-import-position
57 SALOME_EXTDIR = '__SALOME_EXT__'
58 ARCFILE_EXT = 'salomex'
59 BFILE_EXT = 'salomexb'
60 CFILE_EXT = 'salomexc'
61 DFILE_EXT = 'salomexd'
63 ENVPYFILE_SUF = '_env.py'
66 EXTDESCR_KEY = 'descr'
67 EXTDEPENDSON_KEY = 'depends_on'
68 EXTAUTHOR_KEY = 'author'
69 EXTCOMPONENT_KEY = 'components'
72 def create_salomexd(name, descr='', depends_on=None, author='', components=None):
74 Create a basic salomexd file from provided args.
75 Current version is a json file with function args as the keys.
78 name - the name of the corresponding salome extension.
79 depends_on - list of the modules that current extension depends on.
80 author - an author of the extension.
81 components - the names of the modules those current extension was built from.
87 logger.debug('Create salomexd file...')
89 if depends_on is None:
92 if components is None:
95 json_string = json.dumps(
99 EXTDEPENDSON_KEY: depends_on,
100 EXTAUTHOR_KEY: author,
101 EXTCOMPONENT_KEY: components
107 with open(name + '.' + DFILE_EXT, "w", encoding="utf-8") as file:
108 file.write(json_string)
111 logger.error(format_exc())
114 def read_salomexd(file_path):
116 Reads a content of a salomexd file. Current version is a json file.
117 There's no check if the file_path is a valid salomexd file name.
118 It's expected that user call isvalid_filename() in advance.
121 file_path - the path to the salomexd file.
124 A dictionary that represents the content of the salomexd file.
127 logger.debug('Read salomexd file %s', file_path)
130 with open(file_path, 'r', encoding='UTF-8') as file:
131 return json.load(file)
134 logger.error(format_exc())
138 def value_from_salomexd(file_path, key):
140 Reads a content of a salomexd file and return a value for the given key.
143 file_path - the path to the salomexd file.
144 key - the key to search an assigned value.
147 A value assigned to the given key if exist, otherwise None.
150 file_content = read_salomexd(file_path)
151 if key in file_content and file_content[key]:
152 logger.debug('Key: %s, value: %s', key, file_content[key])
153 return file_content[key]
155 logger.warning('Cannot get a value for key %s in salomexd file %s', key, file_path)
159 def ext_info_bykey(salome_root, salomex_name, key):
161 Search a salomexd file by ext name and return a value for the given key.
164 install_dir - directory where the given extension is installed.
165 salomex_name - the given extension's name.
166 key - the key to search an assigned value.
169 A value for key in the given ext salomexd file.
172 salomexd = find_salomexd(salome_root, salomex_name)
174 return value_from_salomexd(salomexd, key)
179 def create_salomexb(name, included):
181 Create a salomexb file from a list of included file names.
188 name - the name of the corresponding salome extension.
189 included - list of the directories those must be included inside a salomex.
195 logger.debug('Create salomexb file...')
198 with open(name + '.' + BFILE_EXT, "w", encoding="utf-8") as file:
199 file.write('\n'.join(included[0:]))
202 logger.error(format_exc())
205 def read_salomexb(file_path):
207 Returns a content af a salomexb file as a list of strings.
208 There's no check if the file_path is a valid salomexb file name.
209 It's expected that user call isvalid_filename() in advance.
212 file_path - the path to the salomexb file.
215 List of strings - paths to the directories those must be included in
216 corresponding salomex archive file.
219 logger.debug('Read salomexb file %s', file_path)
222 with open(file_path, 'r', encoding='UTF-8') as file:
223 return [line.rstrip() for line in file]
226 logger.error(format_exc())
230 def list_files(dir_path):
232 Returns the recursive list of relative paths to files as strings
233 in the dir_path root directory and all subdirectories.
236 dir_path - the path to the directory where you search for files.
239 Raises OSError exception.
242 A list of relative paths to files inside the given directory.
246 for root, _, files in os.walk(dir_path):
248 files_list += os.path.relpath(os.path.join(root, file), dir_path)
253 def filter_to_regex(dir_path, filter_patterns):
255 Makes a regex pattern from a given filter.
258 dir_path - the path to the directory where you search for files.
259 filter_patterns - list of expressions for matching file names.
262 A regex string translated from the filter.
264 Filter: ['SMESH/**.cmake', 'SMESH/share/s*.med']
265 Regex: (?s:SMESH/.*\.cmake)\Z|(?s:SMESH/share/s.*\.med)\Z
266 Matches: SMESH/adm_local/cmake_files/SalomeSMESHConfig.cmake
267 SMESH/share/salome/resources/smesh/padderdata/ferraill.med
270 logger.debug('Convert given filter to regex...')
272 # On Windows, it converts forward slashes to backward slashes.
273 norm_filter = [os.path.normpath(pat) for pat in filter_patterns]
275 # Make a regex pattern
276 # Adding '*' at the end of the folders names to match all the files inside.
277 regex_pattern = r'|'.join(
278 [fnmatch.translate(pat + '*' if os.path.isdir(os.path.join(dir_path, pat)) else pat)
282 logger.debug('Regex pattern: %s', regex_pattern)
287 def list_files_filter(dir_path, filter_patterns):
289 Returns the recursive list of relative paths to files as strings
290 in the dir_path root directory and all subdirectories.
293 dir_path - the path to the directory where you search for files.
294 filter_patterns - list of expressions for matching file names.
297 files_abs - a list of absolute paths to selected files.
298 files_rel - a list of relative paths to selected files.
301 logger.debug('Get list of files to add into archive...')
303 regex_pattern = filter_to_regex(dir_path, filter_patterns)
308 for root, _, files in os.walk(dir_path):
310 filename_abs = os.path.join(root, file)
311 filename_rel = os.path.relpath(filename_abs, dir_path)
313 if re.match(regex_pattern, filename_rel):
314 logger.debug('File name %s matches pattern', filename_rel)
315 files_abs.append(filename_abs)
316 files_rel.append(filename_rel)
318 return files_abs, files_rel
321 def list_files_ext(dir_path, ext):
323 Returns a list of abs paths to files with a given extension
324 in the dir_path directory.
327 dir_path - the path to the directory where you search for files.
328 ext - a given extension.
331 A list of absolute paths to selected files.
334 logger.debug('Get list of files with extension %s...', ext)
337 return [os.path.join(dir_path, f) for f in os.listdir(dir_path) if f.endswith(dot_ext)]
340 def list_tonewline_str(str_list):
342 Converts the given list of strings to a newline separated string.
345 dir_path - the path to the directory where you search for files.
348 A newline separated string.
350 return '\n'.join(file for file in str_list)
353 def isvalid_filename(filename, extension):
355 Checks if a given filename is valid in a sense that it exists and have a given extension.
358 filename - the name of the file to check.
359 extension - expected file name extension.
362 True if the given filename is valid for given extension.
365 logger.debug('Check if the filename %s exists and has an extension %s', filename, extension)
367 # First do we even have something to check here
368 if filename == '' or extension == '':
369 logger.error('A filename and extension cannot be empty! Args: filename=%s, extension=%s',
373 # Check if the filename matchs the provided extension
374 _, ext = os.path.splitext(filename)
375 ext = ext.lstrip('.')
377 logger.error('The filename %s doesnt have a valid extension! \
378 The valid extension must be: %s, but get: %s',
379 filename, extension, ext)
382 # Check if the file base name is not empty
383 base_name = os.path.basename(filename)
385 logger.error('The file name %s has an empty base name!', filename)
388 # Check if a file with given filename exists
389 if not os.path.isfile(filename):
390 logger.error('The filename %s is not an existing regular file!', filename)
393 logger.debug('Filename %s exists and has an extension %s', filename, extension)
397 def isvalid_dirname(dirname):
399 Checks if a given directory name exists.
402 dirname - the name of the directory to check.
405 True if the given dirname is valid.
408 logger.debug('Check if the dirname %s exists', dirname)
410 # First do we even have something to check here
412 logger.error('A dirname argument cannot be empty! dirname=%s', dirname)
415 # Check if a file with given filename exists
416 if not os.path.isdir(dirname):
417 logger.error('The dirname %s is not an existing regular file!', dirname)
420 logger.debug('Directory %s exists', dirname)
424 def list_dependants(install_dir, salomex_name):
426 Checks if we have installed extensions those depend on a given extension.
429 install_dir - path to SALOME install root directory.
430 salomex_name - a name of salome extension to check.
433 True if the given extension has dependants.
436 logger.debug('Check if there are other extensions that depends on %s...', salomex_name)
438 salomexd_files = list_files_ext(install_dir, DFILE_EXT)
440 for salomexd_file in salomexd_files:
441 dependant_name, _ = os.path.splitext(os.path.basename(salomexd_file))
443 # Don't process <salomex_name> extension itself
444 if dependant_name == salomex_name:
447 logger.debug('Check dependencies for %s...', salomexd_file)
448 salomexd_content = read_salomexd(salomexd_file)
450 if EXTDEPENDSON_KEY in salomexd_content and salomexd_content[EXTDEPENDSON_KEY]:
451 depends_on = salomexd_content[EXTDEPENDSON_KEY]
452 logger.debug('List of dependencies: %s', depends_on)
454 if salomex_name in depends_on:
455 dependants.append(dependant_name)
457 if len(dependants) > 0:
458 logger.debug('Followed extensions %s depend on %s',
459 dependants, salomex_name)
464 def is_empty_dir(directory):
466 Checks if the given directory is empty.
469 directory - path to directory to check.
472 True if the given directory is empty.
475 return not next(os.scandir(directory), None)
478 def find_file(directory, file_name):
480 Finds a file in the given directory.
483 directory - path to directory to check.
484 file_name - given base filename with extension
487 Abs path if the file exist, otherwise None.
490 logger.debug('Try to find %s file in %s...', file_name, directory)
491 file = os.path.join(directory, file_name)
492 if os.path.isfile(file):
493 logger.debug('File %s exists.', file)
496 logger.debug('File %s doesnt\'t exist. Return None.', file)
500 def find_salomexd(install_dir, salomex_name):
502 Finds a salomexd file for the given extension.
505 install_dir - path to directory to check.
506 salomex_name - extension's name.
509 Abs path if the file exist, otherwise None.
512 return find_file(install_dir, salomex_name + '.' + DFILE_EXT)
515 def find_salomexc(install_dir, salomex_name):
517 Finds a salomexc file for the given extension.
520 install_dir - path to directory to check.
521 salomex_name - extension's name.
524 Abs path if the file exist, otherwise None.
527 return find_file(install_dir, salomex_name + '.' + CFILE_EXT)
530 def find_envpy(install_dir, salomex_name):
532 Finds a _env.py file for the given extension.
535 install_dir - path to directory to check.
536 salomex_name - extension's name.
539 Abs path if the file exist, otherwise None.
542 return find_file(install_dir, salomex_name + ENVPYFILE_SUF)
545 def module_from_filename(filename):
547 Create and execute a module by filename.
550 filename - a given python filename.
556 # Get the module from the filename
557 basename = os.path.basename(filename)
558 module_name, _ = os.path.splitext(basename)
560 spec = importlib.util.spec_from_file_location(module_name, filename)
562 logger.error('Could not get a spec for %s file!')
565 module = importlib.util.module_from_spec(spec)
567 logger.error('Could not get a module for %s file!')
570 sys.modules[module_name] = module
572 logger.debug('Execute %s module', module_name)
573 spec.loader.exec_module(module)
578 def set_selext_env(install_dir, salomex_name, context=None):
580 Finds and run _env.py file for the given extension.
583 install_dir - path to directory to check.
584 salomex_name - extension's name.
585 context - SalomeContext object.
588 True if an envpy file was found and run its init func.
591 logger.debug('Set an env for salome extension: %s...', salomex_name)
593 # Set the root dir as env variable
595 context = salomeContext.SalomeContext(None)
596 context.setVariable('SALOME_APPLICATION_DIR', install_dir, overwrite=False)
599 ext_envpy = find_envpy(install_dir, salomex_name)
604 envpy_module = module_from_filename(ext_envpy)
608 # Set env if we have something to set
609 ext_dir = os.path.join(install_dir, SALOME_EXTDIR)
610 if hasattr(envpy_module, 'init'):
611 envpy_module.init(context, ext_dir)
614 logger.warning('Env file %s doesnt have init func:!', ext_envpy)
616 logger.warning('Setting an env for salome extension %s failed!', salomex_name)
620 def get_app_root(levels_up=5):
622 Finds an app root by going up on the given steps.
625 levels_up - steps up in dir hierarchy relative to the current file.
628 Path to the app root.
631 app_root = str(Path(__file__).resolve().parents[levels_up - 1])
632 logger.debug('App root: %s', app_root)
637 def check_if_installed(install_dir, salomex_name):
639 Check if a given salome extension is installed in install_dir.
640 Now for install|remove process we consider an ext is installed
641 if we have at least salomexc file with list of files to remove
642 if we need to clean up.
645 install_dir - path to SALOME install root directory.
646 salomex_name - a given ext name.
649 salomexd, salomexc file names.
652 logger.debug('Check if %s extension is installed in %s...', salomex_name, install_dir)
654 salomexd = find_salomexd(install_dir, salomex_name)
656 logger.debug('Extension has been already removed or %s file was deleted by mistake. '
657 'In the former case we can use %s file to clean up.', DFILE_EXT, CFILE_EXT)
659 salomexc = find_salomexc(install_dir, salomex_name)
661 logger.debug('An extension %s IS installed.', salomex_name)
663 logger.debug('An extension %s IS NOT installed.', salomex_name)
665 return salomexd, salomexc