]> SALOME platform Git repositories - tools/sat.git/blob - src/__init__.py
Salome HOME
sat #32534 : warning if the application is used on a not supported platform, and...
[tools/sat.git] / src / __init__.py
1 #!/usr/bin/env python
2 #-*- coding:utf-8 -*-
3 #  Copyright (C) 2010-2013  CEA/DEN
4 #
5 #  This library is free software; you can redistribute it and/or
6 #  modify it under the terms of the GNU Lesser General Public
7 #  License as published by the Free Software Foundation; either
8 #  version 2.1 of the License.
9 #
10 #  This library is distributed in the hope that it will be useful,
11 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 #  Lesser General Public License for more details.
14 #
15 #  You should have received a copy of the GNU Lesser General Public
16 #  License along with this library; if not, write to the Free Software
17 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18
19
20 """\
21 initial imports and utilities methods for salomeTools
22 """
23
24 import os
25 import shutil
26 import errno
27 import stat
28 import fnmatch
29 import pprint as PP
30 from ftplib import FTP
31
32 from . import pyconf
33 from . import architecture
34 from . import printcolors
35 from . import options
36 from . import system
37 from . import ElementTree
38 from . import logger
39 from . import product
40 from . import environment
41 from . import fileEnviron
42 from . import compilation
43 from . import test_module
44 from . import template
45
46 import platform
47 if platform.system() == "Windows" :
48     import colorama
49     colorama.init()
50
51 OK_STATUS = "OK"
52 KO_STATUS = "KO"
53 NA_STATUS = "NA"
54 KNOWNFAILURE_STATUS = "KF"
55 TIMEOUT_STATUS = "TIMEOUT"
56
57 class SatException(Exception):
58     """sat exception class"""
59     def message(self, arg):
60         if sys.version_info[0] >= 3:
61             # message method is not available for python 3.8+
62             return super().msg(arg)
63         else:
64             return super(SatException,self).message(arg)
65
66 def ensure_path_exists(p):
67     """Create a path if not existing
68     
69     :param p str: The path.
70     """
71     if not os.path.exists(p):
72         os.makedirs(p)
73         
74 def check_config_has_application( config, details = None ):
75     """check that the config has the key APPLICATION. Else raise an exception.
76     
77     :param config class 'common.pyconf.Config': The config.
78     """
79     if 'APPLICATION' not in config:
80         message = _("An APPLICATION is required. Use 'config --list' to get the list of available applications.\n")
81         if details :
82             details.append(message)
83         raise SatException( message )
84
85 def check_platform_is_supported( config, logger ):
86     """check that the platform is supported, write warning if not.
87     
88     :param config class 'common.pyconf.Config': The config.
89     """
90     if 'platform' in config.APPLICATION and config.VARS.dist not in config.APPLICATION.platform:
91         msg = "WARNING: Your application configuration is not supported on this platform (%s)\n"\
92               "         Please consider using the native application!" % config.VARS.dist
93         logger.write("\n%s\n\n" % printcolors.printcWarning(msg), 1)
94     return
95
96 def check_config_has_profile( config, details = None ):
97     """\
98     check that the config has the key APPLICATION.profile.
99     else, raise an exception.
100     
101     :param config class 'common.pyconf.Config': The config.
102     """
103     check_config_has_application(config)
104     if 'profile' not in config.APPLICATION:
105         message = _("A profile section is required in your application.\n")
106         if details :
107             details.append(message)
108         raise SatException( message )
109
110 def appli_test_property(config,property_name, property_value):
111     """Generic function to test if an application has a property set to a value
112     :param config class 'common.pyconf.Config': The config.
113     :param property_name : The name of the property to check
114     :param property_value : The value of the property to test
115     :return: True if the application has the property set to property_value
116     :rtype: boolean
117     """
118     # first check if application has property_value
119     if not ("APPLICATION"  in config and
120             "properties"   in config.APPLICATION and
121             property_name  in config.APPLICATION.properties):
122         return False
123
124     # then check to the property is set to property_value
125     eval_expression = 'config.APPLICATION.properties.%s == "%s"' %\
126                       (property_name,property_value)
127     result = eval(eval_expression)
128     return result
129     
130
131 def config_has_application( config ):
132     return 'APPLICATION' in config
133
134 def get_cfg_param(config, param_name, default):
135     """\
136     eearch for param_name value in config.
137     if param_name is not in config 
138     then return default,
139     else return the found value
140        
141     :param config class 'common.pyconf.Config': The config.
142     :param param_name str: the name of the parameter to get the value
143     :param default str: The value to return if param_name is not in config
144     :return: see initial description of the function
145     :rtype: str
146     """
147     if param_name in config:
148         return config[param_name]
149     return default
150
151 def strSplitN(aList, nb, skip="\n     "):
152     """
153     example
154     aStr = 'this-is-a-string'
155     splitN(aStr, 2, '-')
156     split it by every 2nd '-' rather than every '-'
157     """
158     strValue = ""
159     i = 0
160     for v in aList:
161       strValue += "%15s, " % str(v)
162       i += 1
163       if i >= nb:
164         strValue += skip
165         i = 0
166     if len(aList) > nb:
167         strValue = skip + strValue
168     return strValue
169
170 def getProductNames(cfg, wildcards, logger):
171     """get products names using * or ? as wildcards like shell Linux"""
172     res = []
173     if type(wildcards) is list:
174       wilds = wildcards
175     else:
176       wilds = [wildcards]
177     notFound = {}
178     products = cfg.APPLICATION.products.keys()
179     for wild in wildcards:
180       ok = False
181       for prod in products:
182         filtered = fnmatch.filter([prod], wild)
183         # print("filtered", prod, wild, filtered)
184         if len(filtered) > 0:
185           res.append(prod)
186           ok = True
187           continue
188       if not ok:
189         notFound[wild] = None
190     if len(res) == 0:
191       logger.warning("Empty list of products, from %s" % wilds)
192     if len(notFound.keys()) > 0:
193       strProd = strSplitN( sorted(products), 5)
194       logger.warning("products not found: %s\n  availables products are:\n%s" % \
195                      (sorted(notFound.keys()), strProd) )
196     return res
197
198
199 def print_info(logger, info):
200     """\
201     Prints the tuples that are in info variable in a formatted way.
202     
203     :param logger Logger: The logging instance to use for the prints.
204     :param info list: The list of tuples to display
205     """
206     # find the maximum length of the first value of the tuples in info
207     smax = max(map(lambda l: len(l[0]), info))
208     # Print each item of info with good indentation
209     for i in info:
210         sp = " " * (smax - len(i[0]))
211         printcolors.print_value(logger, sp + i[0], i[1], 2)
212     logger.write("\n", 2)
213
214 def get_base_path(config):
215     """\
216     Returns the path of the products base.
217     
218     :param config Config: The global Config instance.
219     :return: The path of the products base.
220     :rtype: str
221     """
222     if "base" not in config.LOCAL:
223         local_file_path = os.path.join(config.VARS.salometoolsway,
224                                       "data",
225                                       "local.pyconf")
226         msg = _("Please define a base path in the file %s" % local_file_path)
227         raise SatException(msg)
228         
229     base_path = os.path.abspath(config.LOCAL.base)
230     
231     return base_path
232
233 def get_launcher_name(config):
234     """\
235     Returns the name of salome launcher.
236     
237     :param config Config: The global Config instance.
238     :return: The name of salome launcher.
239     :rtype: str
240     """
241     check_config_has_application(config)
242     if 'profile' in config.APPLICATION and 'launcher_name' in config.APPLICATION.profile:
243         launcher_name = config.APPLICATION.profile.launcher_name
244     else:
245         launcher_name = 'salome'
246
247     return launcher_name
248
249 def get_launcher_exe(config):
250     """\
251     Returns the name of exe defined in profile section.
252     
253     :param config Config: The global Config instance.
254     :return: The name of the exe to use in launcher.
255     :rtype: str
256     """
257     check_config_has_application(config)
258     if 'profile' in config.APPLICATION and 'exe' in config.APPLICATION.profile:
259         exe_name = config.APPLICATION.profile.exe
260     else:
261         exe_name = None
262
263     return exe_name
264
265
266 def get_log_path(config):
267     """\
268     Returns the path of the logs.
269     
270     :param config Config: The global Config instance.
271     :return: The path of the logs.
272     :rtype: str
273     """
274     if "log_dir" not in config.LOCAL:
275         local_file_path = os.path.join(config.VARS.salometoolsway,
276                                       "data",
277                                       "local.pyconf")
278         msg = _("Please define a log_dir in the file %s" % local_file_path)
279         raise SatException(msg)
280       
281     log_dir_path = os.path.abspath(config.LOCAL.log_dir)
282     
283     return log_dir_path
284
285 def get_salometool_version(config):
286    """Return the salomeTool version.
287
288    :param config Config: The global Config instance.
289    :return: the description of this version of sat in terms of tag and commit
290    """
291    return config.LOCAL.tag
292
293
294 def get_salome_version(config):
295     import versionMinorMajorPatch as VMMP
296
297     if hasattr(config.APPLICATION, 'version_salome'):
298         version = VMMP.MinorMajorPatch(config.APPLICATION.version_salome)
299     else:
300         kernel_info = product.get_product_config(config, "KERNEL")
301         aFile = os.path.join(
302                             kernel_info.install_dir,
303                             "bin",
304                             "salome",
305                             "VERSION")
306         if not os.path.isfile(aFile):
307             return None
308         with open(aFile) as f:
309           line = f.readline()  # example: '[SALOME KERNEL] : 8.4.0'
310         version = VMMP.MinorMajorPatch(line.split(":")[1])
311
312     res = version.strCompact()
313     # print("get_salome_version %s -> %s" % (version, res))
314     return int(res) # TODO may be future avoid test(s) on integer, use MajorMinorPatch
315
316 def read_config_from_a_file(filePath):
317         try:
318             cfg_file = pyconf.Config(filePath)
319         except pyconf.ConfigError as e:
320             raise SatException(_("Error in configuration file: %(file)s\n  %(error)s") % \
321                 { 'file': filePath, 'error': str(e) })
322         return cfg_file
323
324 def get_tmp_filename(cfg, name):
325     if not os.path.exists(cfg.VARS.tmp_root):
326         os.makedirs(cfg.VARS.tmp_root)
327
328     return os.path.join(cfg.VARS.tmp_root, name)
329
330 ##
331 # Utils class to simplify path manipulations.
332 class Path:
333     def __init__(self, path):
334         self.path = str(path)
335
336     def __add__(self, other):
337         return Path(os.path.join(self.path, str(other)))
338
339     def __abs__(self):
340         return Path(os.path.abspath(self.path))
341
342     def __str__(self):
343         return self.path
344
345     def __eq__(self, other):
346         return self.path == other.path
347
348     def exists(self):
349         return self.islink() or os.path.exists(self.path)
350
351     def islink(self):
352         return os.path.islink(self.path)
353
354     def isdir(self):
355         return os.path.isdir(self.path)
356
357     def isfile(self):
358         return os.path.isfile(self.path)
359
360     def list(self):
361         return [Path(p) for p in os.listdir(self.path)]
362
363     def dir(self):
364         return Path(os.path.dirname(self.path))
365
366     def base(self):
367         return Path(os.path.basename(self.path))
368
369     def make(self, mode=None):
370         os.makedirs(self.path)        
371         if mode:
372             os.chmod(self.path, mode)
373         
374     def chmod(self, mode):
375         os.chmod(self.path, mode)
376
377     def rm(self):    
378         if self.islink():
379             os.remove(self.path)
380         else:
381             shutil.rmtree( self.path, onerror = handleRemoveReadonly )
382
383     def copy(self, path, smart=False):
384         if not isinstance(path, Path):
385             path = Path(path)
386
387         if os.path.islink(self.path):
388             return self.copylink(path)
389         elif os.path.isdir(self.path):
390             return self.copydir(path, smart)
391         else:
392             return self.copyfile(path)
393
394     def smartcopy(self, path):
395         return self.copy(path, True)
396
397     def readlink(self):
398         if self.islink():
399             return os.readlink(self.path)
400         else:
401             return False
402
403     def symlink(self, path):
404         try:
405             os.symlink(str(path), self.path)
406             return True
407         except:
408             return False
409
410     def copylink(self, path):
411         try:
412             os.symlink(os.readlink(self.path), str(path))
413             return True
414         except:
415             return False
416
417     def copydir(self, dst, smart=False):
418         try:
419             names = self.list()
420
421             if not dst.exists():
422                 dst.make()
423
424             for name in names:
425                 if name == dst:
426                     continue
427                 if smart and (str(name) in [".git", "CVS", ".svn"]):
428                     continue
429                 srcname = self + name
430                 dstname = dst + name
431                 srcname.copy(dstname, smart)
432             return True
433         except:
434             return False
435
436     def copyfile(self, path):
437         try:
438             shutil.copy2(self.path, str(path))
439             return True
440         except:
441             return False
442
443 def find_file_in_lpath(file_name, lpath, additional_dir = ""):
444     """\
445     Find in all the directories in lpath list the file that has the same name
446     as file_name. 
447     If it is found 
448     then return the full path of the file
449     else return False.
450  
451     The additional_dir (optional) is the name of the directory to add to all 
452     paths in lpath.
453     
454     :param file_name str: The file name to search
455     :param lpath List: The list of directories where to search
456     :param additional_dir str: The name of the additional directory
457     :return: the full path of the file or False if not found
458     :rtype: str
459     """
460     for directory in lpath:
461         dir_complete = os.path.join(directory, additional_dir)
462         if not os.path.isdir(directory) or not os.path.isdir(dir_complete):
463             continue
464         l_files = os.listdir(dir_complete)
465         for file_n in l_files:
466             if file_n == file_name:
467                 return os.path.join(dir_complete, file_name)
468     return False
469
470 def find_file_in_ftppath(file_name, ftppath, installation_dir, logger, additional_dir = ""):
471     """\
472     Find in all ftp servers in ftppath the file called file_name
473     If it is found then return the destination path of the file
474     (the place where the file was downloaded"
475     else return False.
476     
477     :param file_name str: The file name to search
478     :param ftppath, List: The list of ftp servers where to search
479     :param installation_dir str: The name of the installation directory
480     :return: the full path of the file or False if not found
481     :param logger Logger: The logging instance to use for the prints.
482     :rtype: str
483     """
484
485     # make sure installation_dir exists
486     if not os.path.exists(installation_dir):
487         os.makedirs(installation_dir)
488
489     destination=os.path.join(installation_dir, file_name)
490
491     # paths in ftppath may contain several paths separated by ":"
492     # we plit them, and push all paths in bigftppath
493     bigftppath=[]
494     for ipath in ftppath:
495         splpath=ipath.split(":")
496         bigftppath+=splpath
497         
498     for ftp_archive in bigftppath:
499        try:
500            # ftp_archive has the form ftp.xxx.yyy/dir1/dir2/...
501            ftp_archive_split=ftp_archive.split("/")
502            ftp_server=ftp_archive_split[0]
503            ftp = FTP(ftp_server)
504            logger.write("   Connect to ftp server %s\n" % ftp_server, 3)
505            ftp.login()
506            for directory in ftp_archive_split[1:]:
507                logger.write("   Change directory to %s\n" % directory, 3)
508                ftp.cwd(directory)
509            if additional_dir:
510                ftp.cwd(additional_dir)
511        except:
512            logger.error("while connecting to ftp server %s\n" % ftp_server)
513            continue
514
515        try:  # get md5 file if it exists
516            file_name_md5=file_name + ".md5"
517            destination_md5=destination + ".md5"
518            if ftp.size(file_name_md5) > 0:
519                with open(destination_md5,'wb') as dest_file_md5:
520                    ftp.retrbinary("RETR "+file_name_md5, dest_file_md5.write)
521        except:
522            pass
523
524        try:
525            if ftp.size(file_name) > 0:
526                # if file exists and is non empty
527                with open(destination,'wb') as dest_file:
528                    ftp.retrbinary("RETR "+file_name, dest_file.write)
529                logger.write("   Archive %s was retrieved and stored in %s\n" % (file_name, destination), 3)
530                return destination
531        except:
532            logger.error("File not found in ftp_archive %s\n" % ftp_server)
533
534     return False
535
536 def handleRemoveReadonly(func, path, exc):
537     excvalue = exc[1]
538     if func in (os.rmdir, os.remove) and excvalue.errno == errno.EACCES:
539         os.chmod(path, stat.S_IRWXU| stat.S_IRWXG| stat.S_IRWXO) # 0777
540         func(path)
541     else:
542         raise
543
544 def deepcopy_list(input_list):
545     """\
546     Do a deep copy of a list
547     
548     :param input_list List: The list to copy
549     :return: The copy of the list
550     :rtype: List
551     """
552     res = []
553     for elem in input_list:
554         res.append(elem)
555     return res
556
557 def remove_item_from_list(input_list, item):
558     """\
559     Remove all occurences of item from input_list
560     
561     :param input_list List: The list to modify
562     :return: The without any item
563     :rtype: List
564     """
565     res = []
566     for elem in input_list:
567         if elem == item:
568             continue
569         res.append(elem)
570     return res
571
572 def parse_date(date):
573     """\
574     Transform YYYYMMDD_hhmmss into YYYY-MM-DD hh:mm:ss.
575     
576     :param date str: The date to transform
577     :return: The date in the new format
578     :rtype: str
579     """
580     if len(date) != 15:
581         return date
582     res = "%s-%s-%s %s:%s:%s" % (date[0:4],
583                                  date[4:6],
584                                  date[6:8],
585                                  date[9:11],
586                                  date[11:13],
587                                  date[13:])
588     return res
589
590 def merge_dicts(*dict_args):
591     """\
592     Given any number of dicts, shallow copy and merge into a new dict,
593     precedence goes to key value pairs in latter dicts.
594     """
595     result = {}
596     for dictionary in dict_args:
597         result.update(dictionary)
598     return result
599
600 def replace_in_file(filein, strin, strout):
601     """Replace <strin> by <strout> in file <filein>"""
602     shutil.move(filein, filein + "_old")
603     fileout= filein
604     filein = filein + "_old"
605     fin = open(filein, "r")
606     fout = open(fileout, "w")
607     for line in fin:
608         fout.write(line.replace(strin, strout))
609
610 def get_property_in_product_cfg(product_cfg, pprty):
611     if not "properties" in product_cfg:
612         return None
613     if not pprty in product_cfg.properties:
614         return None
615     return product_cfg.properties[pprty]
616
617 def activate_mesa_property(config):
618     """Add mesa property into application properties
619     
620     :param config Config: The global configuration. It must have an application!
621     """
622     # Verify the existence of the file
623     if not 'properties' in config.APPLICATION:
624         config.APPLICATION.addMapping( 'properties', pyconf.Mapping(), None )
625     config.APPLICATION.properties.use_mesa="yes"
626