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 doc <module(s)> Show online module documentation (if available).
55 Module names must be separated by blank characters.
56 help Show this message.
58 If no command is given, default to start.
62 Use salome <command> --help to show help on command ; available for commands:
63 start, shell, connect, test, info.
65 --config=<file,folder,...>
66 ==========================
67 Initialize SALOME context from a list of context files and/or a list
68 of folders containing context files. The list is comma-separated, whithout
76 The SalomeContext class in an API to configure SALOME context then
77 start SALOME using a single python command.
82 Initialize context from a list of configuration files
83 identified by their names.
84 These files should be in appropriate (new .cfg) format.
85 However you can give old .sh environment files; in this case,
86 the SalomeContext class will try to automatically convert them
87 to .cfg format before setting the context.
89 def __init__(self, configFileNames=0):
90 self.getLogger().setLevel(logging.INFO)
91 #it could be None explicitely (if user use multiples setVariable...for standalone)
92 if configFileNames is None:
94 configFileNames = configFileNames or []
95 if len(configFileNames) == 0:
96 raise SalomeContextException("No configuration files given")
98 reserved=['PATH', 'DYLD_LIBRARY_PATH', 'LD_LIBRARY_PATH', 'PYTHONPATH', 'MANPATH', 'PV_PLUGIN_PATH', 'INCLUDE', 'LIBPATH', 'SALOME_PLUGINS_PATH']
99 for filename in configFileNames:
100 basename, extension = os.path.splitext(filename)
101 if extension == ".cfg":
102 self.__setContextFromConfigFile(filename, reserved)
103 elif extension == ".sh":
104 #new convert procedures, temporary could be use not to be automatically deleted
105 #temp = tempfile.NamedTemporaryFile(suffix='.cfg', delete=False)
106 temp = tempfile.NamedTemporaryFile(suffix='.cfg')
108 convertEnvFileToConfigFile(filename, temp.name, reserved)
109 self.__setContextFromConfigFile(temp.name, reserved)
111 except (ConfigParser.ParsingError, ValueError) as e:
112 self.getLogger().error("Invalid token found when parsing file: %s\n"%(filename))
116 self.getLogger().warning("Unrecognized extension for configuration file: %s", filename)
119 def __loadMPI(self, module_name):
120 print "Trying to load MPI module: %s..."%module_name,
122 out, err = subprocess.Popen(["modulecmd", "python", "load", module_name], stdout=subprocess.PIPE).communicate()
123 exec out # define specific environment variables
126 print " ** Failed **"
130 def runSalome(self, args):
132 # Run this module as a script, in order to use appropriate Python interpreter
133 # according to current path (initialized from context files).
134 mpi_module_option = "--with-mpi-module="
135 mpi_module = [x for x in args if x.startswith(mpi_module_option)]
137 mpi_module = mpi_module[0][len(mpi_module_option):]
138 self.__loadMPI(mpi_module)
139 args = [x for x in args if not x.startswith(mpi_module_option)]
141 mpi_module = os.getenv("SALOME_MPI_MODULE_NAME", None)
143 self.__loadMPI(mpi_module)
145 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
146 env_copy = os.environ.copy()
147 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)
148 out, err = proc.communicate()
149 return out, err, proc.returncode
152 """Append value to PATH environment variable"""
153 def addToPath(self, value):
154 self.addToVariable('PATH', value)
157 """Append value to LD_LIBRARY_PATH environment variable"""
158 def addToLdLibraryPath(self, value):
159 if platform.system() == 'Windows':
160 self.addToVariable('PATH', value)
162 self.addToVariable('LD_LIBRARY_PATH', value)
165 """Append value to DYLD_LIBRARY_PATH environment variable"""
166 def addToDyldLibraryPath(self, value):
167 self.addToVariable('DYLD_LIBRARY_PATH', value)
170 """Append value to PYTHONPATH environment variable"""
171 def addToPythonPath(self, value):
172 self.addToVariable('PYTHONPATH', value)
175 """Set environment variable to value"""
176 def setVariable(self, name, value, overwrite=False):
177 env = os.getenv(name, '')
178 if env and not overwrite:
179 self.getLogger().warning("Environment variable already existing (and not overwritten): %s=%s", name, value)
183 self.getLogger().warning("Overwriting environment variable: %s=%s", name, value)
185 value = os.path.expandvars(value) # expand environment variables
186 self.getLogger().debug("Set environment variable: %s=%s", name, value)
187 os.environ[name] = value
190 """Unset environment variable"""
191 def unsetVariable(self, name):
192 if os.environ.has_key(name):
196 """Append value to environment variable"""
197 def addToVariable(self, name, value, separator=os.pathsep):
201 value = os.path.expandvars(value) # expand environment variables
202 self.getLogger().debug("Add to %s: %s", name, value)
203 env = os.getenv(name, None)
205 os.environ[name] = value
207 os.environ[name] = value + separator + env
210 ###################################
211 # This begins the private section #
212 ###################################
214 def __parseArguments(self, args):
215 if len(args) == 0 or args[0].startswith("-"):
221 availableCommands = {
222 'start' : '_runAppli',
223 'context' : '_setContext',
224 'shell' : '_runSession',
225 'connect' : '_runConsole',
227 'killall' : '_killAll',
228 'test' : '_runTests',
229 'info' : '_showInfo',
232 'coffee' : '_makeCoffee',
236 if not command in availableCommands.keys():
240 return availableCommands[command], options
245 Args consist in a mandatory command followed by optionnal parameters.
246 See usage for details on commands.
248 def _startSalome(self, args):
252 from setenv import add_path
253 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
254 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome"))
255 add_path(path, "PYTHONPATH")
256 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome", "appliskel"))
257 add_path(path, "PYTHONPATH")
262 command, options = self.__parseArguments(args)
266 if args and args[0] in ["-h","--help","help"]:
269 # try to default to "start" command
270 command = "_runAppli"
273 res = getattr(self, command)(options) # run appropriate method
274 return res or (None, None)
275 except SystemExit, returncode:
277 self.getLogger().warning("SystemExit %s in method %s.", returncode, command)
279 except StandardError:
280 self.getLogger().error("Unexpected error:")
282 traceback.print_exc()
284 except SalomeContextException, e:
285 self.getLogger().error(e)
289 def __setContextFromConfigFile(self, filename, reserved=None):
293 unsetVars, configVars, reservedDict = parseConfigFile(filename, reserved)
294 except SalomeContextException, e:
296 file_dir = os.path.dirname(filename)
297 file_base = os.path.basename(filename)
298 base_no_ext, ext = os.path.splitext(file_base)
299 sh_file = os.path.join(file_dir, base_no_ext+'.sh')
300 #if ext == ".cfg" and os.path.isfile(sh_file):
302 msg += "Found similar %s file; trying to parse this one instead..."%(base_no_ext+'.sh')
303 temp = tempfile.NamedTemporaryFile(suffix='.cfg')
305 convertEnvFileToConfigFile(sh_file, temp.name, reserved)
306 self.__setContextFromConfigFile(temp.name, reserved)
308 self.getLogger().warning(msg)
311 except (ConfigParser.ParsingError, ValueError) as e:
312 msg += "Invalid token found when parsing file: %s\n"%(sh_file)
313 self.getLogger().error(msg)
317 self.getLogger().error(msg)
321 for var in unsetVars:
322 self.unsetVariable(var)
325 for reserved in reservedDict:
326 a = filter(None, reservedDict[reserved]) # remove empty elements
327 a = [ os.path.realpath(x) for x in a ]
328 reformattedVals = os.pathsep.join(a)
329 if reserved in ["INCLUDE", "LIBPATH"]:
330 self.addToVariable(reserved, reformattedVals, separator=' ')
332 self.addToVariable(reserved, reformattedVals)
335 for key,val in configVars:
336 self.setVariable(key, val, overwrite=True)
339 pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
340 pythonpath = [ os.path.realpath(x) for x in pythonpath ]
341 sys.path[:0] = pythonpath
344 def _runAppli(self, args=None):
347 # Initialize SALOME environment
348 sys.argv = ['runSalome'] + args
350 setenv.main(True, exeName="salome start")
353 runSalome.runSalome()
356 def _setContext(self, args=None):
357 salome_context_set = os.getenv("SALOME_CONTEXT_SET")
358 if salome_context_set:
360 print "*** SALOME context has already been set."
361 print "*** Enter 'exit' (only once!) to leave SALOME context."
365 os.environ["SALOME_CONTEXT_SET"] = "yes"
367 print "*** SALOME context is now set."
368 print "*** Enter 'exit' (only once!) to leave SALOME context."
372 proc = subprocess.Popen(cmd, shell=False, close_fds=True)
373 return proc.communicate()
376 def _runSession(self, args=None):
379 sys.argv = ['runSession'] + args
381 params, args = runSession.configureSession(args, exe="salome shell")
383 sys.argv = ['runSession'] + args
387 return runSession.runSession(params, args)
390 def _runConsole(self, args=None):
393 # Initialize SALOME environment
394 sys.argv = ['runConsole']
399 return runConsole.connect(args)
402 def _kill(self, args=None):
407 print "Port number(s) not provided to command: salome kill <port(s)>"
410 from multiprocessing import Process
411 from killSalomeWithPort import killMyPort
414 with tempfile.NamedTemporaryFile():
415 p = Process(target = killMyPort, args=(port,))
421 def _killAll(self, unused=None):
423 import PortManager # mandatory
424 from multiprocessing import Process
425 from killSalomeWithPort import killMyPort
426 ports = PortManager.getBusyPorts()
431 with tempfile.NamedTemporaryFile():
432 p = Process(target = killMyPort, args=(port,))
436 # :TODO: should be declared obsolete
437 from killSalome import killAllPorts
442 def _runTests(self, args=None):
445 sys.argv = ['runTests']
450 return runTests.runTests(args, exe="salome test")
453 def _showSoftwareVersions(self, softwares=None):
454 config = ConfigParser.SafeConfigParser()
455 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
456 filename = os.path.join(absoluteAppliPath, "sha1_collections.txt")
459 with open(filename) as f:
462 software, version, sha1 = line.split()
463 versions[software.upper()] = version
464 if len(software) > max_len:
465 max_len = len(software)
471 for soft in softwares:
472 if versions.has_key(soft.upper()):
473 print soft.upper().rjust(max_len), versions[soft.upper()]
475 for name, version in versions.items():
476 print name.rjust(max_len), versions[name]
479 def _showInfo(self, args=None):
483 usage = "Usage: salome info [options]"
485 Display some information about SALOME.\n
486 Available options are:
487 -p,--ports Show the list of busy ports (running SALOME instances).
488 -s,--softwares Show the list and versions of SALOME softwares.
489 -v,--version Show running SALOME version.
490 -h,--help Show this message.
495 if "-h" in args or "--help" in args:
499 if "-p" in args or "--ports" in args:
501 ports = PortManager.getBusyPorts()
502 print "SALOME instances are running on ports:", ports
504 print "Last started instance on port %s"%ports[-1]
506 if "-s" in args or "--softwares" in args:
507 self._showSoftwareVersions()
509 if "-v" in args or "--version" in args:
510 print "Running with python", platform.python_version()
511 self._runAppli(["--version"])
514 def _showDoc(self, args=None):
520 print "Module(s) not provided to command: salome doc <module(s)>"
523 appliPath = os.getenv("ABSOLUTE_APPLI_PATH")
525 raise SalomeContextException("Unable to find application path. Please check that the variable ABSOLUTE_APPLI_PATH is set.")
526 baseDir = os.path.join(appliPath, "share", "doc", "salome")
527 for module in modules:
528 docfile = os.path.join(baseDir, "gui", module.upper(), "index.html")
529 if not os.path.isfile(docfile):
530 docfile = os.path.join(baseDir, "tui", module.upper(), "index.html")
531 if not os.path.isfile(docfile):
532 docfile = os.path.join(baseDir, "dev", module.upper(), "index.html")
533 if os.path.isfile(docfile):
534 out, err = subprocess.Popen(["xdg-open", docfile]).communicate()
536 print "Online documentation is not accessible for module:", module
538 def _usage(self, unused=None):
542 def _makeCoffee(self, unused=None):
545 print " ___...(-------)-....___"
546 print " .-\"\" ) ( \"\"-."
547 print " .-\'``\'|-._ ) _.-|"
548 print " / .--.| `\"\"---...........---\"\"` |"
553 print " `\\ `| SALOME |"
554 print " _/ /\\ 4 EVER /"
555 print " (__/ \\ <3 /"
556 print " _..---\"\"` \\ /`\"\"---.._"
557 print " .-\' \\ / \'-."
558 print " : `-.__ __.-\' :"
559 print " : ) \"\"---...---\"\" ( :"
560 print " \'._ `\"--...___...--\"` _.\'"
561 print " \\\"\"--..__ __..--\"\"/"
562 print " \'._ \"\"\"----.....______.....----\"\"\" _.\'"
563 print " `\"\"--..,,_____ _____,,..--\"\"`"
564 print " `\"\"\"----\"\"\"`"
566 print " SALOME is working for you; what else?"
571 def _getCar(self, unused=None):
572 print " _____________"
573 print " ..---:::::::-----------. ::::;;."
574 print " .\'\"\"\"\"\"\" ;; \\ \":."
575 print " .\'\' ; \\ \"\\__."
576 print " .\' ;; ; \\\\\";"
577 print " .\' ; _____; \\\\/"
578 print " .\' :; ;\" \\ ___:\'."
579 print " .\'--........................... : = ____:\" \\ \\"
580 print " ..-\"\" \"\"\"\' o\"\"\" ; ; :"
581 print " .--\"\" .----- ..----... _.- --. ..-\" ; ; ; ;"
582 print " .\"\"_- \"--\"\"-----\'\"\" _-\" .-\"\" ; ; .-."
583 print " .\' .\' SALOME .\" .\" ; ; /. |"
584 print " /-./\' 4 EVER <3 .\" / _.. ; ; ;;;|"
585 print " : ;-.______ / _________==. /_ \\ ; ; ;;;;"
586 print " ; / | \"\"\"\"\"\"\"\"\"\"\".---.\"\"\"\"\"\"\" : /\" \". |; ; _; ;;;"
587 print " /\"-/ | / / / / ;|; ;-\" | ;\';"
588 print ":- : \"\"\"----______ / / ____. . .\"\'. ;; .-\"..T\" ."
589 print "\'. \" ___ \"\": \'\"\"\"\"\"\"\"\"\"\"\"\"\"\" . ; ; ;; ;.\" .\" \'--\""
590 print " \", __ \"\"\" \"\"---... :- - - - - - - - - \' \' ; ; ; ;;\" .\""
591 print " /. ; \"\"\"---___ ; ; ; ;|.\"\""
592 print " : \": \"\"\"----. .-------. ; ; ; ;:"
593 print " \\ \'--__ \\ \\ \\ / | ; ;;"
594 print " \'-.. \"\"\"\"---___ : .______..\\ __/..-\"\"| ; ; ;"
595 print " \"\"--.. \"\"\"--\" m l s . \". . ;"
596 print " \"\"------... ..--\"\" \" :"
597 print " \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\" \\ /"
600 print " Drive your simulation properly with SALOME!"
605 # Add the following two methods since logger is not pickable
606 # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
607 def __getstate__(self):
608 d = dict(self.__dict__)
609 if hasattr(self, '_logger'):
613 def __setstate__(self, d):
614 self.__dict__.update(d) # I *think* this is a safe way to do it
616 # Excluding self._logger from pickle operation imply using the following method to access logger
618 if not hasattr(self, '_logger'):
619 self._logger = logging.getLogger(__name__)
620 #self._logger.setLevel(logging.DEBUG)
621 #self._logger.setLevel(logging.WARNING)
622 self._logger.setLevel(logging.ERROR)
626 if __name__ == "__main__":
627 if len(sys.argv) == 3:
628 context = pickle.loads(sys.argv[1])
629 args = pickle.loads(sys.argv[2])
631 (out, err) = context._startSalome(args)
633 sys.stdout.write(out)
635 sys.stderr.write(err)