Salome HOME
Merge branch 'eap/23514'
[modules/kernel.git] / bin / salomeContext.py
index cb580069388fa384bca46f1365d67b9dade68733..b67209092e82ac095f26d1542f5f7af2d06a5fed 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (C) 2013-2014  CEA/DEN, EDF R&D, OPEN CASCADE
+# Copyright (C) 2013-2017  CEA/DEN, EDF R&D, OPEN CASCADE
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -23,7 +23,6 @@ import logging
 import ConfigParser
 
 from parseConfigFile import parseConfigFile
-from parseConfigFile import convertEnvFileToConfigFile
 
 import tempfile
 import pickle
@@ -33,87 +32,105 @@ import platform
 from salomeContextUtils import SalomeContextException
 
 def usage():
-  #exeName = os.path.splitext(os.path.basename(__file__))[0]
-
   msg = '''\
-Usage: salome [command] [options] [--config=file1,...,filen]
+Usage: salome [command] [options] [--config=<file,folder,...>]
 
 Commands:
-    start         Launches SALOME virtual application [DEFAULT]
-    shell         Executes a script under SALOME application environment
-    connect       Connects a Python console to the active SALOME session
-    killall       Kill all SALOME running sessions
-    info          Display some information about SALOME
-    help          Show this message
-    coffee        Yes! SALOME can also make coffee!!"
-
-Use salome start --help or salome shell --help
-to show help on start and shell commands.
+=========
+    start           Start a new SALOME instance.
+    context         Initialize SALOME context. Current environment is extended.
+    shell           Initialize SALOME context, attached to the last created SALOME
+                    instance if any, and executes scripts passed as command arguments.
+                    User works in a Shell terminal. SALOME environment is set but
+                    application is not started.
+    connect         Connect a Python console to the active SALOME instance.
+    kill <port(s)>  Terminate SALOME instances running on given ports for current user.
+                    Port numbers must be separated by blank characters.
+    killall         Terminate *all* SALOME running instances for current user.
+                    Do not start a new one.
+    test            Run SALOME tests.
+    info            Display some information about SALOME.
+    doc <module(s)> Show online module documentation (if available).
+                    Module names must be separated by blank characters.
+    help            Show this message.
+
+If no command is given, default is start.
+
+Command options:
+================
+    Use salome <command> --help to show help on command. Available for the
+    following commands: start, shell, connect, test, info.
+
+--config=<file,folder,...>
+==========================
+    Initialize SALOME context from a list of context files and/or a list
+    of folders containing context files. The list is comma-separated, whithout
+    any blank characters.
 '''
 
   print msg
 #
 
 """
-The SalomeContext class in an API to configure SALOME environment then
+The SalomeContext class in an API to configure SALOME context then
 start SALOME using a single python command.
 
 """
 class SalomeContext:
   """
-  Initialize environment from a list of configuration files
+  Initialize context from a list of configuration files
   identified by their names.
-  These files should be in appropriate (new .cfg) format.
-  However you can give old .sh environment files; in this case,
-  the SalomeContext class will try to automatically convert them
-  to .cfg format before setting the environment.
+  These files should be in appropriate .cfg format.
   """
   def __init__(self, configFileNames=0):
-    #it could be None explicitely (if user use multiples setVariable...for standalone)
+    self.getLogger().setLevel(logging.INFO)
+    #it could be None explicitly (if user use multiples setVariable...for standalone)
     if configFileNames is None:
        return
     configFileNames = configFileNames or []
     if len(configFileNames) == 0:
       raise SalomeContextException("No configuration files given")
 
-    reserved=['PATH', 'DYLD_LIBRARY_PATH', 'LD_LIBRARY_PATH', 'PYTHONPATH', 'MANPATH', 'PV_PLUGIN_PATH']
+    reserved=['PATH', 'DYLD_FALLBACK_LIBRARY_PATH', 'DYLD_LIBRARY_PATH', 'LD_LIBRARY_PATH', 'PYTHONPATH', 'MANPATH', 'PV_PLUGIN_PATH', 'INCLUDE', 'LIBPATH', 'SALOME_PLUGINS_PATH', 'LIBRARY_PATH', 'QT_PLUGIN_PATH']
     for filename in configFileNames:
       basename, extension = os.path.splitext(filename)
       if extension == ".cfg":
-        self.__setEnvironmentFromConfigFile(filename, reserved)
-      elif extension == ".sh":
-        #new convert procedures, temporary could be use not to be automatically deleted
-        #temp = tempfile.NamedTemporaryFile(suffix='.cfg', delete=False)
-        temp = tempfile.NamedTemporaryFile(suffix='.cfg')
-        try:
-          convertEnvFileToConfigFile(filename, temp.name, reserved)
-          self.__setEnvironmentFromConfigFile(temp.name, reserved)
-          temp.close()
-        except (ConfigParser.ParsingError, ValueError) as e:
-          self.getLogger().error("Invalid token found when parsing file: %s\n"%(filename))
-          temp.close()
-          sys.exit(1)
+        self.__setContextFromConfigFile(filename, reserved)
       else:
-        self.getLogger().warning("Unrecognized extension for configuration file: %s", filename)
+        self.getLogger().error("Unrecognized extension for configuration file: %s", filename)
+  #
+
+  def __loadEnvModules(self, env_modules):
+    print("Trying to load env modules: %s..." % ' '.join(env_modules))
+    try:
+      out, err = subprocess.Popen(["modulecmd", "python", "load"] + env_modules, stdout=subprocess.PIPE).communicate()
+      exec(out) # define specific environment variables
+      print("OK")
+    except:
+      print("** Failed **")
+      pass
   #
 
   def runSalome(self, args):
+    import os
     # Run this module as a script, in order to use appropriate Python interpreter
-    # according to current path (initialized from environment files).
-#    kill = False
-#    for e in args:
-#      if "--shutdown-server" in e:
-#        kill = True
-#        args.remove(e)
+    # according to current path (initialized from context files).
+    env_modules_option = "--with-env-modules="
+    env_modules_l = [x for x in args if x.startswith(env_modules_option)]
+    if env_modules_l:
+      env_modules = env_modules_l[-1][len(env_modules_option):].split(',')
+      self.__loadEnvModules(env_modules)
+      args = [x for x in args if not x.startswith(env_modules_option)]
+    else:
+      env_modules = os.getenv("SALOME_ENV_MODULES", None)
+      if env_modules:
+        self.__loadEnvModules(env_modules.split(','))
 
-    import os
     absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
     env_copy = os.environ.copy()
     proc = subprocess.Popen(['python', os.path.join(absoluteAppliPath,"bin","salome","salomeContext.py"), pickle.dumps(self), pickle.dumps(args)], shell=False, close_fds=True, env=env_copy)
-    msg = proc.communicate()
- #   if kill:
- #     self._killAll(args)
-    return msg, proc.returncode
+    out, err = proc.communicate()
+    return out, err, proc.returncode
   #
 
   """Append value to PATH environment variable"""
@@ -123,7 +140,15 @@ class SalomeContext:
 
   """Append value to LD_LIBRARY_PATH environment variable"""
   def addToLdLibraryPath(self, value):
-    self.addToVariable('LD_LIBRARY_PATH', value)
+    if platform.system() == 'Windows':
+      self.addToVariable('PATH', value)
+    elif platform.system() == 'Darwin':
+      if "LAPACK" in value:
+        self.addToVariable('DYLD_FALLBACK_LIBRARY_PATH', value)
+      else:
+        self.addToVariable('DYLD_LIBRARY_PATH', value)
+    else:
+      self.addToVariable('LD_LIBRARY_PATH', value)
   #
 
   """Append value to DYLD_LIBRARY_PATH environment variable"""
@@ -140,11 +165,11 @@ class SalomeContext:
   def setVariable(self, name, value, overwrite=False):
     env = os.getenv(name, '')
     if env and not overwrite:
-      self.getLogger().warning("Environment variable already existing (and not overwritten): %s=%s", name, value)
+      self.getLogger().error("Environment variable already existing (and not overwritten): %s=%s", name, value)
       return
 
     if env:
-      self.getLogger().warning("Overwriting environment variable: %s=%s", name, value)
+      self.getLogger().debug("Overwriting environment variable: %s=%s", name, value)
 
     value = os.path.expandvars(value) # expand environment variables
     self.getLogger().debug("Set environment variable: %s=%s", name, value)
@@ -183,13 +208,18 @@ class SalomeContext:
     options = args[1:]
 
     availableCommands = {
-      'start' :   '_runAppli',
-      'shell' :   '_runSession',
+      'start'   : '_runAppli',
+      'context' : '_setContext',
+      'shell'   : '_runSession',
       'connect' : '_runConsole',
-      'killall':  '_killAll',
-      'info':     '_showInfo',
-      'help':     '_usage',
-      'coffee' :  '_makeCoffee'
+      'kill'    : '_kill',
+      'killall' : '_killAll',
+      'test'    : '_runTests',
+      'info'    : '_showInfo',
+      'doc'     : '_showDoc',
+      'help'    : '_usage',
+      'coffee'  : '_makeCoffee',
+      'car'     : '_getCar',
       }
 
     if not command in availableCommands.keys():
@@ -201,17 +231,20 @@ class SalomeContext:
 
   """
   Run SALOME!
-  Args consist in a mandatory command followed by optionnal parameters.
+  Args consist in a mandatory command followed by optional parameters.
   See usage for details on commands.
   """
   def _startSalome(self, args):
+    import os
+    import sys
     try:
-      import os
+      from setenv import add_path
       absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
-      import sys
-      path = os.path.join(absoluteAppliPath, "bin", "salome")
-      if not path in sys.path:
-        sys.path[:0] = [path]
+      path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome"))
+      add_path(path, "PYTHONPATH")
+      path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome", "appliskel"))
+      add_path(path, "PYTHONPATH")
+
     except:
       pass
 
@@ -221,73 +254,59 @@ class SalomeContext:
     if command is None:
       if args and args[0] in ["-h","--help","help"]:
         usage()
-        sys.exit(0)
+        return 0
       # try to default to "start" command
       command = "_runAppli"
 
     try:
       res = getattr(self, command)(options) # run appropriate method
-      return res or (None, None)
-    except SystemExit, returncode:
-      if returncode != 0:
-        self.getLogger().warning("SystemExit %s in method %s.", returncode, command)
-      sys.exit(returncode)
+      return res or 0
+    except SystemExit as ex:
+      if ex.code != 0:
+        self.getLogger().error("SystemExit %s in method %s.", ex.code, command)
+      return ex.code
     except StandardError:
       self.getLogger().error("Unexpected error:")
       import traceback
       traceback.print_exc()
-      sys.exit(1)
+      return 1
     except SalomeContextException, e:
       self.getLogger().error(e)
-      sys.exit(1)
+      return 1
   #
 
-  def __setEnvironmentFromConfigFile(self, filename, reserved=None):
+  def __setContextFromConfigFile(self, filename, reserved=None):
     if reserved is None:
       reserved = []
     try:
       unsetVars, configVars, reservedDict = parseConfigFile(filename, reserved)
     except SalomeContextException, e:
       msg = "%s"%e
-      file_dir = os.path.dirname(filename)
-      file_base = os.path.basename(filename)
-      base_no_ext, ext = os.path.splitext(file_base)
-      sh_file = os.path.join(file_dir, base_no_ext+'.sh')
-      if ext == ".cfg" and os.path.isfile(sh_file):
-        msg += "Found similar %s file; trying to parse this one instead..."%(base_no_ext+'.sh')
-        temp = tempfile.NamedTemporaryFile(suffix='.cfg')
-        try:
-          convertEnvFileToConfigFile(sh_file, temp.name, reserved)
-          self.__setEnvironmentFromConfigFile(temp.name, reserved)
-          msg += "OK\n"
-          self.getLogger().warning(msg)
-          temp.close()
-          return
-        except (ConfigParser.ParsingError, ValueError) as e:
-          msg += "Invalid token found when parsing file: %s\n"%(sh_file)
-          self.getLogger().error(msg)
-          temp.close()
-          sys.exit(1)
-      else:
-        self.getLogger().error(msg)
-        sys.exit(1)
+      self.getLogger().error(msg)
+      return 1
 
     # unset variables
     for var in unsetVars:
       self.unsetVariable(var)
 
-    # set environment
+    # set context
     for reserved in reservedDict:
       a = filter(None, reservedDict[reserved]) # remove empty elements
+      a = [ os.path.realpath(x) for x in a ]
       reformattedVals = os.pathsep.join(a)
-      self.addToVariable(reserved, reformattedVals)
+      if reserved in ["INCLUDE", "LIBPATH"]:
+        self.addToVariable(reserved, reformattedVals, separator=' ')
+      else:
+        self.addToVariable(reserved, reformattedVals)
       pass
 
     for key,val in configVars:
       self.setVariable(key, val, overwrite=True)
       pass
 
-    sys.path[:0] = os.getenv('PYTHONPATH','').split(os.pathsep)
+    pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
+    pythonpath = [ os.path.realpath(x) for x in pythonpath ]
+    sys.path[:0] = pythonpath
   #
 
   def _runAppli(self, args=None):
@@ -296,10 +315,32 @@ class SalomeContext:
     # Initialize SALOME environment
     sys.argv = ['runSalome'] + args
     import setenv
-    setenv.main(True)
+    setenv.main(True, exeName="salome start")
 
     import runSalome
     runSalome.runSalome()
+    return 0
+  #
+
+  def _setContext(self, args=None):
+    salome_context_set = os.getenv("SALOME_CONTEXT_SET")
+    if salome_context_set:
+      print "***"
+      print "*** SALOME context has already been set."
+      print "*** Enter 'exit' (only once!) to leave SALOME context."
+      print "***"
+      return 0
+
+    os.environ["SALOME_CONTEXT_SET"] = "yes"
+    print "***"
+    print "*** SALOME context is now set."
+    print "*** Enter 'exit' (only once!) to leave SALOME context."
+    print "***"
+
+    cmd = ["/bin/bash"]
+    proc = subprocess.Popen(cmd, shell=False, close_fds=True)
+    proc.communicate()
+    return proc.returncode
   #
 
   def _runSession(self, args=None):
@@ -307,7 +348,7 @@ class SalomeContext:
       args = []
     sys.argv = ['runSession'] + args
     import runSession
-    params, args = runSession.configureSession(args)
+    params, args = runSession.configureSession(args, exe="salome shell")
 
     sys.argv = ['runSession'] + args
     import setenv
@@ -320,23 +361,39 @@ class SalomeContext:
     if args is None:
       args = []
     # Initialize SALOME environment
-    sys.argv = ['runConsole'] + args
+    sys.argv = ['runConsole']
     import setenv
     setenv.main(True)
 
-    cmd = ["python", "-c", "import runConsole\nrunConsole.connect()" ]
-    proc = subprocess.Popen(cmd, shell=False, close_fds=True)
-    return proc.communicate()
+    import runConsole
+    return runConsole.connect(args)
   #
 
-  def _killAll(self, args=None):
+  def _kill(self, args=None):
     if args is None:
       args = []
+    ports = args
+    if not ports:
+      print "Port number(s) not provided to command: salome kill <port(s)>"
+      return 1
+
+    from multiprocessing import Process
+    from killSalomeWithPort import killMyPort
+    import tempfile
+    for port in ports:
+      with tempfile.NamedTemporaryFile():
+        p = Process(target = killMyPort, args=(port,))
+        p.start()
+        p.join()
+    return 0
+  #
+
+  def _killAll(self, unused=None):
     try:
       import PortManager # mandatory
       from multiprocessing import Process
       from killSalomeWithPort import killMyPort
-      ports = PortManager.getBusyPorts()
+      ports = PortManager.getBusyPorts()['this']
 
       if ports:
         import tempfile
@@ -346,22 +403,138 @@ class SalomeContext:
             p.start()
             p.join()
     except ImportError:
+      # :TODO: should be declared obsolete
       from killSalome import killAllPorts
       killAllPorts()
       pass
+    return 0
+  #
+
+  def _runTests(self, args=None):
+    if args is None:
+      args = []
+    sys.argv = ['runTests']
+    import setenv
+    setenv.main(True)
 
+    import runTests
+    return runTests.runTests(args, exe="salome test")
   #
 
+  def _showSoftwareVersions(self, softwares=None):
+    config = ConfigParser.SafeConfigParser()
+    absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
+    filename = os.path.join(absoluteAppliPath, "sha1_collections.txt")
+    versions = {}
+    max_len = 0
+    with open(filename) as f:
+      for line in f:
+        try:
+          software, version, sha1 = line.split()
+          versions[software.upper()] = version
+          if len(software) > max_len:
+            max_len = len(software)
+        except:
+          pass
+        pass
+      pass
+    if softwares:
+      for soft in softwares:
+        if versions.has_key(soft.upper()):
+          print soft.upper().rjust(max_len), versions[soft.upper()]
+    else:
+      import collections
+      od = collections.OrderedDict(sorted(versions.items()))
+      for name, version in od.iteritems():
+        print name.rjust(max_len), versions[name]
+    pass
+
   def _showInfo(self, args=None):
-    print "Running with python", platform.python_version()
-    self._runAppli(["--version"])
+    if args is None:
+      args = []
+
+    usage = "Usage: salome info [options]"
+    epilog  = """\n
+Display some information about SALOME.\n
+Available options are:
+    -p,--ports                     Show the list of busy ports (running SALOME instances).
+    -s,--softwares [software(s)]   Show the list and versions of SALOME softwares.
+                                   Software names must be separated by blank characters.
+                                   If no software is given, show version of all softwares.
+    -v,--version                   Show running SALOME version.
+    -h,--help                      Show this message.
+"""
+    if not args:
+      args = ["--version"]
+
+    if "-h" in args or "--help" in args:
+      print usage + epilog
+      return 0
+
+    if "-p" in args or "--ports" in args:
+      import PortManager
+      ports = PortManager.getBusyPorts()
+      this_ports = ports['this']
+      other_ports = ports['other']
+      if this_ports or other_ports:
+          print "SALOME instances are running on the following ports:"
+          if this_ports:
+              print "   This application:", this_ports
+          else:
+              print "   No SALOME instances of this application"
+          if other_ports:
+              print "   Other applications:", other_ports
+          else:
+              print "   No SALOME instances of other applications"
+      else:
+          print "No SALOME instances are running"
+
+    if "-s" in args or "--softwares" in args:
+      if "-s" in args:
+        index = args.index("-s")
+      else:
+        index = args.index("--softwares")
+      indexEnd=index+1
+      while indexEnd < len(args) and args[indexEnd][0] != "-":
+        indexEnd = indexEnd + 1
+      self._showSoftwareVersions(softwares=args[index+1:indexEnd])
+
+    if "-v" in args or "--version" in args:
+      print "Running with python", platform.python_version()
+      return self._runAppli(["--version"])
+
+    return 0
   #
 
+  def _showDoc(self, args=None):
+    if args is None:
+      args = []
+
+    modules = args
+    if not modules:
+      print "Module(s) not provided to command: salome doc <module(s)>"
+      return 1
+
+    appliPath = os.getenv("ABSOLUTE_APPLI_PATH")
+    if not appliPath:
+      raise SalomeContextException("Unable to find application path. Please check that the variable ABSOLUTE_APPLI_PATH is set.")
+    baseDir = os.path.join(appliPath, "share", "doc", "salome")
+    for module in modules:
+      docfile = os.path.join(baseDir, "gui", module.upper(), "index.html")
+      if not os.path.isfile(docfile):
+        docfile = os.path.join(baseDir, "tui", module.upper(), "index.html")
+      if not os.path.isfile(docfile):
+        docfile = os.path.join(baseDir, "dev", module.upper(), "index.html")
+      if os.path.isfile(docfile):
+        out, err = subprocess.Popen(["xdg-open", docfile]).communicate()
+      else:
+        print "Online documentation is not accessible for module:", module
+
   def _usage(self, unused=None):
     usage()
   #
 
-  def _makeCoffee(self, args=None):
+  def _makeCoffee(self, unused=None):
     print "                        ("
     print "                          )     ("
     print "                   ___...(-------)-....___"
@@ -372,9 +545,9 @@ class SalomeContext:
     print "       |  |    |                             |"
     print "        \\  \\   |                             |"
     print "         `\\ `\\ |                             |"
-    print "           `\\ `|                             |"
-    print "           _/ /\\                             /"
-    print "          (__/  \\                           /"
+    print "           `\\ `|            SALOME           |"
+    print "           _/ /\\            4 EVER           /"
+    print "          (__/  \\             <3            /"
     print "       _..---\"\"` \\                         /`\"\"---.._"
     print "    .-\'           \\                       /          \'-."
     print "   :               `-.__             __.-\'              :"
@@ -384,7 +557,42 @@ class SalomeContext:
     print "       \'._     \"\"\"----.....______.....----\"\"\"     _.\'"
     print "          `\"\"--..,,_____            _____,,..--\"\"`"
     print "                        `\"\"\"----\"\"\"`"
-    sys.exit(0)
+    print ""
+    print "                    SALOME is working for you; what else?"
+    print ""
+  #
+
+  def _getCar(self, unused=None):
+    print "                                              _____________"
+    print "                                  ..---:::::::-----------. ::::;;."
+    print "                               .\'\"\"\"\"\"\"                  ;;   \\  \":."
+    print "                            .\'\'                          ;     \\   \"\\__."
+    print "                          .\'                            ;;      ;   \\\\\";"
+    print "                        .\'                              ;   _____;   \\\\/"
+    print "                      .\'                               :; ;\"     \\ ___:\'."
+    print "                    .\'--...........................    : =   ____:\"    \\ \\"
+    print "               ..-\"\"                               \"\"\"\'  o\"\"\"     ;     ; :"
+    print "          .--\"\"  .----- ..----...    _.-    --.  ..-\"     ;       ;     ; ;"
+    print "       .\"\"_-     \"--\"\"-----\'\"\"    _-\"        .-\"\"         ;        ;    .-."
+    print "    .\'  .\'   SALOME             .\"         .\"              ;       ;   /. |"
+    print "   /-./\'         4 EVER <3    .\"          /           _..  ;       ;   ;;;|"
+    print "  :  ;-.______               /       _________==.    /_  \\ ;       ;   ;;;;"
+    print "  ;  / |      \"\"\"\"\"\"\"\"\"\"\".---.\"\"\"\"\"\"\"          :    /\" \". |;       ; _; ;;;"
+    print " /\"-/  |                /   /                  /   /     ;|;      ;-\" | ;\';"
+    print ":-  :   \"\"\"----______  /   /              ____.   .  .\"\'. ;;   .-\"..T\"   ."
+    print "\'. \"  ___            \"\":   \'\"\"\"\"\"\"\"\"\"\"\"\"\"\"    .   ; ;    ;; ;.\" .\"   \'--\""
+    print " \",   __ \"\"\"  \"\"---... :- - - - - - - - - \' \'  ; ;  ;    ;;\"  .\""
+    print "  /. ;  \"\"\"---___                             ;  ; ;     ;|.\"\""
+    print " :  \":           \"\"\"----.    .-------.       ;   ; ;     ;:"
+    print "  \\  \'--__               \\   \\        \\     /    | ;     ;;"
+    print "   \'-..   \"\"\"\"---___      :   .______..\\ __/..-\"\"|  ;   ; ;"
+    print "       \"\"--..       \"\"\"--\"        m l s         .   \". . ;"
+    print "             \"\"------...                  ..--\"\"      \" :"
+    print "                        \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"    \\        /"
+    print "                                               \"------\""
+    print ""
+    print "                                Drive your simulation properly with SALOME!"
+    print ""
   #
 
   # Add the following two methods since logger is not pickable
@@ -413,11 +621,8 @@ if __name__ == "__main__":
     context = pickle.loads(sys.argv[1])
     args = pickle.loads(sys.argv[2])
 
-    (out, err) = context._startSalome(args)
-    if out:
-      sys.stdout.write(out)
-    if err:
-      sys.stderr.write(err)
+    status = context._startSalome(args)
+    sys.exit(status)
   else:
     usage()
 #