Salome HOME
95a28c0021a1068e8d80caa0cd8f9a6646f0cb10
[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                 pathlist=cfg.PROJECTS.projects[project][PATH].split(":")
411                 for path in pathlist:
412                     cfg.PATHS[PATH].append(path, "")
413         
414         # apply overwrite from command line if needed
415         for rule in self.get_command_line_overrides(options, ["PATHS"]):
416             exec('cfg.' + rule) # this cannot be factorized because of the exec
417
418         # AT END append APPLI_TEST directory in APPLICATIONPATH, for unittest
419         appli_test_dir =  osJoin(satdir, "test", "APPLI_TEST")
420         if appli_test_dir not in cfg.PATHS.APPLICATIONPATH:
421           cfg.PATHS.APPLICATIONPATH.append(appli_test_dir, "unittest APPLI_TEST path")
422
423         # =====================================================================
424         # Load APPLICATION config file
425         if application is not None:
426             # search APPLICATION file in all directories in configPath
427             cp = cfg.PATHS.APPLICATIONPATH
428             src.pyconf.streamOpener = ConfigOpener(cp)
429             do_merge = True
430             try:
431                 application_cfg = src.pyconf.Config(application + '.pyconf')
432             except IOError as e:
433                 raise src.SatException(
434                    _("%s, use 'config --list' to get the list of available applications.") % e)
435             except src.pyconf.ConfigError as e:
436                 if (not ('-e' in parser.parse_args()[1]) 
437                                          or ('--edit' in parser.parse_args()[1]) 
438                                          and command == 'config'):
439                     raise src.SatException(_("Error in configuration file: "
440                                              "%(application)s.pyconf\n "
441                                              " %(error)s") % \
442                         { 'application': application, 'error': str(e) } )
443                 else:
444                     sys.stdout.write(src.printcolors.printcWarning(
445                                         "There is an error in the file"
446                                         " %s.pyconf.\n" % cfg.VARS.application))
447                     do_merge = False
448             except Exception as e:
449                 if (not ('-e' in parser.parse_args()[1]) 
450                                         or ('--edit' in parser.parse_args()[1]) 
451                                         and command == 'config'):
452                     sys.stdout.write(src.printcolors.printcWarning("%s\n" % str(e)))
453                     raise src.SatException(_("Error in configuration file:"
454                                              " %(application)s.pyconf\n") % \
455                         { 'application': application} )
456                 else:
457                     sys.stdout.write(src.printcolors.printcWarning(
458                                 "There is an error in the file"
459                                 " %s.pyconf. Opening the file with the"
460                                 " default viewer\n" % cfg.VARS.application))
461                     sys.stdout.write("The error:"
462                                  " %s\n" % src.printcolors.printcWarning(
463                                                                       str(e)))
464                     do_merge = False
465         
466             else:
467                 cfg['open_application'] = 'yes'
468         # =====================================================================
469         # Load product config files in PRODUCTS section
470         products_cfg = src.pyconf.Config()
471         products_cfg.addMapping("PRODUCTS",
472                                 src.pyconf.Mapping(products_cfg),
473                                 "The products\n")
474         if application is not None:
475             src.pyconf.streamOpener = ConfigOpener(cfg.PATHS.PRODUCTPATH)
476             for product_name in application_cfg.APPLICATION.products.keys():
477                 # Loop on all files that are in softsDir directory
478                 # and read their config
479                 product_file_name = product_name + ".pyconf"
480                 product_file_path = src.find_file_in_lpath(product_file_name, cfg.PATHS.PRODUCTPATH)
481                 if product_file_path:
482                     products_dir = os.path.dirname(product_file_path)
483                     # for a relative path (archive case) we complete with sat path
484                     if not os.path.isabs(products_dir):
485                         products_dir = os.path.join(cfg.VARS.salometoolsway,
486                                                     products_dir)
487                     try:
488                         prod_cfg = src.pyconf.Config(open(product_file_path),
489                                                      PWD=("", products_dir))
490                         prod_cfg.from_file = product_file_path
491                         products_cfg.PRODUCTS[product_name] = prod_cfg
492                     except Exception as e:
493                         msg = _(
494                             "WARNING: Error in configuration file"
495                             ": %(prod)s\n  %(error)s" % \
496                             {'prod' :  product_name, 'error': str(e) })
497                         sys.stdout.write(msg)
498             
499             merger.merge(cfg, products_cfg)
500             
501             # apply overwrite from command line if needed
502             for rule in self.get_command_line_overrides(options, ["PRODUCTS"]):
503                 exec('cfg.' + rule) # this cannot be factorized because of the exec
504             
505             if do_merge:
506                 merger.merge(cfg, application_cfg)
507
508                 # default launcher name ('salome')
509                 if ('profile' in cfg.APPLICATION and 
510                     'launcher_name' not in cfg.APPLICATION.profile):
511                     cfg.APPLICATION.profile.launcher_name = 'salome'
512
513                 # apply overwrite from command line if needed
514                 for rule in self.get_command_line_overrides(options,
515                                                              ["APPLICATION"]):
516                     # this cannot be factorized because of the exec
517                     exec('cfg.' + rule)
518             
519         # =====================================================================
520         # load USER config
521         self.set_user_config_file(cfg)
522         user_cfg_file = self.get_user_config_file()
523         user_cfg = src.pyconf.Config(open(user_cfg_file))
524         merger.merge(cfg, user_cfg)
525
526         # apply overwrite from command line if needed
527         for rule in self.get_command_line_overrides(options, ["USER"]):
528             exec('cfg.' + rule) # this cannot be factorize because of the exec
529         
530         # remove application products "blacklisted" in rm_products field
531         if "APPLICATION" in cfg and "rm_products" in cfg.APPLICATION:
532             for prod_to_remove in cfg.APPLICATION.rm_products:
533                 cfg.APPLICATION.products.__delitem__(prod_to_remove)
534             # remove rm_products section after usage
535             cfg.APPLICATION.__delitem__("rm_products")
536         return cfg
537
538     def set_user_config_file(self, config):
539         '''Set the user config file name and path.
540         If necessary, build it from another one or create it from scratch.
541         
542         :param config class 'src.pyconf.Config': The global config 
543                                                  (containing all pyconf).
544         '''
545         # get the expected name and path of the file
546         self.config_file_name = 'SAT.pyconf'
547         self.user_config_file_path =  osJoin(config.VARS.personalDir, self.config_file_name)
548         
549         # if pyconf does not exist, create it from scratch
550         if not os.path.isfile(self.user_config_file_path): 
551             self.create_config_file(config)
552     
553     def create_config_file(self, config):
554         '''This method is called when there are no user config file. 
555            It build it from scratch.
556         
557         :param config class 'src.pyconf.Config': The global config.
558         :return: the config corresponding to the file created.
559         :rtype: config class 'src.pyconf.Config'
560         '''
561         
562         cfg_name = self.get_user_config_file()
563
564         user_cfg = src.pyconf.Config()
565         #
566         user_cfg.addMapping('USER', src.pyconf.Mapping(user_cfg), "")
567
568         user_cfg.USER.addMapping('cvs_user', config.VARS.user,
569             "This is the user name used to access salome cvs base.\n")
570         user_cfg.USER.addMapping('svn_user', config.VARS.user,
571             "This is the user name used to access salome svn base.\n")
572         user_cfg.USER.addMapping('output_verbose_level', 3,
573             "This is the default output_verbose_level you want."
574             " 0=>no output, 5=>debug.\n")
575         user_cfg.USER.addMapping('publish_dir', 
576                                   osJoin(os.path.expanduser('~'),
577                                  'websupport', 
578                                  'satreport'), 
579                                  "")
580         user_cfg.USER.addMapping('editor',
581                                  'vi', 
582                                  "This is the editor used to "
583                                  "modify configuration files\n")
584         user_cfg.USER.addMapping('browser', 
585                                  'firefox', 
586                                  "This is the browser used to "
587                                  "read html documentation\n")
588         user_cfg.USER.addMapping('pdf_viewer', 
589                                  'evince', 
590                                  "This is the pdf_viewer used "
591                                  "to read pdf documentation\n")
592
593         src.ensure_path_exists(config.VARS.personalDir)
594         src.ensure_path_exists( osJoin(config.VARS.personalDir,
595                                             'Applications'))
596
597         f = open(cfg_name, 'w')
598         user_cfg.__save__(f)
599         f.close()
600
601         return user_cfg   
602
603     def get_user_config_file(self):
604         '''Get the user config file
605         :return: path to the user config file.
606         :rtype: str
607         '''
608         if not self.user_config_file_path:
609             raise src.SatException(_("Error in get_user_config_file: "
610                                      "missing user config file path"))
611         return self.user_config_file_path     
612
613 def check_path(path, ext=[]):
614     '''Construct a text with the input path and "not found" if it does not
615        exist.
616     
617     :param path Str: the path to check.
618     :param ext List: An extension. Verify that the path extension 
619                      is in the list
620     :return: The string of the path with information
621     :rtype: Str
622     '''
623     # check if file exists
624     if not os.path.exists(path):
625         return "'%s'" % path + " " + src.printcolors.printcError(_(
626                                                             "** not found"))
627
628     # check extension
629     if len(ext) > 0:
630         fe = os.path.splitext(path)[1].lower()
631         if fe not in ext:
632             return "'%s'" % path + " " + src.printcolors.printcError(_(
633                                                         "** bad extension"))
634
635     return path
636
637 def show_product_info(config, name, logger):
638     '''Display on the terminal and logger information about a product.
639     
640     :param config Config: the global configuration.
641     :param name Str: The name of the product
642     :param logger Logger: The logger instance to use for the display
643     '''
644     
645     logger.write(_("%s is a product\n") % src.printcolors.printcLabel(name), 2)
646     pinfo = src.product.get_product_config(config, name)
647     
648     if "depend" in pinfo:
649         src.printcolors.print_value(logger, "depends on", sorted(pinfo.depend), 2)
650
651     if "opt_depend" in pinfo:
652         src.printcolors.print_value(logger, "optional", sorted(pinfo.opt_depend), 2)
653
654     if "build_depend" in pinfo:
655         src.printcolors.print_value(logger, "build depend on", sorted(pinfo.build_depend), 2)
656
657
658     # information on pyconf
659     logger.write("\n", 2)
660     logger.write(src.printcolors.printcLabel("configuration:") + "\n", 2)
661     if "from_file" in pinfo:
662         src.printcolors.print_value(logger,
663                                     "pyconf file path",
664                                     pinfo.from_file,
665                                     2)
666     if "section" in pinfo:
667         src.printcolors.print_value(logger,
668                                     "section",
669                                     pinfo.section,
670                                     2)
671
672     # information on prepare
673     logger.write("\n", 2)
674     logger.write(src.printcolors.printcLabel("prepare:") + "\n", 2)
675
676     is_dev = src.product.product_is_dev(pinfo)
677     method = pinfo.get_source
678     if is_dev:
679         method += " (dev)"
680     src.printcolors.print_value(logger, "get method", method, 2)
681
682     if method == 'cvs':
683         src.printcolors.print_value(logger, "server", pinfo.cvs_info.server, 2)
684         src.printcolors.print_value(logger, "base module",
685                                     pinfo.cvs_info.module_base, 2)
686         src.printcolors.print_value(logger, "source", pinfo.cvs_info.source, 2)
687         src.printcolors.print_value(logger, "tag", pinfo.cvs_info.tag, 2)
688
689     elif method == 'svn':
690         src.printcolors.print_value(logger, "repo", pinfo.svn_info.repo, 2)
691
692     elif method == 'git':
693         src.printcolors.print_value(logger, "repo", pinfo.git_info.repo, 2)
694         src.printcolors.print_value(logger, "tag", pinfo.git_info.tag, 2)
695
696     elif method == 'archive':
697         src.printcolors.print_value(logger,
698                                     "get from",
699                                     check_path(pinfo.archive_info.archive_name),
700                                     2)
701
702     if 'patches' in pinfo:
703         for patch in pinfo.patches:
704             src.printcolors.print_value(logger, "patch", check_path(patch), 2)
705
706     if src.product.product_is_fixed(pinfo):
707         src.printcolors.print_value(logger, "install_dir",
708                                     check_path(pinfo.install_dir), 2)
709
710     if src.product.product_is_native(pinfo) or src.product.product_is_fixed(pinfo):
711         return
712     
713     # information on compilation
714     if src.product.product_compiles(pinfo):
715         logger.write("\n", 2)
716         logger.write(src.printcolors.printcLabel("compile:") + "\n", 2)
717         src.printcolors.print_value(logger,
718                                     "compilation method",
719                                     pinfo.build_source,
720                                     2)
721         
722         if pinfo.build_source == "script" and "compil_script" in pinfo:
723             src.printcolors.print_value(logger, 
724                                         "Compilation script", 
725                                         pinfo.compil_script, 
726                                         2)
727         
728         if 'nb_proc' in pinfo:
729             src.printcolors.print_value(logger, "make -j", pinfo.nb_proc, 2)
730     
731         src.printcolors.print_value(logger, 
732                                     "source dir", 
733                                     check_path(pinfo.source_dir), 
734                                     2)
735         if 'install_dir' in pinfo:
736             src.printcolors.print_value(logger, 
737                                         "build dir", 
738                                         check_path(pinfo.build_dir), 
739                                         2)
740             src.printcolors.print_value(logger, 
741                                         "install dir", 
742                                         check_path(pinfo.install_dir), 
743                                         2)
744         else:
745             logger.write("  " + 
746                          src.printcolors.printcWarning(_("no install dir")) + 
747                          "\n", 2)
748
749         src.printcolors.print_value(logger, "debug ", pinfo.debug, 2)
750         src.printcolors.print_value(logger, "verbose ", pinfo.verbose, 2)
751         src.printcolors.print_value(logger, "hpc ", pinfo.hpc, 2)
752         src.printcolors.print_value(logger, "dev ", pinfo.dev, 2)
753
754     else:
755         logger.write("\n", 2)
756         msg = _("This product does not compile")
757         logger.write("%s\n" % msg, 2)
758
759     # information on environment
760     logger.write("\n", 2)
761     logger.write(src.printcolors.printcLabel("environ :") + "\n", 2)
762     if "environ" in pinfo and "env_script" in pinfo.environ:
763         src.printcolors.print_value(logger, 
764                                     "script", 
765                                     check_path(pinfo.environ.env_script), 
766                                     2)
767
768     # display run-time environment
769     zz = src.environment.SalomeEnviron(config,
770                                        src.fileEnviron.ScreenEnviron(logger), 
771                                        False)
772     zz.set_python_libdirs()
773     zz.set_a_product(name, logger)
774     logger.write("\n", 2)
775
776
777 def show_patchs(config, logger):
778   '''Prints all the used patchs in the application.
779
780   :param config Config: the global configuration.
781   :param logger Logger: The logger instance to use for the display
782   '''
783   oneOrMore = False
784   for product in sorted(config.APPLICATION.products):
785     try:
786       product_info = src.product.get_product_config(config, product)
787       if src.product.product_has_patches(product_info):
788         oneOrMore = True
789         logger.write("%s:\n" % product, 1)
790         for i in product_info.patches:
791           logger.write(src.printcolors.printcInfo("    %s\n" % i), 1)
792     except Exception as e:
793       msg = "problem on product %s\n%s\n" % (product, str(e))
794       logger.error(msg)
795
796   if oneOrMore:
797     logger.write("\n", 1)
798   else:
799     logger.write("No patchs found\n", 1)
800
801 def check_install_system(config, logger):
802   '''Check the installation of all (declared) system products
803
804   :param config Config: the global configuration.
805   :param logger Logger: The logger instance to use for the display
806   '''
807   # get the command to use for checking the system dependencies
808   # (either rmp or apt)
809   check_cmd=src.system.get_pkg_check_cmd(config.VARS.dist_name)
810   logger.write("\nCheck the system dependencies declared in the application\n",1)
811   pkgmgr=check_cmd[0]
812   run_dep_ko=[] # list of missing run time dependencies
813   build_dep_ko=[] # list of missing compile time dependencies
814   for product in sorted(config.APPLICATION.products):
815     try:
816       product_info = src.product.get_product_config(config, product)
817       if src.product.product_is_native(product_info):
818         # if the product is native, get (in two dictionnaries the runtime and compile time 
819         # system dependencies with the status (OK/KO)
820         run_pkg,build_pkg=src.product.check_system_dep(config.VARS.dist, check_cmd, product_info)
821         #logger.write("\n*** %s ***\n" % product, 1)
822         for pkg in run_pkg:
823             logger.write("\n   - "+pkg + " : " + run_pkg[pkg], 1)
824             if "KO" in run_pkg[pkg]:
825                 run_dep_ko.append(pkg)
826         for pkg in build_pkg:
827             logger.write("\n   - "+pkg + " : " + build_pkg[pkg], 1)
828             if "KO" in build_pkg[pkg]:
829                 build_dep_ko.append(pkg)
830         #  logger.write(src.printcolors.printcInfo("    %s\n" % i), 1)
831
832     except Exception as e:
833       msg = "\nproblem with the check of system prerequisite %s\n%s\n" % (product, str(e))
834       logger.error(msg)
835       raise Exception(msg)
836
837   logger.write("\n\n",1)
838   if run_dep_ko:
839       msg="Some run time system dependencies are missing!\n"+\
840           "Please install them with %s before running salome" % pkgmgr
841       logger.warning(msg)
842       logger.write("missing run time dependencies : ",1)
843       for md in run_dep_ko: 
844         logger.write(md+" ",1)
845       logger.write("\n\n")
846         
847   if build_dep_ko:
848       msg="Some compile time system dependencies are missing!\n"+\
849           "Please install them with %s before compiling salome" % pkgmgr
850       logger.warning(msg)
851       logger.write("missing compile time dependencies : ",1)
852       for md in build_dep_ko: 
853         logger.write(md+" ",1)
854       logger.write("\n\n")
855     
856
857 def show_install_dir(config, logger):
858   '''Prints all the used installed directories in the application.
859
860   :param config Config: the global configuration.
861   :param logger Logger: The logger instance to use for the display
862   '''
863   for product in sorted(config.APPLICATION.products):
864     try:
865       product_info = src.product.get_product_config(config, product)
866       install_path=src.Path(product_info.install_dir)
867       if (src.product.product_is_native(product_info)):
868           install_path="Native"
869       elif (src.product.product_is_fixed(product_info)):
870           install_path+=" (Fixed)"
871       logger.write("%s : %s\n" % (product, install_path) , 1)
872     except Exception as e:
873       msg = "problem on product %s\n%s\n" % (product, str(e))
874       logger.error(msg)
875   logger.write("\n", 1)
876
877
878 def show_properties(config, logger):
879   '''Prints all the used properties in the application.
880
881   :param config Config: the global configuration.
882   :param logger Logger: The logger instance to use for the display
883   '''
884   if "properties" in config.APPLICATION:
885       # some properties are defined at application level, we display them
886       logger.write("Application properties:\n", 1)
887       for prop in config.APPLICATION.properties:
888           logger.write(src.printcolors.printcInfo("    %s : %s\n" % (prop, config.APPLICATION.properties[prop])), 1)
889   oneOrMore = False
890   for product in sorted(config.APPLICATION.products):
891     try:
892       product_info = src.product.get_product_config(config, product)
893       done = False
894       try:
895         for prop in product_info.properties:
896           if not done:
897             logger.write("%s:\n" % product, 1)
898             done = True
899           oneOrMore = True
900           logger.write(src.printcolors.printcInfo("    %s : %s\n" % (prop, product_info.properties[prop])), 1)
901       except Exception as e:
902         pass
903     except Exception as e:
904       # logger.write(src.printcolors.printcInfo("    %s\n" % "no properties"), 1)
905       msg = "problem on product %s\n%s\n" % (product, e)
906       logger.error(msg)
907
908   if oneOrMore:
909     logger.write("\n", 1)
910   else:
911     logger.write("No properties found\n", 1)
912
913 def print_value(config, path, show_label, logger, level=0, show_full_path=False):
914     '''Prints a value from the configuration. Prints recursively the values 
915        under the initial path.
916     
917     :param config class 'src.pyconf.Config': The configuration 
918                                              from which the value is displayed.
919     :param path str : the path in the configuration of the value to print.
920     :param show_label boolean: if True, do a basic display. 
921                                (useful for bash completion)
922     :param logger Logger: the logger instance
923     :param level int: The number of spaces to add before display.
924     :param show_full_path :
925     '''            
926     
927     # Make sure that the path does not ends with a point
928     if path.endswith('.'):
929         path = path[:-1]
930     
931     # display all the path or not
932     if show_full_path:
933         vname = path
934     else:
935         vname = path.split('.')[-1]
936
937     # number of spaces before the display
938     tab_level = "  " * level
939     
940     # call to the function that gets the value of the path.
941     try:
942         val = config.getByPath(path)
943     except Exception as e:
944         logger.write(tab_level)
945         logger.write("%s: ERROR %s\n" % (src.printcolors.printcLabel(vname), 
946                                          src.printcolors.printcError(str(e))))
947         return
948
949     # in this case, display only the value
950     if show_label:
951         logger.write(tab_level)
952         logger.write("%s: " % src.printcolors.printcLabel(vname))
953
954     # The case where the value has under values, 
955     # do a recursive call to the function
956     if dir(val).__contains__('keys'):
957         if show_label: logger.write("\n")
958         for v in sorted(val.keys()):
959             print_value(config, path + '.' + v, show_label, logger, level + 1)
960     elif val.__class__ == src.pyconf.Sequence or isinstance(val, list): 
961         # in this case, value is a list (or a Sequence)
962         if show_label: logger.write("\n")
963         index = 0
964         for v in val:
965             print_value(config, path + "[" + str(index) + "]", 
966                         show_label, logger, level + 1)
967             index = index + 1
968     else: # case where val is just a str
969         logger.write("%s\n" % val)
970
971 def get_config_children(config, args):
972     '''Gets the names of the children of the given parameter.
973        Useful only for completion mechanism
974     
975     :param config Config: The configuration where to read the values
976     :param args: The path in the config from which get the keys
977     '''
978     vals = []
979     rootkeys = config.keys()
980     
981     if len(args) == 0:
982         # no parameter returns list of root keys
983         vals = rootkeys
984     else:
985         parent = args[0]
986         pos = parent.rfind('.')
987         if pos < 0:
988             # Case where there is only on key as parameter.
989             # For example VARS
990             vals = [m for m in rootkeys if m.startswith(parent)]
991         else:
992             # Case where there is a part from a key
993             # for example VARS.us  (for VARS.user)
994             head = parent[0:pos]
995             tail = parent[pos+1:]
996             try:
997                 a = config.getByPath(head)
998                 if dir(a).__contains__('keys'):
999                     vals = map(lambda x: head + '.' + x,
1000                                [m for m in a.keys() if m.startswith(tail)])
1001             except:
1002                 pass
1003
1004     for v in sorted(vals):
1005         sys.stdout.write("%s\n" % v)
1006
1007 def description():
1008     '''method that is called when salomeTools is called with --help option.
1009     
1010     :return: The text to display for the config command description.
1011     :rtype: str
1012     '''
1013     return _("The config command allows manipulation "
1014              "and operation on config files.\n\nexample:\nsat config "
1015              "SALOME-master --info ParaView")
1016     
1017
1018 def run(args, runner, logger):
1019     '''method that is called when salomeTools is called with config parameter.
1020     '''
1021     # Parse the options
1022     (options, args) = parser.parse_args(args)
1023
1024     # Only useful for completion mechanism : print the keys of the config
1025     if options.schema:
1026         get_config_children(runner.cfg, args)
1027         return
1028
1029     # case : print a value of the config
1030     if options.value:
1031         if options.value == ".":
1032             # if argument is ".", print all the config
1033             for val in sorted(runner.cfg.keys()):
1034                 print_value(runner.cfg, val, not options.no_label, logger)
1035         else:
1036             print_value(runner.cfg, options.value, not options.no_label, logger, 
1037                         level=0, show_full_path=False)
1038     
1039     # case : print a debug value of the config
1040     if options.debug:
1041         if options.debug == ".":
1042             # if argument is ".", print all the config
1043             res = DBG.indent(DBG.getStrConfigDbg(runner.cfg))
1044             logger.write("\nConfig of application %s:\n\n%s\n" % (runner.cfg.VARS.application, res))
1045         else:
1046             if options.debug[0] == ".": # accept ".PRODUCT.etc" as "PRODUCT.etc"
1047               od = options.debug[1:]
1048             else:
1049               od = options.debug
1050             try:
1051               aCode = "a = runner.cfg.%s" % od
1052               # https://stackoverflow.com/questions/15086040/behavior-of-exec-function-in-python-2-and-python-3
1053               aDict = {"runner": runner}
1054               exec(aCode, globals(), aDict)
1055               # DBG.write("globals()", globals(), True)
1056               # DBG.write("aDict", aDict, True)
1057               res = DBG.indent(DBG.getStrConfigDbg(aDict["a"]))
1058               logger.write("\nConfig.%s of application %s:\n\n%s\n" % (od, runner.cfg.VARS.application, res))
1059             except Exception as e:
1060               msg = "\nConfig.%s of application %s: Unknown pyconf key\n" % (od, runner.cfg.VARS.application)
1061               logger.write(src.printcolors.printcError(msg), 1)
1062
1063     
1064     # case : edit user pyconf file or application file
1065     if options.edit:
1066         editor = runner.cfg.USER.editor
1067         if ('APPLICATION' not in runner.cfg and
1068                        'open_application' not in runner.cfg): # edit user pyconf
1069             usercfg =  osJoin(runner.cfg.VARS.personalDir,
1070                                    'SAT.pyconf')
1071             logger.write(_("Opening %s\n" % usercfg), 3)
1072             src.system.show_in_editor(editor, usercfg, logger)
1073         else:
1074             # search for file <application>.pyconf and open it
1075             for path in runner.cfg.PATHS.APPLICATIONPATH:
1076                 pyconf_path =  osJoin(path,
1077                                     runner.cfg.VARS.application + ".pyconf")
1078                 if os.path.exists(pyconf_path):
1079                     logger.write(_("Opening %s\n" % pyconf_path), 3)
1080                     src.system.show_in_editor(editor, pyconf_path, logger)
1081                     break
1082     
1083     # case : give information about the product(s) in parameter
1084     if options.products:
1085       if options.info is not None:
1086         logger.warning('options.products %s overrides options.info %s' % (options.products, options.info))
1087       options.info = options.products
1088
1089     if options.info:
1090       # DBG.write("products", sorted(runner.cfg.APPLICATION.products.keys()), True)
1091       src.check_config_has_application(runner.cfg)
1092       taggedProducts = src.getProductNames(runner.cfg, options.info, logger)
1093       DBG.write("tagged products", sorted(taggedProducts))
1094       for prod in sorted(taggedProducts):
1095         if prod in runner.cfg.APPLICATION.products:
1096           try:
1097             if len(taggedProducts) > 1:
1098               logger.write("#################### ", 2)
1099             show_product_info(runner.cfg, prod, logger)
1100           except Exception as e:
1101             msg = "problem on product %s\n%s\n" % (prod, str(e))
1102             logger.error(msg)
1103           # return
1104         else:
1105           msg = _("%s is not a product of %s.\n") % \
1106                 (prod, runner.cfg.VARS.application)
1107           logger.warning(msg)
1108           #raise Exception(msg)
1109     
1110     # case : copy an existing <application>.pyconf 
1111     # to ~/.salomeTools/Applications/LOCAL_<application>.pyconf
1112     if options.copy:
1113         # product is required
1114         src.check_config_has_application( runner.cfg )
1115
1116         # get application file path 
1117         source = runner.cfg.VARS.application + '.pyconf'
1118         source_full_path = ""
1119         for path in runner.cfg.PATHS.APPLICATIONPATH:
1120             # ignore personal directory
1121             if path == runner.cfg.VARS.personalDir:
1122                 continue
1123             # loop on all directories that can have pyconf applications
1124             zz =  osJoin(path, source)
1125             if os.path.exists(zz):
1126                 source_full_path = zz
1127                 break
1128
1129         if len(source_full_path) == 0:
1130             raise src.SatException(_(
1131                         "Config file for product %s not found\n") % source)
1132         else:
1133             if len(args) > 0:
1134                 # a name is given as parameter, use it
1135                 dest = args[0]
1136             elif 'copy_prefix' in runner.cfg.INTERNAL.config:
1137                 # use prefix
1138                 dest = (runner.cfg.INTERNAL.config.copy_prefix 
1139                         + runner.cfg.VARS.application)
1140             else:
1141                 # use same name as source
1142                 dest = runner.cfg.VARS.application
1143                 
1144             # the full path
1145             dest_file =  osJoin(runner.cfg.VARS.personalDir,
1146                                      'Applications', dest + '.pyconf')
1147             if os.path.exists(dest_file):
1148                 raise src.SatException(_("A personal application"
1149                                          " '%s' already exists") % dest)
1150             
1151             # perform the copy
1152             shutil.copyfile(source_full_path, dest_file)
1153             logger.write(_("%s has been created.\n") % dest_file)
1154     
1155     # case : display all the available pyconf applications
1156     if options.list:
1157         lproduct = list()
1158         # search in all directories that can have pyconf applications
1159         for path in runner.cfg.PATHS.APPLICATIONPATH:
1160             # print a header
1161             if not options.no_label:
1162                 logger.write("------ %s\n" % src.printcolors.printcHeader(path))
1163
1164             if not os.path.exists(path):
1165                 logger.write(src.printcolors.printcError(_(
1166                                             "Directory not found")) + "\n")
1167             else:
1168                 for f in sorted(os.listdir(path)):
1169                     # ignore file that does not ends with .pyconf
1170                     if not f.endswith('.pyconf'):
1171                         continue
1172
1173                     appliname = f[:-len('.pyconf')]
1174                     if appliname not in lproduct:
1175                         lproduct.append(appliname)
1176                         if path.startswith(runner.cfg.VARS.personalDir) \
1177                                     and not options.no_label:
1178                             logger.write("%s*\n" % appliname)
1179                         else:
1180                             logger.write("%s\n" % appliname)
1181                             
1182             logger.write("\n")
1183
1184     # case: print all the products name of the application (internal use for completion)
1185     if options.completion:
1186         for product_name in runner.cfg.APPLICATION.products.keys():
1187             logger.write("%s\n" % product_name)
1188         
1189     # case : give a synthetic view of all patches used in the application
1190     if options.show_patchs:
1191         src.check_config_has_application(runner.cfg)
1192         # Print some informations
1193         logger.write(_('Patchs of application %s\n') %
1194                     src.printcolors.printcLabel(runner.cfg.VARS.application), 3)
1195         logger.write("\n", 2, False)
1196         show_patchs(runner.cfg, logger)
1197
1198     # case : give a synthetic view of all install directories used in the application
1199     if options.show_install:
1200         src.check_config_has_application(runner.cfg)
1201         # Print some informations
1202         logger.write(_('Installation directories of application %s\n') %
1203                     src.printcolors.printcLabel(runner.cfg.VARS.application), 3)
1204         logger.write("\n", 2, False)
1205         show_install_dir(runner.cfg, logger)
1206
1207     # case : give a synthetic view of all patches used in the application
1208     if options.show_properties:
1209         src.check_config_has_application(runner.cfg)
1210
1211         # Print some informations
1212         logger.write(_('Properties of application %s\n') %
1213                     src.printcolors.printcLabel(runner.cfg.VARS.application), 3)
1214         logger.write("\n", 2, False)
1215         show_properties(runner.cfg, logger)
1216
1217     # check system prerequisites
1218     if options.check_system:
1219        check_install_system(runner.cfg, logger)
1220        pass