1 # Copyright (C) 2013-2017 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
32 from salomeContextUtils import SalomeContextException
36 Usage: salome [command] [options] [--config=<file,folder,...>]
40 start Start a new SALOME instance.
41 context Initialize SALOME context. Current environment is extended.
42 shell Initialize SALOME context, attached to the last created SALOME
43 instance if any, and executes scripts passed as command arguments.
44 User works in a Shell terminal. SALOME environment is set but
45 application is not started.
46 connect Connect a Python console to the active SALOME instance.
47 kill <port(s)> Terminate SALOME instances running on given ports for current user.
48 Port numbers must be separated by blank characters.
49 killall Terminate *all* SALOME running instances for current user.
50 Do not start a new one.
51 test Run SALOME tests.
52 info Display some information about SALOME.
53 doc <module(s)> Show online module documentation (if available).
54 Module names must be separated by blank characters.
55 help Show this message.
57 If no command is given, default is start.
61 Use salome <command> --help to show help on command. Available for the
62 following commands: start, shell, connect, test, info.
64 --config=<file,folder,...>
65 ==========================
66 Initialize SALOME context from a list of context files and/or a list
67 of folders containing context files. The list is comma-separated, whithout
75 The SalomeContext class in an API to configure SALOME context then
76 start SALOME using a single python command.
81 Initialize context from a list of configuration files
82 identified by their names.
83 These files should be in appropriate .cfg format.
85 def __init__(self, configFileNames=0):
86 self.getLogger().setLevel(logging.INFO)
87 #it could be None explicitely (if user use multiples setVariable...for standalone)
88 if configFileNames is None:
90 configFileNames = configFileNames or []
91 if len(configFileNames) == 0:
92 raise SalomeContextException("No configuration files given")
94 reserved=['PATH', 'DYLD_FALLBACK_LIBRARY_PATH', 'DYLD_LIBRARY_PATH', 'LD_LIBRARY_PATH', 'PYTHONPATH', 'MANPATH', 'PV_PLUGIN_PATH', 'INCLUDE', 'LIBPATH', 'SALOME_PLUGINS_PATH', 'LIBRARY_PATH', 'QT_PLUGIN_PATH']
95 for filename in configFileNames:
96 basename, extension = os.path.splitext(filename)
97 if extension == ".cfg":
98 self.__setContextFromConfigFile(filename, reserved)
100 self.getLogger().error("Unrecognized extension for configuration file: %s", filename)
103 def __loadMPI(self, module_name):
104 print("Trying to load MPI module: %s..."%module_name)
106 out, err = subprocess.Popen(["modulecmd", "python", "load", module_name], stdout=subprocess.PIPE).communicate()
107 exec(out) # define specific environment variables
110 print(" ** Failed **")
114 def runSalome(self, args):
116 # Run this module as a script, in order to use appropriate Python interpreter
117 # according to current path (initialized from context files).
118 mpi_module_option = "--with-mpi-module="
119 mpi_module = [x for x in args if x.startswith(mpi_module_option)]
121 mpi_module = mpi_module[0][len(mpi_module_option):]
122 self.__loadMPI(mpi_module)
123 args = [x for x in args if not x.startswith(mpi_module_option)]
125 mpi_module = os.getenv("SALOME_MPI_MODULE_NAME", None)
127 self.__loadMPI(mpi_module)
129 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
130 env_copy = os.environ.copy()
131 selfBytes= pickle.dumps(self,protocol=0)
132 argsBytes= pickle.dumps(args,protocol=0)
133 proc = subprocess.Popen(['python', os.path.join(absoluteAppliPath,"bin","salome","salomeContext.py"), selfBytes.decode(), argsBytes.decode()], shell=False, close_fds=True, env=env_copy)
134 out, err = proc.communicate()
135 return out, err, proc.returncode
138 """Append value to PATH environment variable"""
139 def addToPath(self, value):
140 self.addToVariable('PATH', value)
143 """Append value to LD_LIBRARY_PATH environment variable"""
144 def addToLdLibraryPath(self, value):
145 if platform.system() == 'Windows':
146 self.addToVariable('PATH', value)
147 elif platform.system() == 'Darwin':
148 if "LAPACK" in value:
149 self.addToVariable('DYLD_FALLBACK_LIBRARY_PATH', value)
151 self.addToVariable('DYLD_LIBRARY_PATH', value)
153 self.addToVariable('LD_LIBRARY_PATH', value)
156 """Append value to DYLD_LIBRARY_PATH environment variable"""
157 def addToDyldLibraryPath(self, value):
158 self.addToVariable('DYLD_LIBRARY_PATH', value)
161 """Append value to PYTHONPATH environment variable"""
162 def addToPythonPath(self, value):
163 self.addToVariable('PYTHONPATH', value)
166 """Set environment variable to value"""
167 def setVariable(self, name, value, overwrite=False):
168 env = os.getenv(name, '')
169 if env and not overwrite:
170 self.getLogger().error("Environment variable already existing (and not overwritten): %s=%s", name, value)
174 self.getLogger().debug("Overwriting environment variable: %s=%s", name, value)
176 value = os.path.expandvars(value) # expand environment variables
177 self.getLogger().debug("Set environment variable: %s=%s", name, value)
178 os.environ[name] = value
181 """Unset environment variable"""
182 def unsetVariable(self, name):
183 if os.environ.has_key(name):
187 """Append value to environment variable"""
188 def addToVariable(self, name, value, separator=os.pathsep):
192 value = os.path.expandvars(value) # expand environment variables
193 self.getLogger().debug("Add to %s: %s", name, value)
194 env = os.getenv(name, None)
196 os.environ[name] = value
198 os.environ[name] = value + separator + env
201 ###################################
202 # This begins the private section #
203 ###################################
205 def __parseArguments(self, args):
206 if len(args) == 0 or args[0].startswith("-"):
212 availableCommands = {
213 'start' : '_runAppli',
214 'context' : '_setContext',
215 'shell' : '_runSession',
216 'connect' : '_runConsole',
218 'killall' : '_killAll',
219 'test' : '_runTests',
220 'info' : '_showInfo',
223 'coffee' : '_makeCoffee',
227 if command not in availableCommands:
231 return availableCommands[command], options
236 Args consist in a mandatory command followed by optionnal parameters.
237 See usage for details on commands.
239 def _startSalome(self, args):
243 from setenv import add_path
244 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
245 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome"))
246 add_path(path, "PYTHONPATH")
247 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome", "appliskel"))
248 add_path(path, "PYTHONPATH")
253 command, options = self.__parseArguments(args)
257 if args and args[0] in ["-h","--help","help"]:
260 # try to default to "start" command
261 command = "_runAppli"
264 res = getattr(self, command)(options) # run appropriate method
266 except SystemExit as returncode:
268 self.getLogger().error("SystemExit %s in method %s.", returncode, command)
271 self.getLogger().error("Unexpected error:")
273 traceback.print_exc()
275 except SalomeContextException as e:
276 self.getLogger().error(e)
280 def __setContextFromConfigFile(self, filename, reserved=None):
284 unsetVars, configVars, reservedDict = parseConfigFile(filename, reserved)
285 except SalomeContextException as e:
287 self.getLogger().error(msg)
291 for var in unsetVars:
292 self.unsetVariable(var)
295 for reserved in reservedDict:
296 a = [_f for _f in reservedDict[reserved] if _f] # remove empty elements
297 a = [ os.path.realpath(x) for x in a ]
298 reformattedVals = os.pathsep.join(a)
299 if reserved in ["INCLUDE", "LIBPATH"]:
300 self.addToVariable(reserved, reformattedVals, separator=' ')
302 self.addToVariable(reserved, reformattedVals)
305 for key,val in configVars:
306 self.setVariable(key, val, overwrite=True)
309 pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
310 pythonpath = [ os.path.realpath(x) for x in pythonpath ]
311 sys.path[:0] = pythonpath
314 def _runAppli(self, args=None):
317 # Initialize SALOME environment
318 sys.argv = ['runSalome'] + args
320 setenv.main(True, exeName="salome start")
323 runSalome.runSalome()
327 def _setContext(self, args=None):
328 salome_context_set = os.getenv("SALOME_CONTEXT_SET")
329 if salome_context_set:
331 print("*** SALOME context has already been set.")
332 print("*** Enter 'exit' (only once!) to leave SALOME context.")
336 os.environ["SALOME_CONTEXT_SET"] = "yes"
338 print("*** SALOME context is now set.")
339 print("*** Enter 'exit' (only once!) to leave SALOME context.")
343 proc = subprocess.Popen(cmd, shell=False, close_fds=True)
345 return proc.returncode()
348 def _runSession(self, args=None):
351 sys.argv = ['runSession'] + args
353 params, args = runSession.configureSession(args, exe="salome shell")
355 sys.argv = ['runSession'] + args
359 return runSession.runSession(params, args)
362 def _runConsole(self, args=None):
365 # Initialize SALOME environment
366 sys.argv = ['runConsole']
371 return runConsole.connect(args)
374 def _kill(self, args=None):
379 print("Port number(s) not provided to command: salome kill <port(s)>")
382 from multiprocessing import Process
383 from killSalomeWithPort import killMyPort
386 with tempfile.NamedTemporaryFile():
387 p = Process(target = killMyPort, args=(port,))
393 def _killAll(self, unused=None):
395 import PortManager # mandatory
396 from multiprocessing import Process
397 from killSalomeWithPort import killMyPort
398 ports = PortManager.getBusyPorts()['this']
403 with tempfile.NamedTemporaryFile():
404 p = Process(target = killMyPort, args=(port,))
408 # :TODO: should be declared obsolete
409 from killSalome import killAllPorts
415 def _runTests(self, args=None):
418 sys.argv = ['runTests']
423 return runTests.runTests(args, exe="salome test")
426 def _showSoftwareVersions(self, softwares=None):
427 config = configparser.SafeConfigParser()
428 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
429 filename = os.path.join(absoluteAppliPath, "sha1_collections.txt")
432 with open(filename) as f:
435 software, version, sha1 = line.split()
436 versions[software.upper()] = version
437 if len(software) > max_len:
438 max_len = len(software)
444 for soft in softwares:
445 if versions.has_key(soft.upper()):
446 print(soft.upper().rjust(max_len), versions[soft.upper()])
449 od = collections.OrderedDict(sorted(versions.items()))
450 for name, version in od.iteritems():
451 print(name.rjust(max_len), versions[name])
454 def _showInfo(self, args=None):
458 usage = "Usage: salome info [options]"
460 Display some information about SALOME.\n
461 Available options are:
462 -p,--ports Show the list of busy ports (running SALOME instances).
463 -s,--softwares [software(s)] Show the list and versions of SALOME softwares.
464 Software names must be separated by blank characters.
465 If no software is given, show version of all softwares.
466 -v,--version Show running SALOME version.
467 -h,--help Show this message.
472 if "-h" in args or "--help" in args:
473 print(usage + epilog)
476 if "-p" in args or "--ports" in args:
478 ports = PortManager.getBusyPorts()
479 this_ports = ports['this']
480 other_ports = ports['other']
481 if this_ports or other_ports:
482 print("SALOME instances are running on the following ports:")
484 print(" This application:", this_ports)
486 print(" No SALOME instances of this application")
488 print(" Other applications:", other_ports)
490 print(" No SALOME instances of other applications")
492 print("No SALOME instances are running")
494 if "-s" in args or "--softwares" in args:
496 index = args.index("-s")
498 index = args.index("--softwares")
500 while indexEnd < len(args) and args[indexEnd][0] != "-":
501 indexEnd = indexEnd + 1
502 self._showSoftwareVersions(softwares=args[index+1:indexEnd])
504 if "-v" in args or "--version" in args:
505 print("Running with python", platform.python_version())
506 return self._runAppli(["--version"])
511 def _showDoc(self, args=None):
517 print("Module(s) not provided to command: salome doc <module(s)>")
520 appliPath = os.getenv("ABSOLUTE_APPLI_PATH")
522 raise SalomeContextException("Unable to find application path. Please check that the variable ABSOLUTE_APPLI_PATH is set.")
523 baseDir = os.path.join(appliPath, "share", "doc", "salome")
524 for module in modules:
525 docfile = os.path.join(baseDir, "gui", module.upper(), "index.html")
526 if not os.path.isfile(docfile):
527 docfile = os.path.join(baseDir, "tui", module.upper(), "index.html")
528 if not os.path.isfile(docfile):
529 docfile = os.path.join(baseDir, "dev", module.upper(), "index.html")
530 if os.path.isfile(docfile):
531 out, err = subprocess.Popen(["xdg-open", docfile]).communicate()
533 print("Online documentation is not accessible for module:", module)
535 def _usage(self, unused=None):
539 def _makeCoffee(self, unused=None):
542 print(" ___...(-------)-....___")
543 print(" .-\"\" ) ( \"\"-.")
544 print(" .-\'``\'|-._ ) _.-|")
545 print(" / .--.| `\"\"---...........---\"\"` |")
549 print(" `\\ `\\ | |")
550 print(" `\\ `| SALOME |")
551 print(" _/ /\\ 4 EVER /")
552 print(" (__/ \\ <3 /")
553 print(" _..---\"\"` \\ /`\"\"---.._")
554 print(" .-\' \\ / \'-.")
555 print(" : `-.__ __.-\' :")
556 print(" : ) \"\"---...---\"\" ( :")
557 print(" \'._ `\"--...___...--\"` _.\'")
558 print(" \\\"\"--..__ __..--\"\"/")
559 print(" \'._ \"\"\"----.....______.....----\"\"\" _.\'")
560 print(" `\"\"--..,,_____ _____,,..--\"\"`")
561 print(" `\"\"\"----\"\"\"`")
563 print(" SALOME is working for you; what else?")
567 def _getCar(self, unused=None):
568 print(" _____________")
569 print(" ..---:::::::-----------. ::::;;.")
570 print(" .\'\"\"\"\"\"\" ;; \\ \":.")
571 print(" .\'\' ; \\ \"\\__.")
572 print(" .\' ;; ; \\\\\";")
573 print(" .\' ; _____; \\\\/")
574 print(" .\' :; ;\" \\ ___:\'.")
575 print(" .\'--........................... : = ____:\" \\ \\")
576 print(" ..-\"\" \"\"\"\' o\"\"\" ; ; :")
577 print(" .--\"\" .----- ..----... _.- --. ..-\" ; ; ; ;")
578 print(" .\"\"_- \"--\"\"-----\'\"\" _-\" .-\"\" ; ; .-.")
579 print(" .\' .\' SALOME .\" .\" ; ; /. |")
580 print(" /-./\' 4 EVER <3 .\" / _.. ; ; ;;;|")
581 print(" : ;-.______ / _________==. /_ \\ ; ; ;;;;")
582 print(" ; / | \"\"\"\"\"\"\"\"\"\"\".---.\"\"\"\"\"\"\" : /\" \". |; ; _; ;;;")
583 print(" /\"-/ | / / / / ;|; ;-\" | ;\';")
584 print(":- : \"\"\"----______ / / ____. . .\"\'. ;; .-\"..T\" .")
585 print("\'. \" ___ \"\": \'\"\"\"\"\"\"\"\"\"\"\"\"\"\" . ; ; ;; ;.\" .\" \'--\"")
586 print(" \", __ \"\"\" \"\"---... :- - - - - - - - - \' \' ; ; ; ;;\" .\"")
587 print(" /. ; \"\"\"---___ ; ; ; ;|.\"\"")
588 print(" : \": \"\"\"----. .-------. ; ; ; ;:")
589 print(" \\ \'--__ \\ \\ \\ / | ; ;;")
590 print(" \'-.. \"\"\"\"---___ : .______..\\ __/..-\"\"| ; ; ;")
591 print(" \"\"--.. \"\"\"--\" m l s . \". . ;")
592 print(" \"\"------... ..--\"\" \" :")
593 print(" \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\" \\ /")
596 print(" Drive your simulation properly with SALOME!")
600 # Add the following two methods since logger is not pickable
601 # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
602 def __getstate__(self):
603 d = dict(self.__dict__)
604 if hasattr(self, '_logger'):
608 def __setstate__(self, d):
609 self.__dict__.update(d) # I *think* this is a safe way to do it
611 # Excluding self._logger from pickle operation imply using the following method to access logger
613 if not hasattr(self, '_logger'):
614 self._logger = logging.getLogger(__name__)
615 #self._logger.setLevel(logging.DEBUG)
616 #self._logger.setLevel(logging.WARNING)
617 self._logger.setLevel(logging.ERROR)
621 if __name__ == "__main__":
622 if len(sys.argv) == 3:
623 context = pickle.loads(sys.argv[1].encode())
624 args = pickle.loads(sys.argv[2].encode())
626 status = context._startSalome(args)