Salome HOME
replace subprocess wait by communicate
[modules/yacs.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', '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         except ConfigParser.ParsingError, e:
93           self.getLogger().warning("Invalid token found when parsing file: %s\n"%(filename))
94           print e
95           print '\n'
96         finally:
97           # Automatically cleans up the file
98           temp.close()
99       else:
100         self.getLogger().warning("Unrecognized extension for configuration file: %s", filename)
101   #
102
103   def runSalome(self, args):
104     # Run this module as a script, in order to use appropriate Python interpreter
105     # according to current path (initialized from environment files).
106     kill = False
107     for e in args:
108       if "--shutdown-server" in e:
109         kill = True
110         args.remove(e)
111
112     absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
113     proc = subprocess.Popen(['python', os.path.join(absoluteAppliPath,"bin","salome","salomeContext.py"), pickle.dumps(self), pickle.dumps(args)], shell=False, close_fds=True)
114     msg = proc.communicate()
115     if kill:
116       self._killAll(args)
117     return msg
118   #
119
120   """Append value to PATH environment variable"""
121   def addToPath(self, value):
122     self.addToVariable('PATH', value)
123   #
124
125   """Append value to LD_LIBRARY_PATH environment variable"""
126   def addToLdLibraryPath(self, value):
127     self.addToVariable('LD_LIBRARY_PATH', value)
128   #
129
130   """Append value to PYTHONPATH environment variable"""
131   def addToPythonPath(self, value):
132     self.addToVariable('PYTHONPATH', value)
133   #
134
135   """Set environment variable to value"""
136   def setVariable(self, name, value, overwrite=False):
137     env = os.getenv(name, '')
138     if env and not overwrite:
139       self.getLogger().warning("Environment variable already existing (and not overwritten): %s=%s", name, value)
140       return
141
142     if env:
143       self.getLogger().warning("Overwriting environment variable: %s=%s", name, value)
144
145     value = os.path.expandvars(value) # expand environment variables
146     self.getLogger().debug("Set environment variable: %s=%s", name, value)
147     os.environ[name] = value
148   #
149
150   """Unset environment variable"""
151   def unsetVariable(self, name):
152     if os.environ.has_key(name):
153       del os.environ[name]
154   #
155
156   """Append value to environment variable"""
157   def addToVariable(self, name, value, separator=os.pathsep):
158     if value == '':
159       return
160
161     value = os.path.expandvars(value) # expand environment variables
162     self.getLogger().debug("Add to %s: %s", name, value)
163     env = os.getenv(name, None)
164     if env is None:
165       os.environ[name] = value
166     else:
167       os.environ[name] = value + separator + env
168   #
169
170   ###################################
171   # This begins the private section #
172   ###################################
173
174   def __parseArguments(self, args):
175     if len(args) == 0 or args[0].startswith("-"):
176       return None, args
177
178     command = args[0]
179     options = args[1:]
180
181     availableCommands = {
182       'start' :   '_runAppli',
183       'shell' :   '_runSession',
184       'connect' : '_runConsole',
185       'killall':  '_killAll',
186       'info':     '_showInfo',
187       'help':     '_usage',
188       'coffee' :  '_makeCoffee'
189       }
190
191     if not command in availableCommands.keys():
192       command = "start"
193       options = args
194
195     return availableCommands[command], options
196   #
197
198   """
199   Run SALOME!
200   Args consist in a mandatory command followed by optionnal parameters.
201   See usage for details on commands.
202   """
203   def _startSalome(self, args):
204     command, options = self.__parseArguments(args)
205     sys.argv = options
206
207     if command is None:
208       if args and args[0] in ["-h","--help","help"]:
209         usage()
210         sys.exit(0)
211       # try to default to "start" command
212       command = "_runAppli"
213
214     try:
215       res = getattr(self, command)(options) # run appropriate method
216       return res or (None, None)
217     except SystemExit, exc:
218       if exc==0:
219         sys.exit(0) #catch sys.exit(0) happy end no warning
220       if exc==1:
221         self.getLogger().warning("SystemExit 1 in method %s.", command)
222       sys.exit(1)
223     except StandardError:
224       self.getLogger().error("Unexpected error:")
225       import traceback
226       traceback.print_exc()
227       sys.exit(1)
228     except SalomeContextException, e:
229       self.getLogger().error(e)
230       sys.exit(1)
231   #
232
233   def __setEnvironmentFromConfigFile(self, filename, reserved=[]):
234     unsetVars, configVars, reservedDict = parseConfigFile(filename, reserved)
235
236     # unset variables
237     for var in unsetVars:
238       self.unsetVariable(var)
239
240     # set environment
241     for reserved in reservedDict:
242       a = filter(None, reservedDict[reserved]) # remove empty elements
243       reformattedVals = ':'.join(a)
244       self.addToVariable(reserved, reformattedVals)
245       pass
246
247     for key,val in configVars:
248       self.setVariable(key, val, overwrite=True)
249       pass
250
251     sys.path[:0] = os.getenv('PYTHONPATH','').split(':')
252   #
253
254   def _runAppli(self, args=[]):
255     # Initialize SALOME environment
256     sys.argv = ['runSalome'] + args
257     import setenv
258     setenv.main(True)
259
260     import runSalome
261     runSalome.runSalome()
262   #
263
264   def _runSession(self, args=[]):
265     sys.argv = ['runSession'] + args
266     import runSession
267     runSession.configureSession(args)
268
269     import setenv
270     setenv.main(True)
271
272     scriptArgs = getScriptsAndArgs(args)
273     command = formatScriptsAndArgs(scriptArgs)
274     if command:
275       sep = ";"
276       if sys.platform == "win32":
277         sep= "&"
278       command = command.split(sep)
279       outmsg = []
280       errmsg = []
281       for cmd in command:
282         cmd = cmd.strip().split(' ')
283         #proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
284         proc = subprocess.Popen(cmd)
285         (stdoutdata, stderrdata) = proc.communicate()
286         if stdoutdata or stderrdata:
287           outmsg.append(stdoutdata)
288           errmsg.append(stderrdata)
289
290       return ("".join(outmsg), "".join(errmsg))
291     else:
292       absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
293       cmd = ["/bin/bash",  "--rcfile", absoluteAppliPath + "/.bashrc" ]
294       proc = subprocess.Popen(cmd, shell=False, close_fds=True)
295       return proc.communicate()
296   #
297
298   def _runConsole(self, args=[]):
299     # Initialize SALOME environment
300     sys.argv = ['runConsole'] + args
301     import setenv
302     setenv.main(True)
303
304     cmd = ["python", "-c", "import runConsole\nrunConsole.connect()" ]
305     proc = subprocess.Popen(cmd, shell=False, close_fds=True)
306     return proc.communicate()
307   #
308
309   def _killAll(self, args=[]):
310     absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
311     try:
312       import PortManager # mandatory
313       from multiprocessing import Process
314       from killSalomeWithPort import killMyPort
315       ports = PortManager.getBusyPorts()
316
317       if ports:
318         import tempfile
319         for port in ports:
320           with tempfile.NamedTemporaryFile():
321             p = Process(target = killMyPort, args=(port,))
322             p.start()
323             p.join()
324
325       p = Process(target = killMyPort, args=(2809,))
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.ERROR)
387     return self._logger
388   #
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     (out, err) = context._startSalome(args)
397     if out:
398       sys.stdout.write(out)
399     if err:
400       sys.stderr.write(err)
401   else:
402     usage()
403 #