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
43 # Usually logging verbosity is set inside bin/runSalomeCommon.py when salome is starting.
44 # Here we do just the same for a case if we call this package stand alone.
45 FORMAT = '%(levelname)s : %(asctime)s : [%(filename)s:%(funcName)s:%(lineno)s] : %(message)s'
46 logging.basicConfig(format=FORMAT, level=logging.DEBUG, force=False)
47 logger = logging.getLogger()
49 # SalomeContext sets the logging verbosity level on its own,
50 # and we put it here, so it doesn't override the local format above.
51 #pylint: disable=wrong-import-position
53 #pylint: enable=wrong-import-position
55 SALOME_EXTDIR = '__SALOME_EXT__'
56 ARCFILE_EXT = 'salomex'
57 BFILE_EXT = 'salomexb'
58 CFILE_EXT = 'salomexc'
59 DFILE_EXT = 'salomexd'
61 ENVPYFILE_SUF = '_env.py'
64 EXTDESCR_KEY = 'descr'
65 EXTDEPENDSON_KEY = 'depends_on'
66 EXTAUTHOR_KEY = 'author'
67 EXTCOMPONENT_KEY = 'components'
70 def create_salomexd(name, descr='', depends_on=None, author='', components=None):
72 Create a basic salomexd file from provided args.
73 Current version is a json file with function args as the keys.
76 name - the name of the corresponding salome extension.
77 depends_on - list of the modules that current extension depends on.
78 author - an author of the extension.
79 components - the names of the modules those current extension was built from.
85 logger.debug('Create salomexd file...')
87 if depends_on is None:
90 if components is None:
93 json_string = json.dumps(
97 EXTDEPENDSON_KEY: depends_on,
98 EXTAUTHOR_KEY: author,
99 EXTCOMPONENT_KEY: components
105 with open(name + '.' + DFILE_EXT, "w", encoding="utf-8") as file:
106 file.write(json_string)
109 logger.error(format_exc())
112 def read_salomexd(file_path):
114 Reads a content of a salomexd file. Current version is a json file.
115 There's no check if the file_path is a valid salomexd file name.
116 It's expected that user call isvalid_filename() in advance.
119 file_path - the path to the salomexd file.
122 A dictionary that represents the content of the salomexd file.
125 logger.debug('Read salomexd file %s', file_path)
128 with open(file_path, 'r', encoding='UTF-8') as file:
129 return json.load(file)
132 logger.error(format_exc())
136 def value_from_salomexd(file_path, key):
138 Reads a content of a salomexd file and return a value for the given key.
141 file_path - the path to the salomexd file.
142 key - the key to search an assigned value.
145 A value assigned to the given key if exist, otherwise None.
148 file_content = read_salomexd(file_path)
149 if key in file_content and file_content[key]:
150 logger.debug('Key: %s, value: %s', key, file_content[key])
151 return file_content[key]
153 logger.warning('Cannot get a value for key %s in salomexd file %s', key, file_path)
157 def ext_info_bykey(salome_root, salomex_name, key):
159 Search a salomexd file by ext name and return a value for the given key.
162 install_dir - directory where the given extension is installed.
163 salomex_name - the given extension's name.
164 key - the key to search an assigned value.
167 A value for key in the given ext salomexd file.
170 salomexd = find_salomexd(salome_root, salomex_name)
172 return value_from_salomexd(salomexd, key)
177 def create_salomexb(name, included):
179 Create a salomexb file from a list of included file names.
186 name - the name of the corresponding salome extension.
187 included - list of the directories those must be included inside a salomex.
193 logger.debug('Create salomexb file...')
196 with open(name + '.' + BFILE_EXT, "w", encoding="utf-8") as file:
197 file.write('\n'.join(included[0:]))
200 logger.error(format_exc())
203 def read_salomexb(file_path):
205 Returns a content af a salomexb file as a list of strings.
206 There's no check if the file_path is a valid salomexb file name.
207 It's expected that user call isvalid_filename() in advance.
210 file_path - the path to the salomexb file.
213 List of strings - paths to the directories those must be included in
214 corresponding salomex archive file.
217 logger.debug('Read salomexb file %s', file_path)
220 with open(file_path, 'r', encoding='UTF-8') as file:
221 return [line.rstrip() for line in file]
224 logger.error(format_exc())
228 def list_files(dir_path):
230 Returns the recursive list of relative paths to files as strings
231 in the dir_path root directory and all subdirectories.
234 dir_path - the path to the directory where you search for files.
237 Raises OSError exception.
240 A list of relative paths to files inside the given directory.
244 for root, _, files in os.walk(dir_path):
246 files_list += os.path.relpath(os.path.join(root, file), dir_path)
251 def list_files_filter(dir_path, filter_patterns):
253 Returns the recursive list of relative paths to files as strings
254 in the dir_path root directory and all subdirectories.
257 dir_path - the path to the directory where you search for files.
258 filter_patterns - list of expressions for matching file names.
261 files_abs - a list of absolute paths to selected files.
262 files_rel - a list of relative paths to selected files.
265 logger.debug('Get list of files to add into archive...')
270 for root, _, files in os.walk(dir_path):
272 for pattern in filter_patterns:
273 filename_abs = os.path.join(root, file)
274 filename_rel = os.path.relpath(filename_abs, dir_path)
276 if filename_rel.startswith(pattern):
277 logger.debug('File name %s matches pattern %s', filename_rel, pattern)
278 files_abs.append(filename_abs)
279 files_rel.append(filename_rel)
281 return files_abs, files_rel
284 def list_files_ext(dir_path, ext):
286 Returns a list of abs paths to files with a given extension
287 in the dir_path directory.
290 dir_path - the path to the directory where you search for files.
291 ext - a given extension.
294 A list of absolute paths to selected files.
297 logger.debug('Get list of files with extension %s...', ext)
300 return [os.path.join(dir_path, f) for f in os.listdir(dir_path) if f.endswith(dot_ext)]
303 def list_tonewline_str(str_list):
305 Converts the given list of strings to a newline separated string.
308 dir_path - the path to the directory where you search for files.
311 A newline separated string.
313 return '\n'.join(file for file in str_list)
316 def isvalid_filename(filename, extension):
318 Checks if a given filename is valid in a sense that it exists and have a given extension.
321 filename - the name of the file to check.
322 extension - expected file name extension.
325 True if the given filename is valid for given extension.
328 logger.debug('Check if the filename %s exists and has an extension %s', filename, extension)
330 # First do we even have something to check here
331 if filename == '' or extension == '':
332 logger.error('A filename and extension cannot be empty! Args: filename=%s, extension=%s',
336 # Check if the filename matchs the provided extension
337 _, ext = os.path.splitext(filename)
338 ext = ext.lstrip('.')
340 logger.error('The filename %s doesnt have a valid extension! \
341 The valid extension must be: %s, but get: %s',
342 filename, extension, ext)
345 # Check if the file base name is not empty
346 base_name = os.path.basename(filename)
348 logger.error('The file name %s has an empty base name!', filename)
351 # Check if a file with given filename exists
352 if not os.path.isfile(filename):
353 logger.error('The filename %s is not an existing regular file!', filename)
356 logger.debug('Filename %s exists and has an extension %s', filename, extension)
360 def isvalid_dirname(dirname):
362 Checks if a given directory name exists.
365 dirname - the name of the directory to check.
368 True if the given dirname is valid.
371 logger.debug('Check if the dirname %s exists', dirname)
373 # First do we even have something to check here
375 logger.error('A dirname argument cannot be empty! dirname=%s', dirname)
378 # Check if a file with given filename exists
379 if not os.path.isdir(dirname):
380 logger.error('The dirname %s is not an existing regular file!', dirname)
383 logger.debug('Directory %s exists', dirname)
387 def list_dependants(install_dir, salomex_name):
389 Checks if we have installed extensions those depend on a given extension.
392 install_dir - path to SALOME install root directory.
393 salomex_name - a name of salome extension to check.
396 True if the given extension has dependants.
399 logger.debug('Check if there are other extensions that depends on %s...', salomex_name)
401 salomexd_files = list_files_ext(install_dir, DFILE_EXT)
403 for salomexd_file in salomexd_files:
404 dependant_name, _ = os.path.splitext(os.path.basename(salomexd_file))
406 # Don't process <salomex_name> extension itself
407 if dependant_name == salomex_name:
410 logger.debug('Check dependencies for %s...', salomexd_file)
411 salomexd_content = read_salomexd(salomexd_file)
413 if EXTDEPENDSON_KEY in salomexd_content and salomexd_content[EXTDEPENDSON_KEY]:
414 depends_on = salomexd_content[EXTDEPENDSON_KEY]
415 logger.debug('List of dependencies: %s', depends_on)
417 if salomex_name in depends_on:
418 dependants.append(dependant_name)
420 if len(dependants) > 0:
421 logger.debug('Followed extensions %s depend on %s',
422 dependants, salomex_name)
427 def is_empty_dir(directory):
429 Checks if the given directory is empty.
432 directory - path to directory to check.
435 True if the given directory is empty.
438 return not next(os.scandir(directory), None)
441 def find_file(directory, file_name):
443 Finds a file in the given directory.
446 directory - path to directory to check.
447 file_name - given base filename with extension
450 Abs path if the file exist, otherwise None.
453 logger.debug('Try to find %s file in %s...', file_name, directory)
454 file = os.path.join(directory, file_name)
455 if os.path.isfile(file):
456 logger.debug('File %s exists.', file)
459 logger.debug('File %s doesnt\'t exist. Return None.', file)
463 def find_salomexd(install_dir, salomex_name):
465 Finds a salomexd file for the given extension.
468 install_dir - path to directory to check.
469 salomex_name - extension's name.
472 Abs path if the file exist, otherwise None.
475 return find_file(install_dir, salomex_name + '.' + DFILE_EXT)
478 def find_salomexc(install_dir, salomex_name):
480 Finds a salomexc file for the given extension.
483 install_dir - path to directory to check.
484 salomex_name - extension's name.
487 Abs path if the file exist, otherwise None.
490 return find_file(install_dir, salomex_name + '.' + CFILE_EXT)
493 def find_envpy(install_dir, salomex_name):
495 Finds a _env.py file for the given extension.
498 install_dir - path to directory to check.
499 salomex_name - extension's name.
502 Abs path if the file exist, otherwise None.
505 return find_file(install_dir, salomex_name + ENVPYFILE_SUF)
508 def module_from_filename(filename):
510 Create and execute a module by filename.
513 filename - a given python filename.
519 # Get the module from the filename
520 basename = os.path.basename(filename)
521 module_name, _ = os.path.splitext(basename)
523 spec = importlib.util.spec_from_file_location(module_name, filename)
525 logger.error('Could not get a spec for %s file!')
528 module = importlib.util.module_from_spec(spec)
530 logger.error('Could not get a module for %s file!')
533 sys.modules[module_name] = module
535 logger.debug('Execute %s module', module_name)
536 spec.loader.exec_module(module)
541 def set_selext_env(install_dir, salomex_name, context=None):
543 Finds and run _env.py file for the given extension.
546 install_dir - path to directory to check.
547 salomex_name - extension's name.
548 context - SalomeContext object.
551 True if an envpy file was found and run its init func.
554 logger.debug('Set an env for salome extension: %s...', salomex_name)
556 # Set the root dir as env variable
558 context = salomeContext.SalomeContext(None)
559 context.setVariable('SALOME_APPLICATION_DIR', install_dir, overwrite=False)
562 ext_envpy = find_envpy(install_dir, salomex_name)
567 envpy_module = module_from_filename(ext_envpy)
571 # Set env if we have something to set
572 ext_root = os.path.join(install_dir, SALOME_EXTDIR)
573 if hasattr(envpy_module, 'init'):
574 envpy_module.init(context, ext_root)
577 logger.warning('Env file %s doesnt have init func:!', ext_envpy)
579 logger.warning('Setting an env for salome extension %s failed!', salomex_name)
583 def get_app_root(levels_up=5):
585 Finds an app root by going up on the given steps.
588 levels_up - steps up in dir hierarchy relative to the current file.
591 Path to the app root.
594 app_root = str(Path(__file__).resolve().parents[levels_up - 1])
595 logger.debug('App root: %s', app_root)