Salome HOME
add 'context' command
[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     killall       Kill all SALOME running sessions for current user
49     info          Display some information about SALOME
50     help          Show this message
51     coffee        Yes! SALOME can also make coffee!!
52
53 If no command is given, default to start.
54
55 Command options:
56 ================
57     Use salome <command> --help to show help on command ; available for start
58     and shell commands.
59
60 --config=<file,folder,...>
61 ==========================
62     Initialize SALOME context from a list of context files and/or a list
63     of folders containing context files. The list is comma-separated, whithout
64     any blank characters.
65 '''
66
67   print msg
68 #
69
70 """
71 The SalomeContext class in an API to configure SALOME context then
72 start SALOME using a single python command.
73
74 """
75 class SalomeContext:
76   """
77   Initialize context from a list of configuration files
78   identified by their names.
79   These files should be in appropriate (new .cfg) format.
80   However you can give old .sh environment files; in this case,
81   the SalomeContext class will try to automatically convert them
82   to .cfg format before setting the context.
83   """
84   def __init__(self, configFileNames=0):
85     #it could be None explicitely (if user use multiples setVariable...for standalone)
86     if configFileNames is None:
87        return
88     configFileNames = configFileNames or []
89     if len(configFileNames) == 0:
90       raise SalomeContextException("No configuration files given")
91
92     reserved=['PATH', 'DYLD_LIBRARY_PATH', 'LD_LIBRARY_PATH', 'PYTHONPATH', 'MANPATH', 'PV_PLUGIN_PATH']
93     for filename in configFileNames:
94       basename, extension = os.path.splitext(filename)
95       if extension == ".cfg":
96         self.__setContextFromConfigFile(filename, reserved)
97       elif extension == ".sh":
98         #new convert procedures, temporary could be use not to be automatically deleted
99         #temp = tempfile.NamedTemporaryFile(suffix='.cfg', delete=False)
100         temp = tempfile.NamedTemporaryFile(suffix='.cfg')
101         try:
102           convertEnvFileToConfigFile(filename, temp.name, reserved)
103           self.__setContextFromConfigFile(temp.name, reserved)
104           temp.close()
105         except (ConfigParser.ParsingError, ValueError) as e:
106           self.getLogger().error("Invalid token found when parsing file: %s\n"%(filename))
107           temp.close()
108           sys.exit(1)
109       else:
110         self.getLogger().warning("Unrecognized extension for configuration file: %s", filename)
111   #
112
113   def __loadMPI(self, module_name):
114     print "Trying to load MPI module: %s..."%module_name,
115     try:
116       out, err = subprocess.Popen(["modulecmd", "python", "load", module_name], stdout=subprocess.PIPE).communicate()
117       exec out # define specific environment variables
118       print " OK"
119     except:
120       print " ** Failed **"
121       pass
122   #
123
124   def runSalome(self, args):
125     import os
126     # Run this module as a script, in order to use appropriate Python interpreter
127     # according to current path (initialized from context files).
128     mpi_module_option = "--with-mpi-module="
129     mpi_module = [x for x in args if x.startswith(mpi_module_option)]
130     if mpi_module:
131       mpi_module = mpi_module[0][len(mpi_module_option):]
132       self.__loadMPI(mpi_module)
133       args = [x for x in args if not x.startswith(mpi_module_option)]
134     else:
135       mpi_module = os.getenv("SALOME_MPI_MODULE_NAME", None)
136       if mpi_module:
137         self.__loadMPI(mpi_module)
138
139     absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
140     env_copy = os.environ.copy()
141     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)
142     msg = proc.communicate()
143     return msg, proc.returncode
144   #
145
146   """Append value to PATH environment variable"""
147   def addToPath(self, value):
148     self.addToVariable('PATH', value)
149   #
150
151   """Append value to LD_LIBRARY_PATH environment variable"""
152   def addToLdLibraryPath(self, value):
153     self.addToVariable('LD_LIBRARY_PATH', value)
154   #
155
156   """Append value to DYLD_LIBRARY_PATH environment variable"""
157   def addToDyldLibraryPath(self, value):
158     self.addToVariable('DYLD_LIBRARY_PATH', value)
159   #
160
161   """Append value to PYTHONPATH environment variable"""
162   def addToPythonPath(self, value):
163     self.addToVariable('PYTHONPATH', value)
164   #
165
166   """Set environment variable to value"""
167   def setVariable(self, name, value, overwrite=False):
168     env = os.getenv(name, '')
169     if env and not overwrite:
170       self.getLogger().warning("Environment variable already existing (and not overwritten): %s=%s", name, value)
171       return
172
173     if env:
174       self.getLogger().warning("Overwriting environment variable: %s=%s", name, value)
175
176     value = os.path.expandvars(value) # expand environment variables
177     self.getLogger().debug("Set environment variable: %s=%s", name, value)
178     os.environ[name] = value
179   #
180
181   """Unset environment variable"""
182   def unsetVariable(self, name):
183     if os.environ.has_key(name):
184       del os.environ[name]
185   #
186
187   """Append value to environment variable"""
188   def addToVariable(self, name, value, separator=os.pathsep):
189     if value == '':
190       return
191
192     value = os.path.expandvars(value) # expand environment variables
193     self.getLogger().debug("Add to %s: %s", name, value)
194     env = os.getenv(name, None)
195     if env is None:
196       os.environ[name] = value
197     else:
198       os.environ[name] = value + separator + env
199   #
200
201   ###################################
202   # This begins the private section #
203   ###################################
204
205   def __parseArguments(self, args):
206     if len(args) == 0 or args[0].startswith("-"):
207       return None, args
208
209     command = args[0]
210     options = args[1:]
211
212     availableCommands = {
213       'start' :   '_runAppli',
214       'context' : '_setContext',
215       'shell' :   '_runSession',
216       'connect' : '_runConsole',
217       'killall':  '_killAll',
218       'info':     '_showInfo',
219       'help':     '_usage',
220       'coffee' :  '_makeCoffee'
221       }
222
223     if not command in availableCommands.keys():
224       command = "start"
225       options = args
226
227     return availableCommands[command], options
228   #
229
230   """
231   Run SALOME!
232   Args consist in a mandatory command followed by optionnal parameters.
233   See usage for details on commands.
234   """
235   def _startSalome(self, args):
236     try:
237       import os
238       absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
239       import sys
240       path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome"))
241       if not path in sys.path:
242         sys.path[:0] = [path]
243     except:
244       pass
245
246     command, options = self.__parseArguments(args)
247     sys.argv = options
248
249     if command is None:
250       if args and args[0] in ["-h","--help","help"]:
251         usage()
252         sys.exit(0)
253       # try to default to "start" command
254       command = "_runAppli"
255
256     try:
257       res = getattr(self, command)(options) # run appropriate method
258       return res or (None, None)
259     except SystemExit, returncode:
260       if returncode != 0:
261         self.getLogger().warning("SystemExit %s in method %s.", returncode, command)
262       sys.exit(returncode)
263     except StandardError:
264       self.getLogger().error("Unexpected error:")
265       import traceback
266       traceback.print_exc()
267       sys.exit(1)
268     except SalomeContextException, e:
269       self.getLogger().error(e)
270       sys.exit(1)
271   #
272
273   def __setContextFromConfigFile(self, filename, reserved=None):
274     if reserved is None:
275       reserved = []
276     try:
277       unsetVars, configVars, reservedDict = parseConfigFile(filename, reserved)
278     except SalomeContextException, e:
279       msg = "%s"%e
280       file_dir = os.path.dirname(filename)
281       file_base = os.path.basename(filename)
282       base_no_ext, ext = os.path.splitext(file_base)
283       sh_file = os.path.join(file_dir, base_no_ext+'.sh')
284       if ext == ".cfg" and os.path.isfile(sh_file):
285         msg += "Found similar %s file; trying to parse this one instead..."%(base_no_ext+'.sh')
286         temp = tempfile.NamedTemporaryFile(suffix='.cfg')
287         try:
288           convertEnvFileToConfigFile(sh_file, temp.name, reserved)
289           self.__setContextFromConfigFile(temp.name, reserved)
290           msg += "OK\n"
291           self.getLogger().warning(msg)
292           temp.close()
293           return
294         except (ConfigParser.ParsingError, ValueError) as e:
295           msg += "Invalid token found when parsing file: %s\n"%(sh_file)
296           self.getLogger().error(msg)
297           temp.close()
298           sys.exit(1)
299       else:
300         self.getLogger().error(msg)
301         sys.exit(1)
302
303     # unset variables
304     for var in unsetVars:
305       self.unsetVariable(var)
306
307     # set context
308     for reserved in reservedDict:
309       a = filter(None, reservedDict[reserved]) # remove empty elements
310       a = [ os.path.realpath(x) for x in a ]
311       reformattedVals = os.pathsep.join(a)
312       self.addToVariable(reserved, reformattedVals)
313       pass
314
315     for key,val in configVars:
316       self.setVariable(key, val, overwrite=True)
317       pass
318
319     pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
320     pythonpath = [ os.path.realpath(x) for x in pythonpath ]
321     sys.path[:0] = pythonpath
322   #
323
324   def _runAppli(self, args=None):
325     if args is None:
326       args = []
327     # Initialize SALOME context
328     sys.argv = ['runSalome'] + args
329     import setenv
330     setenv.main(True)
331
332     import runSalome
333     runSalome.runSalome()
334   #
335
336   def _setContext(self, args=None):
337     salome_context_set = os.getenv("SALOME_CONTEXT_SET")
338     if salome_context_set:
339       print "***"
340       print "*** SALOME context has already been set."
341       print "*** Enter 'exit' (only once!) to leave SALOME context."
342       print "***"
343       return
344
345     os.environ["SALOME_CONTEXT_SET"] = "yes"
346     print "***"
347     print "*** SALOME context is now set."
348     print "*** Enter 'exit' (only once!) to leave SALOME context."
349     print "***"
350
351     cmd = ["/bin/bash"]
352     proc = subprocess.Popen(cmd, shell=False, close_fds=True)
353     return proc.communicate()
354   #
355
356   def _runSession(self, args=None):
357     if args is None:
358       args = []
359     sys.argv = ['runSession'] + args
360     import runSession
361     params, args = runSession.configureSession(args, exe="salome shell")
362
363     sys.argv = ['runSession'] + args
364     import setenv
365     setenv.main(True)
366
367     return runSession.runSession(params, args)
368   #
369
370   def _runConsole(self, args=None):
371     if args is None:
372       args = []
373     # Initialize SALOME context
374     sys.argv = ['runConsole'] + args
375     import setenv
376     setenv.main(True)
377
378     cmd = ["python", "-c", "import runConsole\nrunConsole.connect()" ]
379     proc = subprocess.Popen(cmd, shell=False, close_fds=True)
380     return proc.communicate()
381   #
382
383   def _killAll(self, args=None):
384     if args is None:
385       args = []
386     try:
387       import PortManager # mandatory
388       from multiprocessing import Process
389       from killSalomeWithPort import killMyPort
390       ports = PortManager.getBusyPorts()
391
392       if ports:
393         import tempfile
394         for port in ports:
395           with tempfile.NamedTemporaryFile():
396             p = Process(target = killMyPort, args=(port,))
397             p.start()
398             p.join()
399     except ImportError:
400       from killSalome import killAllPorts
401       killAllPorts()
402       pass
403
404   #
405
406   def _showInfo(self, args=None):
407     print "Running with python", platform.python_version()
408     self._runAppli(["--version"])
409   #
410
411   def _usage(self, unused=None):
412     usage()
413   #
414
415   def _makeCoffee(self, args=None):
416     print "                        ("
417     print "                          )     ("
418     print "                   ___...(-------)-....___"
419     print "               .-\"\"       )    (          \"\"-."
420     print "         .-\'``\'|-._             )         _.-|"
421     print "        /  .--.|   `\"\"---...........---\"\"`   |"
422     print "       /  /    |                             |"
423     print "       |  |    |                             |"
424     print "        \\  \\   |                             |"
425     print "         `\\ `\\ |                             |"
426     print "           `\\ `|                             |"
427     print "           _/ /\\                             /"
428     print "          (__/  \\                           /"
429     print "       _..---\"\"` \\                         /`\"\"---.._"
430     print "    .-\'           \\                       /          \'-."
431     print "   :               `-.__             __.-\'              :"
432     print "   :                  ) \"\"---...---\"\" (                 :"
433     print "    \'._               `\"--...___...--\"`              _.\'"
434     print "      \\\"\"--..__                              __..--\"\"/"
435     print "       \'._     \"\"\"----.....______.....----\"\"\"     _.\'"
436     print "          `\"\"--..,,_____            _____,,..--\"\"`"
437     print "                        `\"\"\"----\"\"\"`"
438     sys.exit(0)
439   #
440
441   # Add the following two methods since logger is not pickable
442   # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
443   def __getstate__(self):
444     d = dict(self.__dict__)
445     if hasattr(self, '_logger'):
446       del d['_logger']
447     return d
448   #
449   def __setstate__(self, d):
450     self.__dict__.update(d) # I *think* this is a safe way to do it
451   #
452   # Excluding self._logger from pickle operation imply using the following method to access logger
453   def getLogger(self):
454     if not hasattr(self, '_logger'):
455       self._logger = logging.getLogger(__name__)
456       #self._logger.setLevel(logging.DEBUG)
457       #self._logger.setLevel(logging.WARNING)
458       self._logger.setLevel(logging.ERROR)
459     return self._logger
460   #
461
462 if __name__ == "__main__":
463   if len(sys.argv) == 3:
464     context = pickle.loads(sys.argv[1])
465     args = pickle.loads(sys.argv[2])
466
467     (out, err) = context._startSalome(args)
468     if out:
469       sys.stdout.write(out)
470     if err:
471       sys.stderr.write(err)
472   else:
473     usage()
474 #