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