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