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