Salome HOME
sat #19109 : choix plus robuste du gestionnaire de paquets, et utilisation de platfor...
[tools/sat.git] / commands / config.py
1 #!/usr/bin/env python
2 #-*- coding:utf-8 -*-
3 #  Copyright (C) 2010-2012  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 import os
20 import sys
21 import platform
22 import datetime
23 import shutil
24 import gettext
25 import pprint as PP
26
27 import src
28 import src.logger as LOG
29 import src.debug as DBG
30 import src.callerName as CALN
31
32 logger = LOG.getDefaultLogger()
33
34 verbose = False # True for debug
35
36 # internationalization
37 satdir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
38 gettext.install('salomeTools', os.path.join(satdir, 'src', 'i18n'))
39
40 # Define all possible option for config command :  sat config <options>
41 parser = src.options.Options()
42 parser.add_option('v', 'value', 'string', 'value',
43     _("Optional: print the value of CONFIG_VARIABLE."))
44 parser.add_option('g', 'debug', 'string', 'debug',
45     _("Optional: print the debugging mode value of CONFIG_VARIABLE."))
46 parser.add_option('e', 'edit', 'boolean', 'edit',
47     _("Optional: edit the product configuration file."))
48 parser.add_option('i', 'info', 'list2', 'info',
49     _("Optional: get information on product(s). This option accepts a comma separated list."))
50 parser.add_option('p', 'products', 'list2', 'products',
51     _("Optional: same as --info, for convenience."))
52 parser.add_option('l', 'list', 'boolean', 'list',
53     _("Optional: list all available applications."))
54 parser.add_option('', 'show_patchs', 'boolean', 'show_patchs',
55     _("Optional: synthetic list of all patches used in the application"))
56 parser.add_option('', 'show_install', 'boolean', 'show_install',
57     _("Optional: synthetic list of all install directories in the application"))
58 parser.add_option('', 'show_properties', 'boolean', 'show_properties',
59     _("Optional: synthetic list of all properties used in the application"))
60 parser.add_option('', 'check_system', 'boolean', 'check_system',
61     _("Optional: check if system products are installed"))
62 parser.add_option('c', 'copy', 'boolean', 'copy',
63     _("""Optional: copy a config file to the personal config files directory.
64 WARNING: the included files are not copied.
65 If a name is given the new config file takes the given name."""))
66 parser.add_option('n', 'no_label', 'boolean', 'no_label',
67     _("Internal use: do not print labels, Works only with --value and --list."))
68 parser.add_option('', 'completion', 'boolean', 'completion',
69     _("Internal use: print only keys, works only with --value."))
70 parser.add_option('s', 'schema', 'boolean', 'schema',
71     _("Internal use."))
72
73 def osJoin(*args):
74   """
75   shortcut wrapper to os.path.join
76   plus optionaly print for debug
77   """
78   res = os.path.realpath(os.path.join(*args))
79   if verbose:
80     if True: # ".pyconf" in res:
81       logger.info("osJoin %-80s in %s" % (res, CALN.caller_name(1)))
82   return res
83
84 class ConfigOpener:
85     '''Class that helps to find an application pyconf 
86        in all the possible directories (pathList)
87     '''
88     def __init__(self, pathList):
89         '''Initialization
90         
91         :param pathList list: The list of paths where to search a pyconf.
92         '''
93         self.pathList = pathList
94         if verbose:
95           for path in pathList:
96             if not os.path.isdir(path):
97               logger.warning("ConfigOpener inexisting directory: %s" % path)
98
99     def __call__(self, name):
100         if os.path.isabs(name):
101             return src.pyconf.ConfigInputStream(open(name, 'rb'))
102         else:
103             return src.pyconf.ConfigInputStream(open(osJoin(self.get_path(name), name), 'rb'))
104         raise IOError(_("Configuration file '%s' not found") % name)
105
106     def get_path( self, name ):
107         '''The method that returns the entire path of the pyconf searched
108         returns first found in self.pathList directories
109
110         :param name str: The name of the searched pyconf.
111         '''
112         for path in self.pathList:
113             if os.path.exists(osJoin(path, name)):
114                 return path
115         raise IOError(_("Configuration file '%s' not found") % name)
116
117 class ConfigManager:
118     '''Class that manages the read of all the configuration files of salomeTools
119     '''
120     def __init__(self, datadir=None):
121         pass
122
123     def _create_vars(self, application=None, command=None, datadir=None):
124         '''Create a dictionary that stores all information about machine,
125            user, date, repositories, etc...
126         
127         :param application str: The application for which salomeTools is called.
128         :param command str: The command that is called.
129         :param datadir str: The repository that contain external data 
130                             for salomeTools.
131         :return: The dictionary that stores all information.
132         :rtype: dict
133         '''
134         var = {}      
135         var['user'] = src.architecture.get_user()
136         var['salometoolsway'] = os.path.dirname( os.path.dirname(os.path.abspath(__file__)))
137         var['srcDir'] =  osJoin(var['salometoolsway'], 'src')
138         var['internal_dir'] =  osJoin(var['srcDir'], 'internal_config')
139         var['sep']= os.path.sep
140         if src.architecture.is_windows():
141           var['scriptExtension'] = '.bat'
142         else:
143           var['scriptExtension'] = '.sh'
144         
145         # datadir has a default location
146         var['datadir'] =  osJoin(var['salometoolsway'], 'data')
147         if datadir is not None:
148             var['datadir'] = datadir
149
150         var['personalDir'] =  osJoin(os.path.expanduser('~'), '.salomeTools')
151         src.ensure_path_exists(var['personalDir'])
152
153         var['personal_applications_dir'] =  osJoin(var['personalDir'], "Applications")
154         src.ensure_path_exists(var['personal_applications_dir'])
155         
156         var['personal_products_dir'] =  osJoin(var['personalDir'], "products")
157         src.ensure_path_exists(var['personal_products_dir'])
158         
159         var['personal_archives_dir'] =  osJoin(var['personalDir'], "Archives")
160         src.ensure_path_exists(var['personal_archives_dir'])
161
162         var['personal_jobs_dir'] =  osJoin(var['personalDir'], "Jobs")
163         src.ensure_path_exists(var['personal_jobs_dir'])
164
165         var['personal_machines_dir'] =  osJoin(var['personalDir'], "Machines")
166         src.ensure_path_exists(var['personal_machines_dir'])
167
168         # read linux distributions dictionary
169         distrib_cfg = src.pyconf.Config( osJoin(var['srcDir'], 'internal_config', 'distrib.pyconf'))
170         
171         # set platform parameters
172         dist_name = src.architecture.get_distribution(codes=distrib_cfg.DISTRIBUTIONS)
173         dist_version = src.architecture.get_distrib_version(dist_name)
174         dist_version_full = src.architecture.get_version_XY()
175         dist = dist_name + dist_version
176         
177         var['dist_name'] = dist_name
178         var['dist_version'] = dist_version
179         var['dist'] = dist
180         var['dist_ref'] = dist_name + dist_version_full
181         var['python'] = src.architecture.get_python_version()
182
183         var['nb_proc'] = src.architecture.get_nb_proc()
184         node_name = platform.node()
185         var['node'] = node_name
186         var['hostname'] = node_name
187
188         # set date parameters
189         dt = datetime.datetime.now()
190         var['date'] = dt.strftime('%Y%m%d')
191         var['datehour'] = dt.strftime('%Y%m%d_%H%M%S')
192         var['hour'] = dt.strftime('%H%M%S')
193
194         var['command'] = str(command)
195         var['application'] = str(application)
196
197         # Root dir for temporary files 
198         var['tmp_root'] = os.sep + 'tmp' + os.sep + var['user']
199         # particular win case 
200         if src.architecture.is_windows() : 
201             var['tmp_root'] =  os.path.expanduser('~') + os.sep + 'tmp'
202         
203         return var
204
205     def get_command_line_overrides(self, options, sections):
206         '''get all the overwrites that are in the command line
207         
208         :param options: the options from salomeTools class 
209                         initialization (like -l5 or --overwrite)
210         :param sections str: The config section to overwrite.
211         :return: The list of all the overwrites to apply.
212         :rtype: list
213         '''
214         # when there are no options or not the overwrite option, 
215         # return an empty list
216         if options is None or options.overwrite is None:
217             return []
218         
219         over = []
220         for section in sections:
221             # only overwrite the sections that correspond to the option 
222             over.extend(filter(lambda l: l.startswith(section + "."), 
223                                options.overwrite))
224         return over
225
226     def get_config(self, application=None, options=None, command=None,
227                     datadir=None):
228         '''get the config from all the configuration files.
229         
230         :param application str: The application for which salomeTools is called.
231         :param options class Options: The general salomeToos
232                                       options (--overwrite or -l5, for example)
233         :param command str: The command that is called.
234         :param datadir str: The repository that contain 
235                             external data for salomeTools.
236         :return: The final config.
237         :rtype: class 'src.pyconf.Config'
238         '''        
239         
240         # create a ConfigMerger to handle merge
241         merger = src.pyconf.ConfigMerger()#MergeHandler())
242         
243         # create the configuration instance
244         cfg = src.pyconf.Config()
245         
246         # =====================================================================
247         # create VARS section
248         var = self._create_vars(application=application, command=command, datadir=datadir)
249         # DBG.write("create_vars", var, DBG.isDeveloper())
250
251         # add VARS to config
252         cfg.VARS = src.pyconf.Mapping(cfg)
253         for variable in var:
254             cfg.VARS[variable] = var[variable]
255         
256         # apply overwrite from command line if needed
257         for rule in self.get_command_line_overrides(options, ["VARS"]):
258             exec('cfg.' + rule) # this cannot be factorized because of the exec
259         
260         # =====================================================================
261         # Load INTERNAL config
262         # read src/internal_config/salomeTools.pyconf
263         src.pyconf.streamOpener = ConfigOpener([
264                              osJoin(cfg.VARS.srcDir, 'internal_config')])
265         try:
266             if src.architecture.is_windows(): # special internal config for windows
267                 internal_cfg = src.pyconf.Config(open( osJoin(cfg.VARS.srcDir,
268                                         'internal_config', 'salomeTools_win.pyconf')))
269             else:
270                 internal_cfg = src.pyconf.Config(open( osJoin(cfg.VARS.srcDir,
271                                         'internal_config', 'salomeTools.pyconf')))
272         except src.pyconf.ConfigError as e:
273             raise src.SatException(_("Error in configuration file:"
274                                      " salomeTools.pyconf\n  %(error)s") % \
275                                    {'error': str(e) })
276         
277         merger.merge(cfg, internal_cfg)
278
279         # apply overwrite from command line if needed
280         for rule in self.get_command_line_overrides(options, ["INTERNAL"]):
281             exec('cfg.' + rule) # this cannot be factorized because of the exec        
282                
283         # =====================================================================
284         # Load LOCAL config file
285         # search only in the data directory
286         src.pyconf.streamOpener = ConfigOpener([cfg.VARS.datadir])
287         try:
288             local_cfg = src.pyconf.Config(open( osJoin(cfg.VARS.datadir,
289                                                            'local.pyconf')),
290                                          PWD = ('LOCAL', cfg.VARS.datadir) )
291         except src.pyconf.ConfigError as e:
292             raise src.SatException(_("Error in configuration file: "
293                                      "local.pyconf\n  %(error)s") % \
294                 {'error': str(e) })
295         except IOError as error:
296             e = str(error)
297             raise src.SatException( e );
298         merger.merge(cfg, local_cfg)
299
300         # When the key is "default", put the default value
301         if cfg.LOCAL.base == "default":
302             cfg.LOCAL.base = os.path.abspath(osJoin(cfg.VARS.salometoolsway, "..", "BASE"))
303         if cfg.LOCAL.workdir == "default":
304             cfg.LOCAL.workdir = os.path.abspath(osJoin(cfg.VARS.salometoolsway, ".."))
305         if cfg.LOCAL.log_dir == "default":
306             cfg.LOCAL.log_dir = os.path.abspath(osJoin(cfg.VARS.salometoolsway, "..", "LOGS"))
307
308         if cfg.LOCAL.archive_dir == "default":
309             cfg.LOCAL.archive_dir = os.path.abspath( osJoin(cfg.VARS.salometoolsway, "..", "ARCHIVES"))
310
311         # if the sat tag was not set permanently by user
312         if cfg.LOCAL.tag == "unknown":
313             # get the tag with git, and store it
314             sat_version=src.system.git_describe(cfg.VARS.salometoolsway) 
315             if sat_version == False:
316                 sat_version=cfg.INTERNAL.sat_version
317             cfg.LOCAL.tag=sat_version
318                 
319
320         # apply overwrite from command line if needed
321         for rule in self.get_command_line_overrides(options, ["LOCAL"]):
322             exec('cfg.' + rule) # this cannot be factorized because of the exec
323         
324         # =====================================================================
325         # Load the PROJECTS
326         projects_cfg = src.pyconf.Config()
327         projects_cfg.addMapping("PROJECTS",
328                                 src.pyconf.Mapping(projects_cfg),
329                                 "The projects\n")
330         projects_cfg.PROJECTS.addMapping("projects",
331                                 src.pyconf.Mapping(cfg.PROJECTS),
332                                 "The projects definition\n")
333         
334         for project_pyconf_path in cfg.PROJECTS.project_file_paths:
335             if not os.path.isabs(project_pyconf_path):
336                 # for a relative path (archive case) we complete with sat path
337                 project_pyconf_path = os.path.join(cfg.VARS.salometoolsway,
338                                                   project_pyconf_path)
339             if not os.path.exists(project_pyconf_path):
340                 msg = _("WARNING: The project file %s cannot be found. "
341                         "It will be ignored\n" % project_pyconf_path)
342                 sys.stdout.write(msg)
343                 continue
344             project_name = os.path.basename(
345                                     project_pyconf_path)[:-len(".pyconf")]
346             try:
347                 project_pyconf_dir = os.path.dirname(project_pyconf_path)
348                 project_cfg = src.pyconf.Config(open(project_pyconf_path),
349                                                 PWD=("", project_pyconf_dir))
350             except Exception as e:
351                 msg = _("ERROR: Error in configuration file: "
352                                  "%(file_path)s\n  %(error)s\n") % \
353                             {'file_path' : project_pyconf_path, 'error': str(e) }
354                 sys.stdout.write(msg)
355                 continue
356             projects_cfg.PROJECTS.projects.addMapping(project_name,
357                              src.pyconf.Mapping(projects_cfg.PROJECTS.projects),
358                              "The %s project\n" % project_name)
359             projects_cfg.PROJECTS.projects[project_name]=project_cfg
360             projects_cfg.PROJECTS.projects[project_name]["file_path"] = \
361                                                         project_pyconf_path
362             # store the project tag if any
363             product_project_git_tag = src.system.git_describe(os.path.dirname(project_pyconf_path))
364             if product_project_git_tag:
365                 projects_cfg.PROJECTS.projects[project_name]["git_tag"] = product_project_git_tag
366             else:
367                 projects_cfg.PROJECTS.projects[project_name]["git_tag"] = "unknown"
368                    
369         merger.merge(cfg, projects_cfg)
370
371         # apply overwrite from command line if needed
372         for rule in self.get_command_line_overrides(options, ["PROJECTS"]):
373             exec('cfg.' + rule) # this cannot be factorized because of the exec
374         
375         # =====================================================================
376         # Create the paths where to search the application configurations, 
377         # the product configurations, the products archives, 
378         # the jobs configurations and the machines configurations
379         cfg.addMapping("PATHS", src.pyconf.Mapping(cfg), "The paths\n")
380         cfg.PATHS["APPLICATIONPATH"] = src.pyconf.Sequence(cfg.PATHS)
381         cfg.PATHS.APPLICATIONPATH.append(cfg.VARS.personal_applications_dir, "")
382
383         
384         cfg.PATHS["PRODUCTPATH"] = src.pyconf.Sequence(cfg.PATHS)
385         cfg.PATHS.PRODUCTPATH.append(cfg.VARS.personal_products_dir, "")
386         cfg.PATHS["ARCHIVEPATH"] = src.pyconf.Sequence(cfg.PATHS)
387         cfg.PATHS.ARCHIVEPATH.append(cfg.VARS.personal_archives_dir, "")
388         cfg.PATHS["ARCHIVEFTP"] = src.pyconf.Sequence(cfg.PATHS)
389         cfg.PATHS["JOBPATH"] = src.pyconf.Sequence(cfg.PATHS)
390         cfg.PATHS.JOBPATH.append(cfg.VARS.personal_jobs_dir, "")
391         cfg.PATHS["MACHINEPATH"] = src.pyconf.Sequence(cfg.PATHS)
392         cfg.PATHS.MACHINEPATH.append(cfg.VARS.personal_machines_dir, "")
393         cfg.PATHS["LICENCEPATH"] = src.pyconf.Sequence(cfg.PATHS)
394
395         # initialise the path with local directory
396         cfg.PATHS["ARCHIVEPATH"].append(cfg.LOCAL.archive_dir, "")
397
398         # Loop over the projects in order to complete the PATHS variables
399         # as /data/tmpsalome/salome/prerequis/archives for example ARCHIVEPATH
400         for project in cfg.PROJECTS.projects:
401             for PATH in ["APPLICATIONPATH",
402                          "PRODUCTPATH",
403                          "ARCHIVEPATH", #comment this for default archive       #8646
404                          "ARCHIVEFTP",
405                          "JOBPATH",
406                          "MACHINEPATH",
407                          "LICENCEPATH"]:
408                 if PATH not in cfg.PROJECTS.projects[project]:
409                     continue
410                 cfg.PATHS[PATH].append(cfg.PROJECTS.projects[project][PATH], "")
411         
412         # apply overwrite from command line if needed
413         for rule in self.get_command_line_overrides(options, ["PATHS"]):
414             exec('cfg.' + rule) # this cannot be factorized because of the exec
415
416         # AT END append APPLI_TEST directory in APPLICATIONPATH, for unittest
417         appli_test_dir =  osJoin(satdir, "test", "APPLI_TEST")
418         if appli_test_dir not in cfg.PATHS.APPLICATIONPATH:
419           cfg.PATHS.APPLICATIONPATH.append(appli_test_dir, "unittest APPLI_TEST path")
420
421         # =====================================================================
422         # Load APPLICATION config file
423         if application is not None:
424             # search APPLICATION file in all directories in configPath
425             cp = cfg.PATHS.APPLICATIONPATH
426             src.pyconf.streamOpener = ConfigOpener(cp)
427             do_merge = True
428             try:
429                 application_cfg = src.pyconf.Config(application + '.pyconf')
430             except IOError as e:
431                 raise src.SatException(
432                    _("%s, use 'config --list' to get the list of available applications.") % e)
433             except src.pyconf.ConfigError as e:
434                 if (not ('-e' in parser.parse_args()[1]) 
435                                          or ('--edit' in parser.parse_args()[1]) 
436                                          and command == 'config'):
437                     raise src.SatException(_("Error in configuration file: "
438                                              "%(application)s.pyconf\n "
439                                              " %(error)s") % \
440                         { 'application': application, 'error': str(e) } )
441                 else:
442                     sys.stdout.write(src.printcolors.printcWarning(
443                                         "There is an error in the file"
444                                         " %s.pyconf.\n" % cfg.VARS.application))
445                     do_merge = False
446             except Exception as e:
447                 if (not ('-e' in parser.parse_args()[1]) 
448                                         or ('--edit' in parser.parse_args()[1]) 
449                                         and command == 'config'):
450                     sys.stdout.write(src.printcolors.printcWarning("%s\n" % str(e)))
451                     raise src.SatException(_("Error in configuration file:"
452                                              " %(application)s.pyconf\n") % \
453                         { 'application': application} )
454                 else:
455                     sys.stdout.write(src.printcolors.printcWarning(
456                                 "There is an error in the file"
457                                 " %s.pyconf. Opening the file with the"
458                                 " default viewer\n" % cfg.VARS.application))
459                     sys.stdout.write("The error:"
460                                  " %s\n" % src.printcolors.printcWarning(
461                                                                       str(e)))
462                     do_merge = False
463         
464             else:
465                 cfg['open_application'] = 'yes'
466         # =====================================================================
467         # Load product config files in PRODUCTS section
468         products_cfg = src.pyconf.Config()
469         products_cfg.addMapping("PRODUCTS",
470                                 src.pyconf.Mapping(products_cfg),
471                                 "The products\n")
472         if application is not None:
473             src.pyconf.streamOpener = ConfigOpener(cfg.PATHS.PRODUCTPATH)
474             for product_name in application_cfg.APPLICATION.products.keys():
475                 # Loop on all files that are in softsDir directory
476                 # and read their config
477                 product_file_name = product_name + ".pyconf"
478                 product_file_path = src.find_file_in_lpath(product_file_name, cfg.PATHS.PRODUCTPATH)
479                 if product_file_path:
480                     products_dir = os.path.dirname(product_file_path)
481                     # for a relative path (archive case) we complete with sat path
482                     if not os.path.isabs(products_dir):
483                         products_dir = os.path.join(cfg.VARS.salometoolsway,
484                                                     products_dir)
485                     try:
486                         prod_cfg = src.pyconf.Config(open(product_file_path),
487                                                      PWD=("", products_dir))
488                         prod_cfg.from_file = product_file_path
489                         products_cfg.PRODUCTS[product_name] = prod_cfg
490                     except Exception as e:
491                         msg = _(
492                             "WARNING: Error in configuration file"
493                             ": %(prod)s\n  %(error)s" % \
494                             {'prod' :  product_name, 'error': str(e) })
495                         sys.stdout.write(msg)
496             
497             merger.merge(cfg, products_cfg)
498             
499             # apply overwrite from command line if needed
500             for rule in self.get_command_line_overrides(options, ["PRODUCTS"]):
501                 exec('cfg.' + rule) # this cannot be factorized because of the exec
502             
503             if do_merge:
504                 merger.merge(cfg, application_cfg)
505
506                 # default launcher name ('salome')
507                 if ('profile' in cfg.APPLICATION and 
508                     'launcher_name' not in cfg.APPLICATION.profile):
509                     cfg.APPLICATION.profile.launcher_name = 'salome'
510
511                 # apply overwrite from command line if needed
512                 for rule in self.get_command_line_overrides(options,
513                                                              ["APPLICATION"]):
514                     # this cannot be factorized because of the exec
515                     exec('cfg.' + rule)
516             
517         # =====================================================================
518         # load USER config
519         self.set_user_config_file(cfg)
520         user_cfg_file = self.get_user_config_file()
521         user_cfg = src.pyconf.Config(open(user_cfg_file))
522         merger.merge(cfg, user_cfg)
523
524         # apply overwrite from command line if needed
525         for rule in self.get_command_line_overrides(options, ["USER"]):
526             exec('cfg.' + rule) # this cannot be factorize because of the exec
527         
528         # remove application products "blacklisted" in rm_products field
529         if "APPLICATION" in cfg and "rm_products" in cfg.APPLICATION:
530             for prod_to_remove in cfg.APPLICATION.rm_products:
531                 cfg.APPLICATION.products.__delitem__(prod_to_remove)
532             # remove rm_products section after usage
533             cfg.APPLICATION.__delitem__("rm_products")
534         return cfg
535
536     def set_user_config_file(self, config):
537         '''Set the user config file name and path.
538         If necessary, build it from another one or create it from scratch.
539         
540         :param config class 'src.pyconf.Config': The global config 
541                                                  (containing all pyconf).
542         '''
543         # get the expected name and path of the file
544         self.config_file_name = 'SAT.pyconf'
545         self.user_config_file_path =  osJoin(config.VARS.personalDir, self.config_file_name)
546         
547         # if pyconf does not exist, create it from scratch
548         if not os.path.isfile(self.user_config_file_path): 
549             self.create_config_file(config)
550     
551     def create_config_file(self, config):
552         '''This method is called when there are no user config file. 
553            It build it from scratch.
554         
555         :param config class 'src.pyconf.Config': The global config.
556         :return: the config corresponding to the file created.
557         :rtype: config class 'src.pyconf.Config'
558         '''
559         
560         cfg_name = self.get_user_config_file()
561
562         user_cfg = src.pyconf.Config()
563         #
564         user_cfg.addMapping('USER', src.pyconf.Mapping(user_cfg), "")
565
566         user_cfg.USER.addMapping('cvs_user', config.VARS.user,
567             "This is the user name used to access salome cvs base.\n")
568         user_cfg.USER.addMapping('svn_user', config.VARS.user,
569             "This is the user name used to access salome svn base.\n")
570         user_cfg.USER.addMapping('output_verbose_level', 3,
571             "This is the default output_verbose_level you want."
572             " 0=>no output, 5=>debug.\n")
573         user_cfg.USER.addMapping('publish_dir', 
574                                   osJoin(os.path.expanduser('~'),
575                                  'websupport', 
576                                  'satreport'), 
577                                  "")
578         user_cfg.USER.addMapping('editor',
579                                  'vi', 
580                                  "This is the editor used to "
581                                  "modify configuration files\n")
582         user_cfg.USER.addMapping('browser', 
583                                  'firefox', 
584                                  "This is the browser used to "
585                                  "read html documentation\n")
586         user_cfg.USER.addMapping('pdf_viewer', 
587                                  'evince', 
588                                  "This is the pdf_viewer used "
589                                  "to read pdf documentation\n")
590
591         src.ensure_path_exists(config.VARS.personalDir)
592         src.ensure_path_exists( osJoin(config.VARS.personalDir,
593                                             'Applications'))
594
595         f = open(cfg_name, 'w')
596         user_cfg.__save__(f)
597         f.close()
598
599         return user_cfg   
600
601     def get_user_config_file(self):
602         '''Get the user config file
603         :return: path to the user config file.
604         :rtype: str
605         '''
606         if not self.user_config_file_path:
607             raise src.SatException(_("Error in get_user_config_file: "
608                                      "missing user config file path"))
609         return self.user_config_file_path     
610
611 def check_path(path, ext=[]):
612     '''Construct a text with the input path and "not found" if it does not
613        exist.
614     
615     :param path Str: the path to check.
616     :param ext List: An extension. Verify that the path extension 
617                      is in the list
618     :return: The string of the path with information
619     :rtype: Str
620     '''
621     # check if file exists
622     if not os.path.exists(path):
623         return "'%s'" % path + " " + src.printcolors.printcError(_(
624                                                             "** not found"))
625
626     # check extension
627     if len(ext) > 0:
628         fe = os.path.splitext(path)[1].lower()
629         if fe not in ext:
630             return "'%s'" % path + " " + src.printcolors.printcError(_(
631                                                         "** bad extension"))
632
633     return path
634
635 def show_product_info(config, name, logger):
636     '''Display on the terminal and logger information about a product.
637     
638     :param config Config: the global configuration.
639     :param name Str: The name of the product
640     :param logger Logger: The logger instance to use for the display
641     '''
642     
643     logger.write(_("%s is a product\n") % src.printcolors.printcLabel(name), 2)
644     pinfo = src.product.get_product_config(config, name)
645     
646     if "depend" in pinfo:
647         src.printcolors.print_value(logger, "depends on", sorted(pinfo.depend), 2)
648
649     if "opt_depend" in pinfo:
650         src.printcolors.print_value(logger, "optional", sorted(pinfo.opt_depend), 2)
651
652     # information on pyconf
653     logger.write("\n", 2)
654     logger.write(src.printcolors.printcLabel("configuration:") + "\n", 2)
655     if "from_file" in pinfo:
656         src.printcolors.print_value(logger,
657                                     "pyconf file path",
658                                     pinfo.from_file,
659                                     2)
660     if "section" in pinfo:
661         src.printcolors.print_value(logger,
662                                     "section",
663                                     pinfo.section,
664                                     2)
665
666     # information on prepare
667     logger.write("\n", 2)
668     logger.write(src.printcolors.printcLabel("prepare:") + "\n", 2)
669
670     is_dev = src.product.product_is_dev(pinfo)
671     method = pinfo.get_source
672     if is_dev:
673         method += " (dev)"
674     src.printcolors.print_value(logger, "get method", method, 2)
675
676     if method == 'cvs':
677         src.printcolors.print_value(logger, "server", pinfo.cvs_info.server, 2)
678         src.printcolors.print_value(logger, "base module",
679                                     pinfo.cvs_info.module_base, 2)
680         src.printcolors.print_value(logger, "source", pinfo.cvs_info.source, 2)
681         src.printcolors.print_value(logger, "tag", pinfo.cvs_info.tag, 2)
682
683     elif method == 'svn':
684         src.printcolors.print_value(logger, "repo", pinfo.svn_info.repo, 2)
685
686     elif method == 'git':
687         src.printcolors.print_value(logger, "repo", pinfo.git_info.repo, 2)
688         src.printcolors.print_value(logger, "tag", pinfo.git_info.tag, 2)
689
690     elif method == 'archive':
691         src.printcolors.print_value(logger,
692                                     "get from",
693                                     check_path(pinfo.archive_info.archive_name),
694                                     2)
695
696     if 'patches' in pinfo:
697         for patch in pinfo.patches:
698             src.printcolors.print_value(logger, "patch", check_path(patch), 2)
699
700     if src.product.product_is_fixed(pinfo):
701         src.printcolors.print_value(logger, "install_dir",
702                                     check_path(pinfo.install_dir), 2)
703
704     if src.product.product_is_native(pinfo) or src.product.product_is_fixed(pinfo):
705         return
706     
707     # information on compilation
708     if src.product.product_compiles(pinfo):
709         logger.write("\n", 2)
710         logger.write(src.printcolors.printcLabel("compile:") + "\n", 2)
711         src.printcolors.print_value(logger,
712                                     "compilation method",
713                                     pinfo.build_source,
714                                     2)
715         
716         if pinfo.build_source == "script" and "compil_script" in pinfo:
717             src.printcolors.print_value(logger, 
718                                         "Compilation script", 
719                                         pinfo.compil_script, 
720                                         2)
721         
722         if 'nb_proc' in pinfo:
723             src.printcolors.print_value(logger, "make -j", pinfo.nb_proc, 2)
724     
725         src.printcolors.print_value(logger, 
726                                     "source dir", 
727                                     check_path(pinfo.source_dir), 
728                                     2)
729         if 'install_dir' in pinfo:
730             src.printcolors.print_value(logger, 
731                                         "build dir", 
732                                         check_path(pinfo.build_dir), 
733                                         2)
734             src.printcolors.print_value(logger, 
735                                         "install dir", 
736                                         check_path(pinfo.install_dir), 
737                                         2)
738         else:
739             logger.write("  " + 
740                          src.printcolors.printcWarning(_("no install dir")) + 
741                          "\n", 2)
742     else:
743         logger.write("\n", 2)
744         msg = _("This product does not compile")
745         logger.write("%s\n" % msg, 2)
746
747     # information on environment
748     logger.write("\n", 2)
749     logger.write(src.printcolors.printcLabel("environ :") + "\n", 2)
750     if "environ" in pinfo and "env_script" in pinfo.environ:
751         src.printcolors.print_value(logger, 
752                                     "script", 
753                                     check_path(pinfo.environ.env_script), 
754                                     2)
755
756     # display run-time environment
757     zz = src.environment.SalomeEnviron(config,
758                                        src.fileEnviron.ScreenEnviron(logger), 
759                                        False)
760     zz.set_python_libdirs()
761     zz.set_a_product(name, logger)
762     logger.write("\n", 2)
763
764
765 def show_patchs(config, logger):
766   '''Prints all the used patchs in the application.
767
768   :param config Config: the global configuration.
769   :param logger Logger: The logger instance to use for the display
770   '''
771   oneOrMore = False
772   for product in sorted(config.APPLICATION.products):
773     try:
774       product_info = src.product.get_product_config(config, product)
775       if src.product.product_has_patches(product_info):
776         oneOrMore = True
777         logger.write("%s:\n" % product, 1)
778         for i in product_info.patches:
779           logger.write(src.printcolors.printcInfo("    %s\n" % i), 1)
780     except Exception as e:
781       msg = "problem on product %s\n%s\n" % (product, str(e))
782       logger.error(msg)
783
784   if oneOrMore:
785     logger.write("\n", 1)
786   else:
787     logger.write("No patchs found\n", 1)
788
789 def check_install_system(config, logger):
790   '''Check the installation of all (declared) system products
791
792   :param config Config: the global configuration.
793   :param logger Logger: The logger instance to use for the display
794   '''
795   # get the command to use for checking the system dependencies
796   # (either rmp or apt)
797   check_cmd=src.system.get_pkg_check_cmd(config.VARS.dist_name)
798   logger.write("\nCheck the system dependencies declared in the application\n",1)
799   pkgmgr=check_cmd[0]
800   run_dep_ko=[] # list of missing run time dependencies
801   build_dep_ko=[] # list of missing compile time dependencies
802   for product in sorted(config.APPLICATION.products):
803     try:
804       product_info = src.product.get_product_config(config, product)
805       if src.product.product_is_native(product_info):
806         # if the product is native, get (in two dictionnaries the runtime and compile time 
807         # system dependencies with the status (OK/KO)
808         run_pkg,build_pkg=src.product.check_system_dep(check_cmd, product_info)
809         #logger.write("\n*** %s ***\n" % product, 1)
810         for pkg in run_pkg:
811             logger.write("\n   - "+pkg + " : " + run_pkg[pkg], 1)
812             if "KO" in run_pkg[pkg]:
813                 run_dep_ko.append(pkg)
814         for pkg in build_pkg:
815             logger.write("\n   - "+pkg + " : " + build_pkg[pkg], 1)
816             if "KO" in build_pkg[pkg]:
817                 build_dep_ko.append(pkg)
818         #  logger.write(src.printcolors.printcInfo("    %s\n" % i), 1)
819
820     except Exception as e:
821       msg = "\nproblem with the check of system prerequisite %s\n%s\n" % (product, str(e))
822       logger.error(msg)
823       raise Exception(msg)
824
825   logger.write("\n\n",1)
826   if run_dep_ko:
827       msg="Some run time system dependencies are missing!\n"+\
828           "Please install them with %s before running salome" % pkgmgr
829       logger.warning(msg)
830       logger.write("missing run time dependencies : ",1)
831       for md in run_dep_ko: 
832         logger.write(md+" ",1)
833       logger.write("\n\n")
834         
835   if build_dep_ko:
836       msg="Some compile time system dependencies are missing!\n"+\
837           "Please install them with %s before compiling salome" % pkgmgr
838       logger.warning(msg)
839       logger.write("missing compile time dependencies : ",1)
840       for md in build_dep_ko: 
841         logger.write(md+" ",1)
842       logger.write("\n\n")
843     
844
845 def show_install_dir(config, logger):
846   '''Prints all the used installed directories in the application.
847
848   :param config Config: the global configuration.
849   :param logger Logger: The logger instance to use for the display
850   '''
851   for product in sorted(config.APPLICATION.products):
852     try:
853       product_info = src.product.get_product_config(config, product)
854       install_path=src.Path(product_info.install_dir)
855       if (src.product.product_is_native(product_info)):
856           install_path="Native"
857       elif (src.product.product_is_fixed(product_info)):
858           install_path+=" (Fixed)"
859       logger.write("%s : %s\n" % (product, install_path) , 1)
860     except Exception as e:
861       msg = "problem on product %s\n%s\n" % (product, str(e))
862       logger.error(msg)
863   logger.write("\n", 1)
864
865
866 def show_properties(config, logger):
867   '''Prints all the used properties in the application.
868
869   :param config Config: the global configuration.
870   :param logger Logger: The logger instance to use for the display
871   '''
872   if "properties" in config.APPLICATION:
873       # some properties are defined at application level, we display them
874       logger.write("Application properties:\n", 1)
875       for prop in config.APPLICATION.properties:
876           logger.write(src.printcolors.printcInfo("    %s : %s\n" % (prop, config.APPLICATION.properties[prop])), 1)
877   oneOrMore = False
878   for product in sorted(config.APPLICATION.products):
879     try:
880       product_info = src.product.get_product_config(config, product)
881       done = False
882       try:
883         for prop in product_info.properties:
884           if not done:
885             logger.write("%s:\n" % product, 1)
886             done = True
887           oneOrMore = True
888           logger.write(src.printcolors.printcInfo("    %s : %s\n" % (prop, product_info.properties[prop])), 1)
889       except Exception as e:
890         pass
891     except Exception as e:
892       # logger.write(src.printcolors.printcInfo("    %s\n" % "no properties"), 1)
893       msg = "problem on product %s\n%s\n" % (product, e)
894       logger.error(msg)
895
896   if oneOrMore:
897     logger.write("\n", 1)
898   else:
899     logger.write("No properties found\n", 1)
900
901 def print_value(config, path, show_label, logger, level=0, show_full_path=False):
902     '''Prints a value from the configuration. Prints recursively the values 
903        under the initial path.
904     
905     :param config class 'src.pyconf.Config': The configuration 
906                                              from which the value is displayed.
907     :param path str : the path in the configuration of the value to print.
908     :param show_label boolean: if True, do a basic display. 
909                                (useful for bash completion)
910     :param logger Logger: the logger instance
911     :param level int: The number of spaces to add before display.
912     :param show_full_path :
913     '''            
914     
915     # Make sure that the path does not ends with a point
916     if path.endswith('.'):
917         path = path[:-1]
918     
919     # display all the path or not
920     if show_full_path:
921         vname = path
922     else:
923         vname = path.split('.')[-1]
924
925     # number of spaces before the display
926     tab_level = "  " * level
927     
928     # call to the function that gets the value of the path.
929     try:
930         val = config.getByPath(path)
931     except Exception as e:
932         logger.write(tab_level)
933         logger.write("%s: ERROR %s\n" % (src.printcolors.printcLabel(vname), 
934                                          src.printcolors.printcError(str(e))))
935         return
936
937     # in this case, display only the value
938     if show_label:
939         logger.write(tab_level)
940         logger.write("%s: " % src.printcolors.printcLabel(vname))
941
942     # The case where the value has under values, 
943     # do a recursive call to the function
944     if dir(val).__contains__('keys'):
945         if show_label: logger.write("\n")
946         for v in sorted(val.keys()):
947             print_value(config, path + '.' + v, show_label, logger, level + 1)
948     elif val.__class__ == src.pyconf.Sequence or isinstance(val, list): 
949         # in this case, value is a list (or a Sequence)
950         if show_label: logger.write("\n")
951         index = 0
952         for v in val:
953             print_value(config, path + "[" + str(index) + "]", 
954                         show_label, logger, level + 1)
955             index = index + 1
956     else: # case where val is just a str
957         logger.write("%s\n" % val)
958
959 def get_config_children(config, args):
960     '''Gets the names of the children of the given parameter.
961        Useful only for completion mechanism
962     
963     :param config Config: The configuration where to read the values
964     :param args: The path in the config from which get the keys
965     '''
966     vals = []
967     rootkeys = config.keys()
968     
969     if len(args) == 0:
970         # no parameter returns list of root keys
971         vals = rootkeys
972     else:
973         parent = args[0]
974         pos = parent.rfind('.')
975         if pos < 0:
976             # Case where there is only on key as parameter.
977             # For example VARS
978             vals = [m for m in rootkeys if m.startswith(parent)]
979         else:
980             # Case where there is a part from a key
981             # for example VARS.us  (for VARS.user)
982             head = parent[0:pos]
983             tail = parent[pos+1:]
984             try:
985                 a = config.getByPath(head)
986                 if dir(a).__contains__('keys'):
987                     vals = map(lambda x: head + '.' + x,
988                                [m for m in a.keys() if m.startswith(tail)])
989             except:
990                 pass
991
992     for v in sorted(vals):
993         sys.stdout.write("%s\n" % v)
994
995 def description():
996     '''method that is called when salomeTools is called with --help option.
997     
998     :return: The text to display for the config command description.
999     :rtype: str
1000     '''
1001     return _("The config command allows manipulation "
1002              "and operation on config files.\n\nexample:\nsat config "
1003              "SALOME-master --info ParaView")
1004     
1005
1006 def run(args, runner, logger):
1007     '''method that is called when salomeTools is called with config parameter.
1008     '''
1009     # Parse the options
1010     (options, args) = parser.parse_args(args)
1011
1012     # Only useful for completion mechanism : print the keys of the config
1013     if options.schema:
1014         get_config_children(runner.cfg, args)
1015         return
1016
1017     # case : print a value of the config
1018     if options.value:
1019         if options.value == ".":
1020             # if argument is ".", print all the config
1021             for val in sorted(runner.cfg.keys()):
1022                 print_value(runner.cfg, val, not options.no_label, logger)
1023         else:
1024             print_value(runner.cfg, options.value, not options.no_label, logger, 
1025                         level=0, show_full_path=False)
1026     
1027     # case : print a debug value of the config
1028     if options.debug:
1029         if options.debug == ".":
1030             # if argument is ".", print all the config
1031             res = DBG.indent(DBG.getStrConfigDbg(runner.cfg))
1032             logger.write("\nConfig of application %s:\n\n%s\n" % (runner.cfg.VARS.application, res))
1033         else:
1034             if options.debug[0] == ".": # accept ".PRODUCT.etc" as "PRODUCT.etc"
1035               od = options.debug[1:]
1036             else:
1037               od = options.debug
1038             try:
1039               aCode = "a = runner.cfg.%s" % od
1040               # https://stackoverflow.com/questions/15086040/behavior-of-exec-function-in-python-2-and-python-3
1041               aDict = {"runner": runner}
1042               exec(aCode, globals(), aDict)
1043               # DBG.write("globals()", globals(), True)
1044               # DBG.write("aDict", aDict, True)
1045               res = DBG.indent(DBG.getStrConfigDbg(aDict["a"]))
1046               logger.write("\nConfig.%s of application %s:\n\n%s\n" % (od, runner.cfg.VARS.application, res))
1047             except Exception as e:
1048               msg = "\nConfig.%s of application %s: Unknown pyconf key\n" % (od, runner.cfg.VARS.application)
1049               logger.write(src.printcolors.printcError(msg), 1)
1050
1051     
1052     # case : edit user pyconf file or application file
1053     if options.edit:
1054         editor = runner.cfg.USER.editor
1055         if ('APPLICATION' not in runner.cfg and
1056                        'open_application' not in runner.cfg): # edit user pyconf
1057             usercfg =  osJoin(runner.cfg.VARS.personalDir,
1058                                    'SAT.pyconf')
1059             logger.write(_("Opening %s\n" % usercfg), 3)
1060             src.system.show_in_editor(editor, usercfg, logger)
1061         else:
1062             # search for file <application>.pyconf and open it
1063             for path in runner.cfg.PATHS.APPLICATIONPATH:
1064                 pyconf_path =  osJoin(path,
1065                                     runner.cfg.VARS.application + ".pyconf")
1066                 if os.path.exists(pyconf_path):
1067                     logger.write(_("Opening %s\n" % pyconf_path), 3)
1068                     src.system.show_in_editor(editor, pyconf_path, logger)
1069                     break
1070     
1071     # case : give information about the product(s) in parameter
1072     if options.products:
1073       if options.info is not None:
1074         logger.warning('options.products %s overrides options.info %s' % (options.products, options.info))
1075       options.info = options.products
1076
1077     if options.info:
1078       # DBG.write("products", sorted(runner.cfg.APPLICATION.products.keys()), True)
1079       src.check_config_has_application(runner.cfg)
1080       taggedProducts = src.getProductNames(runner.cfg, options.info, logger)
1081       DBG.write("tagged products", sorted(taggedProducts))
1082       for prod in sorted(taggedProducts):
1083         if prod in runner.cfg.APPLICATION.products:
1084           try:
1085             if len(taggedProducts) > 1:
1086               logger.write("#################### ", 2)
1087             show_product_info(runner.cfg, prod, logger)
1088           except Exception as e:
1089             msg = "problem on product %s\n%s\n" % (prod, str(e))
1090             logger.error(msg)
1091           # return
1092         else:
1093           msg = _("%s is not a product of %s.\n") % \
1094                 (prod, runner.cfg.VARS.application)
1095           logger.warning(msg)
1096           #raise Exception(msg)
1097     
1098     # case : copy an existing <application>.pyconf 
1099     # to ~/.salomeTools/Applications/LOCAL_<application>.pyconf
1100     if options.copy:
1101         # product is required
1102         src.check_config_has_application( runner.cfg )
1103
1104         # get application file path 
1105         source = runner.cfg.VARS.application + '.pyconf'
1106         source_full_path = ""
1107         for path in runner.cfg.PATHS.APPLICATIONPATH:
1108             # ignore personal directory
1109             if path == runner.cfg.VARS.personalDir:
1110                 continue
1111             # loop on all directories that can have pyconf applications
1112             zz =  osJoin(path, source)
1113             if os.path.exists(zz):
1114                 source_full_path = zz
1115                 break
1116
1117         if len(source_full_path) == 0:
1118             raise src.SatException(_(
1119                         "Config file for product %s not found\n") % source)
1120         else:
1121             if len(args) > 0:
1122                 # a name is given as parameter, use it
1123                 dest = args[0]
1124             elif 'copy_prefix' in runner.cfg.INTERNAL.config:
1125                 # use prefix
1126                 dest = (runner.cfg.INTERNAL.config.copy_prefix 
1127                         + runner.cfg.VARS.application)
1128             else:
1129                 # use same name as source
1130                 dest = runner.cfg.VARS.application
1131                 
1132             # the full path
1133             dest_file =  osJoin(runner.cfg.VARS.personalDir,
1134                                      'Applications', dest + '.pyconf')
1135             if os.path.exists(dest_file):
1136                 raise src.SatException(_("A personal application"
1137                                          " '%s' already exists") % dest)
1138             
1139             # perform the copy
1140             shutil.copyfile(source_full_path, dest_file)
1141             logger.write(_("%s has been created.\n") % dest_file)
1142     
1143     # case : display all the available pyconf applications
1144     if options.list:
1145         lproduct = list()
1146         # search in all directories that can have pyconf applications
1147         for path in runner.cfg.PATHS.APPLICATIONPATH:
1148             # print a header
1149             if not options.no_label:
1150                 logger.write("------ %s\n" % src.printcolors.printcHeader(path))
1151
1152             if not os.path.exists(path):
1153                 logger.write(src.printcolors.printcError(_(
1154                                             "Directory not found")) + "\n")
1155             else:
1156                 for f in sorted(os.listdir(path)):
1157                     # ignore file that does not ends with .pyconf
1158                     if not f.endswith('.pyconf'):
1159                         continue
1160
1161                     appliname = f[:-len('.pyconf')]
1162                     if appliname not in lproduct:
1163                         lproduct.append(appliname)
1164                         if path.startswith(runner.cfg.VARS.personalDir) \
1165                                     and not options.no_label:
1166                             logger.write("%s*\n" % appliname)
1167                         else:
1168                             logger.write("%s\n" % appliname)
1169                             
1170             logger.write("\n")
1171
1172     # case: print all the products name of the application (internal use for completion)
1173     if options.completion:
1174         for product_name in runner.cfg.APPLICATION.products.keys():
1175             logger.write("%s\n" % product_name)
1176         
1177     # case : give a synthetic view of all patches used in the application
1178     if options.show_patchs:
1179         src.check_config_has_application(runner.cfg)
1180         # Print some informations
1181         logger.write(_('Patchs of application %s\n') %
1182                     src.printcolors.printcLabel(runner.cfg.VARS.application), 3)
1183         logger.write("\n", 2, False)
1184         show_patchs(runner.cfg, logger)
1185
1186     # case : give a synthetic view of all install directories used in the application
1187     if options.show_install:
1188         src.check_config_has_application(runner.cfg)
1189         # Print some informations
1190         logger.write(_('Installation directories of application %s\n') %
1191                     src.printcolors.printcLabel(runner.cfg.VARS.application), 3)
1192         logger.write("\n", 2, False)
1193         show_install_dir(runner.cfg, logger)
1194
1195     # case : give a synthetic view of all patches used in the application
1196     if options.show_properties:
1197         src.check_config_has_application(runner.cfg)
1198
1199         # Print some informations
1200         logger.write(_('Properties of application %s\n') %
1201                     src.printcolors.printcLabel(runner.cfg.VARS.application), 3)
1202         logger.write("\n", 2, False)
1203         show_properties(runner.cfg, logger)
1204
1205     # check system prerequisites
1206     if options.check_system:
1207        check_install_system(runner.cfg, logger)
1208        pass