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