Salome HOME
fix bug in salome connect
[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', '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     proc.communicate()
115     if kill:
116       self._killAll(args)
117   #
118
119   """Append value to PATH environment variable"""
120   def addToPath(self, value):
121     self.addToVariable('PATH', value)
122   #
123
124   """Append value to LD_LIBRARY_PATH environment variable"""
125   def addToLdLibraryPath(self, value):
126     self.addToVariable('LD_LIBRARY_PATH', value)
127   #
128
129   """Append value to PYTHONPATH environment variable"""
130   def addToPythonPath(self, value):
131     self.addToVariable('PYTHONPATH', value)
132   #
133
134   """Set environment variable to value"""
135   def setVariable(self, name, value, overwrite=False):
136     env = os.getenv(name, '')
137     if env and not overwrite:
138       self.getLogger().warning("Environment variable already existing (and not overwritten): %s=%s", name, value)
139       return
140
141     if env:
142       self.getLogger().warning("Overwriting environment variable: %s=%s", name, value)
143
144     value = os.path.expandvars(value) # expand environment variables
145     self.getLogger().debug("Set environment variable: %s=%s", name, value)
146     os.environ[name] = value
147   #
148
149   """Unset environment variable"""
150   def unsetVariable(self, name):
151     if os.environ.has_key(name):
152       del os.environ[name]
153   #
154
155   """Append value to environment variable"""
156   def addToVariable(self, name, value, separator=os.pathsep):
157     if value == '':
158       return
159
160     value = os.path.expandvars(value) # expand environment variables
161     self.getLogger().debug("Add to %s: %s", name, value)
162     env = os.getenv(name, None)
163     if env is None:
164       os.environ[name] = value
165     else:
166       os.environ[name] = value + separator + env
167   #
168
169   ###################################
170   # This begins the private section #
171   ###################################
172
173   def __parseArguments(self, args):
174     if len(args) == 0 or args[0].startswith("-"):
175       return None, args
176
177     command = args[0]
178     options = args[1:]
179
180     availableCommands = {
181       'start' :   '_runAppli',
182       'shell' :   '_runSession',
183       'connect' : '_runConsole',
184       'killall':  '_killAll',
185       'info':     '_showInfo',
186       'help':     '_usage',
187       'coffee' :  '_makeCoffee'
188       }
189
190     if not command in availableCommands.keys():
191       command = "start"
192       options = args
193
194     return availableCommands[command], options
195   #
196
197   """
198   Run SALOME!
199   Args consist in a mandatory command followed by optionnal parameters.
200   See usage for details on commands.
201   """
202   def _startSalome(self, args):
203     command, options = self.__parseArguments(args)
204     sys.argv = options
205
206     if command is None:
207       if args and args[0] in ["-h","--help","help"]:
208         usage()
209         sys.exit(0)
210       # try to default to "start" command
211       command = "_runAppli"
212
213     try:
214       res = getattr(self, command)(options) # run appropriate method
215       return res or (None, None)
216     except SystemExit, exc:
217       if exc==0:
218         sys.exit(0) #catch sys.exit(0) happy end no warning
219       if exc==1:
220         self.getLogger().warning("SystemExit 1 in method %s.", command)
221       sys.exit(1)
222     except StandardError:
223       self.getLogger().error("Unexpected error:")
224       import traceback
225       traceback.print_exc()
226       sys.exit(1)
227     except SalomeContextException, e:
228       self.getLogger().error(e)
229       sys.exit(1)
230   #
231
232   def __setEnvironmentFromConfigFile(self, filename, reserved=[]):
233     unsetVars, configVars, reservedDict = parseConfigFile(filename, reserved)
234
235     # unset variables
236     for var in unsetVars:
237       self.unsetVariable(var)
238
239     # set environment
240     for reserved in reservedDict:
241       a = filter(None, reservedDict[reserved]) # remove empty elements
242       reformattedVals = ':'.join(a)
243       self.addToVariable(reserved, reformattedVals)
244       pass
245
246     for key,val in configVars:
247       self.setVariable(key, val, overwrite=True)
248       pass
249
250     sys.path[:0] = os.getenv('PYTHONPATH','').split(':')
251   #
252
253   def _runAppli(self, args=[]):
254     # Initialize SALOME environment
255     sys.argv = ['runSalome'] + args
256     import setenv
257     setenv.main(True)
258
259     import runSalome
260     runSalome.runSalome()
261   #
262
263   def _runSession(self, args=[]):
264     sys.argv = ['runSession'] + args
265     import runSession
266     runSession.configureSession(args)
267
268     import setenv
269     setenv.main(True)
270
271     scriptArgs = getScriptsAndArgs(args)
272     command = formatScriptsAndArgs(scriptArgs)
273     if command:
274       sep = ";"
275       if sys.platform == "win32":
276         sep= "&"
277       command = command.split(sep)
278       outmsg = []
279       errmsg = []
280       for cmd in command:
281         cmd = cmd.strip().split(' ')
282         #proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
283         proc = subprocess.Popen(cmd)
284         (stdoutdata, stderrdata) = proc.communicate()
285         if stdoutdata or stderrdata:
286           outmsg.append(stdoutdata)
287           errmsg.append(stderrdata)
288
289       return ("".join(outmsg), "".join(errmsg))
290     else:
291       absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
292       cmd = ["/bin/bash",  "--rcfile", absoluteAppliPath + "/.bashrc" ]
293       proc = subprocess.Popen(cmd, shell=False, close_fds=True)
294       proc.wait()
295   #
296
297   def _runConsole(self, args=[]):
298     # Initialize SALOME environment
299     sys.argv = ['runConsole'] + args
300     import setenv
301     setenv.main(True)
302
303     cmd = ["python", "-c", "import runConsole\nrunConsole.connect()" ]
304     proc = subprocess.Popen(cmd, shell=False, close_fds=True)
305     proc.wait()
306   #
307
308   def _killAll(self, args=[]):
309     absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
310     try:
311       import PortManager # mandatory
312       from multiprocessing import Process
313       from killSalomeWithPort import killMyPort
314       ports = PortManager.getBusyPorts()
315
316       if ports:
317         import tempfile
318         for port in ports:
319           with tempfile.NamedTemporaryFile():
320             p = Process(target = killMyPort, args=(port,))
321             p.start()
322             p.join()
323
324       p = Process(target = killMyPort, args=(2809,))
325       p.start()
326       p.join()
327     except ImportError:
328       from killSalome import killAllPorts
329       killAllPorts()
330       pass
331
332   #
333
334   def _showInfo(self, args=[]):
335     print "Running with python", platform.python_version()
336     self._runAppli(["--version"])
337   #
338
339   def _usage(self, unused=[]):
340     usage()
341   #
342
343   def _makeCoffee(self, args=[]):
344     print "                        ("
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     sys.exit(0)
367   #
368
369   # Add the following two methods since logger is not pickable
370   # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
371   def __getstate__(self):
372     d = dict(self.__dict__)
373     if hasattr(self, '_logger'):
374       del d['_logger']
375     return d
376   #
377   def __setstate__(self, d):
378     self.__dict__.update(d) # I *think* this is a safe way to do it
379   #
380   # Excluding self._logger from pickle operation imply using the following method to access logger
381   def getLogger(self):
382     if not hasattr(self, '_logger'):
383       self._logger = logging.getLogger(__name__)
384       #self._logger.setLevel(logging.DEBUG)
385       self._logger.setLevel(logging.ERROR)
386     return self._logger
387   #
388
389 ###
390 import pickle
391 if __name__ == "__main__":
392   if len(sys.argv) == 3:
393     context = pickle.loads(sys.argv[1])
394     args = pickle.loads(sys.argv[2])
395     (out, err) = context._startSalome(args)
396     if out:
397       sys.stdout.write(out)
398     if err:
399       sys.stderr.write(err)
400   else:
401     usage()
402 #