]> SALOME platform Git repositories - modules/kernel.git/blob - bin/salomeContext.py
Salome HOME
fix
[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 runSalome(self, args):
113     # Run this module as a script, in order to use appropriate Python interpreter
114     # according to current path (initialized from environment files).
115     mpi_module_option = "--with_mpi_module="
116     mpi_module = [x for x in args if x.startswith(mpi_module_option)]
117     if mpi_module:
118       mpi_module = mpi_module[0][len(mpi_module_option):]
119       print "Trying to load MPI module: %s..."%mpi_module,
120       try:
121         out, err = subprocess.call(["modulecmd", "python", "load", mpi_module]).communicate()
122         exec out # define specific environment variables
123         print " OK"
124       except:
125         print " ** Failed **"
126         pass
127       args = [x for x in args if not x.startswith(mpi_module_option)]
128       pass
129
130     import os
131     absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
132     env_copy = os.environ.copy()
133     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)
134     msg = proc.communicate()
135     return msg, proc.returncode
136   #
137
138   """Append value to PATH environment variable"""
139   def addToPath(self, value):
140     self.addToVariable('PATH', value)
141   #
142
143   """Append value to LD_LIBRARY_PATH environment variable"""
144   def addToLdLibraryPath(self, value):
145     self.addToVariable('LD_LIBRARY_PATH', value)
146   #
147
148   """Append value to DYLD_LIBRARY_PATH environment variable"""
149   def addToDyldLibraryPath(self, value):
150     self.addToVariable('DYLD_LIBRARY_PATH', value)
151   #
152
153   """Append value to PYTHONPATH environment variable"""
154   def addToPythonPath(self, value):
155     self.addToVariable('PYTHONPATH', value)
156   #
157
158   """Set environment variable to value"""
159   def setVariable(self, name, value, overwrite=False):
160     env = os.getenv(name, '')
161     if env and not overwrite:
162       self.getLogger().warning("Environment variable already existing (and not overwritten): %s=%s", name, value)
163       return
164
165     if env:
166       self.getLogger().warning("Overwriting environment variable: %s=%s", name, value)
167
168     value = os.path.expandvars(value) # expand environment variables
169     self.getLogger().debug("Set environment variable: %s=%s", name, value)
170     os.environ[name] = value
171   #
172
173   """Unset environment variable"""
174   def unsetVariable(self, name):
175     if os.environ.has_key(name):
176       del os.environ[name]
177   #
178
179   """Append value to environment variable"""
180   def addToVariable(self, name, value, separator=os.pathsep):
181     if value == '':
182       return
183
184     value = os.path.expandvars(value) # expand environment variables
185     self.getLogger().debug("Add to %s: %s", name, value)
186     env = os.getenv(name, None)
187     if env is None:
188       os.environ[name] = value
189     else:
190       os.environ[name] = value + separator + env
191   #
192
193   ###################################
194   # This begins the private section #
195   ###################################
196
197   def __parseArguments(self, args):
198     if len(args) == 0 or args[0].startswith("-"):
199       return None, args
200
201     command = args[0]
202     options = args[1:]
203
204     availableCommands = {
205       'start' :   '_runAppli',
206       'shell' :   '_runSession',
207       'connect' : '_runConsole',
208       'killall':  '_killAll',
209       'info':     '_showInfo',
210       'help':     '_usage',
211       'coffee' :  '_makeCoffee'
212       }
213
214     if not command in availableCommands.keys():
215       command = "start"
216       options = args
217
218     return availableCommands[command], options
219   #
220
221   """
222   Run SALOME!
223   Args consist in a mandatory command followed by optionnal parameters.
224   See usage for details on commands.
225   """
226   def _startSalome(self, args):
227     try:
228       import os
229       absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
230       import sys
231       path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome"))
232       if not path in sys.path:
233         sys.path[:0] = [path]
234     except:
235       pass
236
237     command, options = self.__parseArguments(args)
238     sys.argv = options
239
240     if command is None:
241       if args and args[0] in ["-h","--help","help"]:
242         usage()
243         sys.exit(0)
244       # try to default to "start" command
245       command = "_runAppli"
246
247     try:
248       res = getattr(self, command)(options) # run appropriate method
249       return res or (None, None)
250     except SystemExit, returncode:
251       if returncode != 0:
252         self.getLogger().warning("SystemExit %s in method %s.", returncode, command)
253       sys.exit(returncode)
254     except StandardError:
255       self.getLogger().error("Unexpected error:")
256       import traceback
257       traceback.print_exc()
258       sys.exit(1)
259     except SalomeContextException, e:
260       self.getLogger().error(e)
261       sys.exit(1)
262   #
263
264   def __setEnvironmentFromConfigFile(self, filename, reserved=None):
265     if reserved is None:
266       reserved = []
267     try:
268       unsetVars, configVars, reservedDict = parseConfigFile(filename, reserved)
269     except SalomeContextException, e:
270       msg = "%s"%e
271       file_dir = os.path.dirname(filename)
272       file_base = os.path.basename(filename)
273       base_no_ext, ext = os.path.splitext(file_base)
274       sh_file = os.path.join(file_dir, base_no_ext+'.sh')
275       if ext == ".cfg" and os.path.isfile(sh_file):
276         msg += "Found similar %s file; trying to parse this one instead..."%(base_no_ext+'.sh')
277         temp = tempfile.NamedTemporaryFile(suffix='.cfg')
278         try:
279           convertEnvFileToConfigFile(sh_file, temp.name, reserved)
280           self.__setEnvironmentFromConfigFile(temp.name, reserved)
281           msg += "OK\n"
282           self.getLogger().warning(msg)
283           temp.close()
284           return
285         except (ConfigParser.ParsingError, ValueError) as e:
286           msg += "Invalid token found when parsing file: %s\n"%(sh_file)
287           self.getLogger().error(msg)
288           temp.close()
289           sys.exit(1)
290       else:
291         self.getLogger().error(msg)
292         sys.exit(1)
293
294     # unset variables
295     for var in unsetVars:
296       self.unsetVariable(var)
297
298     # set environment
299     for reserved in reservedDict:
300       a = filter(None, reservedDict[reserved]) # remove empty elements
301       a = [ os.path.realpath(x) for x in a ]
302       reformattedVals = os.pathsep.join(a)
303       self.addToVariable(reserved, reformattedVals)
304       pass
305
306     for key,val in configVars:
307       self.setVariable(key, val, overwrite=True)
308       pass
309
310     pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
311     pythonpath = [ os.path.realpath(x) for x in pythonpath ]
312     sys.path[:0] = pythonpath
313   #
314
315   def _runAppli(self, args=None):
316     if args is None:
317       args = []
318     # Initialize SALOME environment
319     sys.argv = ['runSalome'] + args
320     import setenv
321     setenv.main(True)
322
323     import runSalome
324     runSalome.runSalome()
325   #
326
327   def _runSession(self, args=None):
328     if args is None:
329       args = []
330     sys.argv = ['runSession'] + args
331     import runSession
332     params, args = runSession.configureSession(args, exe="salome shell")
333
334     sys.argv = ['runSession'] + args
335     import setenv
336     setenv.main(True)
337
338     return runSession.runSession(params, args)
339   #
340
341   def _runConsole(self, args=None):
342     if args is None:
343       args = []
344     # Initialize SALOME environment
345     sys.argv = ['runConsole'] + args
346     import setenv
347     setenv.main(True)
348
349     cmd = ["python", "-c", "import runConsole\nrunConsole.connect()" ]
350     proc = subprocess.Popen(cmd, shell=False, close_fds=True)
351     return proc.communicate()
352   #
353
354   def _killAll(self, args=None):
355     if args is None:
356       args = []
357     try:
358       import PortManager # mandatory
359       from multiprocessing import Process
360       from killSalomeWithPort import killMyPort
361       ports = PortManager.getBusyPorts()
362
363       if ports:
364         import tempfile
365         for port in ports:
366           with tempfile.NamedTemporaryFile():
367             p = Process(target = killMyPort, args=(port,))
368             p.start()
369             p.join()
370     except ImportError:
371       from killSalome import killAllPorts
372       killAllPorts()
373       pass
374
375   #
376
377   def _showInfo(self, args=None):
378     print "Running with python", platform.python_version()
379     self._runAppli(["--version"])
380   #
381
382   def _usage(self, unused=None):
383     usage()
384   #
385
386   def _makeCoffee(self, args=None):
387     print "                        ("
388     print "                          )     ("
389     print "                   ___...(-------)-....___"
390     print "               .-\"\"       )    (          \"\"-."
391     print "         .-\'``\'|-._             )         _.-|"
392     print "        /  .--.|   `\"\"---...........---\"\"`   |"
393     print "       /  /    |                             |"
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     sys.exit(0)
410   #
411
412   # Add the following two methods since logger is not pickable
413   # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
414   def __getstate__(self):
415     d = dict(self.__dict__)
416     if hasattr(self, '_logger'):
417       del d['_logger']
418     return d
419   #
420   def __setstate__(self, d):
421     self.__dict__.update(d) # I *think* this is a safe way to do it
422   #
423   # Excluding self._logger from pickle operation imply using the following method to access logger
424   def getLogger(self):
425     if not hasattr(self, '_logger'):
426       self._logger = logging.getLogger(__name__)
427       #self._logger.setLevel(logging.DEBUG)
428       #self._logger.setLevel(logging.WARNING)
429       self._logger.setLevel(logging.ERROR)
430     return self._logger
431   #
432
433 if __name__ == "__main__":
434   if len(sys.argv) == 3:
435     context = pickle.loads(sys.argv[1])
436     args = pickle.loads(sys.argv[2])
437
438     (out, err) = context._startSalome(args)
439     if out:
440       sys.stdout.write(out)
441     if err:
442       sys.stderr.write(err)
443   else:
444     usage()
445 #