]> SALOME platform Git repositories - tools/sat.git/commitdiff
Salome HOME
merge with master
authorcrouzet <nicolas.crouzet@cea.fr>
Fri, 21 Dec 2018 09:35:50 +0000 (10:35 +0100)
committercrouzet <nicolas.crouzet@cea.fr>
Fri, 21 Dec 2018 09:35:50 +0000 (10:35 +0100)
1  2 
commands/config.py
commands/package.py
data/templates/PythonComponent8/src/PYCMPGUI/PYCMPGUI.py
src/__init__.py
src/product.py

diff --combined commands/config.py
index a5fd2d8e38218986da00f5fc0dcfbde660030786,8b4859f2572a63f848d60f2e908f10db36b27b83..15f7ce6f9fee847af126303184ddc0f1f9906382
  #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
  
  import os
 +import sys
  import platform
  import datetime
  import shutil
  import gettext
 -import sys
 +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__)))
@@@ -57,8 -50,8 +57,8 @@@ parser.add_option('', 'show_properties'
      _("Optional: synthetic list of all properties used in the application"))
  parser.add_option('c', 'copy', 'boolean', 'copy',
      _("""Optional: copy a config file to the personal config files directory.
 -\tWARNING the included files are not copied.
 -\tIf a name is given the new config file takes the given name."""))
 +WARNING: the included files are not copied.
 +If a name is given the new config file takes the given name."""))
  parser.add_option('n', 'no_label', 'boolean', 'no_label',
      _("Internal use: do not print labels, Works only with --value and --list."))
  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()))
 +  return res
 +
  class ConfigOpener:
      '''Class that helps to find an application pyconf 
         in all the possible directories (pathList)
          :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)
  
@@@ -129,47 -106,56 +129,47 @@@ 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
          
          # 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_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 = dist_name + dist_version
          
          var['dist_name'] = dist_name
          var['dist_version'] = dist_version
          var['dist'] = dist
 +        var['dist_ref'] = dist_name + dist_version_full
          var['python'] = src.architecture.get_python_version()
  
          var['nb_proc'] = src.architecture.get_nb_proc()
          
          # =====================================================================
          # create VARS section
 -        var = self._create_vars(application=application, command=command, 
 -                                datadir=datadir)
 +        var = self._create_vars(application=application, command=command, datadir=datadir)
 +        # DBG.write("create_vars", var, DBG.isDeveloper())
 +
          # add VARS to config
          cfg.VARS = src.pyconf.Mapping(cfg)
          for variable in var:
          # 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_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:"
          # 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:
  
          # 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"))
  
          # apply overwrite from command line if needed
          for rule in self.get_command_line_overrides(options, ["LOCAL"]):
          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)
              for PATH in ["APPLICATIONPATH",
                           "PRODUCTPATH",
                           "ARCHIVEPATH", #comment this for default archive     #8646
+                          "ARCHIVEFTP",
                           "JOBPATH",
                           "MACHINEPATH"]:
                  if PATH not in cfg.PROJECTS.projects[project]:
              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")
  
          '''
          # 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): 
              "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'), 
                                   "")
  #                                 "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')
@@@ -609,23 -609,27 +611,23 @@@ def show_product_info(config, name, log
      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)
  
      # 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
          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:
              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):
      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:
                                      check_path(pinfo.environ.env_script), 
                                      2)
  
 -    zz = src.environment.SalomeEnviron(config, 
 +    zz = src.environment.SalomeEnviron(config,
                                         src.fileEnviron.ScreenEnviron(logger), 
                                         False)
      zz.set_python_libdirs()
@@@ -918,18 -922,9 +920,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
          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)
              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
                  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"
diff --combined commands/package.py
index a54408bd6308e607d8d4f7f0b32d55a46b05b598,4c24033f999711f0831508b4115bdb9360306f81..7b424939fd4cbb39a1d5a39e74cda2e0f93446e9
@@@ -134,7 -134,7 +134,7 @@@ def add_files(tar, name_archive, d_cont
  
      for name in names:
          # display information
 -        len_points = max_len - len(name)
 +        len_points = max_len - len(name) + 3
          local_path, archive_path = d_content[name]
          in_archive = os.path.join(name_archive, archive_path)
          logger.write(name + " " + len_points * "." + " "+ in_archive + " ", 3)
@@@ -197,7 -197,8 +197,7 @@@ def produce_relative_launcher(config
          bin_kernel_install_dir = os.path.join(kernel_root_dir,"bin","salome") 
  
      # check if the application contains an application module
 -    l_product_info = src.product.get_products_infos(config.APPLICATION.products.keys(),
 -                                                    config)
 +    l_product_info = src.product.get_products_infos(config.APPLICATION.products.keys(), config)
      salome_application_name="Not defined" 
      for prod_name, prod_info in l_product_info:
          # look for a salome application
@@@ -487,6 -488,12 +487,12 @@@ def binary_package(config, logger, opti
      l_not_installed = []
      l_sources_not_present = []
      generate_mesa_launcher = False  # a flag to know if we generate a mesa launcher
+     if ("APPLICATION" in config  and
+         "properties"  in config.APPLICATION  and
+         "mesa_launcher_in_package"    in config.APPLICATION.properties  and
+         config.APPLICATION.properties.mesa_launcher_in_package == "yes") :
+             generate_mesa_launcher=True
      for prod_name, prod_info in l_product_info:
          # skip product with property not_in_package set to yes
          if src.get_property_in_product_cfg(prod_info, "not_in_package") == "yes":
              else:
                  l_sources_not_present.append(prod_name)
  
-         # if at least one of the application products has the "is_mesa" property
-         if src.get_property_in_product_cfg(prod_info, "is_mesa") == "yes":
-             generate_mesa_launcher = True  # we will generate a mesa launcher
          # ignore the native and fixed products for install directories
          if (src.product.product_is_native(prod_info) 
                  or src.product.product_is_fixed(prod_info)
@@@ -1277,7 -1280,8 +1279,7 @@@ def run(args, runner, logger)
                                                      runner.cfg.VARS.application), 1)
          
          # Get the default directory where to put the packages
 -        package_default_path = os.path.join(runner.cfg.APPLICATION.workdir,
 -                                            "PACKAGE")
 +        package_default_path = os.path.join(runner.cfg.APPLICATION.workdir, "PACKAGE")
          src.ensure_path_exists(package_default_path)
          
      # if the package contains a project:
                  break
  
          if foundProject is None:
 -            local_path = os.path.join(runner.cfg.VARS.salometoolsway,
 -                                     "data",
 -                                     "local.pyconf")
 +            local_path = os.path.join(runner.cfg.VARS.salometoolsway, "data", "local.pyconf")
              msg = _("""ERROR: the project %(1)s is not visible by salomeTools.
  known projects are:
  %(2)s
@@@ -1450,9 -1456,9 +1452,9 @@@ Please add it in file
              d_files_to_add[file_name] = (file_path, file_name)
  
      logger.write("\n", 2)
 -
      logger.write(src.printcolors.printcLabel(_("Actually do the package")), 2)
      logger.write("\n", 2)
 +    logger.write("\nfiles and directories to add:\n%s\n\n" % PP.pformat(d_files_to_add), 5)
  
      res = 0
      try:
          logger.write(_("\n"), 1)
          return 1
      
 +    # case if no application, only package sat as 'sat package -t'
 +    try:
 +        app = runner.cfg.APPLICATION
 +    except:
 +        app = None
 +
      # unconditionaly remove the tmp_local_working_dir
 -    tmp_local_working_dir = os.path.join(runner.cfg.APPLICATION.workdir, "tmp_package")
 -    if os.path.isdir(tmp_local_working_dir):
 -      shutil.rmtree(tmp_local_working_dir)
 +    if app is not None:
 +        tmp_local_working_dir = os.path.join(app.workdir, "tmp_package")
 +        if os.path.isdir(tmp_local_working_dir):
 +            shutil.rmtree(tmp_local_working_dir)
  
      # have to decide some time
      DBG.tofix("make shutil.rmtree('%s') effective" % tmp_working_dir, "", DBG.isDeveloper())
index 0000000000000000000000000000000000000000,f33ec81be3cf7e3e87acb1facfb5944186d6b4d8..71a4e0f7e726be2eea9513f22f9fc8e9fe9665c5
mode 000000,100755..100755
--- /dev/null
@@@ -1,0 -1,256 +1,256 @@@
 -    if not moduleDesktop.has_key( studyID ):
+ #  Copyright (C) 2007-2010  CEA/DEN, EDF R&D, OPEN CASCADE
+ #
+ #  Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
+ #  CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
+ #
+ #  This library is free software; you can redistribute it and/or
+ #  modify it under the terms of the GNU Lesser General Public
+ #  License as published by the Free Software Foundation; either
+ #  version 2.1 of the License.
+ #
+ #  This library is distributed in the hope that it will be useful,
+ #  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ #  Lesser General Public License for more details.
+ #
+ #  You should have received a copy of the GNU Lesser General Public
+ #  License along with this library; if not, write to the Free Software
+ #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ #
+ #  See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+ #
+ # ---
+ # File   : :sat:{PYCMP}GUI.py
+ # Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com)
+ # ---
+ #
+ import traceback
+ import string
+ import os
+ import sys
+ from qtsalome import *
+ #from :sat:{PYCMP}_utils import *
+ import salome
+ from Controller import Controller
+ from TreeWidget import TreeWidget
+ from :sat:{PYCMP}Desktop import :sat:{PYCMP}Desktop
+ # Get SALOME PyQt interface
+ import SalomePyQt
+ import libSALOME_Swig
+ ########################################################
+ # Global variables
+ ########################################################
+ sgPyQt = SalomePyQt.SalomePyQt()
+ sg = libSALOME_Swig.SALOMEGUI_Swig()
+ sgDesktop = sgPyQt.getDesktop()
+ widgetDialogBox = None
+ objectsManager = Controller( None )
+ moduleDesktop   = {}
+ currentDesktop = None
+ CURVE_MENU_ID = 1000
+ ADVANCED_MENU_ID = 1001
+ POLYLINE_ID = 1002
+ CIRCLE_ID = 1003
+ DEL_ALL_ID = 1004
+ ########################################################
+ # Internal methods
+ ########################################################
+ def getStudyId():
+     """This method returns the active study ID"""
+     return sgPyQt.getStudyId()
+ def getStudy():
+     """This method returns the active study"""
+     studyId = _getStudyId()
+     study = getStudyManager().GetStudyByID( studyId )
+     return study
+ def getDesktop():
+     """This method returns the current :sat:{PYCMP} desktop"""
+     global currentDesktop
+     return currentDesktop
+ def setDesktop( studyID ):
+     """This method sets and returns :sat:{PYCMP} desktop"""
+     global moduleDesktop, currentDesktop, objectsManager
 -    if dict_command.has_key( commandID ):
++    if not studyID in moduleDesktop:
+         moduleDesktop[studyID] = :sat:{PYCMP}Desktop( sgPyQt, sg )
+         objectsManager = Controller( moduleDesktop[studyID] )
+         moduleDesktop[studyID].setController( objectsManager )
+         pass
+     currentDesktop = moduleDesktop[studyID]
+     return currentDesktop
+ def incObjToMap( m, id ):
+     """This method incrementes the object counter in the map"""
+     if id not in m: m[id] = 0
+     m[id] += 1
+     pass
+ def getSelection():
+     """This method analyses selection"""
+     selcount = sg.SelectedCount()
+     seltypes = {}
+     for i in range( selcount ):
+         incObjToMap( seltypes, getObjectID( getStudy(), sg.getSelected( i ) ) )
+         pass
+     return selcount, seltypes
+ ################################################
+ # Callback functions
+ ################################################
+ def initialize():
+     """This method is called when module is initialized. It performs initialization actions"""
+     setDesktop( getStudyId() )
+     pass
+ def windows():
+     """This method is called when module is initialized. It returns a map of popup windows to be used by the module"""
+     wm = {}
+     wm[SalomePyQt.WT_ObjectBrowser] = Qt.LeftDockWidgetArea
+     wm[SalomePyQt.WT_PyConsole]     = Qt.BottomDockWidgetArea
+     return wm
+ def views():
+     """This method is called when module is initialized. It returns a list of 2D/3D views to be used by the module"""
+     return []
+ def createPreferences():
+     """This method is called when module is initialized. It exports module preferences"""
+     pass
+ def activate():
+     """This method is called when module is initialized. It returns True if activating is successfull, False otherwise"""
+     global moduleDesktop, sgPyQt, widgetDialogBox
+     widgetDialogBox = QDockWidget( sgDesktop )
+     moduleDesktop[getStudyId()].createActions()
+     moduleDesktop[getStudyId()].createMenus()
+     moduleDesktop[getStudyId()].createToolBars()
+     moduleDesktop[getStudyId()].createPopups()
+     moduleDesktop[getStudyId()].getDockGlobalTree().show()
+     moduleDesktop[getStudyId()].getGlobalGraphicsView().show()
+     sgPyQt.activateView( moduleDesktop[getStudyId()].getGlobalGraphicsViewID() )
+     return True
+ def viewTryClose( wid ):
+     sgPyQt.setViewClosable(wid, True)
+     pass
+ def deactivate():
+     """This method is called when module is deactivated"""
+     global moduleDesktop, widgetDialogBox
+     widgetDialogBox.close()
+     moduleDesktop[getStudyId()].getDockGlobalTree().hide()
+     moduleDesktop[getStudyId()].updateGlobalGraphicsView( None )
+     moduleDesktop[getStudyId()].getGlobalGraphicsView().hide()
+     pass
+ def activeStudyChanged( studyID ):
+     """This method is called when active study is changed"""
+     setDesktop( getStudyId() )
+     pass
+ def createPopupMenu( popup, context ):
+     """This method is called when popup menu is invocked"""
+     pass
+ def OnGUIEvent( commandID ):
+     """This method is called when a GUI action is activated"""
++    if commandID in dict_command:
+        dict_command[commandID]()
+        pass
+     pass
+ def preferenceChanged( section, setting ):
+     """This method is called when module's preferences are changed"""
+     pass
+ def activeViewChanged( viewID ):
+     """This method is called when active view is changed"""
+     pass
+ def viewCloned( viewID ):
+     """This method is called when active view is cloned"""
+     pass
+ def viewClosed( viewID ):
+     """This method is called when active view viewClosed"""
+     pass
+ def engineIOR():
+     """This method is called when study is opened. It returns engine IOR"""
+     return getEngineIOR()
+ ################################################
+ # GUI actions implementation
+ ################################################
+ def showCreatePolylineDialog() :
+     from CreatePolylineDialog import CreatePolylineDialog
+     global widgetDialogBox
+     widgetDialogBox = QDockWidget( sgDesktop )
+     myDialog = CreatePolylineDialog( "www.cea.fr", objectsManager, widgetDialogBox )
+     widgetDialogBox.setWidget( myDialog )
+     widgetDialogBox.setWindowTitle( "Polyline definition" )
+     sgDesktop.addDockWidget(Qt.LeftDockWidgetArea, widgetDialogBox)
+     pass
+ def showCreateCircleDialog() :
+     from CreateCircleDialog import CreateCircleDialog
+     global widgetDialogBox
+     widgetDialogBox = QDockWidget( sgDesktop )
+     myDialog = CreateCircleDialog( "www.cea.fr", objectsManager, widgetDialogBox )
+     widgetDialogBox.setWidget( myDialog )
+     widgetDialogBox.setWindowTitle( "Circle definition" )
+     sgDesktop.addDockWidget(Qt.LeftDockWidgetArea, widgetDialogBox)
+     pass
+ def deleteAll() :
+     models = moduleDesktop[getStudyId()].getController().getModels()
+     if len( models ) == 0 : return
+     answer = QMessageBox.question( moduleDesktop[getStudyId()], 'Confirmation', 'Do you really want to delete all the existing objects ?' , QMessageBox.Yes | QMessageBox.No )
+     if answer == QMessageBox.Yes :
+        for model in models :
+           moduleDesktop[getStudyId()].getController().removeModel( model )
+           pass
+        pass
+     pass
+ ########################################################
+ # Commands dictionary
+ ########################################################
+ dict_command = { POLYLINE_ID : showCreatePolylineDialog,
+                  CIRCLE_ID   : showCreateCircleDialog,
+                  DEL_ALL_ID : deleteAll
+                 }
+ ########################################################
diff --combined src/__init__.py
index e1696dbfd0f46aadee33ab685559c34258d83468,2a4a7420b9bc057174b0812b0205594caf432ff2..e24085b207a950b248de18793aba7e9bfa0ed5a9
@@@ -26,7 -26,7 +26,8 @@@ import shuti
  import errno
  import stat
  import fnmatch
 +import pprint as PP
+ from ftplib import FTP
  
  from . import pyconf
  from . import architecture
@@@ -110,24 -110,6 +111,24 @@@ def get_cfg_param(config, param_name, d
          return config[param_name]
      return default
  
 +def strSplitN(aList, nb, skip="\n     "):
 +    """
 +    example
 +    aStr = 'this-is-a-string'
 +    splitN(aStr, 2, '-')
 +    split it by every 2nd '-' rather than every '-'
 +    """
 +    strValue = ""
 +    i = 0
 +    for v in aList:
 +      strValue += "%15s, " % str(v)
 +      i += 1
 +      if i >= nb:
 +        strValue += skip
 +        i = 0
 +    if len(aList) > nb:
 +        strValue = skip + strValue
 +    return strValue
  
  def getProductNames(cfg, wildcards, logger):
      """get products names using * or ? as wildcards like shell Linux"""
        wilds = wildcards
      else:
        wilds = [wildcards]
 +    notFound = {}
      products = cfg.APPLICATION.products.keys()
 -    for prod in products:
 -      for wild in wildcards:
 +    for wild in wildcards:
 +      ok = False
 +      for prod in products:
          filtered = fnmatch.filter([prod], wild)
          # print("filtered", prod, wild, filtered)
          if len(filtered) > 0:
            res.append(prod)
 +          ok = True
            break
 +      if not ok:
 +        notFound[wild] = None
      if len(res) == 0:
        logger.warning("Empty list of products, from %s" % wilds)
 +    if len(notFound.keys()) > 0:
 +      strProd = strSplitN( sorted(products), 5)
 +      logger.warning("products not found: %s\n  availables products are:\n%s" % \
 +                     (sorted(notFound.keys()), strProd) )
      return res
  
  
@@@ -403,6 -376,48 +404,48 @@@ def find_file_in_lpath(file_name, lpath
                  return os.path.join(dir_complete, file_name)
      return False
  
+ def find_file_in_ftppath(file_name, ftppath, installation_dir, logger):
+     """\
+     Find in all ftp servers in ftppath the file called file_name
+     If it is found then return the destination path of the file
+     (the place where the file was downloaded"
+     else return False.
+     
+     :param file_name str: The file name to search
+     :param ftppath, List: The list of ftp servers where to search
+     :param installation_dir str: The name of the installation directory
+     :return: the full path of the file or False if not found
+     :param logger Logger: The logging instance to use for the prints.
+     :rtype: str
+     """
+     destination=os.path.join(installation_dir, file_name)
+     for ftp_archive in ftppath:
+        try:
+            # ftp_archive has the form ftp.xxx.yyy/dir1/dir2/...
+            ftp_archive_split=ftp_archive.split("/")
+            ftp_server=ftp_archive_split[0]
+            ftp = FTP(ftp_server)
+            logger.write("   Connect to ftp server %s\n" % ftp_server, 3)
+            ftp.login()
+            for directory in ftp_archive_split[1:]:
+                logger.write("   Change directory to %s\n" % directory, 3)
+                ftp.cwd(directory)
+        except:
+            logger.error("while connecting to ftp server %s\n" % ftp_server)
+        try:
+            if ftp.size(file_name) > 0:
+                # if file exists and is non empty
+                with open(destination,'wb') as dest_file:
+                    ftp.retrbinary("RETR "+file_name, dest_file.write)
+                logger.write("   Archive %s was retrieved and stored in %s\n" % (file_name, destination), 3)
+                return destination
+        except:
+            logger.error("File not found in ftp_archive %s\n" % ftp_server)
+            pass
+     return False
  def handleRemoveReadonly(func, path, exc):
      excvalue = exc[1]
      if func in (os.rmdir, os.remove) and excvalue.errno == errno.EACCES:
diff --combined src/product.py
index 6c8c12caae9a8875482e4686112bae77202efacf,04ade2e8a6ad1e021eaa6fc2537cdc0c4019e44c..627e82a1cfa468b2d9018a7c62e103b6f8cf6c7a
@@@ -182,33 -182,23 +182,23 @@@ Please add a section in it.""") % {"1" 
              prod_info.addMapping("archive_info",
                                   src.pyconf.Mapping(prod_info),
                                   "")
-         if "archive_name" not in prod_info.archive_info: 
+         if "archive_name" in prod_info.archive_info: 
+             arch_name = prod_info.archive_info.archive_name
+         else:
+             # standard name
              arch_name = product_name + "-" + version + ".tar.gz"
-             arch_path = src.find_file_in_lpath(arch_name,
-                                                config.PATHS.ARCHIVEPATH)
-             if not arch_path:
-                 msg = _("Archive %(1)s for %(2)s not found in config.PATHS.ARCHIVEPATH") % \
-                        {"1" : arch_name, "2" : prod_info.name}
-                 DBG.tofix(msg, config.PATHS.ARCHIVEPATH)
-                 prod_info.archive_info.archive_name = arch_name #without path
-                 # raise src.SatException(msg) #may be a warning, continue #8646
-             else:
-                 prod_info.archive_info.archive_name = arch_path
+         arch_path = src.find_file_in_lpath(arch_name,
+                                            config.PATHS.ARCHIVEPATH)
+         if not arch_path:
+             # arch_path is not found. It may generate an error in sat source,
+             #                         unless the archive is found in ftp serveur
+             msg = _("Archive %(1)s for %(2)s not found in config.PATHS.ARCHIVEPATH") % \
+                    {"1" : arch_name, "2" : prod_info.name}
+             DBG.tofix(msg, config.PATHS.ARCHIVEPATH)
+             prod_info.archive_info.archive_name = arch_name #without path
          else:
-             if (os.path.basename(prod_info.archive_info.archive_name) == 
-                                         prod_info.archive_info.archive_name):
-                 arch_name = prod_info.archive_info.archive_name
-                 arch_path = src.find_file_in_lpath(
-                                             arch_name,
-                                             config.PATHS.ARCHIVEPATH)
-                 if not arch_path:
-                     msg = _("Archive %(1)s for %(2)s not found in config.PATHS.ARCHIVEPATH") % \
-                            {"1" : arch_name, "2" : prod_info.name}
-                     DBG.tofix(msg, config.PATHS.ARCHIVEPATH) #avoid 2 messages in compile
-                     prod_info.archive_info.archive_name = arch_name #without path
-                     # raise src.SatException(msg) #may be a warning, continue #8646
-                 else:
-                     prod_info.archive_info.archive_name = arch_path
+             prod_info.archive_info.archive_name = arch_path
  
          
      # If the product compiles with a script, check the script existence
@@@ -480,23 -470,19 +470,23 @@@ def add_compile_config_file(p_info, con
      with open(aFile, 'w') as f:
        res.__save__(f)
  
 -    # this file is for human eye reading
 +    # this file is not mandatory, is for human eye reading
      aFile = os.path.join(p_info.install_dir, PRODUCT_FILENAME)
 -    with open(aFile, 'w') as f:
 -      # f.write(DBG.getStrConfigDbg(p_info)) # debug mode
 -      try:
 -          p_info.__save__(f, evaluated=True) # evaluated expressions mode
 -      except:
 -          # the second file is not mandatory. In the context of non VCS archives, p_info cannot be evaluated
 -          # because information on git server is not available.  In this case, we skip the writing without raising an error.
 -          #DBG.write("cannot evaluate product info - do not write ", PRODUCT_FILENAME, True)
 -          pass
 -          
 -
 +    try:
 +      with open(aFile, 'w') as f:
 +        p_info.__save__(f, evaluated=True) # evaluated expressions mode
 +    except:
 +      DBG.write("cannot evaluate product info - problem in file %s" % aFile, p_info, True)
 +      # write DBG mode, as no problem if evaluation not possible
 +      msg = """\
 +# Some informations cannot be evaluated.
 +# for example:
 +# In the context of non VCS archives, information on git server is not available.
 +  
 +"""
 +      with open(aFile, 'w') as f:
 +        f.write(msg)
 +        f.write(DBG.getStrConfigDbg(p_info))
  
  def check_config_exists(config, prod_dir, prod_info, verbose=False):
      """\