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