Salome HOME
fix for LIBPATH and INCLUDE
[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', 'INCLUDE', 'LIBPATH', 'SALOME_PLUGINS_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       if reserved in ["INCLUDE", "LIBPATH"]:
313         self.addToVariable(reserved, reformattedVals, separator=' ')
314       else:
315         self.addToVariable(reserved, reformattedVals)
316       pass
317
318     for key,val in configVars:
319       self.setVariable(key, val, overwrite=True)
320       pass
321
322     pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
323     pythonpath = [ os.path.realpath(x) for x in pythonpath ]
324     sys.path[:0] = pythonpath
325   #
326
327   def _runAppli(self, args=None):
328     if args is None:
329       args = []
330     # Initialize SALOME context
331     sys.argv = ['runSalome'] + args
332     import setenv
333     setenv.main(True)
334
335     import runSalome
336     runSalome.runSalome()
337   #
338
339   def _setContext(self, args=None):
340     salome_context_set = os.getenv("SALOME_CONTEXT_SET")
341     if salome_context_set:
342       print "***"
343       print "*** SALOME context has already been set."
344       print "*** Enter 'exit' (only once!) to leave SALOME context."
345       print "***"
346       return
347
348     os.environ["SALOME_CONTEXT_SET"] = "yes"
349     print "***"
350     print "*** SALOME context is now set."
351     print "*** Enter 'exit' (only once!) to leave SALOME context."
352     print "***"
353
354     cmd = ["/bin/bash"]
355     proc = subprocess.Popen(cmd, shell=False, close_fds=True)
356     return proc.communicate()
357   #
358
359   def _runSession(self, args=None):
360     if args is None:
361       args = []
362     sys.argv = ['runSession'] + args
363     import runSession
364     params, args = runSession.configureSession(args, exe="salome shell")
365
366     sys.argv = ['runSession'] + args
367     import setenv
368     setenv.main(True)
369
370     return runSession.runSession(params, args)
371   #
372
373   def _runConsole(self, args=None):
374     if args is None:
375       args = []
376     # Initialize SALOME context
377     sys.argv = ['runConsole'] + args
378     import setenv
379     setenv.main(True)
380
381     cmd = ["python", "-c", "import runConsole\nrunConsole.connect()" ]
382     proc = subprocess.Popen(cmd, shell=False, close_fds=True)
383     return proc.communicate()
384   #
385
386   def _killAll(self, args=None):
387     if args is None:
388       args = []
389     try:
390       import PortManager # mandatory
391       from multiprocessing import Process
392       from killSalomeWithPort import killMyPort
393       ports = PortManager.getBusyPorts()
394
395       if ports:
396         import tempfile
397         for port in ports:
398           with tempfile.NamedTemporaryFile():
399             p = Process(target = killMyPort, args=(port,))
400             p.start()
401             p.join()
402     except ImportError:
403       from killSalome import killAllPorts
404       killAllPorts()
405       pass
406
407   #
408
409   def _showInfo(self, args=None):
410     print "Running with python", platform.python_version()
411     self._runAppli(["--version"])
412   #
413
414   def _usage(self, unused=None):
415     usage()
416   #
417
418   def _makeCoffee(self, args=None):
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     print "       \'._     \"\"\"----.....______.....----\"\"\"     _.\'"
439     print "          `\"\"--..,,_____            _____,,..--\"\"`"
440     print "                        `\"\"\"----\"\"\"`"
441     sys.exit(0)
442   #
443
444   # Add the following two methods since logger is not pickable
445   # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
446   def __getstate__(self):
447     d = dict(self.__dict__)
448     if hasattr(self, '_logger'):
449       del d['_logger']
450     return d
451   #
452   def __setstate__(self, d):
453     self.__dict__.update(d) # I *think* this is a safe way to do it
454   #
455   # Excluding self._logger from pickle operation imply using the following method to access logger
456   def getLogger(self):
457     if not hasattr(self, '_logger'):
458       self._logger = logging.getLogger(__name__)
459       #self._logger.setLevel(logging.DEBUG)
460       #self._logger.setLevel(logging.WARNING)
461       self._logger.setLevel(logging.ERROR)
462     return self._logger
463   #
464
465 if __name__ == "__main__":
466   if len(sys.argv) == 3:
467     context = pickle.loads(sys.argv[1])
468     args = pickle.loads(sys.argv[2])
469
470     (out, err) = context._startSalome(args)
471     if out:
472       sys.stdout.write(out)
473     if err:
474       sys.stderr.write(err)
475   else:
476     usage()
477 #