1 # Copyright (C) 2013-2015 CEA/DEN, EDF R&D, OPEN CASCADE
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.
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.
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
17 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
25 from parseConfigFile import parseConfigFile
26 from parseConfigFile import convertEnvFileToConfigFile
33 from salomeContextUtils import SalomeContextException
36 #exeName = os.path.splitext(os.path.basename(__file__))[0]
39 Usage: salome [command] [options] [--config=<file,folder,...>]
43 start Starts a SALOME session (through virtual application)
44 shell Initializes SALOME environment, and executes scripts passed
46 connect Connects a Python console to the active SALOME session
47 killall Kill all SALOME running sessions for current user
48 info Display some information about SALOME
49 help Show this message
50 coffee Yes! SALOME can also make coffee!!
52 If no command is given, default to start.
56 Use salome <command> --help to show help on command ; available for start
59 --config=<file,folder,...>
60 ==========================
61 Initialize SALOME environment from a list of context files and/or a list
62 of folders containing context files. The list is comma-separated, whithout
70 The SalomeContext class in an API to configure SALOME environment then
71 start SALOME using a single python command.
76 Initialize environment from a list of configuration files
77 identified by their names.
78 These files should be in appropriate (new .cfg) format.
79 However you can give old .sh environment files; in this case,
80 the SalomeContext class will try to automatically convert them
81 to .cfg format before setting the environment.
83 def __init__(self, configFileNames=0):
84 #it could be None explicitely (if user use multiples setVariable...for standalone)
85 if configFileNames is None:
87 configFileNames = configFileNames or []
88 if len(configFileNames) == 0:
89 raise SalomeContextException("No configuration files given")
91 reserved=['PATH', 'DYLD_LIBRARY_PATH', 'LD_LIBRARY_PATH', 'PYTHONPATH', 'MANPATH', 'PV_PLUGIN_PATH']
92 for filename in configFileNames:
93 basename, extension = os.path.splitext(filename)
94 if extension == ".cfg":
95 self.__setEnvironmentFromConfigFile(filename, reserved)
96 elif extension == ".sh":
97 #new convert procedures, temporary could be use not to be automatically deleted
98 #temp = tempfile.NamedTemporaryFile(suffix='.cfg', delete=False)
99 temp = tempfile.NamedTemporaryFile(suffix='.cfg')
101 convertEnvFileToConfigFile(filename, temp.name, reserved)
102 self.__setEnvironmentFromConfigFile(temp.name, reserved)
104 except (ConfigParser.ParsingError, ValueError) as e:
105 self.getLogger().error("Invalid token found when parsing file: %s\n"%(filename))
109 self.getLogger().warning("Unrecognized extension for configuration file: %s", filename)
112 def __loadMPI(self, module_name):
113 print "Trying to load MPI module: %s..."%module_name,
115 out, err = subprocess.Popen(["modulecmd", "python", "load", module_name], stdout=subprocess.PIPE).communicate()
116 exec out # define specific environment variables
119 print " ** Failed **"
123 def runSalome(self, args):
125 # Run this module as a script, in order to use appropriate Python interpreter
126 # according to current path (initialized from environment files).
127 mpi_module_option = "--with-mpi-module="
128 mpi_module = [x for x in args if x.startswith(mpi_module_option)]
130 mpi_module = mpi_module[0][len(mpi_module_option):]
131 self.__loadMPI(mpi_module)
132 args = [x for x in args if not x.startswith(mpi_module_option)]
134 mpi_module = os.getenv("SALOME_MPI_MODULE_NAME", None)
136 self.__loadMPI(mpi_module)
138 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
139 env_copy = os.environ.copy()
140 proc = subprocess.Popen(['python', os.path.join(absoluteAppliPath,"bin","salome","salomeContext.py"), pickle.dumps(self), pickle.dumps(args)], shell=False, close_fds=True, env=env_copy)
141 msg = proc.communicate()
142 return msg, proc.returncode
145 """Append value to PATH environment variable"""
146 def addToPath(self, value):
147 self.addToVariable('PATH', value)
150 """Append value to LD_LIBRARY_PATH environment variable"""
151 def addToLdLibraryPath(self, value):
152 self.addToVariable('LD_LIBRARY_PATH', value)
155 """Append value to DYLD_LIBRARY_PATH environment variable"""
156 def addToDyldLibraryPath(self, value):
157 self.addToVariable('DYLD_LIBRARY_PATH', value)
160 """Append value to PYTHONPATH environment variable"""
161 def addToPythonPath(self, value):
162 self.addToVariable('PYTHONPATH', value)
165 """Set environment variable to value"""
166 def setVariable(self, name, value, overwrite=False):
167 env = os.getenv(name, '')
168 if env and not overwrite:
169 self.getLogger().warning("Environment variable already existing (and not overwritten): %s=%s", name, value)
173 self.getLogger().warning("Overwriting environment variable: %s=%s", name, value)
175 value = os.path.expandvars(value) # expand environment variables
176 self.getLogger().debug("Set environment variable: %s=%s", name, value)
177 os.environ[name] = value
180 """Unset environment variable"""
181 def unsetVariable(self, name):
182 if os.environ.has_key(name):
186 """Append value to environment variable"""
187 def addToVariable(self, name, value, separator=os.pathsep):
191 value = os.path.expandvars(value) # expand environment variables
192 self.getLogger().debug("Add to %s: %s", name, value)
193 env = os.getenv(name, None)
195 os.environ[name] = value
197 os.environ[name] = value + separator + env
200 ###################################
201 # This begins the private section #
202 ###################################
204 def __parseArguments(self, args):
205 if len(args) == 0 or args[0].startswith("-"):
211 availableCommands = {
212 'start' : '_runAppli',
213 'shell' : '_runSession',
214 'connect' : '_runConsole',
215 'killall': '_killAll',
218 'coffee' : '_makeCoffee'
221 if not command in availableCommands.keys():
225 return availableCommands[command], options
230 Args consist in a mandatory command followed by optionnal parameters.
231 See usage for details on commands.
233 def _startSalome(self, args):
236 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
238 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome"))
239 if not path in sys.path:
240 sys.path[:0] = [path]
244 command, options = self.__parseArguments(args)
248 if args and args[0] in ["-h","--help","help"]:
251 # try to default to "start" command
252 command = "_runAppli"
255 res = getattr(self, command)(options) # run appropriate method
256 return res or (None, None)
257 except SystemExit, returncode:
259 self.getLogger().warning("SystemExit %s in method %s.", returncode, command)
261 except StandardError:
262 self.getLogger().error("Unexpected error:")
264 traceback.print_exc()
266 except SalomeContextException, e:
267 self.getLogger().error(e)
271 def __setEnvironmentFromConfigFile(self, filename, reserved=None):
275 unsetVars, configVars, reservedDict = parseConfigFile(filename, reserved)
276 except SalomeContextException, e:
278 file_dir = os.path.dirname(filename)
279 file_base = os.path.basename(filename)
280 base_no_ext, ext = os.path.splitext(file_base)
281 sh_file = os.path.join(file_dir, base_no_ext+'.sh')
282 if ext == ".cfg" and os.path.isfile(sh_file):
283 msg += "Found similar %s file; trying to parse this one instead..."%(base_no_ext+'.sh')
284 temp = tempfile.NamedTemporaryFile(suffix='.cfg')
286 convertEnvFileToConfigFile(sh_file, temp.name, reserved)
287 self.__setEnvironmentFromConfigFile(temp.name, reserved)
289 self.getLogger().warning(msg)
292 except (ConfigParser.ParsingError, ValueError) as e:
293 msg += "Invalid token found when parsing file: %s\n"%(sh_file)
294 self.getLogger().error(msg)
298 self.getLogger().error(msg)
302 for var in unsetVars:
303 self.unsetVariable(var)
306 for reserved in reservedDict:
307 a = filter(None, reservedDict[reserved]) # remove empty elements
308 a = [ os.path.realpath(x) for x in a ]
309 reformattedVals = os.pathsep.join(a)
310 self.addToVariable(reserved, reformattedVals)
313 for key,val in configVars:
314 self.setVariable(key, val, overwrite=True)
317 pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
318 pythonpath = [ os.path.realpath(x) for x in pythonpath ]
319 sys.path[:0] = pythonpath
322 def _runAppli(self, args=None):
325 # Initialize SALOME environment
326 sys.argv = ['runSalome'] + args
331 runSalome.runSalome()
334 def _runSession(self, args=None):
337 sys.argv = ['runSession'] + args
339 params, args = runSession.configureSession(args, exe="salome shell")
341 sys.argv = ['runSession'] + args
345 return runSession.runSession(params, args)
348 def _runConsole(self, args=None):
351 # Initialize SALOME environment
352 sys.argv = ['runConsole'] + args
356 cmd = ["python", "-c", "import runConsole\nrunConsole.connect()" ]
357 proc = subprocess.Popen(cmd, shell=False, close_fds=True)
358 return proc.communicate()
361 def _killAll(self, args=None):
365 import PortManager # mandatory
366 from multiprocessing import Process
367 from killSalomeWithPort import killMyPort
368 ports = PortManager.getBusyPorts()
373 with tempfile.NamedTemporaryFile():
374 p = Process(target = killMyPort, args=(port,))
378 from killSalome import killAllPorts
384 def _showInfo(self, args=None):
385 print "Running with python", platform.python_version()
386 self._runAppli(["--version"])
389 def _usage(self, unused=None):
393 def _makeCoffee(self, args=None):
396 print " ___...(-------)-....___"
397 print " .-\"\" ) ( \"\"-."
398 print " .-\'``\'|-._ ) _.-|"
399 print " / .--.| `\"\"---...........---\"\"` |"
407 print " _..---\"\"` \\ /`\"\"---.._"
408 print " .-\' \\ / \'-."
409 print " : `-.__ __.-\' :"
410 print " : ) \"\"---...---\"\" ( :"
411 print " \'._ `\"--...___...--\"` _.\'"
412 print " \\\"\"--..__ __..--\"\"/"
413 print " \'._ \"\"\"----.....______.....----\"\"\" _.\'"
414 print " `\"\"--..,,_____ _____,,..--\"\"`"
415 print " `\"\"\"----\"\"\"`"
419 # Add the following two methods since logger is not pickable
420 # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
421 def __getstate__(self):
422 d = dict(self.__dict__)
423 if hasattr(self, '_logger'):
427 def __setstate__(self, d):
428 self.__dict__.update(d) # I *think* this is a safe way to do it
430 # Excluding self._logger from pickle operation imply using the following method to access logger
432 if not hasattr(self, '_logger'):
433 self._logger = logging.getLogger(__name__)
434 #self._logger.setLevel(logging.DEBUG)
435 #self._logger.setLevel(logging.WARNING)
436 self._logger.setLevel(logging.ERROR)
440 if __name__ == "__main__":
441 if len(sys.argv) == 3:
442 context = pickle.loads(sys.argv[1])
443 args = pickle.loads(sys.argv[2])
445 (out, err) = context._startSalome(args)
447 sys.stdout.write(out)
449 sys.stderr.write(err)