Salome HOME
b2fdc14bfcf04fd8fe1130d9e1faccce8294087f
[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     if platform.system() == 'Windows':
157       self.addToVariable('PATH', value)
158     else:
159       self.addToVariable('LD_LIBRARY_PATH', value)
160   #
161
162   """Append value to DYLD_LIBRARY_PATH environment variable"""
163   def addToDyldLibraryPath(self, value):
164     self.addToVariable('DYLD_LIBRARY_PATH', value)
165   #
166
167   """Append value to PYTHONPATH environment variable"""
168   def addToPythonPath(self, value):
169     self.addToVariable('PYTHONPATH', value)
170   #
171
172   """Set environment variable to value"""
173   def setVariable(self, name, value, overwrite=False):
174     env = os.getenv(name, '')
175     if env and not overwrite:
176       self.getLogger().warning("Environment variable already existing (and not overwritten): %s=%s", name, value)
177       return
178
179     if env:
180       self.getLogger().warning("Overwriting environment variable: %s=%s", name, value)
181
182     value = os.path.expandvars(value) # expand environment variables
183     self.getLogger().debug("Set environment variable: %s=%s", name, value)
184     os.environ[name] = value
185   #
186
187   """Unset environment variable"""
188   def unsetVariable(self, name):
189     if os.environ.has_key(name):
190       del os.environ[name]
191   #
192
193   """Append value to environment variable"""
194   def addToVariable(self, name, value, separator=os.pathsep):
195     if value == '':
196       return
197
198     value = os.path.expandvars(value) # expand environment variables
199     self.getLogger().debug("Add to %s: %s", name, value)
200     env = os.getenv(name, None)
201     if env is None:
202       os.environ[name] = value
203     else:
204       os.environ[name] = value + separator + env
205   #
206
207   ###################################
208   # This begins the private section #
209   ###################################
210
211   def __parseArguments(self, args):
212     if len(args) == 0 or args[0].startswith("-"):
213       return None, args
214
215     command = args[0]
216     options = args[1:]
217
218     availableCommands = {
219       'start'   : '_runAppli',
220       'context' : '_setContext',
221       'shell'   : '_runSession',
222       'connect' : '_runConsole',
223       'kill'    : '_kill',
224       'killall' : '_killAll',
225       'test'    : '_runTests',
226       'info'    : '_showInfo',
227       'help'    : '_usage',
228       'coffee'  : '_makeCoffee',
229       'car'     : '_getCar',
230       }
231
232     if not command in availableCommands.keys():
233       command = "start"
234       options = args
235
236     return availableCommands[command], options
237   #
238
239   """
240   Run SALOME!
241   Args consist in a mandatory command followed by optionnal parameters.
242   See usage for details on commands.
243   """
244   def _startSalome(self, args):
245     import os
246     import sys
247     try:
248       from setenv import add_path
249       absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
250       path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome"))
251       add_path(path, "PYTHONPATH")
252       path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome", "appliskel"))
253       add_path(path, "PYTHONPATH")
254
255     except:
256       pass
257
258     command, options = self.__parseArguments(args)
259     sys.argv = options
260
261     if command is None:
262       if args and args[0] in ["-h","--help","help"]:
263         usage()
264         sys.exit(0)
265       # try to default to "start" command
266       command = "_runAppli"
267
268     try:
269       res = getattr(self, command)(options) # run appropriate method
270       return res or (None, None)
271     except SystemExit, returncode:
272       if returncode != 0:
273         self.getLogger().warning("SystemExit %s in method %s.", returncode, command)
274       sys.exit(returncode)
275     except StandardError:
276       self.getLogger().error("Unexpected error:")
277       import traceback
278       traceback.print_exc()
279       sys.exit(1)
280     except SalomeContextException, e:
281       self.getLogger().error(e)
282       sys.exit(1)
283   #
284
285   def __setContextFromConfigFile(self, filename, reserved=None):
286     if reserved is None:
287       reserved = []
288     try:
289       unsetVars, configVars, reservedDict = parseConfigFile(filename, reserved)
290     except SalomeContextException, e:
291       msg = "%s"%e
292       file_dir = os.path.dirname(filename)
293       file_base = os.path.basename(filename)
294       base_no_ext, ext = os.path.splitext(file_base)
295       sh_file = os.path.join(file_dir, base_no_ext+'.sh')
296       if ext == ".cfg" and os.path.isfile(sh_file):
297         msg += "Found similar %s file; trying to parse this one instead..."%(base_no_ext+'.sh')
298         temp = tempfile.NamedTemporaryFile(suffix='.cfg')
299         try:
300           convertEnvFileToConfigFile(sh_file, temp.name, reserved)
301           self.__setContextFromConfigFile(temp.name, reserved)
302           msg += "OK\n"
303           self.getLogger().warning(msg)
304           temp.close()
305           return
306         except (ConfigParser.ParsingError, ValueError) as e:
307           msg += "Invalid token found when parsing file: %s\n"%(sh_file)
308           self.getLogger().error(msg)
309           temp.close()
310           sys.exit(1)
311       else:
312         self.getLogger().error(msg)
313         sys.exit(1)
314
315     # unset variables
316     for var in unsetVars:
317       self.unsetVariable(var)
318
319     # set context
320     for reserved in reservedDict:
321       a = filter(None, reservedDict[reserved]) # remove empty elements
322       a = [ os.path.realpath(x) for x in a ]
323       reformattedVals = os.pathsep.join(a)
324       if reserved in ["INCLUDE", "LIBPATH"]:
325         self.addToVariable(reserved, reformattedVals, separator=' ')
326       else:
327         self.addToVariable(reserved, reformattedVals)
328       pass
329
330     for key,val in configVars:
331       self.setVariable(key, val, overwrite=True)
332       pass
333
334     pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
335     pythonpath = [ os.path.realpath(x) for x in pythonpath ]
336     sys.path[:0] = pythonpath
337   #
338
339   def _runAppli(self, args=None):
340     if args is None:
341       args = []
342     # Initialize SALOME environment
343     sys.argv = ['runSalome'] + args
344     import setenv
345     setenv.main(True, exeName="salome start")
346
347     import runSalome
348     runSalome.runSalome()
349   #
350
351   def _setContext(self, args=None):
352     salome_context_set = os.getenv("SALOME_CONTEXT_SET")
353     if salome_context_set:
354       print "***"
355       print "*** SALOME context has already been set."
356       print "*** Enter 'exit' (only once!) to leave SALOME context."
357       print "***"
358       return
359
360     os.environ["SALOME_CONTEXT_SET"] = "yes"
361     print "***"
362     print "*** SALOME context is now set."
363     print "*** Enter 'exit' (only once!) to leave SALOME context."
364     print "***"
365
366     cmd = ["/bin/bash"]
367     proc = subprocess.Popen(cmd, shell=False, close_fds=True)
368     return proc.communicate()
369   #
370
371   def _runSession(self, args=None):
372     if args is None:
373       args = []
374     sys.argv = ['runSession'] + args
375     import runSession
376     params, args = runSession.configureSession(args, exe="salome shell")
377
378     sys.argv = ['runSession'] + args
379     import setenv
380     setenv.main(True)
381
382     return runSession.runSession(params, args)
383   #
384
385   def _runConsole(self, args=None):
386     if args is None:
387       args = []
388     # Initialize SALOME environment
389     sys.argv = ['runConsole']
390     import setenv
391     setenv.main(True)
392
393     import runConsole
394     return runConsole.connect(args)
395   #
396
397   def _kill(self, args=None):
398     if args is None:
399       args = []
400     ports = args
401     if not ports:
402       print "Port number(s) not provided to command: salome kill <port(s)>"
403       return
404
405     from multiprocessing import Process
406     from killSalomeWithPort import killMyPort
407     import tempfile
408     for port in ports:
409       with tempfile.NamedTemporaryFile():
410         p = Process(target = killMyPort, args=(port,))
411         p.start()
412         p.join()
413     pass
414   #
415
416   def _killAll(self, unused=None):
417     try:
418       import PortManager # mandatory
419       from multiprocessing import Process
420       from killSalomeWithPort import killMyPort
421       ports = PortManager.getBusyPorts()
422
423       if ports:
424         import tempfile
425         for port in ports:
426           with tempfile.NamedTemporaryFile():
427             p = Process(target = killMyPort, args=(port,))
428             p.start()
429             p.join()
430     except ImportError:
431       # :TODO: should be declared obsolete
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 #