Salome HOME
initiate application test process
[modules/kernel.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   #exeName = os.path.splitext(os.path.basename(__file__))[0]
37
38   msg = '''\
39 Usage: salome [command] [options] [--config=<file,folder,...>]
40
41 Commands:
42 =========
43     start           Starts a SALOME session (through virtual application)
44     context         Initializes SALOME context.
45     shell           Initializes SALOME context, and executes scripts passed
46                     as command arguments
47     connect         Connects a Python console to the active SALOME session
48     kill <port(s)>  Terminate SALOME session running on given ports for current user
49                     Port numbers must be separated by blank characters
50     killall         Kill *all* SALOME running sessions for current user
51     test            Run SALOME tests.
52     info            Display some information about SALOME
53     help            Show this message
54     coffee          Yes! SALOME can also make coffee!!
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, test.
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     try:
243       import os
244       absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
245       import sys
246       path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome"))
247       if not path in sys.path:
248         sys.path[:0] = [path]
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=[]):
334     # Initialize SALOME environment
335     sys.argv = ['runSalome'] + args
336     import setenv
337     setenv.main(True)
338
339     import runSalome
340     runSalome.runSalome()
341   #
342
343   def _setContext(self, args=None):
344     salome_context_set = os.getenv("SALOME_CONTEXT_SET")
345     if salome_context_set:
346       print "***"
347       print "*** SALOME context has already been set."
348       print "*** Enter 'exit' (only once!) to leave SALOME context."
349       print "***"
350       return
351
352     os.environ["SALOME_CONTEXT_SET"] = "yes"
353     print "***"
354     print "*** SALOME context is now set."
355     print "*** Enter 'exit' (only once!) to leave SALOME context."
356     print "***"
357
358     cmd = ["/bin/bash"]
359     proc = subprocess.Popen(cmd, shell=False, close_fds=True)
360     return proc.communicate()
361   #
362
363   def _runSession(self, args=[]):
364     sys.argv = ['runSession'] + args
365     import runSession
366     params, args = runSession.configureSession(args, exe="salome shell")
367
368     sys.argv = ['runSession'] + args
369     import setenv
370     setenv.main(True)
371
372     return runSession.runSession(params, args)
373   #
374
375   def _runConsole(self, args=[]):
376     # Initialize SALOME environment
377     sys.argv = ['runConsole'] + args
378     import setenv
379     setenv.main(True)
380
381     cmd = ["python", "-c", "import runConsole\nrunConsole.connect()" ]
382     proc = subprocess.Popen(cmd, shell=False, close_fds=True)
383     return proc.communicate()
384   #
385
386   def _kill(self, args=[]):
387     ports = args
388     if not ports:
389       print "Port number(s) not provided to command: salome kill <port(s)>"
390       return
391
392     from multiprocessing import Process
393     from killSalomeWithPort import killMyPort
394     import tempfile
395     for port in ports:
396       with tempfile.NamedTemporaryFile():
397         p = Process(target = killMyPort, args=(port,))
398         p.start()
399         p.join()
400     pass
401   #
402
403   def _killAll(self, unused=None):
404     try:
405       import PortManager # mandatory
406       from multiprocessing import Process
407       from killSalomeWithPort import killMyPort
408       ports = PortManager.getBusyPorts()
409
410       if ports:
411         import tempfile
412         for port in ports:
413           with tempfile.NamedTemporaryFile():
414             p = Process(target = killMyPort, args=(port,))
415             p.start()
416             p.join()
417     except ImportError:
418       from killSalome import killAllPorts
419       killAllPorts()
420       pass
421   #
422
423   def _runTests(self, args=[]):
424     sys.argv = ['runTests']
425     import setenv
426     setenv.main(True)
427
428     import runTests
429     return runTests.runTests(args, exe="salome test")
430   #
431
432   def _showInfo(self, unused=None):
433     print "Running with python", platform.python_version()
434     self._runAppli(["--version"])
435   #
436
437   def _usage(self, unused=None):
438     usage()
439   #
440
441   def _makeCoffee(self, unused=None):
442     print "                        ("
443     print "                          )     ("
444     print "                   ___...(-------)-....___"
445     print "               .-\"\"       )    (          \"\"-."
446     print "         .-\'``\'|-._             )         _.-|"
447     print "        /  .--.|   `\"\"---...........---\"\"`   |"
448     print "       /  /    |                             |"
449     print "       |  |    |                             |"
450     print "        \\  \\   |                             |"
451     print "         `\\ `\\ |                             |"
452     print "           `\\ `|            SALOME           |"
453     print "           _/ /\\            4 EVER           /"
454     print "          (__/  \\             <3            /"
455     print "       _..---\"\"` \\                         /`\"\"---.._"
456     print "    .-\'           \\                       /          \'-."
457     print "   :               `-.__             __.-\'              :"
458     print "   :                  ) \"\"---...---\"\" (                 :"
459     print "    \'._               `\"--...___...--\"`              _.\'"
460     print "      \\\"\"--..__                              __..--\"\"/"
461     print "       \'._     \"\"\"----.....______.....----\"\"\"     _.\'"
462     print "          `\"\"--..,,_____            _____,,..--\"\"`"
463     print "                        `\"\"\"----\"\"\"`"
464     print ""
465     print "                    SALOME is working for you; what else?"
466     print ""
467     sys.exit(0)
468   #
469
470   def _getCar(self, unused=None):
471     print "                                              _____________"
472     print "                                  ..---:::::::-----------. ::::;;."
473     print "                               .\'\"\"\"\"\"\"                  ;;   \\  \":."
474     print "                            .\'\'                          ;     \\   \"\\__."
475     print "                          .\'                            ;;      ;   \\\\\";"
476     print "                        .\'                              ;   _____;   \\\\/"
477     print "                      .\'                               :; ;\"     \\ ___:\'."
478     print "                    .\'--...........................    : =   ____:\"    \\ \\"
479     print "               ..-\"\"                               \"\"\"\'  o\"\"\"     ;     ; :"
480     print "          .--\"\"  .----- ..----...    _.-    --.  ..-\"     ;       ;     ; ;"
481     print "       .\"\"_-     \"--\"\"-----\'\"\"    _-\"        .-\"\"         ;        ;    .-."
482     print "    .\'  .\'   SALOME             .\"         .\"              ;       ;   /. |"
483     print "   /-./\'         4 EVER <3    .\"          /           _..  ;       ;   ;;;|"
484     print "  :  ;-.______               /       _________==.    /_  \\ ;       ;   ;;;;"
485     print "  ;  / |      \"\"\"\"\"\"\"\"\"\"\".---.\"\"\"\"\"\"\"          :    /\" \". |;       ; _; ;;;"
486     print " /\"-/  |                /   /                  /   /     ;|;      ;-\" | ;\';"
487     print ":-  :   \"\"\"----______  /   /              ____.   .  .\"\'. ;;   .-\"..T\"   ."
488     print "\'. \"  ___            \"\":   \'\"\"\"\"\"\"\"\"\"\"\"\"\"\"    .   ; ;    ;; ;.\" .\"   \'--\""
489     print " \",   __ \"\"\"  \"\"---... :- - - - - - - - - \' \'  ; ;  ;    ;;\"  .\""
490     print "  /. ;  \"\"\"---___                             ;  ; ;     ;|.\"\""
491     print " :  \":           \"\"\"----.    .-------.       ;   ; ;     ;:"
492     print "  \\  \'--__               \\   \\        \\     /    | ;     ;;"
493     print "   \'-..   \"\"\"\"---___      :   .______..\\ __/..-\"\"|  ;   ; ;"
494     print "       \"\"--..       \"\"\"--\"        m l s         .   \". . ;"
495     print "             \"\"------...                  ..--\"\"      \" :"
496     print "                        \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"    \\        /"
497     print "                                               \"------\""
498     print ""
499     print "                                Drive your simulation properly with SALOME!"
500     print ""
501     sys.exit(0)
502   #
503
504   # Add the following two methods since logger is not pickable
505   # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
506   def __getstate__(self):
507     d = dict(self.__dict__)
508     if hasattr(self, '_logger'):
509       del d['_logger']
510     return d
511   #
512   def __setstate__(self, d):
513     self.__dict__.update(d) # I *think* this is a safe way to do it
514   #
515   # Excluding self._logger from pickle operation imply using the following method to access logger
516   def getLogger(self):
517     if not hasattr(self, '_logger'):
518       self._logger = logging.getLogger(__name__)
519       #self._logger.setLevel(logging.DEBUG)
520       #self._logger.setLevel(logging.WARNING)
521       self._logger.setLevel(logging.ERROR)
522     return self._logger
523   #
524
525 if __name__ == "__main__":
526   if len(sys.argv) == 3:
527     context = pickle.loads(sys.argv[1])
528     args = pickle.loads(sys.argv[2])
529
530     (out, err) = context._startSalome(args)
531     if out:
532       sys.stdout.write(out)
533     if err:
534       sys.stderr.write(err)
535   else:
536     usage()
537 #