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