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