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