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