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