Salome HOME
ac6f616bb167d4716ed0fec45a40edef259bd77e
[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, softwares=None):
451     config = ConfigParser.SafeConfigParser()
452     absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
453     filename = os.path.join(absoluteAppliPath, "sha1_collections.txt")
454     versions = {}
455     max_len = 0
456     with open(filename) as f:
457       for line in f:
458         try:
459           software, version, sha1 = line.split()
460           versions[software.upper()] = version
461           if len(software) > max_len:
462             max_len = len(software)
463         except:
464           pass
465         pass
466       pass
467     if softwares:
468       for soft in softwares:
469         if versions.has_key(soft.upper()):
470           print soft.upper().rjust(max_len), versions[soft.upper()]
471     else:
472       for name, version in versions.items():
473         print name.rjust(max_len), versions[name]
474     pass
475
476   def _showInfo(self, args=None):
477     if args is None:
478       args = []
479
480     usage = "Usage: salome info [options]"
481     epilog  = """\n
482 Display some information about SALOME.\n
483 Available options are:
484     -p,--ports        Show the list of busy ports (running SALOME instances).
485     -s,--softwares    Show the list and versions of SALOME softwares.
486     -v,--version      Show running SALOME version.
487     -h,--help         Show this message.
488 """
489     if not args:
490       args = ["--version"]
491
492     if "-h" in args or "--help" in args:
493       print usage + epilog
494       return
495
496     if "-p" in args or "--ports" in args:
497       import PortManager
498       ports = PortManager.getBusyPorts()
499       print "SALOME instances are running on ports:", ports
500       if ports:
501         print "Last started instance on port %s"%ports[-1]
502
503     if "-s" in args or "--softwares" in args:
504       self._showSoftwareVersions()
505
506     if "-v" in args or "--version" in args:
507       print "Running with python", platform.python_version()
508       self._runAppli(["--version"])
509   #
510
511   def _usage(self, unused=None):
512     usage()
513   #
514
515   def _makeCoffee(self, unused=None):
516     print "                        ("
517     print "                          )     ("
518     print "                   ___...(-------)-....___"
519     print "               .-\"\"       )    (          \"\"-."
520     print "         .-\'``\'|-._             )         _.-|"
521     print "        /  .--.|   `\"\"---...........---\"\"`   |"
522     print "       /  /    |                             |"
523     print "       |  |    |                             |"
524     print "        \\  \\   |                             |"
525     print "         `\\ `\\ |                             |"
526     print "           `\\ `|            SALOME           |"
527     print "           _/ /\\            4 EVER           /"
528     print "          (__/  \\             <3            /"
529     print "       _..---\"\"` \\                         /`\"\"---.._"
530     print "    .-\'           \\                       /          \'-."
531     print "   :               `-.__             __.-\'              :"
532     print "   :                  ) \"\"---...---\"\" (                 :"
533     print "    \'._               `\"--...___...--\"`              _.\'"
534     print "      \\\"\"--..__                              __..--\"\"/"
535     print "       \'._     \"\"\"----.....______.....----\"\"\"     _.\'"
536     print "          `\"\"--..,,_____            _____,,..--\"\"`"
537     print "                        `\"\"\"----\"\"\"`"
538     print ""
539     print "                    SALOME is working for you; what else?"
540     print ""
541     sys.exit(0)
542   #
543
544   def _getCar(self, unused=None):
545     print "                                              _____________"
546     print "                                  ..---:::::::-----------. ::::;;."
547     print "                               .\'\"\"\"\"\"\"                  ;;   \\  \":."
548     print "                            .\'\'                          ;     \\   \"\\__."
549     print "                          .\'                            ;;      ;   \\\\\";"
550     print "                        .\'                              ;   _____;   \\\\/"
551     print "                      .\'                               :; ;\"     \\ ___:\'."
552     print "                    .\'--...........................    : =   ____:\"    \\ \\"
553     print "               ..-\"\"                               \"\"\"\'  o\"\"\"     ;     ; :"
554     print "          .--\"\"  .----- ..----...    _.-    --.  ..-\"     ;       ;     ; ;"
555     print "       .\"\"_-     \"--\"\"-----\'\"\"    _-\"        .-\"\"         ;        ;    .-."
556     print "    .\'  .\'   SALOME             .\"         .\"              ;       ;   /. |"
557     print "   /-./\'         4 EVER <3    .\"          /           _..  ;       ;   ;;;|"
558     print "  :  ;-.______               /       _________==.    /_  \\ ;       ;   ;;;;"
559     print "  ;  / |      \"\"\"\"\"\"\"\"\"\"\".---.\"\"\"\"\"\"\"          :    /\" \". |;       ; _; ;;;"
560     print " /\"-/  |                /   /                  /   /     ;|;      ;-\" | ;\';"
561     print ":-  :   \"\"\"----______  /   /              ____.   .  .\"\'. ;;   .-\"..T\"   ."
562     print "\'. \"  ___            \"\":   \'\"\"\"\"\"\"\"\"\"\"\"\"\"\"    .   ; ;    ;; ;.\" .\"   \'--\""
563     print " \",   __ \"\"\"  \"\"---... :- - - - - - - - - \' \'  ; ;  ;    ;;\"  .\""
564     print "  /. ;  \"\"\"---___                             ;  ; ;     ;|.\"\""
565     print " :  \":           \"\"\"----.    .-------.       ;   ; ;     ;:"
566     print "  \\  \'--__               \\   \\        \\     /    | ;     ;;"
567     print "   \'-..   \"\"\"\"---___      :   .______..\\ __/..-\"\"|  ;   ; ;"
568     print "       \"\"--..       \"\"\"--\"        m l s         .   \". . ;"
569     print "             \"\"------...                  ..--\"\"      \" :"
570     print "                        \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"    \\        /"
571     print "                                               \"------\""
572     print ""
573     print "                                Drive your simulation properly with SALOME!"
574     print ""
575     sys.exit(0)
576   #
577
578   # Add the following two methods since logger is not pickable
579   # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
580   def __getstate__(self):
581     d = dict(self.__dict__)
582     if hasattr(self, '_logger'):
583       del d['_logger']
584     return d
585   #
586   def __setstate__(self, d):
587     self.__dict__.update(d) # I *think* this is a safe way to do it
588   #
589   # Excluding self._logger from pickle operation imply using the following method to access logger
590   def getLogger(self):
591     if not hasattr(self, '_logger'):
592       self._logger = logging.getLogger(__name__)
593       #self._logger.setLevel(logging.DEBUG)
594       #self._logger.setLevel(logging.WARNING)
595       self._logger.setLevel(logging.ERROR)
596     return self._logger
597   #
598
599 if __name__ == "__main__":
600   if len(sys.argv) == 3:
601     context = pickle.loads(sys.argv[1])
602     args = pickle.loads(sys.argv[2])
603
604     (out, err) = context._startSalome(args)
605     if out:
606       sys.stdout.write(out)
607     if err:
608       sys.stderr.write(err)
609   else:
610     usage()
611 #