Salome HOME
Add command to show software versions
[modules/yacs.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       if False:
299         msg += "Found similar %s file; trying to parse this one instead..."%(base_no_ext+'.sh')
300         temp = tempfile.NamedTemporaryFile(suffix='.cfg')
301         try:
302           convertEnvFileToConfigFile(sh_file, temp.name, reserved)
303           self.__setContextFromConfigFile(temp.name, reserved)
304           msg += "OK\n"
305           self.getLogger().warning(msg)
306           temp.close()
307           return
308         except (ConfigParser.ParsingError, ValueError) as e:
309           msg += "Invalid token found when parsing file: %s\n"%(sh_file)
310           self.getLogger().error(msg)
311           temp.close()
312           sys.exit(1)
313       else:
314         self.getLogger().error(msg)
315         sys.exit(1)
316
317     # unset variables
318     for var in unsetVars:
319       self.unsetVariable(var)
320
321     # set context
322     for reserved in reservedDict:
323       a = filter(None, reservedDict[reserved]) # remove empty elements
324       a = [ os.path.realpath(x) for x in a ]
325       reformattedVals = os.pathsep.join(a)
326       if reserved in ["INCLUDE", "LIBPATH"]:
327         self.addToVariable(reserved, reformattedVals, separator=' ')
328       else:
329         self.addToVariable(reserved, reformattedVals)
330       pass
331
332     for key,val in configVars:
333       self.setVariable(key, val, overwrite=True)
334       pass
335
336     pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
337     pythonpath = [ os.path.realpath(x) for x in pythonpath ]
338     sys.path[:0] = pythonpath
339   #
340
341   def _runAppli(self, args=None):
342     if args is None:
343       args = []
344     # Initialize SALOME environment
345     sys.argv = ['runSalome'] + args
346     import setenv
347     setenv.main(True, exeName="salome start")
348
349     import runSalome
350     runSalome.runSalome()
351   #
352
353   def _setContext(self, args=None):
354     salome_context_set = os.getenv("SALOME_CONTEXT_SET")
355     if salome_context_set:
356       print "***"
357       print "*** SALOME context has already been set."
358       print "*** Enter 'exit' (only once!) to leave SALOME context."
359       print "***"
360       return
361
362     os.environ["SALOME_CONTEXT_SET"] = "yes"
363     print "***"
364     print "*** SALOME context is now set."
365     print "*** Enter 'exit' (only once!) to leave SALOME context."
366     print "***"
367
368     cmd = ["/bin/bash"]
369     proc = subprocess.Popen(cmd, shell=False, close_fds=True)
370     return proc.communicate()
371   #
372
373   def _runSession(self, args=None):
374     if args is None:
375       args = []
376     sys.argv = ['runSession'] + args
377     import runSession
378     params, args = runSession.configureSession(args, exe="salome shell")
379
380     sys.argv = ['runSession'] + args
381     import setenv
382     setenv.main(True)
383
384     return runSession.runSession(params, args)
385   #
386
387   def _runConsole(self, args=None):
388     if args is None:
389       args = []
390     # Initialize SALOME environment
391     sys.argv = ['runConsole']
392     import setenv
393     setenv.main(True)
394
395     import runConsole
396     return runConsole.connect(args)
397   #
398
399   def _kill(self, args=None):
400     if args is None:
401       args = []
402     ports = args
403     if not ports:
404       print "Port number(s) not provided to command: salome kill <port(s)>"
405       return
406
407     from multiprocessing import Process
408     from killSalomeWithPort import killMyPort
409     import tempfile
410     for port in ports:
411       with tempfile.NamedTemporaryFile():
412         p = Process(target = killMyPort, args=(port,))
413         p.start()
414         p.join()
415     pass
416   #
417
418   def _killAll(self, unused=None):
419     try:
420       import PortManager # mandatory
421       from multiprocessing import Process
422       from killSalomeWithPort import killMyPort
423       ports = PortManager.getBusyPorts()
424
425       if ports:
426         import tempfile
427         for port in ports:
428           with tempfile.NamedTemporaryFile():
429             p = Process(target = killMyPort, args=(port,))
430             p.start()
431             p.join()
432     except ImportError:
433       # :TODO: should be declared obsolete
434       from killSalome import killAllPorts
435       killAllPorts()
436       pass
437   #
438
439   def _runTests(self, args=None):
440     if args is None:
441       args = []
442     sys.argv = ['runTests']
443     import setenv
444     setenv.main(True)
445
446     import runTests
447     return runTests.runTests(args, exe="salome test")
448   #
449
450   def _showSoftwareVersions(self):
451     config = ConfigParser.SafeConfigParser()
452     absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
453     filename = os.path.join(absoluteAppliPath, ".softwares_versions")
454     try:
455       config.read(filename)
456       sections = config.sections()
457       for section in sections:
458         entries = config.items(section, raw=True) # do not use interpolation
459         for key,val in entries:
460           version,text = [ x.strip() for x in val.split(',') ]
461           print "%s: %s"%(text, version)
462     except:
463       import traceback
464       traceback.print_exc()
465       return
466     pass
467
468   def _showInfo(self, args=None):
469     if args is None:
470       args = []
471
472     usage = "Usage: salome info [options]"
473     epilog  = """\n
474 Display some information about SALOME.\n
475 Available options are:
476     -p,--ports        Show the list of busy ports (running SALOME instances).
477     -s,--softwares    Show the list and versions of SALOME softwares.
478     -v,--version      Show running SALOME version.
479     -h,--help         Show this message.
480 """
481     if not args:
482       args = ["--version"]
483
484     if "-h" in args or "--help" in args:
485       print usage + epilog
486       return
487
488     if "-p" in args or "--ports" in args:
489       import PortManager
490       ports = PortManager.getBusyPorts()
491       print "SALOME instances are running on ports:", ports
492       if ports:
493         print "Last started instance on port %s"%ports[-1]
494
495     if "-s" in args or "--softwares" in args:
496       self._showSoftwareVersions()
497
498     if "-v" in args or "--version" in args:
499       print "Running with python", platform.python_version()
500       self._runAppli(["--version"])
501   #
502
503   def _usage(self, unused=None):
504     usage()
505   #
506
507   def _makeCoffee(self, unused=None):
508     print "                        ("
509     print "                          )     ("
510     print "                   ___...(-------)-....___"
511     print "               .-\"\"       )    (          \"\"-."
512     print "         .-\'``\'|-._             )         _.-|"
513     print "        /  .--.|   `\"\"---...........---\"\"`   |"
514     print "       /  /    |                             |"
515     print "       |  |    |                             |"
516     print "        \\  \\   |                             |"
517     print "         `\\ `\\ |                             |"
518     print "           `\\ `|            SALOME           |"
519     print "           _/ /\\            4 EVER           /"
520     print "          (__/  \\             <3            /"
521     print "       _..---\"\"` \\                         /`\"\"---.._"
522     print "    .-\'           \\                       /          \'-."
523     print "   :               `-.__             __.-\'              :"
524     print "   :                  ) \"\"---...---\"\" (                 :"
525     print "    \'._               `\"--...___...--\"`              _.\'"
526     print "      \\\"\"--..__                              __..--\"\"/"
527     print "       \'._     \"\"\"----.....______.....----\"\"\"     _.\'"
528     print "          `\"\"--..,,_____            _____,,..--\"\"`"
529     print "                        `\"\"\"----\"\"\"`"
530     print ""
531     print "                    SALOME is working for you; what else?"
532     print ""
533     sys.exit(0)
534   #
535
536   def _getCar(self, unused=None):
537     print "                                              _____________"
538     print "                                  ..---:::::::-----------. ::::;;."
539     print "                               .\'\"\"\"\"\"\"                  ;;   \\  \":."
540     print "                            .\'\'                          ;     \\   \"\\__."
541     print "                          .\'                            ;;      ;   \\\\\";"
542     print "                        .\'                              ;   _____;   \\\\/"
543     print "                      .\'                               :; ;\"     \\ ___:\'."
544     print "                    .\'--...........................    : =   ____:\"    \\ \\"
545     print "               ..-\"\"                               \"\"\"\'  o\"\"\"     ;     ; :"
546     print "          .--\"\"  .----- ..----...    _.-    --.  ..-\"     ;       ;     ; ;"
547     print "       .\"\"_-     \"--\"\"-----\'\"\"    _-\"        .-\"\"         ;        ;    .-."
548     print "    .\'  .\'   SALOME             .\"         .\"              ;       ;   /. |"
549     print "   /-./\'         4 EVER <3    .\"          /           _..  ;       ;   ;;;|"
550     print "  :  ;-.______               /       _________==.    /_  \\ ;       ;   ;;;;"
551     print "  ;  / |      \"\"\"\"\"\"\"\"\"\"\".---.\"\"\"\"\"\"\"          :    /\" \". |;       ; _; ;;;"
552     print " /\"-/  |                /   /                  /   /     ;|;      ;-\" | ;\';"
553     print ":-  :   \"\"\"----______  /   /              ____.   .  .\"\'. ;;   .-\"..T\"   ."
554     print "\'. \"  ___            \"\":   \'\"\"\"\"\"\"\"\"\"\"\"\"\"\"    .   ; ;    ;; ;.\" .\"   \'--\""
555     print " \",   __ \"\"\"  \"\"---... :- - - - - - - - - \' \'  ; ;  ;    ;;\"  .\""
556     print "  /. ;  \"\"\"---___                             ;  ; ;     ;|.\"\""
557     print " :  \":           \"\"\"----.    .-------.       ;   ; ;     ;:"
558     print "  \\  \'--__               \\   \\        \\     /    | ;     ;;"
559     print "   \'-..   \"\"\"\"---___      :   .______..\\ __/..-\"\"|  ;   ; ;"
560     print "       \"\"--..       \"\"\"--\"        m l s         .   \". . ;"
561     print "             \"\"------...                  ..--\"\"      \" :"
562     print "                        \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"    \\        /"
563     print "                                               \"------\""
564     print ""
565     print "                                Drive your simulation properly with SALOME!"
566     print ""
567     sys.exit(0)
568   #
569
570   # Add the following two methods since logger is not pickable
571   # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
572   def __getstate__(self):
573     d = dict(self.__dict__)
574     if hasattr(self, '_logger'):
575       del d['_logger']
576     return d
577   #
578   def __setstate__(self, d):
579     self.__dict__.update(d) # I *think* this is a safe way to do it
580   #
581   # Excluding self._logger from pickle operation imply using the following method to access logger
582   def getLogger(self):
583     if not hasattr(self, '_logger'):
584       self._logger = logging.getLogger(__name__)
585       #self._logger.setLevel(logging.DEBUG)
586       #self._logger.setLevel(logging.WARNING)
587       self._logger.setLevel(logging.ERROR)
588     return self._logger
589   #
590
591 if __name__ == "__main__":
592   if len(sys.argv) == 3:
593     context = pickle.loads(sys.argv[1])
594     args = pickle.loads(sys.argv[2])
595
596     (out, err) = context._startSalome(args)
597     if out:
598       sys.stdout.write(out)
599     if err:
600       sys.stderr.write(err)
601   else:
602     usage()
603 #