Salome HOME
Remove use of mutable as default parameters
[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     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)
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'] + args
387     import setenv
388     setenv.main(True)
389
390     cmd = ["python", "-c", "import runConsole\nrunConsole.connect()" ]
391     proc = subprocess.Popen(cmd, shell=False, close_fds=True)
392     return proc.communicate()
393   #
394
395   def _kill(self, args=None):
396     if args is None:
397       args = []
398     ports = args
399     if not ports:
400       print "Port number(s) not provided to command: salome kill <port(s)>"
401       return
402
403     from multiprocessing import Process
404     from killSalomeWithPort import killMyPort
405     import tempfile
406     for port in ports:
407       with tempfile.NamedTemporaryFile():
408         p = Process(target = killMyPort, args=(port,))
409         p.start()
410         p.join()
411     pass
412   #
413
414   def _killAll(self, unused=None):
415     try:
416       import PortManager # mandatory
417       from multiprocessing import Process
418       from killSalomeWithPort import killMyPort
419       ports = PortManager.getBusyPorts()
420
421       if ports:
422         import tempfile
423         for port in ports:
424           with tempfile.NamedTemporaryFile():
425             p = Process(target = killMyPort, args=(port,))
426             p.start()
427             p.join()
428     except ImportError:
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, unused=None):
446     print "Running with python", platform.python_version()
447     self._runAppli(["--version"])
448   #
449
450   def _usage(self, unused=None):
451     usage()
452   #
453
454   def _makeCoffee(self, unused=None):
455     print "                        ("
456     print "                          )     ("
457     print "                   ___...(-------)-....___"
458     print "               .-\"\"       )    (          \"\"-."
459     print "         .-\'``\'|-._             )         _.-|"
460     print "        /  .--.|   `\"\"---...........---\"\"`   |"
461     print "       /  /    |                             |"
462     print "       |  |    |                             |"
463     print "        \\  \\   |                             |"
464     print "         `\\ `\\ |                             |"
465     print "           `\\ `|            SALOME           |"
466     print "           _/ /\\            4 EVER           /"
467     print "          (__/  \\             <3            /"
468     print "       _..---\"\"` \\                         /`\"\"---.._"
469     print "    .-\'           \\                       /          \'-."
470     print "   :               `-.__             __.-\'              :"
471     print "   :                  ) \"\"---...---\"\" (                 :"
472     print "    \'._               `\"--...___...--\"`              _.\'"
473     print "      \\\"\"--..__                              __..--\"\"/"
474     print "       \'._     \"\"\"----.....______.....----\"\"\"     _.\'"
475     print "          `\"\"--..,,_____            _____,,..--\"\"`"
476     print "                        `\"\"\"----\"\"\"`"
477     print ""
478     print "                    SALOME is working for you; what else?"
479     print ""
480     sys.exit(0)
481   #
482
483   def _getCar(self, unused=None):
484     print "                                              _____________"
485     print "                                  ..---:::::::-----------. ::::;;."
486     print "                               .\'\"\"\"\"\"\"                  ;;   \\  \":."
487     print "                            .\'\'                          ;     \\   \"\\__."
488     print "                          .\'                            ;;      ;   \\\\\";"
489     print "                        .\'                              ;   _____;   \\\\/"
490     print "                      .\'                               :; ;\"     \\ ___:\'."
491     print "                    .\'--...........................    : =   ____:\"    \\ \\"
492     print "               ..-\"\"                               \"\"\"\'  o\"\"\"     ;     ; :"
493     print "          .--\"\"  .----- ..----...    _.-    --.  ..-\"     ;       ;     ; ;"
494     print "       .\"\"_-     \"--\"\"-----\'\"\"    _-\"        .-\"\"         ;        ;    .-."
495     print "    .\'  .\'   SALOME             .\"         .\"              ;       ;   /. |"
496     print "   /-./\'         4 EVER <3    .\"          /           _..  ;       ;   ;;;|"
497     print "  :  ;-.______               /       _________==.    /_  \\ ;       ;   ;;;;"
498     print "  ;  / |      \"\"\"\"\"\"\"\"\"\"\".---.\"\"\"\"\"\"\"          :    /\" \". |;       ; _; ;;;"
499     print " /\"-/  |                /   /                  /   /     ;|;      ;-\" | ;\';"
500     print ":-  :   \"\"\"----______  /   /              ____.   .  .\"\'. ;;   .-\"..T\"   ."
501     print "\'. \"  ___            \"\":   \'\"\"\"\"\"\"\"\"\"\"\"\"\"\"    .   ; ;    ;; ;.\" .\"   \'--\""
502     print " \",   __ \"\"\"  \"\"---... :- - - - - - - - - \' \'  ; ;  ;    ;;\"  .\""
503     print "  /. ;  \"\"\"---___                             ;  ; ;     ;|.\"\""
504     print " :  \":           \"\"\"----.    .-------.       ;   ; ;     ;:"
505     print "  \\  \'--__               \\   \\        \\     /    | ;     ;;"
506     print "   \'-..   \"\"\"\"---___      :   .______..\\ __/..-\"\"|  ;   ; ;"
507     print "       \"\"--..       \"\"\"--\"        m l s         .   \". . ;"
508     print "             \"\"------...                  ..--\"\"      \" :"
509     print "                        \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"    \\        /"
510     print "                                               \"------\""
511     print ""
512     print "                                Drive your simulation properly with SALOME!"
513     print ""
514     sys.exit(0)
515   #
516
517   # Add the following two methods since logger is not pickable
518   # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
519   def __getstate__(self):
520     d = dict(self.__dict__)
521     if hasattr(self, '_logger'):
522       del d['_logger']
523     return d
524   #
525   def __setstate__(self, d):
526     self.__dict__.update(d) # I *think* this is a safe way to do it
527   #
528   # Excluding self._logger from pickle operation imply using the following method to access logger
529   def getLogger(self):
530     if not hasattr(self, '_logger'):
531       self._logger = logging.getLogger(__name__)
532       #self._logger.setLevel(logging.DEBUG)
533       #self._logger.setLevel(logging.WARNING)
534       self._logger.setLevel(logging.ERROR)
535     return self._logger
536   #
537
538 if __name__ == "__main__":
539   if len(sys.argv) == 3:
540     context = pickle.loads(sys.argv[1])
541     args = pickle.loads(sys.argv[2])
542
543     (out, err) = context._startSalome(args)
544     if out:
545       sys.stdout.write(out)
546     if err:
547       sys.stderr.write(err)
548   else:
549     usage()
550 #