]> SALOME platform Git repositories - modules/yacs.git/blob - bin/salomeContext.py
Salome HOME
do not read .sh environment files anymore
[modules/yacs.git] / bin / salomeContext.py
1 # Copyright (C) 2013-2016  CEA/DEN, EDF R&D, OPEN CASCADE
2 #
3 # This library is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU Lesser General Public
5 # License as published by the Free Software Foundation; either
6 # version 2.1 of the License, or (at your option) any later version.
7 #
8 # This library is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 # Lesser General Public License for more details.
12 #
13 # You should have received a copy of the GNU Lesser General Public
14 # License along with this library; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16 #
17 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 #
19
20 import os
21 import sys
22 import logging
23 import ConfigParser
24
25 from parseConfigFile import parseConfigFile
26
27 import tempfile
28 import pickle
29 import subprocess
30 import platform
31
32 from salomeContextUtils import SalomeContextException
33
34 def usage():
35   msg = '''\
36 Usage: salome [command] [options] [--config=<file,folder,...>]
37
38 Commands:
39 =========
40     start           Start a new SALOME instance.
41     context         Initialize SALOME context. Current environment is extended.
42     shell           Initialize SALOME context, attached to the last created SALOME
43                     instance if any, and executes scripts passed as command arguments.
44                     User works in a Shell terminal; SALOME environment is set but
45                     application is not started.
46     connect         Connect a Python console to the active SALOME instance.
47     kill <port(s)>  Terminate SALOME instances running on given ports for current user.
48                     Port numbers must be separated by blank characters.
49     killall         Terminate *all* SALOME running instances for current user.
50                     Do not start a new one.
51     test            Run SALOME tests.
52     info            Display some information about SALOME.
53     doc <module(s)> Show online module documentation (if available).
54                     Module names must be separated by blank characters.
55     help            Show this message.
56
57 If no command is given, default to start.
58
59 Command options:
60 ================
61     Use salome <command> --help to show help on command ; available for commands:
62     start, shell, connect, test, info.
63
64 --config=<file,folder,...>
65 ==========================
66     Initialize SALOME context from a list of context files and/or a list
67     of folders containing context files. The list is comma-separated, whithout
68     any blank characters.
69 '''
70
71   print msg
72 #
73
74 """
75 The SalomeContext class in an API to configure SALOME context then
76 start SALOME using a single python command.
77
78 """
79 class SalomeContext:
80   """
81   Initialize context from a list of configuration files
82   identified by their names.
83   These files should be in appropriate .cfg format.
84   """
85   def __init__(self, configFileNames=0):
86     self.getLogger().setLevel(logging.INFO)
87     #it could be None explicitely (if user use multiples setVariable...for standalone)
88     if configFileNames is None:
89        return
90     configFileNames = configFileNames or []
91     if len(configFileNames) == 0:
92       raise SalomeContextException("No configuration files given")
93
94     reserved=['PATH', 'DYLD_LIBRARY_PATH', 'LD_LIBRARY_PATH', 'PYTHONPATH', 'MANPATH', 'PV_PLUGIN_PATH', 'INCLUDE', 'LIBPATH', 'SALOME_PLUGINS_PATH']
95     for filename in configFileNames:
96       basename, extension = os.path.splitext(filename)
97       if extension == ".cfg":
98         self.__setContextFromConfigFile(filename, reserved)
99       else:
100         self.getLogger().warning("Unrecognized extension for configuration file: %s", filename)
101   #
102
103   def __loadMPI(self, module_name):
104     print "Trying to load MPI module: %s..."%module_name,
105     try:
106       out, err = subprocess.Popen(["modulecmd", "python", "load", module_name], stdout=subprocess.PIPE).communicate()
107       exec out # define specific environment variables
108       print " OK"
109     except:
110       print " ** Failed **"
111       pass
112   #
113
114   def runSalome(self, args):
115     import os
116     # Run this module as a script, in order to use appropriate Python interpreter
117     # according to current path (initialized from context files).
118     mpi_module_option = "--with-mpi-module="
119     mpi_module = [x for x in args if x.startswith(mpi_module_option)]
120     if mpi_module:
121       mpi_module = mpi_module[0][len(mpi_module_option):]
122       self.__loadMPI(mpi_module)
123       args = [x for x in args if not x.startswith(mpi_module_option)]
124     else:
125       mpi_module = os.getenv("SALOME_MPI_MODULE_NAME", None)
126       if mpi_module:
127         self.__loadMPI(mpi_module)
128
129     absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
130     env_copy = os.environ.copy()
131     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)
132     out, err = proc.communicate()
133     return out, err, proc.returncode
134   #
135
136   """Append value to PATH environment variable"""
137   def addToPath(self, value):
138     self.addToVariable('PATH', value)
139   #
140
141   """Append value to LD_LIBRARY_PATH environment variable"""
142   def addToLdLibraryPath(self, value):
143     if platform.system() == 'Windows':
144       self.addToVariable('PATH', value)
145     else:
146       self.addToVariable('LD_LIBRARY_PATH', value)
147   #
148
149   """Append value to DYLD_LIBRARY_PATH environment variable"""
150   def addToDyldLibraryPath(self, value):
151     self.addToVariable('DYLD_LIBRARY_PATH', value)
152   #
153
154   """Append value to PYTHONPATH environment variable"""
155   def addToPythonPath(self, value):
156     self.addToVariable('PYTHONPATH', value)
157   #
158
159   """Set environment variable to value"""
160   def setVariable(self, name, value, overwrite=False):
161     env = os.getenv(name, '')
162     if env and not overwrite:
163       self.getLogger().warning("Environment variable already existing (and not overwritten): %s=%s", name, value)
164       return
165
166     if env:
167       self.getLogger().warning("Overwriting environment variable: %s=%s", name, value)
168
169     value = os.path.expandvars(value) # expand environment variables
170     self.getLogger().debug("Set environment variable: %s=%s", name, value)
171     os.environ[name] = value
172   #
173
174   """Unset environment variable"""
175   def unsetVariable(self, name):
176     if os.environ.has_key(name):
177       del os.environ[name]
178   #
179
180   """Append value to environment variable"""
181   def addToVariable(self, name, value, separator=os.pathsep):
182     if value == '':
183       return
184
185     value = os.path.expandvars(value) # expand environment variables
186     self.getLogger().debug("Add to %s: %s", name, value)
187     env = os.getenv(name, None)
188     if env is None:
189       os.environ[name] = value
190     else:
191       os.environ[name] = value + separator + env
192   #
193
194   ###################################
195   # This begins the private section #
196   ###################################
197
198   def __parseArguments(self, args):
199     if len(args) == 0 or args[0].startswith("-"):
200       return None, args
201
202     command = args[0]
203     options = args[1:]
204
205     availableCommands = {
206       'start'   : '_runAppli',
207       'context' : '_setContext',
208       'shell'   : '_runSession',
209       'connect' : '_runConsole',
210       'kill'    : '_kill',
211       'killall' : '_killAll',
212       'test'    : '_runTests',
213       'info'    : '_showInfo',
214       'doc'     : '_showDoc',
215       'help'    : '_usage',
216       'coffee'  : '_makeCoffee',
217       'car'     : '_getCar',
218       }
219
220     if not command in availableCommands.keys():
221       command = "start"
222       options = args
223
224     return availableCommands[command], options
225   #
226
227   """
228   Run SALOME!
229   Args consist in a mandatory command followed by optionnal parameters.
230   See usage for details on commands.
231   """
232   def _startSalome(self, args):
233     import os
234     import sys
235     try:
236       from setenv import add_path
237       absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
238       path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome"))
239       add_path(path, "PYTHONPATH")
240       path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome", "appliskel"))
241       add_path(path, "PYTHONPATH")
242
243     except:
244       pass
245
246     command, options = self.__parseArguments(args)
247     sys.argv = options
248
249     if command is None:
250       if args and args[0] in ["-h","--help","help"]:
251         usage()
252         sys.exit(0)
253       # try to default to "start" command
254       command = "_runAppli"
255
256     try:
257       res = getattr(self, command)(options) # run appropriate method
258       return res or (None, None)
259     except SystemExit, returncode:
260       if returncode != 0:
261         self.getLogger().warning("SystemExit %s in method %s.", returncode, command)
262       sys.exit(returncode)
263     except StandardError:
264       self.getLogger().error("Unexpected error:")
265       import traceback
266       traceback.print_exc()
267       sys.exit(1)
268     except SalomeContextException, e:
269       self.getLogger().error(e)
270       sys.exit(1)
271   #
272
273   def __setContextFromConfigFile(self, filename, reserved=None):
274     if reserved is None:
275       reserved = []
276     try:
277       unsetVars, configVars, reservedDict = parseConfigFile(filename, reserved)
278     except SalomeContextException, e:
279       msg = "%s"%e
280       self.getLogger().error(msg)
281       sys.exit(1)
282
283     # unset variables
284     for var in unsetVars:
285       self.unsetVariable(var)
286
287     # set context
288     for reserved in reservedDict:
289       a = filter(None, reservedDict[reserved]) # remove empty elements
290       a = [ os.path.realpath(x) for x in a ]
291       reformattedVals = os.pathsep.join(a)
292       if reserved in ["INCLUDE", "LIBPATH"]:
293         self.addToVariable(reserved, reformattedVals, separator=' ')
294       else:
295         self.addToVariable(reserved, reformattedVals)
296       pass
297
298     for key,val in configVars:
299       self.setVariable(key, val, overwrite=True)
300       pass
301
302     pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
303     pythonpath = [ os.path.realpath(x) for x in pythonpath ]
304     sys.path[:0] = pythonpath
305   #
306
307   def _runAppli(self, args=None):
308     if args is None:
309       args = []
310     # Initialize SALOME environment
311     sys.argv = ['runSalome'] + args
312     import setenv
313     setenv.main(True, exeName="salome start")
314
315     import runSalome
316     runSalome.runSalome()
317   #
318
319   def _setContext(self, args=None):
320     salome_context_set = os.getenv("SALOME_CONTEXT_SET")
321     if salome_context_set:
322       print "***"
323       print "*** SALOME context has already been set."
324       print "*** Enter 'exit' (only once!) to leave SALOME context."
325       print "***"
326       return
327
328     os.environ["SALOME_CONTEXT_SET"] = "yes"
329     print "***"
330     print "*** SALOME context is now set."
331     print "*** Enter 'exit' (only once!) to leave SALOME context."
332     print "***"
333
334     cmd = ["/bin/bash"]
335     proc = subprocess.Popen(cmd, shell=False, close_fds=True)
336     return proc.communicate()
337   #
338
339   def _runSession(self, args=None):
340     if args is None:
341       args = []
342     sys.argv = ['runSession'] + args
343     import runSession
344     params, args = runSession.configureSession(args, exe="salome shell")
345
346     sys.argv = ['runSession'] + args
347     import setenv
348     setenv.main(True)
349
350     return runSession.runSession(params, args)
351   #
352
353   def _runConsole(self, args=None):
354     if args is None:
355       args = []
356     # Initialize SALOME environment
357     sys.argv = ['runConsole']
358     import setenv
359     setenv.main(True)
360
361     import runConsole
362     return runConsole.connect(args)
363   #
364
365   def _kill(self, args=None):
366     if args is None:
367       args = []
368     ports = args
369     if not ports:
370       print "Port number(s) not provided to command: salome kill <port(s)>"
371       return
372
373     from multiprocessing import Process
374     from killSalomeWithPort import killMyPort
375     import tempfile
376     for port in ports:
377       with tempfile.NamedTemporaryFile():
378         p = Process(target = killMyPort, args=(port,))
379         p.start()
380         p.join()
381     pass
382   #
383
384   def _killAll(self, unused=None):
385     try:
386       import PortManager # mandatory
387       from multiprocessing import Process
388       from killSalomeWithPort import killMyPort
389       ports = PortManager.getBusyPorts()
390
391       if ports:
392         import tempfile
393         for port in ports:
394           with tempfile.NamedTemporaryFile():
395             p = Process(target = killMyPort, args=(port,))
396             p.start()
397             p.join()
398     except ImportError:
399       # :TODO: should be declared obsolete
400       from killSalome import killAllPorts
401       killAllPorts()
402       pass
403   #
404
405   def _runTests(self, args=None):
406     if args is None:
407       args = []
408     sys.argv = ['runTests']
409     import setenv
410     setenv.main(True)
411
412     import runTests
413     return runTests.runTests(args, exe="salome test")
414   #
415
416   def _showSoftwareVersions(self, softwares=None):
417     config = ConfigParser.SafeConfigParser()
418     absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
419     filename = os.path.join(absoluteAppliPath, "sha1_collections.txt")
420     versions = {}
421     max_len = 0
422     with open(filename) as f:
423       for line in f:
424         try:
425           software, version, sha1 = line.split()
426           versions[software.upper()] = version
427           if len(software) > max_len:
428             max_len = len(software)
429         except:
430           pass
431         pass
432       pass
433     if softwares:
434       for soft in softwares:
435         if versions.has_key(soft.upper()):
436           print soft.upper().rjust(max_len), versions[soft.upper()]
437     else:
438       for name, version in versions.items():
439         print name.rjust(max_len), versions[name]
440     pass
441
442   def _showInfo(self, args=None):
443     if args is None:
444       args = []
445
446     usage = "Usage: salome info [options]"
447     epilog  = """\n
448 Display some information about SALOME.\n
449 Available options are:
450     -p,--ports        Show the list of busy ports (running SALOME instances).
451     -s,--softwares    Show the list and versions of SALOME softwares.
452     -v,--version      Show running SALOME version.
453     -h,--help         Show this message.
454 """
455     if not args:
456       args = ["--version"]
457
458     if "-h" in args or "--help" in args:
459       print usage + epilog
460       return
461
462     if "-p" in args or "--ports" in args:
463       import PortManager
464       ports = PortManager.getBusyPorts()
465       print "SALOME instances are running on ports:", ports
466       if ports:
467         print "Last started instance on port %s"%ports[-1]
468
469     if "-s" in args or "--softwares" in args:
470       self._showSoftwareVersions()
471
472     if "-v" in args or "--version" in args:
473       print "Running with python", platform.python_version()
474       self._runAppli(["--version"])
475   #
476
477   def _showDoc(self, args=None):
478     if args is None:
479       args = []
480
481     modules = args
482     if not modules:
483       print "Module(s) not provided to command: salome doc <module(s)>"
484       return
485
486     appliPath = os.getenv("ABSOLUTE_APPLI_PATH")
487     if not appliPath:
488       raise SalomeContextException("Unable to find application path. Please check that the variable ABSOLUTE_APPLI_PATH is set.")
489     baseDir = os.path.join(appliPath, "share", "doc", "salome")
490     for module in modules:
491       docfile = os.path.join(baseDir, "gui", module.upper(), "index.html")
492       if not os.path.isfile(docfile):
493         docfile = os.path.join(baseDir, "tui", module.upper(), "index.html")
494       if not os.path.isfile(docfile):
495         docfile = os.path.join(baseDir, "dev", module.upper(), "index.html")
496       if os.path.isfile(docfile):
497         out, err = subprocess.Popen(["xdg-open", docfile]).communicate()
498       else:
499         print "Online documentation is not accessible for module:", module
500
501   def _usage(self, unused=None):
502     usage()
503   #
504
505   def _makeCoffee(self, unused=None):
506     print "                        ("
507     print "                          )     ("
508     print "                   ___...(-------)-....___"
509     print "               .-\"\"       )    (          \"\"-."
510     print "         .-\'``\'|-._             )         _.-|"
511     print "        /  .--.|   `\"\"---...........---\"\"`   |"
512     print "       /  /    |                             |"
513     print "       |  |    |                             |"
514     print "        \\  \\   |                             |"
515     print "         `\\ `\\ |                             |"
516     print "           `\\ `|            SALOME           |"
517     print "           _/ /\\            4 EVER           /"
518     print "          (__/  \\             <3            /"
519     print "       _..---\"\"` \\                         /`\"\"---.._"
520     print "    .-\'           \\                       /          \'-."
521     print "   :               `-.__             __.-\'              :"
522     print "   :                  ) \"\"---...---\"\" (                 :"
523     print "    \'._               `\"--...___...--\"`              _.\'"
524     print "      \\\"\"--..__                              __..--\"\"/"
525     print "       \'._     \"\"\"----.....______.....----\"\"\"     _.\'"
526     print "          `\"\"--..,,_____            _____,,..--\"\"`"
527     print "                        `\"\"\"----\"\"\"`"
528     print ""
529     print "                    SALOME is working for you; what else?"
530     print ""
531     sys.exit(0)
532   #
533
534   def _getCar(self, unused=None):
535     print "                                              _____________"
536     print "                                  ..---:::::::-----------. ::::;;."
537     print "                               .\'\"\"\"\"\"\"                  ;;   \\  \":."
538     print "                            .\'\'                          ;     \\   \"\\__."
539     print "                          .\'                            ;;      ;   \\\\\";"
540     print "                        .\'                              ;   _____;   \\\\/"
541     print "                      .\'                               :; ;\"     \\ ___:\'."
542     print "                    .\'--...........................    : =   ____:\"    \\ \\"
543     print "               ..-\"\"                               \"\"\"\'  o\"\"\"     ;     ; :"
544     print "          .--\"\"  .----- ..----...    _.-    --.  ..-\"     ;       ;     ; ;"
545     print "       .\"\"_-     \"--\"\"-----\'\"\"    _-\"        .-\"\"         ;        ;    .-."
546     print "    .\'  .\'   SALOME             .\"         .\"              ;       ;   /. |"
547     print "   /-./\'         4 EVER <3    .\"          /           _..  ;       ;   ;;;|"
548     print "  :  ;-.______               /       _________==.    /_  \\ ;       ;   ;;;;"
549     print "  ;  / |      \"\"\"\"\"\"\"\"\"\"\".---.\"\"\"\"\"\"\"          :    /\" \". |;       ; _; ;;;"
550     print " /\"-/  |                /   /                  /   /     ;|;      ;-\" | ;\';"
551     print ":-  :   \"\"\"----______  /   /              ____.   .  .\"\'. ;;   .-\"..T\"   ."
552     print "\'. \"  ___            \"\":   \'\"\"\"\"\"\"\"\"\"\"\"\"\"\"    .   ; ;    ;; ;.\" .\"   \'--\""
553     print " \",   __ \"\"\"  \"\"---... :- - - - - - - - - \' \'  ; ;  ;    ;;\"  .\""
554     print "  /. ;  \"\"\"---___                             ;  ; ;     ;|.\"\""
555     print " :  \":           \"\"\"----.    .-------.       ;   ; ;     ;:"
556     print "  \\  \'--__               \\   \\        \\     /    | ;     ;;"
557     print "   \'-..   \"\"\"\"---___      :   .______..\\ __/..-\"\"|  ;   ; ;"
558     print "       \"\"--..       \"\"\"--\"        m l s         .   \". . ;"
559     print "             \"\"------...                  ..--\"\"      \" :"
560     print "                        \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"    \\        /"
561     print "                                               \"------\""
562     print ""
563     print "                                Drive your simulation properly with SALOME!"
564     print ""
565     sys.exit(0)
566   #
567
568   # Add the following two methods since logger is not pickable
569   # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
570   def __getstate__(self):
571     d = dict(self.__dict__)
572     if hasattr(self, '_logger'):
573       del d['_logger']
574     return d
575   #
576   def __setstate__(self, d):
577     self.__dict__.update(d) # I *think* this is a safe way to do it
578   #
579   # Excluding self._logger from pickle operation imply using the following method to access logger
580   def getLogger(self):
581     if not hasattr(self, '_logger'):
582       self._logger = logging.getLogger(__name__)
583       #self._logger.setLevel(logging.DEBUG)
584       #self._logger.setLevel(logging.WARNING)
585       self._logger.setLevel(logging.ERROR)
586     return self._logger
587   #
588
589 if __name__ == "__main__":
590   if len(sys.argv) == 3:
591     context = pickle.loads(sys.argv[1])
592     args = pickle.loads(sys.argv[2])
593
594     (out, err) = context._startSalome(args)
595     if out:
596       sys.stdout.write(out)
597     if err:
598       sys.stderr.write(err)
599   else:
600     usage()
601 #