Salome HOME
e24085b207a950b248de18793aba7e9bfa0ed5a9
[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           break
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_salome_version(config):
232     import versionMinorMajorPatch as VMMP
233
234     if hasattr(config.APPLICATION, 'version_salome'):
235         version = VMMP.MinorMajorPatch(config.APPLICATION.version_salome)
236     else:
237         kernel_info = product.get_product_config(config, "KERNEL")
238         aFile = os.path.join(
239                             kernel_info.install_dir,
240                             "bin",
241                             "salome",
242                             "VERSION")
243         if not os.path.isfile(aFile):
244             return None
245         with open(aFile) as f:
246           line = f.readline()  # example: '[SALOME KERNEL] : 8.4.0'
247         version = VMMP.MinorMajorPatch(line.split(":")[1])
248
249     res = version.strCompact()
250     # print("get_salome_version %s -> %s" % (version, res))
251     return int(res) # TODO may be future avoid test(s) on integer, use MajorMinorPatch
252
253 def read_config_from_a_file(filePath):
254         try:
255             cfg_file = pyconf.Config(filePath)
256         except pyconf.ConfigError as e:
257             raise SatException(_("Error in configuration file: %(file)s\n  %(error)s") % \
258                 { 'file': filePath, 'error': str(e) })
259         return cfg_file
260
261 def get_tmp_filename(cfg, name):
262     if not os.path.exists(cfg.VARS.tmp_root):
263         os.makedirs(cfg.VARS.tmp_root)
264
265     return os.path.join(cfg.VARS.tmp_root, name)
266
267 ##
268 # Utils class to simplify path manipulations.
269 class Path:
270     def __init__(self, path):
271         self.path = str(path)
272
273     def __add__(self, other):
274         return Path(os.path.join(self.path, str(other)))
275
276     def __abs__(self):
277         return Path(os.path.abspath(self.path))
278
279     def __str__(self):
280         return self.path
281
282     def __eq__(self, other):
283         return self.path == other.path
284
285     def exists(self):
286         return self.islink() or os.path.exists(self.path)
287
288     def islink(self):
289         return os.path.islink(self.path)
290
291     def isdir(self):
292         return os.path.isdir(self.path)
293
294     def isfile(self):
295         return os.path.isfile(self.path)
296
297     def list(self):
298         return [Path(p) for p in os.listdir(self.path)]
299
300     def dir(self):
301         return Path(os.path.dirname(self.path))
302
303     def base(self):
304         return Path(os.path.basename(self.path))
305
306     def make(self, mode=None):
307         os.makedirs(self.path)        
308         if mode:
309             os.chmod(self.path, mode)
310         
311     def chmod(self, mode):
312         os.chmod(self.path, mode)
313
314     def rm(self):    
315         if self.islink():
316             os.remove(self.path)
317         else:
318             shutil.rmtree( self.path, onerror = handleRemoveReadonly )
319
320     def copy(self, path, smart=False):
321         if not isinstance(path, Path):
322             path = Path(path)
323
324         if os.path.islink(self.path):
325             return self.copylink(path)
326         elif os.path.isdir(self.path):
327             return self.copydir(path, smart)
328         else:
329             return self.copyfile(path)
330
331     def smartcopy(self, path):
332         return self.copy(path, True)
333
334     def readlink(self):
335         if self.islink():
336             return os.readlink(self.path)
337         else:
338             return False
339
340     def symlink(self, path):
341         try:
342             os.symlink(str(path), self.path)
343             return True
344         except:
345             return False
346
347     def copylink(self, path):
348         try:
349             os.symlink(os.readlink(self.path), str(path))
350             return True
351         except:
352             return False
353
354     def copydir(self, dst, smart=False):
355         try:
356             names = self.list()
357
358             if not dst.exists():
359                 dst.make()
360
361             for name in names:
362                 if name == dst:
363                     continue
364                 if smart and (str(name) in [".git", "CVS", ".svn"]):
365                     continue
366                 srcname = self + name
367                 dstname = dst + name
368                 srcname.copy(dstname, smart)
369             return True
370         except:
371             return False
372
373     def copyfile(self, path):
374         try:
375             shutil.copy2(self.path, str(path))
376             return True
377         except:
378             return False
379
380 def find_file_in_lpath(file_name, lpath, additional_dir = ""):
381     """\
382     Find in all the directories in lpath list the file that has the same name
383     as file_name. 
384     If it is found 
385     then return the full path of the file
386     else return False.
387  
388     The additional_dir (optional) is the name of the directory to add to all 
389     paths in lpath.
390     
391     :param file_name str: The file name to search
392     :param lpath List: The list of directories where to search
393     :param additional_dir str: The name of the additional directory
394     :return: the full path of the file or False if not found
395     :rtype: str
396     """
397     for directory in lpath:
398         dir_complete = os.path.join(directory, additional_dir)
399         if not os.path.isdir(directory) or not os.path.isdir(dir_complete):
400             continue
401         l_files = os.listdir(dir_complete)
402         for file_n in l_files:
403             if file_n == file_name:
404                 return os.path.join(dir_complete, file_name)
405     return False
406
407 def find_file_in_ftppath(file_name, ftppath, installation_dir, logger):
408     """\
409     Find in all ftp servers in ftppath the file called file_name
410     If it is found then return the destination path of the file
411     (the place where the file was downloaded"
412     else return False.
413     
414     :param file_name str: The file name to search
415     :param ftppath, List: The list of ftp servers where to search
416     :param installation_dir str: The name of the installation directory
417     :return: the full path of the file or False if not found
418     :param logger Logger: The logging instance to use for the prints.
419     :rtype: str
420     """
421     destination=os.path.join(installation_dir, file_name)
422     for ftp_archive in ftppath:
423        try:
424            # ftp_archive has the form ftp.xxx.yyy/dir1/dir2/...
425            ftp_archive_split=ftp_archive.split("/")
426            ftp_server=ftp_archive_split[0]
427            ftp = FTP(ftp_server)
428            logger.write("   Connect to ftp server %s\n" % ftp_server, 3)
429            ftp.login()
430            for directory in ftp_archive_split[1:]:
431                logger.write("   Change directory to %s\n" % directory, 3)
432                ftp.cwd(directory)
433        except:
434            logger.error("while connecting to ftp server %s\n" % ftp_server)
435
436        try:
437            if ftp.size(file_name) > 0:
438                # if file exists and is non empty
439                with open(destination,'wb') as dest_file:
440                    ftp.retrbinary("RETR "+file_name, dest_file.write)
441                logger.write("   Archive %s was retrieved and stored in %s\n" % (file_name, destination), 3)
442                return destination
443        except:
444            logger.error("File not found in ftp_archive %s\n" % ftp_server)
445            pass
446
447     return False
448
449 def handleRemoveReadonly(func, path, exc):
450     excvalue = exc[1]
451     if func in (os.rmdir, os.remove) and excvalue.errno == errno.EACCES:
452         os.chmod(path, stat.S_IRWXU| stat.S_IRWXG| stat.S_IRWXO) # 0777
453         func(path)
454     else:
455         raise
456
457 def deepcopy_list(input_list):
458     """\
459     Do a deep copy of a list
460     
461     :param input_list List: The list to copy
462     :return: The copy of the list
463     :rtype: List
464     """
465     res = []
466     for elem in input_list:
467         res.append(elem)
468     return res
469
470 def remove_item_from_list(input_list, item):
471     """\
472     Remove all occurences of item from input_list
473     
474     :param input_list List: The list to modify
475     :return: The without any item
476     :rtype: List
477     """
478     res = []
479     for elem in input_list:
480         if elem == item:
481             continue
482         res.append(elem)
483     return res
484
485 def parse_date(date):
486     """\
487     Transform YYYYMMDD_hhmmss into YYYY-MM-DD hh:mm:ss.
488     
489     :param date str: The date to transform
490     :return: The date in the new format
491     :rtype: str
492     """
493     if len(date) != 15:
494         return date
495     res = "%s-%s-%s %s:%s:%s" % (date[0:4],
496                                  date[4:6],
497                                  date[6:8],
498                                  date[9:11],
499                                  date[11:13],
500                                  date[13:])
501     return res
502
503 def merge_dicts(*dict_args):
504     """\
505     Given any number of dicts, shallow copy and merge into a new dict,
506     precedence goes to key value pairs in latter dicts.
507     """
508     result = {}
509     for dictionary in dict_args:
510         result.update(dictionary)
511     return result
512
513 def replace_in_file(filein, strin, strout):
514     """Replace <strin> by <strout> in file <filein>"""
515     shutil.move(filein, filein + "_old")
516     fileout= filein
517     filein = filein + "_old"
518     fin = open(filein, "r")
519     fout = open(fileout, "w")
520     for line in fin:
521         fout.write(line.replace(strin, strout))
522
523 def get_property_in_product_cfg(product_cfg, pprty):
524     if not "properties" in product_cfg:
525         return None
526     if not pprty in product_cfg.properties:
527         return None
528     return product_cfg.properties[pprty]
529
530 def activate_mesa_property(config):
531     """Add mesa property into application properties
532     
533     :param config Config: The global configuration. It must have an application!
534     """
535     # Verify the existence of the file
536     if not 'properties' in config.APPLICATION:
537         config.APPLICATION.addMapping( 'properties', pyconf.Mapping(), None )
538     config.APPLICATION.properties.use_mesa="yes"
539