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