Salome HOME
Add QT_PLUGIN_PATH to reserved variables (editable with ADD_TO_ syntax)
[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', 'QT_PLUGIN_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         return 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       return returncode
268     except StandardError:
269       self.getLogger().error("Unexpected error:")
270       import traceback
271       traceback.print_exc()
272       return 1
273     except SalomeContextException, e:
274       self.getLogger().error(e)
275       return 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       return 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()['this']
395
396       if ports:
397         import tempfile
398         for port in ports:
399           with tempfile.NamedTemporaryFile():
400             p = Process(target = killMyPort, args=(port,))
401             p.start()
402             p.join()
403     except ImportError:
404       # :TODO: should be declared obsolete
405       from killSalome import killAllPorts
406       killAllPorts()
407       pass
408   #
409
410   def _runTests(self, args=None):
411     if args is None:
412       args = []
413     sys.argv = ['runTests']
414     import setenv
415     setenv.main(True)
416
417     import runTests
418     return runTests.runTests(args, exe="salome test")
419   #
420
421   def _showSoftwareVersions(self, softwares=None):
422     config = ConfigParser.SafeConfigParser()
423     absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
424     filename = os.path.join(absoluteAppliPath, "sha1_collections.txt")
425     versions = {}
426     max_len = 0
427     with open(filename) as f:
428       for line in f:
429         try:
430           software, version, sha1 = line.split()
431           versions[software.upper()] = version
432           if len(software) > max_len:
433             max_len = len(software)
434         except:
435           pass
436         pass
437       pass
438     if softwares:
439       for soft in softwares:
440         if versions.has_key(soft.upper()):
441           print soft.upper().rjust(max_len), versions[soft.upper()]
442     else:
443       import collections
444       od = collections.OrderedDict(sorted(versions.items()))
445       for name, version in od.iteritems():
446         print name.rjust(max_len), versions[name]
447     pass
448
449   def _showInfo(self, args=None):
450     if args is None:
451       args = []
452
453     usage = "Usage: salome info [options]"
454     epilog  = """\n
455 Display some information about SALOME.\n
456 Available options are:
457     -p,--ports                     Show the list of busy ports (running SALOME instances).
458     -s,--softwares [software(s)]   Show the list and versions of SALOME softwares.
459                                    Software names must be separated by blank characters.
460                                    If no software is given, show version of all softwares.
461     -v,--version                   Show running SALOME version.
462     -h,--help                      Show this message.
463 """
464     if not args:
465       args = ["--version"]
466
467     if "-h" in args or "--help" in args:
468       print usage + epilog
469       return
470
471     if "-p" in args or "--ports" in args:
472       import PortManager
473       ports = PortManager.getBusyPorts()
474       this_ports = ports['this']
475       other_ports = ports['other']
476       if this_ports or other_ports:
477           print "SALOME instances are running on the following ports:"
478           if this_ports:
479               print "   This application:", this_ports
480           else:
481               print "   No SALOME instances of this application"
482           if other_ports:
483               print "   Other applications:", other_ports
484           else:
485               print "   No SALOME instances of other applications"
486       else:
487           print "No SALOME instances are running"
488
489     if "-s" in args or "--softwares" in args:
490       if "-s" in args:
491         index = args.index("-s")
492       else:
493         index = args.index("--softwares")
494       indexEnd=index+1
495       while indexEnd < len(args) and args[indexEnd][0] != "-":
496         indexEnd = indexEnd + 1
497       self._showSoftwareVersions(softwares=args[index+1:indexEnd])
498
499     if "-v" in args or "--version" in args:
500       print "Running with python", platform.python_version()
501       self._runAppli(["--version"])
502   #
503
504   def _showDoc(self, args=None):
505     if args is None:
506       args = []
507
508     modules = args
509     if not modules:
510       print "Module(s) not provided to command: salome doc <module(s)>"
511       return
512
513     appliPath = os.getenv("ABSOLUTE_APPLI_PATH")
514     if not appliPath:
515       raise SalomeContextException("Unable to find application path. Please check that the variable ABSOLUTE_APPLI_PATH is set.")
516     baseDir = os.path.join(appliPath, "share", "doc", "salome")
517     for module in modules:
518       docfile = os.path.join(baseDir, "gui", module.upper(), "index.html")
519       if not os.path.isfile(docfile):
520         docfile = os.path.join(baseDir, "tui", module.upper(), "index.html")
521       if not os.path.isfile(docfile):
522         docfile = os.path.join(baseDir, "dev", module.upper(), "index.html")
523       if os.path.isfile(docfile):
524         out, err = subprocess.Popen(["xdg-open", docfile]).communicate()
525       else:
526         print "Online documentation is not accessible for module:", module
527
528   def _usage(self, unused=None):
529     usage()
530   #
531
532   def _makeCoffee(self, unused=None):
533     print "                        ("
534     print "                          )     ("
535     print "                   ___...(-------)-....___"
536     print "               .-\"\"       )    (          \"\"-."
537     print "         .-\'``\'|-._             )         _.-|"
538     print "        /  .--.|   `\"\"---...........---\"\"`   |"
539     print "       /  /    |                             |"
540     print "       |  |    |                             |"
541     print "        \\  \\   |                             |"
542     print "         `\\ `\\ |                             |"
543     print "           `\\ `|            SALOME           |"
544     print "           _/ /\\            4 EVER           /"
545     print "          (__/  \\             <3            /"
546     print "       _..---\"\"` \\                         /`\"\"---.._"
547     print "    .-\'           \\                       /          \'-."
548     print "   :               `-.__             __.-\'              :"
549     print "   :                  ) \"\"---...---\"\" (                 :"
550     print "    \'._               `\"--...___...--\"`              _.\'"
551     print "      \\\"\"--..__                              __..--\"\"/"
552     print "       \'._     \"\"\"----.....______.....----\"\"\"     _.\'"
553     print "          `\"\"--..,,_____            _____,,..--\"\"`"
554     print "                        `\"\"\"----\"\"\"`"
555     print ""
556     print "                    SALOME is working for you; what else?"
557     print ""
558   #
559
560   def _getCar(self, unused=None):
561     print "                                              _____________"
562     print "                                  ..---:::::::-----------. ::::;;."
563     print "                               .\'\"\"\"\"\"\"                  ;;   \\  \":."
564     print "                            .\'\'                          ;     \\   \"\\__."
565     print "                          .\'                            ;;      ;   \\\\\";"
566     print "                        .\'                              ;   _____;   \\\\/"
567     print "                      .\'                               :; ;\"     \\ ___:\'."
568     print "                    .\'--...........................    : =   ____:\"    \\ \\"
569     print "               ..-\"\"                               \"\"\"\'  o\"\"\"     ;     ; :"
570     print "          .--\"\"  .----- ..----...    _.-    --.  ..-\"     ;       ;     ; ;"
571     print "       .\"\"_-     \"--\"\"-----\'\"\"    _-\"        .-\"\"         ;        ;    .-."
572     print "    .\'  .\'   SALOME             .\"         .\"              ;       ;   /. |"
573     print "   /-./\'         4 EVER <3    .\"          /           _..  ;       ;   ;;;|"
574     print "  :  ;-.______               /       _________==.    /_  \\ ;       ;   ;;;;"
575     print "  ;  / |      \"\"\"\"\"\"\"\"\"\"\".---.\"\"\"\"\"\"\"          :    /\" \". |;       ; _; ;;;"
576     print " /\"-/  |                /   /                  /   /     ;|;      ;-\" | ;\';"
577     print ":-  :   \"\"\"----______  /   /              ____.   .  .\"\'. ;;   .-\"..T\"   ."
578     print "\'. \"  ___            \"\":   \'\"\"\"\"\"\"\"\"\"\"\"\"\"\"    .   ; ;    ;; ;.\" .\"   \'--\""
579     print " \",   __ \"\"\"  \"\"---... :- - - - - - - - - \' \'  ; ;  ;    ;;\"  .\""
580     print "  /. ;  \"\"\"---___                             ;  ; ;     ;|.\"\""
581     print " :  \":           \"\"\"----.    .-------.       ;   ; ;     ;:"
582     print "  \\  \'--__               \\   \\        \\     /    | ;     ;;"
583     print "   \'-..   \"\"\"\"---___      :   .______..\\ __/..-\"\"|  ;   ; ;"
584     print "       \"\"--..       \"\"\"--\"        m l s         .   \". . ;"
585     print "             \"\"------...                  ..--\"\"      \" :"
586     print "                        \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"    \\        /"
587     print "                                               \"------\""
588     print ""
589     print "                                Drive your simulation properly with SALOME!"
590     print ""
591   #
592
593   # Add the following two methods since logger is not pickable
594   # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
595   def __getstate__(self):
596     d = dict(self.__dict__)
597     if hasattr(self, '_logger'):
598       del d['_logger']
599     return d
600   #
601   def __setstate__(self, d):
602     self.__dict__.update(d) # I *think* this is a safe way to do it
603   #
604   # Excluding self._logger from pickle operation imply using the following method to access logger
605   def getLogger(self):
606     if not hasattr(self, '_logger'):
607       self._logger = logging.getLogger(__name__)
608       #self._logger.setLevel(logging.DEBUG)
609       #self._logger.setLevel(logging.WARNING)
610       self._logger.setLevel(logging.ERROR)
611     return self._logger
612   #
613
614 if __name__ == "__main__":
615   if len(sys.argv) == 3:
616     context = pickle.loads(sys.argv[1])
617     args = pickle.loads(sys.argv[2])
618
619     context._startSalome(args)
620   else:
621     usage()
622 #