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