Salome HOME
86aa86a8f60b449014410f2bb1e97d0e23ddce8d
[modules/kernel.git] / bin / salomeContext.py
1 # Copyright (C) 2013-2017  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 is start.
58
59 Command options:
60 ================
61     Use salome <command> --help to show help on command. Available for the
62     following commands: 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_FALLBACK_LIBRARY_PATH', 'DYLD_LIBRARY_PATH', 'LD_LIBRARY_PATH', 'PYTHONPATH', 'MANPATH', 'PV_PLUGIN_PATH', 'INCLUDE', 'LIBPATH', 'SALOME_PLUGINS_PATH', 'LIBRARY_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().error("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     elif platform.system() == 'Darwin':
146       if "LAPACK" in value:
147         self.addToVariable('DYLD_FALLBACK_LIBRARY_PATH', value)
148       else:
149         self.addToVariable('DYLD_LIBRARY_PATH', value)
150     else:
151       self.addToVariable('LD_LIBRARY_PATH', value)
152   #
153
154   """Append value to DYLD_LIBRARY_PATH environment variable"""
155   def addToDyldLibraryPath(self, value):
156     self.addToVariable('DYLD_LIBRARY_PATH', value)
157   #
158
159   """Append value to PYTHONPATH environment variable"""
160   def addToPythonPath(self, value):
161     self.addToVariable('PYTHONPATH', value)
162   #
163
164   """Set environment variable to value"""
165   def setVariable(self, name, value, overwrite=False):
166     env = os.getenv(name, '')
167     if env and not overwrite:
168       self.getLogger().error("Environment variable already existing (and not overwritten): %s=%s", name, value)
169       return
170
171     if env:
172       self.getLogger().debug("Overwriting environment variable: %s=%s", name, value)
173
174     value = os.path.expandvars(value) # expand environment variables
175     self.getLogger().debug("Set environment variable: %s=%s", name, value)
176     os.environ[name] = value
177   #
178
179   """Unset environment variable"""
180   def unsetVariable(self, name):
181     if os.environ.has_key(name):
182       del os.environ[name]
183   #
184
185   """Append value to environment variable"""
186   def addToVariable(self, name, value, separator=os.pathsep):
187     if value == '':
188       return
189
190     value = os.path.expandvars(value) # expand environment variables
191     self.getLogger().debug("Add to %s: %s", name, value)
192     env = os.getenv(name, None)
193     if env is None:
194       os.environ[name] = value
195     else:
196       os.environ[name] = value + separator + env
197   #
198
199   ###################################
200   # This begins the private section #
201   ###################################
202
203   def __parseArguments(self, args):
204     if len(args) == 0 or args[0].startswith("-"):
205       return None, args
206
207     command = args[0]
208     options = args[1:]
209
210     availableCommands = {
211       'start'   : '_runAppli',
212       'context' : '_setContext',
213       'shell'   : '_runSession',
214       'connect' : '_runConsole',
215       'kill'    : '_kill',
216       'killall' : '_killAll',
217       'test'    : '_runTests',
218       'info'    : '_showInfo',
219       'doc'     : '_showDoc',
220       'help'    : '_usage',
221       'coffee'  : '_makeCoffee',
222       'car'     : '_getCar',
223       }
224
225     if not command in availableCommands.keys():
226       command = "start"
227       options = args
228
229     return availableCommands[command], options
230   #
231
232   """
233   Run SALOME!
234   Args consist in a mandatory command followed by optionnal parameters.
235   See usage for details on commands.
236   """
237   def _startSalome(self, args):
238     import os
239     import sys
240     try:
241       from setenv import add_path
242       absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
243       path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome"))
244       add_path(path, "PYTHONPATH")
245       path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome", "appliskel"))
246       add_path(path, "PYTHONPATH")
247
248     except:
249       pass
250
251     command, options = self.__parseArguments(args)
252     sys.argv = options
253
254     if command is None:
255       if args and args[0] in ["-h","--help","help"]:
256         usage()
257         sys.exit(0)
258       # try to default to "start" command
259       command = "_runAppli"
260
261     try:
262       res = getattr(self, command)(options) # run appropriate method
263       return res or (None, None)
264     except SystemExit, returncode:
265       if returncode != 0:
266         self.getLogger().error("SystemExit %s in method %s.", returncode, command)
267       sys.exit(returncode)
268     except StandardError:
269       self.getLogger().error("Unexpected error:")
270       import traceback
271       traceback.print_exc()
272       sys.exit(1)
273     except SalomeContextException, e:
274       self.getLogger().error(e)
275       sys.exit(1)
276   #
277
278   def __setContextFromConfigFile(self, filename, reserved=None):
279     if reserved is None:
280       reserved = []
281     try:
282       unsetVars, configVars, reservedDict = parseConfigFile(filename, reserved)
283     except SalomeContextException, e:
284       msg = "%s"%e
285       self.getLogger().error(msg)
286       sys.exit(1)
287
288     # unset variables
289     for var in unsetVars:
290       self.unsetVariable(var)
291
292     # set context
293     for reserved in reservedDict:
294       a = filter(None, reservedDict[reserved]) # remove empty elements
295       a = [ os.path.realpath(x) for x in a ]
296       reformattedVals = os.pathsep.join(a)
297       if reserved in ["INCLUDE", "LIBPATH"]:
298         self.addToVariable(reserved, reformattedVals, separator=' ')
299       else:
300         self.addToVariable(reserved, reformattedVals)
301       pass
302
303     for key,val in configVars:
304       self.setVariable(key, val, overwrite=True)
305       pass
306
307     pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
308     pythonpath = [ os.path.realpath(x) for x in pythonpath ]
309     sys.path[:0] = pythonpath
310   #
311
312   def _runAppli(self, args=None):
313     if args is None:
314       args = []
315     # Initialize SALOME environment
316     sys.argv = ['runSalome'] + args
317     import setenv
318     setenv.main(True, exeName="salome start")
319
320     import runSalome
321     runSalome.runSalome()
322   #
323
324   def _setContext(self, args=None):
325     salome_context_set = os.getenv("SALOME_CONTEXT_SET")
326     if salome_context_set:
327       print "***"
328       print "*** SALOME context has already been set."
329       print "*** Enter 'exit' (only once!) to leave SALOME context."
330       print "***"
331       return
332
333     os.environ["SALOME_CONTEXT_SET"] = "yes"
334     print "***"
335     print "*** SALOME context is now set."
336     print "*** Enter 'exit' (only once!) to leave SALOME context."
337     print "***"
338
339     cmd = ["/bin/bash"]
340     proc = subprocess.Popen(cmd, shell=False, close_fds=True)
341     return proc.communicate()
342   #
343
344   def _runSession(self, args=None):
345     if args is None:
346       args = []
347     sys.argv = ['runSession'] + args
348     import runSession
349     params, args = runSession.configureSession(args, exe="salome shell")
350
351     sys.argv = ['runSession'] + args
352     import setenv
353     setenv.main(True)
354
355     return runSession.runSession(params, args)
356   #
357
358   def _runConsole(self, args=None):
359     if args is None:
360       args = []
361     # Initialize SALOME environment
362     sys.argv = ['runConsole']
363     import setenv
364     setenv.main(True)
365
366     import runConsole
367     return runConsole.connect(args)
368   #
369
370   def _kill(self, args=None):
371     if args is None:
372       args = []
373     ports = args
374     if not ports:
375       print "Port number(s) not provided to command: salome kill <port(s)>"
376       return
377
378     from multiprocessing import Process
379     from killSalomeWithPort import killMyPort
380     import tempfile
381     for port in ports:
382       with tempfile.NamedTemporaryFile():
383         p = Process(target = killMyPort, args=(port,))
384         p.start()
385         p.join()
386     pass
387   #
388
389   def _killAll(self, unused=None):
390     try:
391       import PortManager # mandatory
392       from multiprocessing import Process
393       from killSalomeWithPort import killMyPort
394       ports = PortManager.getBusyPorts()
395
396       if ports:
397         import tempfile
398         for port,owner in ports:
399           if owner == "this":
400             with tempfile.NamedTemporaryFile():
401               p = Process(target = killMyPort, args=(port,))
402               p.start()
403               p.join()
404     except ImportError:
405       # :TODO: should be declared obsolete
406       from killSalome import killAllPorts
407       killAllPorts()
408       pass
409   #
410
411   def _runTests(self, args=None):
412     if args is None:
413       args = []
414     sys.argv = ['runTests']
415     import setenv
416     setenv.main(True)
417
418     import runTests
419     return runTests.runTests(args, exe="salome test")
420   #
421
422   def _showSoftwareVersions(self, softwares=None):
423     config = ConfigParser.SafeConfigParser()
424     absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
425     filename = os.path.join(absoluteAppliPath, "sha1_collections.txt")
426     versions = {}
427     max_len = 0
428     with open(filename) as f:
429       for line in f:
430         try:
431           software, version, sha1 = line.split()
432           versions[software.upper()] = version
433           if len(software) > max_len:
434             max_len = len(software)
435         except:
436           pass
437         pass
438       pass
439     if softwares:
440       for soft in softwares:
441         if versions.has_key(soft.upper()):
442           print soft.upper().rjust(max_len), versions[soft.upper()]
443     else:
444       import collections
445       od = collections.OrderedDict(sorted(versions.items()))
446       for name, version in od.iteritems():
447         print name.rjust(max_len), versions[name]
448     pass
449
450   def _showInfo(self, args=None):
451     if args is None:
452       args = []
453
454     usage = "Usage: salome info [options]"
455     epilog  = """\n
456 Display some information about SALOME.\n
457 Available options are:
458     -p,--ports                     Show the list of busy ports (running SALOME instances).
459     -s,--softwares [software(s)]   Show the list and versions of SALOME softwares.
460                                    Software names must be separated by blank characters.
461                                    If no software is given, show version of all softwares.
462     -v,--version                   Show running SALOME version.
463     -h,--help                      Show this message.
464 """
465     if not args:
466       args = ["--version"]
467
468     if "-h" in args or "--help" in args:
469       print usage + epilog
470       return
471
472     if "-p" in args or "--ports" in args:
473       import PortManager
474       ports = PortManager.getBusyPorts()
475       print "SALOME instances are running on ports:", ports
476       if ports:
477         print "Last started instance on port %s"%ports[-1][0]
478
479     if "-s" in args or "--softwares" in args:
480       if "-s" in args:
481         index = args.index("-s")
482       else:
483         index = args.index("--softwares")
484       indexEnd=index+1
485       while indexEnd < len(args) and args[indexEnd][0] != "-":
486         indexEnd = indexEnd + 1
487       self._showSoftwareVersions(softwares=args[index+1:indexEnd])
488
489     if "-v" in args or "--version" in args:
490       print "Running with python", platform.python_version()
491       self._runAppli(["--version"])
492   #
493
494   def _showDoc(self, args=None):
495     if args is None:
496       args = []
497
498     modules = args
499     if not modules:
500       print "Module(s) not provided to command: salome doc <module(s)>"
501       return
502
503     appliPath = os.getenv("ABSOLUTE_APPLI_PATH")
504     if not appliPath:
505       raise SalomeContextException("Unable to find application path. Please check that the variable ABSOLUTE_APPLI_PATH is set.")
506     baseDir = os.path.join(appliPath, "share", "doc", "salome")
507     for module in modules:
508       docfile = os.path.join(baseDir, "gui", module.upper(), "index.html")
509       if not os.path.isfile(docfile):
510         docfile = os.path.join(baseDir, "tui", module.upper(), "index.html")
511       if not os.path.isfile(docfile):
512         docfile = os.path.join(baseDir, "dev", module.upper(), "index.html")
513       if os.path.isfile(docfile):
514         out, err = subprocess.Popen(["xdg-open", docfile]).communicate()
515       else:
516         print "Online documentation is not accessible for module:", module
517
518   def _usage(self, unused=None):
519     usage()
520   #
521
522   def _makeCoffee(self, unused=None):
523     print "                        ("
524     print "                          )     ("
525     print "                   ___...(-------)-....___"
526     print "               .-\"\"       )    (          \"\"-."
527     print "         .-\'``\'|-._             )         _.-|"
528     print "        /  .--.|   `\"\"---...........---\"\"`   |"
529     print "       /  /    |                             |"
530     print "       |  |    |                             |"
531     print "        \\  \\   |                             |"
532     print "         `\\ `\\ |                             |"
533     print "           `\\ `|            SALOME           |"
534     print "           _/ /\\            4 EVER           /"
535     print "          (__/  \\             <3            /"
536     print "       _..---\"\"` \\                         /`\"\"---.._"
537     print "    .-\'           \\                       /          \'-."
538     print "   :               `-.__             __.-\'              :"
539     print "   :                  ) \"\"---...---\"\" (                 :"
540     print "    \'._               `\"--...___...--\"`              _.\'"
541     print "      \\\"\"--..__                              __..--\"\"/"
542     print "       \'._     \"\"\"----.....______.....----\"\"\"     _.\'"
543     print "          `\"\"--..,,_____            _____,,..--\"\"`"
544     print "                        `\"\"\"----\"\"\"`"
545     print ""
546     print "                    SALOME is working for you; what else?"
547     print ""
548     sys.exit(0)
549   #
550
551   def _getCar(self, unused=None):
552     print "                                              _____________"
553     print "                                  ..---:::::::-----------. ::::;;."
554     print "                               .\'\"\"\"\"\"\"                  ;;   \\  \":."
555     print "                            .\'\'                          ;     \\   \"\\__."
556     print "                          .\'                            ;;      ;   \\\\\";"
557     print "                        .\'                              ;   _____;   \\\\/"
558     print "                      .\'                               :; ;\"     \\ ___:\'."
559     print "                    .\'--...........................    : =   ____:\"    \\ \\"
560     print "               ..-\"\"                               \"\"\"\'  o\"\"\"     ;     ; :"
561     print "          .--\"\"  .----- ..----...    _.-    --.  ..-\"     ;       ;     ; ;"
562     print "       .\"\"_-     \"--\"\"-----\'\"\"    _-\"        .-\"\"         ;        ;    .-."
563     print "    .\'  .\'   SALOME             .\"         .\"              ;       ;   /. |"
564     print "   /-./\'         4 EVER <3    .\"          /           _..  ;       ;   ;;;|"
565     print "  :  ;-.______               /       _________==.    /_  \\ ;       ;   ;;;;"
566     print "  ;  / |      \"\"\"\"\"\"\"\"\"\"\".---.\"\"\"\"\"\"\"          :    /\" \". |;       ; _; ;;;"
567     print " /\"-/  |                /   /                  /   /     ;|;      ;-\" | ;\';"
568     print ":-  :   \"\"\"----______  /   /              ____.   .  .\"\'. ;;   .-\"..T\"   ."
569     print "\'. \"  ___            \"\":   \'\"\"\"\"\"\"\"\"\"\"\"\"\"\"    .   ; ;    ;; ;.\" .\"   \'--\""
570     print " \",   __ \"\"\"  \"\"---... :- - - - - - - - - \' \'  ; ;  ;    ;;\"  .\""
571     print "  /. ;  \"\"\"---___                             ;  ; ;     ;|.\"\""
572     print " :  \":           \"\"\"----.    .-------.       ;   ; ;     ;:"
573     print "  \\  \'--__               \\   \\        \\     /    | ;     ;;"
574     print "   \'-..   \"\"\"\"---___      :   .______..\\ __/..-\"\"|  ;   ; ;"
575     print "       \"\"--..       \"\"\"--\"        m l s         .   \". . ;"
576     print "             \"\"------...                  ..--\"\"      \" :"
577     print "                        \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"    \\        /"
578     print "                                               \"------\""
579     print ""
580     print "                                Drive your simulation properly with SALOME!"
581     print ""
582     sys.exit(0)
583   #
584
585   # Add the following two methods since logger is not pickable
586   # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
587   def __getstate__(self):
588     d = dict(self.__dict__)
589     if hasattr(self, '_logger'):
590       del d['_logger']
591     return d
592   #
593   def __setstate__(self, d):
594     self.__dict__.update(d) # I *think* this is a safe way to do it
595   #
596   # Excluding self._logger from pickle operation imply using the following method to access logger
597   def getLogger(self):
598     if not hasattr(self, '_logger'):
599       self._logger = logging.getLogger(__name__)
600       #self._logger.setLevel(logging.DEBUG)
601       #self._logger.setLevel(logging.WARNING)
602       self._logger.setLevel(logging.ERROR)
603     return self._logger
604   #
605
606 if __name__ == "__main__":
607   if len(sys.argv) == 3:
608     context = pickle.loads(sys.argv[1])
609     args = pickle.loads(sys.argv[2])
610
611     (out, err) = context._startSalome(args)
612     if out:
613       sys.stdout.write(out)
614     if err:
615       sys.stderr.write(err)
616   else:
617     usage()
618 #