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