Salome HOME
developpement d'une option check_system pour git config
[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 == None:
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()
798   logger.write("\nCheck the system dependencies declared in the application\n",1)
799   pkgmgr=check_cmd[0]
800   for product in sorted(config.APPLICATION.products):
801     try:
802       product_info = src.product.get_product_config(config, product)
803       if src.product.product_is_native(product_info):
804         # if the product is native, get (in two dictionnaries the runtime and compile time 
805         # system dependencies with the status (OK/KO)
806         run_pkg,build_pkg=src.product.check_system_dep(check_cmd, product_info)
807         #logger.write("\n*** %s ***\n" % product, 1)
808         for pkg in run_pkg:
809             logger.write(run_pkg[pkg], 1)
810         for pkg in build_pkg:
811             logger.write(build_pkg[pkg], 1)
812         #  logger.write(src.printcolors.printcInfo("    %s\n" % i), 1)
813
814     except Exception as e:
815       msg = "problem with the check of system prerequisite %s\n%s\n" % (product, str(e))
816       logger.error(msg)
817
818
819 def show_install_dir(config, logger):
820   '''Prints all the used installed directories in the application.
821
822   :param config Config: the global configuration.
823   :param logger Logger: The logger instance to use for the display
824   '''
825   for product in sorted(config.APPLICATION.products):
826     try:
827       product_info = src.product.get_product_config(config, product)
828       install_path=src.Path(product_info.install_dir)
829       if (src.product.product_is_native(product_info)):
830           install_path="Native"
831       elif (src.product.product_is_fixed(product_info)):
832           install_path+=" (Fixed)"
833       logger.write("%s : %s\n" % (product, install_path) , 1)
834     except Exception as e:
835       msg = "problem on product %s\n%s\n" % (product, str(e))
836       logger.error(msg)
837   logger.write("\n", 1)
838
839
840 def show_properties(config, logger):
841   '''Prints all the used properties in the application.
842
843   :param config Config: the global configuration.
844   :param logger Logger: The logger instance to use for the display
845   '''
846   if "properties" in config.APPLICATION:
847       # some properties are defined at application level, we display them
848       logger.write("Application properties:\n", 1)
849       for prop in config.APPLICATION.properties:
850           logger.write(src.printcolors.printcInfo("    %s : %s\n" % (prop, config.APPLICATION.properties[prop])), 1)
851   oneOrMore = False
852   for product in sorted(config.APPLICATION.products):
853     try:
854       product_info = src.product.get_product_config(config, product)
855       done = False
856       try:
857         for prop in product_info.properties:
858           if not done:
859             logger.write("%s:\n" % product, 1)
860             done = True
861           oneOrMore = True
862           logger.write(src.printcolors.printcInfo("    %s : %s\n" % (prop, product_info.properties[prop])), 1)
863       except Exception as e:
864         pass
865     except Exception as e:
866       # logger.write(src.printcolors.printcInfo("    %s\n" % "no properties"), 1)
867       msg = "problem on product %s\n%s\n" % (product, e)
868       logger.error(msg)
869
870   if oneOrMore:
871     logger.write("\n", 1)
872   else:
873     logger.write("No properties found\n", 1)
874
875 def print_value(config, path, show_label, logger, level=0, show_full_path=False):
876     '''Prints a value from the configuration. Prints recursively the values 
877        under the initial path.
878     
879     :param config class 'src.pyconf.Config': The configuration 
880                                              from which the value is displayed.
881     :param path str : the path in the configuration of the value to print.
882     :param show_label boolean: if True, do a basic display. 
883                                (useful for bash completion)
884     :param logger Logger: the logger instance
885     :param level int: The number of spaces to add before display.
886     :param show_full_path :
887     '''            
888     
889     # Make sure that the path does not ends with a point
890     if path.endswith('.'):
891         path = path[:-1]
892     
893     # display all the path or not
894     if show_full_path:
895         vname = path
896     else:
897         vname = path.split('.')[-1]
898
899     # number of spaces before the display
900     tab_level = "  " * level
901     
902     # call to the function that gets the value of the path.
903     try:
904         val = config.getByPath(path)
905     except Exception as e:
906         logger.write(tab_level)
907         logger.write("%s: ERROR %s\n" % (src.printcolors.printcLabel(vname), 
908                                          src.printcolors.printcError(str(e))))
909         return
910
911     # in this case, display only the value
912     if show_label:
913         logger.write(tab_level)
914         logger.write("%s: " % src.printcolors.printcLabel(vname))
915
916     # The case where the value has under values, 
917     # do a recursive call to the function
918     if dir(val).__contains__('keys'):
919         if show_label: logger.write("\n")
920         for v in sorted(val.keys()):
921             print_value(config, path + '.' + v, show_label, logger, level + 1)
922     elif val.__class__ == src.pyconf.Sequence or isinstance(val, list): 
923         # in this case, value is a list (or a Sequence)
924         if show_label: logger.write("\n")
925         index = 0
926         for v in val:
927             print_value(config, path + "[" + str(index) + "]", 
928                         show_label, logger, level + 1)
929             index = index + 1
930     else: # case where val is just a str
931         logger.write("%s\n" % val)
932
933 def get_config_children(config, args):
934     '''Gets the names of the children of the given parameter.
935        Useful only for completion mechanism
936     
937     :param config Config: The configuration where to read the values
938     :param args: The path in the config from which get the keys
939     '''
940     vals = []
941     rootkeys = config.keys()
942     
943     if len(args) == 0:
944         # no parameter returns list of root keys
945         vals = rootkeys
946     else:
947         parent = args[0]
948         pos = parent.rfind('.')
949         if pos < 0:
950             # Case where there is only on key as parameter.
951             # For example VARS
952             vals = [m for m in rootkeys if m.startswith(parent)]
953         else:
954             # Case where there is a part from a key
955             # for example VARS.us  (for VARS.user)
956             head = parent[0:pos]
957             tail = parent[pos+1:]
958             try:
959                 a = config.getByPath(head)
960                 if dir(a).__contains__('keys'):
961                     vals = map(lambda x: head + '.' + x,
962                                [m for m in a.keys() if m.startswith(tail)])
963             except:
964                 pass
965
966     for v in sorted(vals):
967         sys.stdout.write("%s\n" % v)
968
969 def description():
970     '''method that is called when salomeTools is called with --help option.
971     
972     :return: The text to display for the config command description.
973     :rtype: str
974     '''
975     return _("The config command allows manipulation "
976              "and operation on config files.\n\nexample:\nsat config "
977              "SALOME-master --info ParaView")
978     
979
980 def run(args, runner, logger):
981     '''method that is called when salomeTools is called with config parameter.
982     '''
983     # Parse the options
984     (options, args) = parser.parse_args(args)
985
986     # Only useful for completion mechanism : print the keys of the config
987     if options.schema:
988         get_config_children(runner.cfg, args)
989         return
990
991     # case : print a value of the config
992     if options.value:
993         if options.value == ".":
994             # if argument is ".", print all the config
995             for val in sorted(runner.cfg.keys()):
996                 print_value(runner.cfg, val, not options.no_label, logger)
997         else:
998             print_value(runner.cfg, options.value, not options.no_label, logger, 
999                         level=0, show_full_path=False)
1000     
1001     # case : print a debug value of the config
1002     if options.debug:
1003         if options.debug == ".":
1004             # if argument is ".", print all the config
1005             res = DBG.indent(DBG.getStrConfigDbg(runner.cfg))
1006             logger.write("\nConfig of application %s:\n\n%s\n" % (runner.cfg.VARS.application, res))
1007         else:
1008             if options.debug[0] == ".": # accept ".PRODUCT.etc" as "PRODUCT.etc"
1009               od = options.debug[1:]
1010             else:
1011               od = options.debug
1012             try:
1013               aCode = "a = runner.cfg.%s" % od
1014               # https://stackoverflow.com/questions/15086040/behavior-of-exec-function-in-python-2-and-python-3
1015               aDict = {"runner": runner}
1016               exec(aCode, globals(), aDict)
1017               # DBG.write("globals()", globals(), True)
1018               # DBG.write("aDict", aDict, True)
1019               res = DBG.indent(DBG.getStrConfigDbg(aDict["a"]))
1020               logger.write("\nConfig.%s of application %s:\n\n%s\n" % (od, runner.cfg.VARS.application, res))
1021             except Exception as e:
1022               msg = "\nConfig.%s of application %s: Unknown pyconf key\n" % (od, runner.cfg.VARS.application)
1023               logger.write(src.printcolors.printcError(msg), 1)
1024
1025     
1026     # case : edit user pyconf file or application file
1027     if options.edit:
1028         editor = runner.cfg.USER.editor
1029         if ('APPLICATION' not in runner.cfg and
1030                        'open_application' not in runner.cfg): # edit user pyconf
1031             usercfg =  osJoin(runner.cfg.VARS.personalDir,
1032                                    'SAT.pyconf')
1033             logger.write(_("Opening %s\n" % usercfg), 3)
1034             src.system.show_in_editor(editor, usercfg, logger)
1035         else:
1036             # search for file <application>.pyconf and open it
1037             for path in runner.cfg.PATHS.APPLICATIONPATH:
1038                 pyconf_path =  osJoin(path,
1039                                     runner.cfg.VARS.application + ".pyconf")
1040                 if os.path.exists(pyconf_path):
1041                     logger.write(_("Opening %s\n" % pyconf_path), 3)
1042                     src.system.show_in_editor(editor, pyconf_path, logger)
1043                     break
1044     
1045     # case : give information about the product(s) in parameter
1046     if options.products:
1047       if options.info is not None:
1048         logger.warning('options.products %s overrides options.info %s' % (options.products, options.info))
1049       options.info = options.products
1050
1051     if options.info:
1052       # DBG.write("products", sorted(runner.cfg.APPLICATION.products.keys()), True)
1053       src.check_config_has_application(runner.cfg)
1054       taggedProducts = src.getProductNames(runner.cfg, options.info, logger)
1055       DBG.write("tagged products", sorted(taggedProducts))
1056       for prod in sorted(taggedProducts):
1057         if prod in runner.cfg.APPLICATION.products:
1058           try:
1059             if len(taggedProducts) > 1:
1060               logger.write("#################### ", 2)
1061             show_product_info(runner.cfg, prod, logger)
1062           except Exception as e:
1063             msg = "problem on product %s\n%s\n" % (prod, str(e))
1064             logger.error(msg)
1065           # return
1066         else:
1067           msg = _("%s is not a product of %s.\n") % \
1068                 (prod, runner.cfg.VARS.application)
1069           logger.warning(msg)
1070           #raise Exception(msg)
1071     
1072     # case : copy an existing <application>.pyconf 
1073     # to ~/.salomeTools/Applications/LOCAL_<application>.pyconf
1074     if options.copy:
1075         # product is required
1076         src.check_config_has_application( runner.cfg )
1077
1078         # get application file path 
1079         source = runner.cfg.VARS.application + '.pyconf'
1080         source_full_path = ""
1081         for path in runner.cfg.PATHS.APPLICATIONPATH:
1082             # ignore personal directory
1083             if path == runner.cfg.VARS.personalDir:
1084                 continue
1085             # loop on all directories that can have pyconf applications
1086             zz =  osJoin(path, source)
1087             if os.path.exists(zz):
1088                 source_full_path = zz
1089                 break
1090
1091         if len(source_full_path) == 0:
1092             raise src.SatException(_(
1093                         "Config file for product %s not found\n") % source)
1094         else:
1095             if len(args) > 0:
1096                 # a name is given as parameter, use it
1097                 dest = args[0]
1098             elif 'copy_prefix' in runner.cfg.INTERNAL.config:
1099                 # use prefix
1100                 dest = (runner.cfg.INTERNAL.config.copy_prefix 
1101                         + runner.cfg.VARS.application)
1102             else:
1103                 # use same name as source
1104                 dest = runner.cfg.VARS.application
1105                 
1106             # the full path
1107             dest_file =  osJoin(runner.cfg.VARS.personalDir,
1108                                      'Applications', dest + '.pyconf')
1109             if os.path.exists(dest_file):
1110                 raise src.SatException(_("A personal application"
1111                                          " '%s' already exists") % dest)
1112             
1113             # perform the copy
1114             shutil.copyfile(source_full_path, dest_file)
1115             logger.write(_("%s has been created.\n") % dest_file)
1116     
1117     # case : display all the available pyconf applications
1118     if options.list:
1119         lproduct = list()
1120         # search in all directories that can have pyconf applications
1121         for path in runner.cfg.PATHS.APPLICATIONPATH:
1122             # print a header
1123             if not options.no_label:
1124                 logger.write("------ %s\n" % src.printcolors.printcHeader(path))
1125
1126             if not os.path.exists(path):
1127                 logger.write(src.printcolors.printcError(_(
1128                                             "Directory not found")) + "\n")
1129             else:
1130                 for f in sorted(os.listdir(path)):
1131                     # ignore file that does not ends with .pyconf
1132                     if not f.endswith('.pyconf'):
1133                         continue
1134
1135                     appliname = f[:-len('.pyconf')]
1136                     if appliname not in lproduct:
1137                         lproduct.append(appliname)
1138                         if path.startswith(runner.cfg.VARS.personalDir) \
1139                                     and not options.no_label:
1140                             logger.write("%s*\n" % appliname)
1141                         else:
1142                             logger.write("%s\n" % appliname)
1143                             
1144             logger.write("\n")
1145
1146     # case: print all the products name of the application (internal use for completion)
1147     if options.completion:
1148         for product_name in runner.cfg.APPLICATION.products.keys():
1149             logger.write("%s\n" % product_name)
1150         
1151     # case : give a synthetic view of all patches used in the application
1152     if options.show_patchs:
1153         src.check_config_has_application(runner.cfg)
1154         # Print some informations
1155         logger.write(_('Patchs of application %s\n') %
1156                     src.printcolors.printcLabel(runner.cfg.VARS.application), 3)
1157         logger.write("\n", 2, False)
1158         show_patchs(runner.cfg, logger)
1159
1160     # case : give a synthetic view of all install directories used in the application
1161     if options.show_install:
1162         src.check_config_has_application(runner.cfg)
1163         # Print some informations
1164         logger.write(_('Installation directories of application %s\n') %
1165                     src.printcolors.printcLabel(runner.cfg.VARS.application), 3)
1166         logger.write("\n", 2, False)
1167         show_install_dir(runner.cfg, logger)
1168
1169     # case : give a synthetic view of all patches used in the application
1170     if options.show_properties:
1171         src.check_config_has_application(runner.cfg)
1172
1173         # Print some informations
1174         logger.write(_('Properties of application %s\n') %
1175                     src.printcolors.printcLabel(runner.cfg.VARS.application), 3)
1176         logger.write("\n", 2, False)
1177         show_properties(runner.cfg, logger)
1178
1179     # check system prerequisites
1180     if options.check_system:
1181        check_install_system(runner.cfg, logger)
1182        pass