Salome HOME
sat #18867 : pour les url des bases git : substitution des references par leur valeur...
[tools/sat.git] / commands / config.py
index d2fc640cec2c92c907c45e4d89a90c84351182f0..6ef3dbff7dce1326f5c8b457c376dad81a44d3f3 100644 (file)
@@ -25,7 +25,13 @@ import gettext
 import pprint as PP
 
 import src
+import src.logger as LOG
 import src.debug as DBG
+import src.callerName as CALN
+
+logger = LOG.getDefaultLogger()
+
+verbose = False # True for debug
 
 # internationalization
 satdir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
@@ -47,8 +53,12 @@ parser.add_option('l', 'list', 'boolean', 'list',
     _("Optional: list all available applications."))
 parser.add_option('', 'show_patchs', 'boolean', 'show_patchs',
     _("Optional: synthetic list of all patches used in the application"))
+parser.add_option('', 'show_install', 'boolean', 'show_install',
+    _("Optional: synthetic list of all install directories in the application"))
 parser.add_option('', 'show_properties', 'boolean', 'show_properties',
     _("Optional: synthetic list of all properties used in the application"))
+parser.add_option('', 'check_system', 'boolean', 'check_system',
+    _("Optional: check if system products are installed"))
 parser.add_option('c', 'copy', 'boolean', 'copy',
     _("""Optional: copy a config file to the personal config files directory.
 WARNING: the included files are not copied.
@@ -60,6 +70,17 @@ parser.add_option('', 'completion', 'boolean', 'completion',
 parser.add_option('s', 'schema', 'boolean', 'schema',
     _("Internal use."))
 
+def osJoin(*args):
+  """
+  shortcut wrapper to os.path.join
+  plus optionaly print for debug
+  """
+  res = os.path.realpath(os.path.join(*args))
+  if verbose:
+    if True: # ".pyconf" in res:
+      logger.info("osJoin %-80s in %s" % (res, CALN.caller_name(1)))
+  return res
+
 class ConfigOpener:
     '''Class that helps to find an application pyconf 
        in all the possible directories (pathList)
@@ -70,21 +91,26 @@ class ConfigOpener:
         :param pathList list: The list of paths where to search a pyconf.
         '''
         self.pathList = pathList
+        if verbose:
+          for path in pathList:
+            if not os.path.isdir(path):
+              logger.warning("ConfigOpener inexisting directory: %s" % path)
 
     def __call__(self, name):
         if os.path.isabs(name):
             return src.pyconf.ConfigInputStream(open(name, 'rb'))
         else:
-            return src.pyconf.ConfigInputStream( 
-                        open(os.path.join( self.get_path(name), name ), 'rb') )
+            return src.pyconf.ConfigInputStream(open(osJoin(self.get_path(name), name), 'rb'))
         raise IOError(_("Configuration file '%s' not found") % name)
 
     def get_path( self, name ):
         '''The method that returns the entire path of the pyconf searched
+        returns first found in self.pathList directories
+
         :param name str: The name of the searched pyconf.
         '''
         for path in self.pathList:
-            if os.path.exists(os.path.join(path, name)):
+            if os.path.exists(osJoin(path, name)):
                 return path
         raise IOError(_("Configuration file '%s' not found") % name)
 
@@ -107,42 +133,45 @@ class ConfigManager:
         '''
         var = {}      
         var['user'] = src.architecture.get_user()
-        var['salometoolsway'] = os.path.dirname(
-                                    os.path.dirname(os.path.abspath(__file__)))
-        var['srcDir'] = os.path.join(var['salometoolsway'], 'src')
-        var['internal_dir'] = os.path.join(var['srcDir'], 'internal_config')
+        var['salometoolsway'] = os.path.dirname( os.path.dirname(os.path.abspath(__file__)))
+        var['srcDir'] =  osJoin(var['salometoolsway'], 'src')
+        var['internal_dir'] =  osJoin(var['srcDir'], 'internal_config')
         var['sep']= os.path.sep
+        if src.architecture.is_windows():
+          var['scriptExtension'] = '.bat'
+        else:
+          var['scriptExtension'] = '.sh'
         
         # datadir has a default location
-        var['datadir'] = os.path.join(var['salometoolsway'], 'data')
+        var['datadir'] =  osJoin(var['salometoolsway'], 'data')
         if datadir is not None:
             var['datadir'] = datadir
 
-        var['personalDir'] = os.path.join(os.path.expanduser('~'), '.salomeTools')
+        var['personalDir'] =  osJoin(os.path.expanduser('~'), '.salomeTools')
         src.ensure_path_exists(var['personalDir'])
 
-        var['personal_applications_dir'] = os.path.join(var['personalDir'], "Applications")
+        var['personal_applications_dir'] =  osJoin(var['personalDir'], "Applications")
         src.ensure_path_exists(var['personal_applications_dir'])
         
-        var['personal_products_dir'] = os.path.join(var['personalDir'], "products")
+        var['personal_products_dir'] =  osJoin(var['personalDir'], "products")
         src.ensure_path_exists(var['personal_products_dir'])
         
-        var['personal_archives_dir'] = os.path.join(var['personalDir'], "Archives")
+        var['personal_archives_dir'] =  osJoin(var['personalDir'], "Archives")
         src.ensure_path_exists(var['personal_archives_dir'])
 
-        var['personal_jobs_dir'] = os.path.join(var['personalDir'], "Jobs")
+        var['personal_jobs_dir'] =  osJoin(var['personalDir'], "Jobs")
         src.ensure_path_exists(var['personal_jobs_dir'])
 
-        var['personal_machines_dir'] = os.path.join(var['personalDir'], "Machines")
+        var['personal_machines_dir'] =  osJoin(var['personalDir'], "Machines")
         src.ensure_path_exists(var['personal_machines_dir'])
 
         # read linux distributions dictionary
-        distrib_cfg = src.pyconf.Config(os.path.join(var['srcDir'], 'internal_config', 'distrib.pyconf'))
+        distrib_cfg = src.pyconf.Config( osJoin(var['srcDir'], 'internal_config', 'distrib.pyconf'))
         
         # set platform parameters
         dist_name = src.architecture.get_distribution(codes=distrib_cfg.DISTRIBUTIONS)
-        dist_version = src.architecture.get_distrib_version(dist_name,  codes=distrib_cfg.VERSIONS)
-        dist_version_full = src.architecture.get_infosys()
+        dist_version = src.architecture.get_distrib_version(dist_name)
+        dist_version_full = src.architecture.get_version_XY()
         dist = dist_name + dist_version
         
         var['dist_name'] = dist_name
@@ -217,7 +246,7 @@ class ConfigManager:
         # =====================================================================
         # create VARS section
         var = self._create_vars(application=application, command=command, datadir=datadir)
-        DBG.write("create_vars", var, DBG.isDeveloper())
+        DBG.write("create_vars", var, DBG.isDeveloper())
 
         # add VARS to config
         cfg.VARS = src.pyconf.Mapping(cfg)
@@ -232,10 +261,14 @@ class ConfigManager:
         # Load INTERNAL config
         # read src/internal_config/salomeTools.pyconf
         src.pyconf.streamOpener = ConfigOpener([
-                            os.path.join(cfg.VARS.srcDir, 'internal_config')])
+                             osJoin(cfg.VARS.srcDir, 'internal_config')])
         try:
-            internal_cfg = src.pyconf.Config(open(os.path.join(cfg.VARS.srcDir, 
-                                    'internal_config', 'salomeTools.pyconf')))
+            if src.architecture.is_windows(): # special internal config for windows
+                internal_cfg = src.pyconf.Config(open( osJoin(cfg.VARS.srcDir,
+                                        'internal_config', 'salomeTools_win.pyconf')))
+            else:
+                internal_cfg = src.pyconf.Config(open( osJoin(cfg.VARS.srcDir,
+                                        'internal_config', 'salomeTools.pyconf')))
         except src.pyconf.ConfigError as e:
             raise src.SatException(_("Error in configuration file:"
                                      " salomeTools.pyconf\n  %(error)s") % \
@@ -252,7 +285,7 @@ class ConfigManager:
         # search only in the data directory
         src.pyconf.streamOpener = ConfigOpener([cfg.VARS.datadir])
         try:
-            local_cfg = src.pyconf.Config(open(os.path.join(cfg.VARS.datadir, 
+            local_cfg = src.pyconf.Config(open( osJoin(cfg.VARS.datadir,
                                                            'local.pyconf')),
                                          PWD = ('LOCAL', cfg.VARS.datadir) )
         except src.pyconf.ConfigError as e:
@@ -266,25 +299,23 @@ class ConfigManager:
 
         # When the key is "default", put the default value
         if cfg.LOCAL.base == "default":
-            cfg.LOCAL.base = os.path.abspath(
-                                        os.path.join(cfg.VARS.salometoolsway,
-                                                     "..",
-                                                     "BASE"))
+            cfg.LOCAL.base = os.path.abspath(osJoin(cfg.VARS.salometoolsway, "..", "BASE"))
         if cfg.LOCAL.workdir == "default":
-            cfg.LOCAL.workdir = os.path.abspath(
-                                        os.path.join(cfg.VARS.salometoolsway,
-                                                     ".."))
+            cfg.LOCAL.workdir = os.path.abspath(osJoin(cfg.VARS.salometoolsway, ".."))
         if cfg.LOCAL.log_dir == "default":
-            cfg.LOCAL.log_dir = os.path.abspath(
-                                        os.path.join(cfg.VARS.salometoolsway,
-                                                     "..",
-                                                     "LOGS"))
+            cfg.LOCAL.log_dir = os.path.abspath(osJoin(cfg.VARS.salometoolsway, "..", "LOGS"))
 
         if cfg.LOCAL.archive_dir == "default":
-            cfg.LOCAL.archive_dir = os.path.abspath(
-                                        os.path.join(cfg.VARS.salometoolsway,
-                                                     "..",
-                                                     "ARCHIVES"))
+            cfg.LOCAL.archive_dir = os.path.abspath( osJoin(cfg.VARS.salometoolsway, "..", "ARCHIVES"))
+
+        # if the sat tag was not set permanently by user
+        if cfg.LOCAL.tag == "unknown":
+            # get the tag with git, and store it
+            sat_version=src.system.git_describe(cfg.VARS.salometoolsway) 
+            if sat_version == False:
+                sat_version=cfg.INTERNAL.sat_version
+            cfg.LOCAL.tag=sat_version
+                
 
         # apply overwrite from command line if needed
         for rule in self.get_command_line_overrides(options, ["LOCAL"]):
@@ -301,6 +332,10 @@ class ConfigManager:
                                 "The projects definition\n")
         
         for project_pyconf_path in cfg.PROJECTS.project_file_paths:
+            if not os.path.isabs(project_pyconf_path):
+                # for a relative path (archive case) we complete with sat path
+                project_pyconf_path = os.path.join(cfg.VARS.salometoolsway,
+                                                  project_pyconf_path)
             if not os.path.exists(project_pyconf_path):
                 msg = _("WARNING: The project file %s cannot be found. "
                         "It will be ignored\n" % project_pyconf_path)
@@ -324,6 +359,12 @@ class ConfigManager:
             projects_cfg.PROJECTS.projects[project_name]=project_cfg
             projects_cfg.PROJECTS.projects[project_name]["file_path"] = \
                                                         project_pyconf_path
+            # store the project tag if any
+            product_project_git_tag = src.system.git_describe(os.path.dirname(project_pyconf_path))
+            if product_project_git_tag:
+                projects_cfg.PROJECTS.projects[project_name]["git_tag"] = product_project_git_tag
+            else:
+                projects_cfg.PROJECTS.projects[project_name]["git_tag"] = "unknown"
                    
         merger.merge(cfg, projects_cfg)
 
@@ -344,10 +385,12 @@ class ConfigManager:
         cfg.PATHS.PRODUCTPATH.append(cfg.VARS.personal_products_dir, "")
         cfg.PATHS["ARCHIVEPATH"] = src.pyconf.Sequence(cfg.PATHS)
         cfg.PATHS.ARCHIVEPATH.append(cfg.VARS.personal_archives_dir, "")
+        cfg.PATHS["ARCHIVEFTP"] = src.pyconf.Sequence(cfg.PATHS)
         cfg.PATHS["JOBPATH"] = src.pyconf.Sequence(cfg.PATHS)
         cfg.PATHS.JOBPATH.append(cfg.VARS.personal_jobs_dir, "")
         cfg.PATHS["MACHINEPATH"] = src.pyconf.Sequence(cfg.PATHS)
         cfg.PATHS.MACHINEPATH.append(cfg.VARS.personal_machines_dir, "")
+        cfg.PATHS["LICENCEPATH"] = src.pyconf.Sequence(cfg.PATHS)
 
         # initialise the path with local directory
         cfg.PATHS["ARCHIVEPATH"].append(cfg.LOCAL.archive_dir, "")
@@ -358,8 +401,10 @@ class ConfigManager:
             for PATH in ["APPLICATIONPATH",
                          "PRODUCTPATH",
                          "ARCHIVEPATH", #comment this for default archive      #8646
+                         "ARCHIVEFTP",
                          "JOBPATH",
-                         "MACHINEPATH"]:
+                         "MACHINEPATH",
+                         "LICENCEPATH"]:
                 if PATH not in cfg.PROJECTS.projects[project]:
                     continue
                 cfg.PATHS[PATH].append(cfg.PROJECTS.projects[project][PATH], "")
@@ -369,7 +414,7 @@ class ConfigManager:
             exec('cfg.' + rule) # this cannot be factorized because of the exec
 
         # AT END append APPLI_TEST directory in APPLICATIONPATH, for unittest
-        appli_test_dir = os.path.join(satdir, "test", "APPLI_TEST")
+        appli_test_dir =  osJoin(satdir, "test", "APPLI_TEST")
         if appli_test_dir not in cfg.PATHS.APPLICATIONPATH:
           cfg.PATHS.APPLICATIONPATH.append(appli_test_dir, "unittest APPLI_TEST path")
 
@@ -418,7 +463,6 @@ class ConfigManager:
         
             else:
                 cfg['open_application'] = 'yes'
-
         # =====================================================================
         # Load product config files in PRODUCTS section
         products_cfg = src.pyconf.Config()
@@ -434,6 +478,10 @@ class ConfigManager:
                 product_file_path = src.find_file_in_lpath(product_file_name, cfg.PATHS.PRODUCTPATH)
                 if product_file_path:
                     products_dir = os.path.dirname(product_file_path)
+                    # for a relative path (archive case) we complete with sat path
+                    if not os.path.isabs(products_dir):
+                        products_dir = os.path.join(cfg.VARS.salometoolsway,
+                                                    products_dir)
                     try:
                         prod_cfg = src.pyconf.Config(open(product_file_path),
                                                      PWD=("", products_dir))
@@ -477,6 +525,12 @@ class ConfigManager:
         for rule in self.get_command_line_overrides(options, ["USER"]):
             exec('cfg.' + rule) # this cannot be factorize because of the exec
         
+        # remove application products "blacklisted" in rm_products field
+        if "APPLICATION" in cfg and "rm_products" in cfg.APPLICATION:
+            for prod_to_remove in cfg.APPLICATION.rm_products:
+                cfg.APPLICATION.products.__delitem__(prod_to_remove)
+            # remove rm_products section after usage
+            cfg.APPLICATION.__delitem__("rm_products")
         return cfg
 
     def set_user_config_file(self, config):
@@ -488,8 +542,7 @@ class ConfigManager:
         '''
         # get the expected name and path of the file
         self.config_file_name = 'SAT.pyconf'
-        self.user_config_file_path = os.path.join(config.VARS.personalDir,
-                                                   self.config_file_name)
+        self.user_config_file_path =  osJoin(config.VARS.personalDir, self.config_file_name)
         
         # if pyconf does not exist, create it from scratch
         if not os.path.isfile(self.user_config_file_path): 
@@ -518,7 +571,7 @@ class ConfigManager:
             "This is the default output_verbose_level you want."
             " 0=>no output, 5=>debug.\n")
         user_cfg.USER.addMapping('publish_dir', 
-                                 os.path.join(os.path.expanduser('~'),
+                                  osJoin(os.path.expanduser('~'),
                                  'websupport', 
                                  'satreport'), 
                                  "")
@@ -534,19 +587,9 @@ class ConfigManager:
                                  'evince', 
                                  "This is the pdf_viewer used "
                                  "to read pdf documentation\n")
-# CNC 25/10/17 : plus nĂ©cessaire a priori
-#        user_cfg.USER.addMapping("base",
-#                                 src.pyconf.Reference(
-#                                            user_cfg,
-#                                            src.pyconf.DOLLAR,
-#                                            'workdir  + $VARS.sep + "BASE"'),
-#                                 "The products installation base (could be "
-#                                 "ignored if this key exists in the local.pyconf"
-#                                 " file of salomTools).\n")
-               
-        # 
+
         src.ensure_path_exists(config.VARS.personalDir)
-        src.ensure_path_exists(os.path.join(config.VARS.personalDir, 
+        src.ensure_path_exists( osJoin(config.VARS.personalDir,
                                             'Applications'))
 
         f = open(cfg_name, 'w')
@@ -601,27 +644,27 @@ def show_product_info(config, name, logger):
     pinfo = src.product.get_product_config(config, name)
     
     if "depend" in pinfo:
-        src.printcolors.print_value(logger, 
-                                    "depends on", 
-                                    ', '.join(pinfo.depend), 2)
+        src.printcolors.print_value(logger, "depends on", sorted(pinfo.depend), 2)
 
     if "opt_depend" in pinfo:
-        src.printcolors.print_value(logger, 
-                                    "optional", 
-                                    ', '.join(pinfo.opt_depend), 2)
+        src.printcolors.print_value(logger, "optional", sorted(pinfo.opt_depend), 2)
+
+    if "build_depend" in pinfo:
+        src.printcolors.print_value(logger, "build depend on", sorted(pinfo.build_depend), 2)
+
 
     # information on pyconf
     logger.write("\n", 2)
     logger.write(src.printcolors.printcLabel("configuration:") + "\n", 2)
     if "from_file" in pinfo:
-        src.printcolors.print_value(logger, 
-                                    "pyconf file path", 
-                                    pinfo.from_file, 
+        src.printcolors.print_value(logger,
+                                    "pyconf file path",
+                                    pinfo.from_file,
                                     2)
     if "section" in pinfo:
-        src.printcolors.print_value(logger, 
-                                    "section", 
-                                    pinfo.section, 
+        src.printcolors.print_value(logger,
+                                    "section",
+                                    pinfo.section,
                                     2)
 
     # information on prepare
@@ -649,9 +692,9 @@ def show_product_info(config, name, logger):
         src.printcolors.print_value(logger, "tag", pinfo.git_info.tag, 2)
 
     elif method == 'archive':
-        src.printcolors.print_value(logger, 
-                                    "get from", 
-                                    check_path(pinfo.archive_info.archive_name), 
+        src.printcolors.print_value(logger,
+                                    "get from",
+                                    check_path(pinfo.archive_info.archive_name),
                                     2)
 
     if 'patches' in pinfo:
@@ -659,7 +702,7 @@ def show_product_info(config, name, logger):
             src.printcolors.print_value(logger, "patch", check_path(patch), 2)
 
     if src.product.product_is_fixed(pinfo):
-        src.printcolors.print_value(logger, "install_dir", 
+        src.printcolors.print_value(logger, "install_dir",
                                     check_path(pinfo.install_dir), 2)
 
     if src.product.product_is_native(pinfo) or src.product.product_is_fixed(pinfo):
@@ -669,9 +712,9 @@ def show_product_info(config, name, logger):
     if src.product.product_compiles(pinfo):
         logger.write("\n", 2)
         logger.write(src.printcolors.printcLabel("compile:") + "\n", 2)
-        src.printcolors.print_value(logger, 
-                                    "compilation method", 
-                                    pinfo.build_source, 
+        src.printcolors.print_value(logger,
+                                    "compilation method",
+                                    pinfo.build_source,
                                     2)
         
         if pinfo.build_source == "script" and "compil_script" in pinfo:
@@ -700,6 +743,12 @@ def show_product_info(config, name, logger):
             logger.write("  " + 
                          src.printcolors.printcWarning(_("no install dir")) + 
                          "\n", 2)
+
+        src.printcolors.print_value(logger, "debug ", pinfo.debug, 2)
+        src.printcolors.print_value(logger, "verbose ", pinfo.verbose, 2)
+        src.printcolors.print_value(logger, "hpc ", pinfo.hpc, 2)
+        src.printcolors.print_value(logger, "dev ", pinfo.dev, 2)
+
     else:
         logger.write("\n", 2)
         msg = _("This product does not compile")
@@ -714,7 +763,8 @@ def show_product_info(config, name, logger):
                                     check_path(pinfo.environ.env_script), 
                                     2)
 
-    zz = src.environment.SalomeEnviron(config, 
+    # display run-time environment
+    zz = src.environment.SalomeEnviron(config,
                                        src.fileEnviron.ScreenEnviron(logger), 
                                        False)
     zz.set_python_libdirs()
@@ -746,6 +796,82 @@ def show_patchs(config, logger):
   else:
     logger.write("No patchs found\n", 1)
 
+def check_install_system(config, logger):
+  '''Check the installation of all (declared) system products
+
+  :param config Config: the global configuration.
+  :param logger Logger: The logger instance to use for the display
+  '''
+  # get the command to use for checking the system dependencies
+  # (either rmp or apt)
+  check_cmd=src.system.get_pkg_check_cmd(config.VARS.dist_name)
+  logger.write("\nCheck the system dependencies declared in the application\n",1)
+  pkgmgr=check_cmd[0]
+  run_dep_ko=[] # list of missing run time dependencies
+  build_dep_ko=[] # list of missing compile time dependencies
+  for product in sorted(config.APPLICATION.products):
+    try:
+      product_info = src.product.get_product_config(config, product)
+      if src.product.product_is_native(product_info):
+        # if the product is native, get (in two dictionnaries the runtime and compile time 
+        # system dependencies with the status (OK/KO)
+        run_pkg,build_pkg=src.product.check_system_dep(check_cmd, product_info)
+        #logger.write("\n*** %s ***\n" % product, 1)
+        for pkg in run_pkg:
+            logger.write("\n   - "+pkg + " : " + run_pkg[pkg], 1)
+            if "KO" in run_pkg[pkg]:
+                run_dep_ko.append(pkg)
+        for pkg in build_pkg:
+            logger.write("\n   - "+pkg + " : " + build_pkg[pkg], 1)
+            if "KO" in build_pkg[pkg]:
+                build_dep_ko.append(pkg)
+        #  logger.write(src.printcolors.printcInfo("    %s\n" % i), 1)
+
+    except Exception as e:
+      msg = "\nproblem with the check of system prerequisite %s\n%s\n" % (product, str(e))
+      logger.error(msg)
+      raise Exception(msg)
+
+  logger.write("\n\n",1)
+  if run_dep_ko:
+      msg="Some run time system dependencies are missing!\n"+\
+          "Please install them with %s before running salome" % pkgmgr
+      logger.warning(msg)
+      logger.write("missing run time dependencies : ",1)
+      for md in run_dep_ko: 
+        logger.write(md+" ",1)
+      logger.write("\n\n")
+        
+  if build_dep_ko:
+      msg="Some compile time system dependencies are missing!\n"+\
+          "Please install them with %s before compiling salome" % pkgmgr
+      logger.warning(msg)
+      logger.write("missing compile time dependencies : ",1)
+      for md in build_dep_ko: 
+        logger.write(md+" ",1)
+      logger.write("\n\n")
+    
+
+def show_install_dir(config, logger):
+  '''Prints all the used installed directories in the application.
+
+  :param config Config: the global configuration.
+  :param logger Logger: The logger instance to use for the display
+  '''
+  for product in sorted(config.APPLICATION.products):
+    try:
+      product_info = src.product.get_product_config(config, product)
+      install_path=src.Path(product_info.install_dir)
+      if (src.product.product_is_native(product_info)):
+          install_path="Native"
+      elif (src.product.product_is_fixed(product_info)):
+          install_path+=" (Fixed)"
+      logger.write("%s : %s\n" % (product, install_path) , 1)
+    except Exception as e:
+      msg = "problem on product %s\n%s\n" % (product, str(e))
+      logger.error(msg)
+  logger.write("\n", 1)
+
 
 def show_properties(config, logger):
   '''Prints all the used properties in the application.
@@ -753,18 +879,23 @@ def show_properties(config, logger):
   :param config Config: the global configuration.
   :param logger Logger: The logger instance to use for the display
   '''
+  if "properties" in config.APPLICATION:
+      # some properties are defined at application level, we display them
+      logger.write("Application properties:\n", 1)
+      for prop in config.APPLICATION.properties:
+          logger.write(src.printcolors.printcInfo("    %s : %s\n" % (prop, config.APPLICATION.properties[prop])), 1)
   oneOrMore = False
   for product in sorted(config.APPLICATION.products):
     try:
       product_info = src.product.get_product_config(config, product)
       done = False
       try:
-        for i in product_info.properties:
+        for prop in product_info.properties:
           if not done:
             logger.write("%s:\n" % product, 1)
             done = True
           oneOrMore = True
-          logger.write(src.printcolors.printcInfo("    %s\n" % i), 1)
+          logger.write(src.printcolors.printcInfo("    %s : %s\n" % (prop, product_info.properties[prop])), 1)
       except Exception as e:
         pass
     except Exception as e:
@@ -914,9 +1045,18 @@ def run(args, runner, logger):
               od = options.debug[1:]
             else:
               od = options.debug
-            exec("a = runner.cfg.%s" % od)
-            res = DBG.indent(DBG.getStrConfigDbg(a))
-            logger.write("\nConfig.%s of application %s:\n\n%s\n" % (od, runner.cfg.VARS.application, res))
+            try:
+              aCode = "a = runner.cfg.%s" % od
+              # https://stackoverflow.com/questions/15086040/behavior-of-exec-function-in-python-2-and-python-3
+              aDict = {"runner": runner}
+              exec(aCode, globals(), aDict)
+              # DBG.write("globals()", globals(), True)
+              # DBG.write("aDict", aDict, True)
+              res = DBG.indent(DBG.getStrConfigDbg(aDict["a"]))
+              logger.write("\nConfig.%s of application %s:\n\n%s\n" % (od, runner.cfg.VARS.application, res))
+            except Exception as e:
+              msg = "\nConfig.%s of application %s: Unknown pyconf key\n" % (od, runner.cfg.VARS.application)
+              logger.write(src.printcolors.printcError(msg), 1)
 
     
     # case : edit user pyconf file or application file
@@ -924,14 +1064,14 @@ def run(args, runner, logger):
         editor = runner.cfg.USER.editor
         if ('APPLICATION' not in runner.cfg and
                        'open_application' not in runner.cfg): # edit user pyconf
-            usercfg = os.path.join(runner.cfg.VARS.personalDir, 
+            usercfg =  osJoin(runner.cfg.VARS.personalDir,
                                    'SAT.pyconf')
             logger.write(_("Opening %s\n" % usercfg), 3)
             src.system.show_in_editor(editor, usercfg, logger)
         else:
             # search for file <application>.pyconf and open it
             for path in runner.cfg.PATHS.APPLICATIONPATH:
-                pyconf_path = os.path.join(path, 
+                pyconf_path =  osJoin(path,
                                     runner.cfg.VARS.application + ".pyconf")
                 if os.path.exists(pyconf_path):
                     logger.write(_("Opening %s\n" % pyconf_path), 3)
@@ -979,7 +1119,7 @@ def run(args, runner, logger):
             if path == runner.cfg.VARS.personalDir:
                 continue
             # loop on all directories that can have pyconf applications
-            zz = os.path.join(path, source)
+            zz =  osJoin(path, source)
             if os.path.exists(zz):
                 source_full_path = zz
                 break
@@ -1000,7 +1140,7 @@ def run(args, runner, logger):
                 dest = runner.cfg.VARS.application
                 
             # the full path
-            dest_file = os.path.join(runner.cfg.VARS.personalDir, 
+            dest_file =  osJoin(runner.cfg.VARS.personalDir,
                                      'Applications', dest + '.pyconf')
             if os.path.exists(dest_file):
                 raise src.SatException(_("A personal application"
@@ -1053,12 +1193,26 @@ def run(args, runner, logger):
         logger.write("\n", 2, False)
         show_patchs(runner.cfg, logger)
 
+    # case : give a synthetic view of all install directories used in the application
+    if options.show_install:
+        src.check_config_has_application(runner.cfg)
+        # Print some informations
+        logger.write(_('Installation directories of application %s\n') %
+                    src.printcolors.printcLabel(runner.cfg.VARS.application), 3)
+        logger.write("\n", 2, False)
+        show_install_dir(runner.cfg, logger)
+
     # case : give a synthetic view of all patches used in the application
     if options.show_properties:
         src.check_config_has_application(runner.cfg)
+
         # Print some informations
         logger.write(_('Properties of application %s\n') %
                     src.printcolors.printcLabel(runner.cfg.VARS.application), 3)
         logger.write("\n", 2, False)
         show_properties(runner.cfg, logger)
 
+    # check system prerequisites
+    if options.check_system:
+       check_install_system(runner.cfg, logger)
+       pass