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