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