1 # Copyright (C) 2013-2016 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
37 Usage: salome [command] [options] [--config=<file,folder,...>]
41 start Start a new SALOME instance.
42 context Initialize SALOME context. Current environment is extended.
43 shell Initialize SALOME context, attached to the last created SALOME
44 instance if any, and executes scripts passed as command arguments.
45 User works in a Shell terminal; SALOME environment is set but
46 application is not started.
47 connect Connect a Python console to the active SALOME instance.
48 kill <port(s)> Terminate SALOME instances running on given ports for current user.
49 Port numbers must be separated by blank characters.
50 killall Terminate *all* SALOME running instances for current user.
51 Do not start a new one.
52 test Run SALOME tests.
53 info Display some information about SALOME.
54 help Show this message.
56 If no command is given, default to start.
60 Use salome <command> --help to show help on command ; available for commands:
61 start, shell, connect, test, info.
63 --config=<file,folder,...>
64 ==========================
65 Initialize SALOME context from a list of context files and/or a list
66 of folders containing context files. The list is comma-separated, whithout
74 The SalomeContext class in an API to configure SALOME context then
75 start SALOME using a single python command.
80 Initialize context from a list of configuration files
81 identified by their names.
82 These files should be in appropriate (new .cfg) format.
83 However you can give old .sh environment files; in this case,
84 the SalomeContext class will try to automatically convert them
85 to .cfg format before setting the context.
87 def __init__(self, configFileNames=0):
88 self.getLogger().setLevel(logging.INFO)
89 #it could be None explicitely (if user use multiples setVariable...for standalone)
90 if configFileNames is None:
92 configFileNames = configFileNames or []
93 if len(configFileNames) == 0:
94 raise SalomeContextException("No configuration files given")
96 reserved=['PATH', 'DYLD_LIBRARY_PATH', 'LD_LIBRARY_PATH', 'PYTHONPATH', 'MANPATH', 'PV_PLUGIN_PATH', 'INCLUDE', 'LIBPATH', 'SALOME_PLUGINS_PATH']
97 for filename in configFileNames:
98 basename, extension = os.path.splitext(filename)
99 if extension == ".cfg":
100 self.__setContextFromConfigFile(filename, reserved)
101 elif extension == ".sh":
102 #new convert procedures, temporary could be use not to be automatically deleted
103 #temp = tempfile.NamedTemporaryFile(suffix='.cfg', delete=False)
104 temp = tempfile.NamedTemporaryFile(suffix='.cfg')
106 convertEnvFileToConfigFile(filename, temp.name, reserved)
107 self.__setContextFromConfigFile(temp.name, reserved)
109 except (ConfigParser.ParsingError, ValueError) as e:
110 self.getLogger().error("Invalid token found when parsing file: %s\n"%(filename))
114 self.getLogger().warning("Unrecognized extension for configuration file: %s", filename)
117 def __loadMPI(self, module_name):
118 print "Trying to load MPI module: %s..."%module_name,
120 out, err = subprocess.Popen(["modulecmd", "python", "load", module_name], stdout=subprocess.PIPE).communicate()
121 exec out # define specific environment variables
124 print " ** Failed **"
128 def runSalome(self, args):
130 # Run this module as a script, in order to use appropriate Python interpreter
131 # according to current path (initialized from context files).
132 mpi_module_option = "--with-mpi-module="
133 mpi_module = [x for x in args if x.startswith(mpi_module_option)]
135 mpi_module = mpi_module[0][len(mpi_module_option):]
136 self.__loadMPI(mpi_module)
137 args = [x for x in args if not x.startswith(mpi_module_option)]
139 mpi_module = os.getenv("SALOME_MPI_MODULE_NAME", None)
141 self.__loadMPI(mpi_module)
143 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
144 env_copy = os.environ.copy()
145 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)
146 out, err = proc.communicate()
147 return out, err, proc.returncode
150 """Append value to PATH environment variable"""
151 def addToPath(self, value):
152 self.addToVariable('PATH', value)
155 """Append value to LD_LIBRARY_PATH environment variable"""
156 def addToLdLibraryPath(self, value):
157 if platform.system() == 'Windows':
158 self.addToVariable('PATH', value)
160 self.addToVariable('LD_LIBRARY_PATH', value)
163 """Append value to DYLD_LIBRARY_PATH environment variable"""
164 def addToDyldLibraryPath(self, value):
165 self.addToVariable('DYLD_LIBRARY_PATH', value)
168 """Append value to PYTHONPATH environment variable"""
169 def addToPythonPath(self, value):
170 self.addToVariable('PYTHONPATH', value)
173 """Set environment variable to value"""
174 def setVariable(self, name, value, overwrite=False):
175 env = os.getenv(name, '')
176 if env and not overwrite:
177 self.getLogger().warning("Environment variable already existing (and not overwritten): %s=%s", name, value)
181 self.getLogger().warning("Overwriting environment variable: %s=%s", name, value)
183 value = os.path.expandvars(value) # expand environment variables
184 self.getLogger().debug("Set environment variable: %s=%s", name, value)
185 os.environ[name] = value
188 """Unset environment variable"""
189 def unsetVariable(self, name):
190 if os.environ.has_key(name):
194 """Append value to environment variable"""
195 def addToVariable(self, name, value, separator=os.pathsep):
199 value = os.path.expandvars(value) # expand environment variables
200 self.getLogger().debug("Add to %s: %s", name, value)
201 env = os.getenv(name, None)
203 os.environ[name] = value
205 os.environ[name] = value + separator + env
208 ###################################
209 # This begins the private section #
210 ###################################
212 def __parseArguments(self, args):
213 if len(args) == 0 or args[0].startswith("-"):
219 availableCommands = {
220 'start' : '_runAppli',
221 'context' : '_setContext',
222 'shell' : '_runSession',
223 'connect' : '_runConsole',
225 'killall' : '_killAll',
226 'test' : '_runTests',
227 'info' : '_showInfo',
229 'coffee' : '_makeCoffee',
233 if not command in availableCommands.keys():
237 return availableCommands[command], options
242 Args consist in a mandatory command followed by optionnal parameters.
243 See usage for details on commands.
245 def _startSalome(self, args):
249 from setenv import add_path
250 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
251 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome"))
252 add_path(path, "PYTHONPATH")
253 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome", "appliskel"))
254 add_path(path, "PYTHONPATH")
259 command, options = self.__parseArguments(args)
263 if args and args[0] in ["-h","--help","help"]:
266 # try to default to "start" command
267 command = "_runAppli"
270 res = getattr(self, command)(options) # run appropriate method
271 return res or (None, None)
272 except SystemExit, returncode:
274 self.getLogger().warning("SystemExit %s in method %s.", returncode, command)
276 except StandardError:
277 self.getLogger().error("Unexpected error:")
279 traceback.print_exc()
281 except SalomeContextException, e:
282 self.getLogger().error(e)
286 def __setContextFromConfigFile(self, filename, reserved=None):
290 unsetVars, configVars, reservedDict = parseConfigFile(filename, reserved)
291 except SalomeContextException, e:
293 file_dir = os.path.dirname(filename)
294 file_base = os.path.basename(filename)
295 base_no_ext, ext = os.path.splitext(file_base)
296 sh_file = os.path.join(file_dir, base_no_ext+'.sh')
297 #if ext == ".cfg" and os.path.isfile(sh_file):
299 msg += "Found similar %s file; trying to parse this one instead..."%(base_no_ext+'.sh')
300 temp = tempfile.NamedTemporaryFile(suffix='.cfg')
302 convertEnvFileToConfigFile(sh_file, temp.name, reserved)
303 self.__setContextFromConfigFile(temp.name, reserved)
305 self.getLogger().warning(msg)
308 except (ConfigParser.ParsingError, ValueError) as e:
309 msg += "Invalid token found when parsing file: %s\n"%(sh_file)
310 self.getLogger().error(msg)
314 self.getLogger().error(msg)
318 for var in unsetVars:
319 self.unsetVariable(var)
322 for reserved in reservedDict:
323 a = filter(None, reservedDict[reserved]) # remove empty elements
324 a = [ os.path.realpath(x) for x in a ]
325 reformattedVals = os.pathsep.join(a)
326 if reserved in ["INCLUDE", "LIBPATH"]:
327 self.addToVariable(reserved, reformattedVals, separator=' ')
329 self.addToVariable(reserved, reformattedVals)
332 for key,val in configVars:
333 self.setVariable(key, val, overwrite=True)
336 pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
337 pythonpath = [ os.path.realpath(x) for x in pythonpath ]
338 sys.path[:0] = pythonpath
341 def _runAppli(self, args=None):
344 # Initialize SALOME environment
345 sys.argv = ['runSalome'] + args
347 setenv.main(True, exeName="salome start")
350 runSalome.runSalome()
353 def _setContext(self, args=None):
354 salome_context_set = os.getenv("SALOME_CONTEXT_SET")
355 if salome_context_set:
357 print "*** SALOME context has already been set."
358 print "*** Enter 'exit' (only once!) to leave SALOME context."
362 os.environ["SALOME_CONTEXT_SET"] = "yes"
364 print "*** SALOME context is now set."
365 print "*** Enter 'exit' (only once!) to leave SALOME context."
369 proc = subprocess.Popen(cmd, shell=False, close_fds=True)
370 return proc.communicate()
373 def _runSession(self, args=None):
376 sys.argv = ['runSession'] + args
378 params, args = runSession.configureSession(args, exe="salome shell")
380 sys.argv = ['runSession'] + args
384 return runSession.runSession(params, args)
387 def _runConsole(self, args=None):
390 # Initialize SALOME environment
391 sys.argv = ['runConsole']
396 return runConsole.connect(args)
399 def _kill(self, args=None):
404 print "Port number(s) not provided to command: salome kill <port(s)>"
407 from multiprocessing import Process
408 from killSalomeWithPort import killMyPort
411 with tempfile.NamedTemporaryFile():
412 p = Process(target = killMyPort, args=(port,))
418 def _killAll(self, unused=None):
420 import PortManager # mandatory
421 from multiprocessing import Process
422 from killSalomeWithPort import killMyPort
423 ports = PortManager.getBusyPorts()
428 with tempfile.NamedTemporaryFile():
429 p = Process(target = killMyPort, args=(port,))
433 # :TODO: should be declared obsolete
434 from killSalome import killAllPorts
439 def _runTests(self, args=None):
442 sys.argv = ['runTests']
447 return runTests.runTests(args, exe="salome test")
450 def _showSoftwareVersions(self, softwares=None):
451 config = ConfigParser.SafeConfigParser()
452 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
453 filename = os.path.join(absoluteAppliPath, "sha1_collections.txt")
456 with open(filename) as f:
459 software, version, sha1 = line.split()
460 versions[software.upper()] = version
461 if len(software) > max_len:
462 max_len = len(software)
468 for soft in softwares:
469 if versions.has_key(soft.upper()):
470 print soft.upper().rjust(max_len), versions[soft.upper()]
472 for name, version in versions.items():
473 print name.rjust(max_len), versions[name]
476 def _showInfo(self, args=None):
480 usage = "Usage: salome info [options]"
482 Display some information about SALOME.\n
483 Available options are:
484 -p,--ports Show the list of busy ports (running SALOME instances).
485 -s,--softwares Show the list and versions of SALOME softwares.
486 -v,--version Show running SALOME version.
487 -h,--help Show this message.
492 if "-h" in args or "--help" in args:
496 if "-p" in args or "--ports" in args:
498 ports = PortManager.getBusyPorts()
499 print "SALOME instances are running on ports:", ports
501 print "Last started instance on port %s"%ports[-1]
503 if "-s" in args or "--softwares" in args:
504 self._showSoftwareVersions()
506 if "-v" in args or "--version" in args:
507 print "Running with python", platform.python_version()
508 self._runAppli(["--version"])
511 def _usage(self, unused=None):
515 def _makeCoffee(self, unused=None):
518 print " ___...(-------)-....___"
519 print " .-\"\" ) ( \"\"-."
520 print " .-\'``\'|-._ ) _.-|"
521 print " / .--.| `\"\"---...........---\"\"` |"
526 print " `\\ `| SALOME |"
527 print " _/ /\\ 4 EVER /"
528 print " (__/ \\ <3 /"
529 print " _..---\"\"` \\ /`\"\"---.._"
530 print " .-\' \\ / \'-."
531 print " : `-.__ __.-\' :"
532 print " : ) \"\"---...---\"\" ( :"
533 print " \'._ `\"--...___...--\"` _.\'"
534 print " \\\"\"--..__ __..--\"\"/"
535 print " \'._ \"\"\"----.....______.....----\"\"\" _.\'"
536 print " `\"\"--..,,_____ _____,,..--\"\"`"
537 print " `\"\"\"----\"\"\"`"
539 print " SALOME is working for you; what else?"
544 def _getCar(self, unused=None):
545 print " _____________"
546 print " ..---:::::::-----------. ::::;;."
547 print " .\'\"\"\"\"\"\" ;; \\ \":."
548 print " .\'\' ; \\ \"\\__."
549 print " .\' ;; ; \\\\\";"
550 print " .\' ; _____; \\\\/"
551 print " .\' :; ;\" \\ ___:\'."
552 print " .\'--........................... : = ____:\" \\ \\"
553 print " ..-\"\" \"\"\"\' o\"\"\" ; ; :"
554 print " .--\"\" .----- ..----... _.- --. ..-\" ; ; ; ;"
555 print " .\"\"_- \"--\"\"-----\'\"\" _-\" .-\"\" ; ; .-."
556 print " .\' .\' SALOME .\" .\" ; ; /. |"
557 print " /-./\' 4 EVER <3 .\" / _.. ; ; ;;;|"
558 print " : ;-.______ / _________==. /_ \\ ; ; ;;;;"
559 print " ; / | \"\"\"\"\"\"\"\"\"\"\".---.\"\"\"\"\"\"\" : /\" \". |; ; _; ;;;"
560 print " /\"-/ | / / / / ;|; ;-\" | ;\';"
561 print ":- : \"\"\"----______ / / ____. . .\"\'. ;; .-\"..T\" ."
562 print "\'. \" ___ \"\": \'\"\"\"\"\"\"\"\"\"\"\"\"\"\" . ; ; ;; ;.\" .\" \'--\""
563 print " \", __ \"\"\" \"\"---... :- - - - - - - - - \' \' ; ; ; ;;\" .\""
564 print " /. ; \"\"\"---___ ; ; ; ;|.\"\""
565 print " : \": \"\"\"----. .-------. ; ; ; ;:"
566 print " \\ \'--__ \\ \\ \\ / | ; ;;"
567 print " \'-.. \"\"\"\"---___ : .______..\\ __/..-\"\"| ; ; ;"
568 print " \"\"--.. \"\"\"--\" m l s . \". . ;"
569 print " \"\"------... ..--\"\" \" :"
570 print " \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\" \\ /"
573 print " Drive your simulation properly with SALOME!"
578 # Add the following two methods since logger is not pickable
579 # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
580 def __getstate__(self):
581 d = dict(self.__dict__)
582 if hasattr(self, '_logger'):
586 def __setstate__(self, d):
587 self.__dict__.update(d) # I *think* this is a safe way to do it
589 # Excluding self._logger from pickle operation imply using the following method to access logger
591 if not hasattr(self, '_logger'):
592 self._logger = logging.getLogger(__name__)
593 #self._logger.setLevel(logging.DEBUG)
594 #self._logger.setLevel(logging.WARNING)
595 self._logger.setLevel(logging.ERROR)
599 if __name__ == "__main__":
600 if len(sys.argv) == 3:
601 context = pickle.loads(sys.argv[1])
602 args = pickle.loads(sys.argv[2])
604 (out, err) = context._startSalome(args)
606 sys.stdout.write(out)
608 sys.stderr.write(err)