1 #! /usr/bin/env python3
2 # Copyright (C) 2013-2019 CEA/DEN, EDF R&D, OPEN CASCADE
4 # This library is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU Lesser General Public
6 # License as published by the Free Software Foundation; either
7 # version 2.1 of the License, or (at your option) any later version.
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # Lesser General Public License for more details.
14 # You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
26 from parseConfigFile import parseConfigFile
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 remote run command in SALOME environment from remote call, ssh or rsh.
49 kill <port(s)> Terminate SALOME instances running on given ports for current user.
50 Port numbers must be separated by blank characters.
51 killall Terminate *all* SALOME running instances for current user.
52 Do not start a new one.
53 test Run SALOME tests.
54 info Display some information about SALOME.
55 doc <module(s)> Show online module documentation (if available).
56 Module names must be separated by blank characters.
57 help Show this message.
59 If no command is given, default is start.
63 Use salome <command> --help to show help on command. Available for the
64 following commands: start, shell, connect, test, info.
66 --config=<file,folder,...>
67 ==========================
68 Initialize SALOME context from a list of context files and/or a list
69 of folders containing context files. The list is comma-separated, without
77 The SalomeContext class in an API to configure SALOME context then
78 start SALOME using a single python command.
83 Initialize context from a list of configuration files
84 identified by their names.
85 These files should be in appropriate .cfg format.
87 def __init__(self, configFileNames=0):
88 self.getLogger().setLevel(logging.INFO)
89 #it could be None explicitly (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_FALLBACK_LIBRARY_PATH', 'DYLD_LIBRARY_PATH', 'LD_LIBRARY_PATH', 'PYTHONPATH', 'MANPATH', 'PV_PLUGIN_PATH', 'INCLUDE', 'LIBPATH', 'SALOME_PLUGINS_PATH', 'LIBRARY_PATH', 'QT_PLUGIN_PATH']
97 for filename in configFileNames:
98 basename, extension = os.path.splitext(filename)
99 if extension == ".cfg":
100 self.__setContextFromConfigFile(filename, reserved)
102 self.getLogger().error("Unrecognized extension for configuration file: %s", filename)
105 def __loadEnvModules(self, env_modules):
106 modulecmd = os.getenv('LMOD_CMD')
108 raise SalomeContextException("Module environment not present")
111 out, err = subprocess.Popen([modulecmd, "python", "load"] + env_modules, stdout=subprocess.PIPE).communicate()
112 exec(out) # define specific environment variables
114 raise SalomeContextException("Failed to load env modules: %s ..." % ' '.join(env_modules))
118 def runSalome(self, args):
120 # Run this module as a script, in order to use appropriate Python interpreter
121 # according to current path (initialized from context files).
122 env_modules_option = "--with-env-modules="
123 env_modules_l = [x for x in args if x.startswith(env_modules_option)]
125 env_modules = env_modules_l[-1][len(env_modules_option):].split(',')
126 self.__loadEnvModules(env_modules)
127 args = [x for x in args if not x.startswith(env_modules_option)]
129 env_modules = os.getenv("SALOME_ENV_MODULES", None)
131 self.__loadEnvModules(env_modules.split(','))
133 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
134 env_copy = os.environ.copy()
135 selfBytes= pickle.dumps(self, protocol=0)
136 argsBytes= pickle.dumps(args, protocol=0)
137 proc = subprocess.Popen(['python3', os.path.join(absoluteAppliPath,"bin","salome","salomeContext.py"), selfBytes.decode(), argsBytes.decode()], shell=False, close_fds=True, env=env_copy)
138 out, err = proc.communicate()
139 return out, err, proc.returncode
142 """Append value to PATH environment variable"""
143 def addToPath(self, value):
144 self.addToVariable('PATH', value)
147 """Append value to LD_LIBRARY_PATH environment variable"""
148 def addToLdLibraryPath(self, value):
149 if sys.platform == 'win32':
150 self.addToVariable('PATH', value)
151 elif sys.platform == 'darwin':
152 if "LAPACK" in value:
153 self.addToVariable('DYLD_FALLBACK_LIBRARY_PATH', value)
155 self.addToVariable('DYLD_LIBRARY_PATH', value)
157 self.addToVariable('LD_LIBRARY_PATH', value)
160 """Append value to DYLD_LIBRARY_PATH environment variable"""
161 def addToDyldLibraryPath(self, value):
162 self.addToVariable('DYLD_LIBRARY_PATH', value)
165 """Append value to PYTHONPATH environment variable"""
166 def addToPythonPath(self, value):
167 self.addToVariable('PYTHONPATH', value)
170 """Set environment variable to value"""
171 def setVariable(self, name, value, overwrite=False):
172 env = os.getenv(name, '')
173 if env and not overwrite:
174 self.getLogger().error("Environment variable already existing (and not overwritten): %s=%s", name, value)
178 self.getLogger().debug("Overwriting environment variable: %s=%s", name, value)
180 value = os.path.expandvars(value) # expand environment variables
181 self.getLogger().debug("Set environment variable: %s=%s", name, value)
182 os.environ[name] = value
185 """Unset environment variable"""
186 def unsetVariable(self, name):
187 if os.environ.has_key(name):
191 """Append value to environment variable"""
192 def addToVariable(self, name, value, separator=os.pathsep):
196 value = os.path.expandvars(value) # expand environment variables
197 self.getLogger().debug("Add to %s: %s", name, value)
198 env = os.getenv(name, None)
200 os.environ[name] = value
202 os.environ[name] = value + separator + env
205 ###################################
206 # This begins the private section #
207 ###################################
209 def __parseArguments(self, args):
210 if len(args) == 0 or args[0].startswith("-"):
216 availableCommands = {
217 'start' : '_runAppli',
218 'context' : '_setContext',
219 'shell' : '_runSession',
220 'remote' : '_runRemote',
221 'connect' : '_runConsole',
223 'killall' : '_killAll',
224 'test' : '_runTests',
225 'info' : '_showInfo',
228 'coffee' : '_makeCoffee',
232 if command not in availableCommands:
236 return availableCommands[command], options
241 Args consist in a mandatory command followed by optional parameters.
242 See usage for details on commands.
244 def _startSalome(self, args):
248 from setenv import add_path
249 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
250 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome"))
251 add_path(path, "PYTHONPATH")
252 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome", "appliskel"))
253 add_path(path, "PYTHONPATH")
258 command, options = self.__parseArguments(args)
262 if args and args[0] in ["-h","--help","help"]:
265 # try to default to "start" command
266 command = "_runAppli"
269 res = getattr(self, command)(options) # run appropriate method
271 except SystemExit as ex:
273 self.getLogger().error("SystemExit %s in method %s.", ex.code, command)
275 except SalomeContextException as e:
276 self.getLogger().error(e)
279 self.getLogger().error("Unexpected error:")
281 traceback.print_exc()
285 def __setContextFromConfigFile(self, filename, reserved=None):
289 unsetVars, configVars, reservedDict = parseConfigFile(filename, reserved)
290 except SalomeContextException as e:
292 self.getLogger().error(msg)
296 for var in unsetVars:
297 self.unsetVariable(var)
300 for reserved in reservedDict:
301 a = [_f for _f in reservedDict[reserved] if _f] # remove empty elements
302 a = [ os.path.realpath(x) for x in a ]
303 reformattedVals = os.pathsep.join(a)
304 if reserved in ["INCLUDE", "LIBPATH"]:
305 self.addToVariable(reserved, reformattedVals, separator=' ')
307 self.addToVariable(reserved, reformattedVals)
310 for key,val in configVars:
311 self.setVariable(key, val, overwrite=True)
314 pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
315 pythonpath = [ os.path.realpath(x) for x in pythonpath ]
316 sys.path[:0] = pythonpath
319 def _runAppli(self, args=None):
322 # Initialize SALOME environment
323 sys.argv = ['runSalome'] + args
325 setenv.main(True, exeName="salome start")
328 runSalome.runSalome()
332 def _setContext(self, args=None):
333 salome_context_set = os.getenv("SALOME_CONTEXT_SET")
334 if salome_context_set:
336 print("*** SALOME context has already been set.")
337 print("*** Enter 'exit' (only once!) to leave SALOME context.")
341 os.environ["SALOME_CONTEXT_SET"] = "yes"
343 print("*** SALOME context is now set.")
344 print("*** Enter 'exit' (only once!) to leave SALOME context.")
347 if sys.platform == 'win32':
351 proc = subprocess.Popen(cmd, shell=False, close_fds=True)
353 return proc.returncode
356 def _runSession(self, args=None):
359 sys.argv = ['runSession'] + args
361 params, args = runSession.configureSession(args, exe="salome shell")
363 sys.argv = ['runSession'] + args
367 return runSession.runSession(params, args)
370 def _runRemote(self, args=None):
373 # complete salome environment
374 sys.argv = ['runRemote']
379 return runRemote.runRemote(args)
382 def _runConsole(self, args=None):
385 # Initialize SALOME environment
386 sys.argv = ['runConsole']
391 return runConsole.connect(args)
394 def _kill(self, args=None):
399 print("Port number(s) not provided to command: salome kill <port(s)>")
406 if os.getenv("NSHOST") == "no_host":
407 os.unsetenv("NSHOST")
409 proc = subprocess.Popen(["killSalomeWithPort.py", port])
415 def _killAll(self, unused=None):
416 sys.argv = ['killAll']
419 if os.getenv("NSHOST") == "no_host":
420 os.unsetenv("NSHOST")
422 import PortManager # mandatory
424 ports = PortManager.getBusyPorts()['this']
428 proc = subprocess.Popen(["killSalomeWithPort.py", str(port)])
431 # :TODO: should be declared obsolete
432 from killSalome import killAllPorts
438 def _runTests(self, args=None):
441 sys.argv = ['runTests']
446 return runTests.runTests(args, exe="salome test")
449 def _showSoftwareVersions(self, softwares=None):
450 config = configparser.SafeConfigParser()
451 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
452 filename = os.path.join(absoluteAppliPath, "sha1_collections.txt")
455 with open(filename) as f:
458 software, version, sha1 = line.split()
459 versions[software.upper()] = version
460 if len(software) > max_len:
461 max_len = len(software)
467 for soft in softwares:
468 if soft.upper() in versions:
469 print(soft.upper().rjust(max_len), versions[soft.upper()])
472 od = collections.OrderedDict(sorted(versions.items()))
473 for name, version in od.items():
474 print(name.rjust(max_len), versions[name])
477 def _showInfo(self, args=None):
481 usage = "Usage: salome info [options]"
483 Display some information about SALOME.\n
484 Available options are:
485 -p,--ports Show the list of busy ports (running SALOME instances).
486 -s,--softwares [software(s)] Show the list and versions of SALOME softwares.
487 Software names must be separated by blank characters.
488 If no software is given, show version of all softwares.
489 -v,--version Show running SALOME version.
490 -h,--help Show this message.
495 if "-h" in args or "--help" in args:
496 print(usage + epilog)
499 if "-p" in args or "--ports" in args:
501 ports = PortManager.getBusyPorts()
502 this_ports = ports['this']
503 other_ports = ports['other']
504 if this_ports or other_ports:
505 print("SALOME instances are running on the following ports:")
507 print(" This application:", this_ports)
509 print(" No SALOME instances of this application")
511 print(" Other applications:", other_ports)
513 print(" No SALOME instances of other applications")
515 print("No SALOME instances are running")
517 if "-s" in args or "--softwares" in args:
519 index = args.index("-s")
521 index = args.index("--softwares")
523 while indexEnd < len(args) and args[indexEnd][0] != "-":
524 indexEnd = indexEnd + 1
525 self._showSoftwareVersions(softwares=args[index+1:indexEnd])
527 if "-v" in args or "--version" in args:
528 print("Running with python", platform.python_version())
529 return self._runAppli(["--version"])
534 def _showDoc(self, args=None):
540 print("Module(s) not provided to command: salome doc <module(s)>")
543 appliPath = os.getenv("ABSOLUTE_APPLI_PATH")
545 raise SalomeContextException("Unable to find application path. Please check that the variable ABSOLUTE_APPLI_PATH is set.")
546 baseDir = os.path.join(appliPath, "share", "doc", "salome")
547 for module in modules:
548 docfile = os.path.join(baseDir, "gui", module.upper(), "index.html")
549 if not os.path.isfile(docfile):
550 docfile = os.path.join(baseDir, "tui", module.upper(), "index.html")
551 if not os.path.isfile(docfile):
552 docfile = os.path.join(baseDir, "dev", module.upper(), "index.html")
553 if os.path.isfile(docfile):
554 out, err = subprocess.Popen(["xdg-open", docfile]).communicate()
556 print("Online documentation is not accessible for module:", module)
558 def _usage(self, unused=None):
562 def _makeCoffee(self, unused=None):
565 print(" ___...(-------)-....___")
566 print(" .-\"\" ) ( \"\"-.")
567 print(" .-\'``\'|-._ ) _.-|")
568 print(" / .--.| `\"\"---...........---\"\"` |")
572 print(" `\\ `\\ | |")
573 print(" `\\ `| SALOME |")
574 print(" _/ /\\ 4 EVER /")
575 print(" (__/ \\ <3 /")
576 print(" _..---\"\"` \\ /`\"\"---.._")
577 print(" .-\' \\ / \'-.")
578 print(" : `-.__ __.-\' :")
579 print(" : ) \"\"---...---\"\" ( :")
580 print(" \'._ `\"--...___...--\"` _.\'")
581 print(" \\\"\"--..__ __..--\"\"/")
582 print(" \'._ \"\"\"----.....______.....----\"\"\" _.\'")
583 print(" `\"\"--..,,_____ _____,,..--\"\"`")
584 print(" `\"\"\"----\"\"\"`")
586 print(" SALOME is working for you; what else?")
590 def _getCar(self, unused=None):
591 print(" _____________")
592 print(" ..---:::::::-----------. ::::;;.")
593 print(" .\'\"\"\"\"\"\" ;; \\ \":.")
594 print(" .\'\' ; \\ \"\\__.")
595 print(" .\' ;; ; \\\\\";")
596 print(" .\' ; _____; \\\\/")
597 print(" .\' :; ;\" \\ ___:\'.")
598 print(" .\'--........................... : = ____:\" \\ \\")
599 print(" ..-\"\" \"\"\"\' o\"\"\" ; ; :")
600 print(" .--\"\" .----- ..----... _.- --. ..-\" ; ; ; ;")
601 print(" .\"\"_- \"--\"\"-----\'\"\" _-\" .-\"\" ; ; .-.")
602 print(" .\' .\' SALOME .\" .\" ; ; /. |")
603 print(" /-./\' 4 EVER <3 .\" / _.. ; ; ;;;|")
604 print(" : ;-.______ / _________==. /_ \\ ; ; ;;;;")
605 print(" ; / | \"\"\"\"\"\"\"\"\"\"\".---.\"\"\"\"\"\"\" : /\" \". |; ; _; ;;;")
606 print(" /\"-/ | / / / / ;|; ;-\" | ;\';")
607 print(":- : \"\"\"----______ / / ____. . .\"\'. ;; .-\"..T\" .")
608 print("\'. \" ___ \"\": \'\"\"\"\"\"\"\"\"\"\"\"\"\"\" . ; ; ;; ;.\" .\" \'--\"")
609 print(" \", __ \"\"\" \"\"---... :- - - - - - - - - \' \' ; ; ; ;;\" .\"")
610 print(" /. ; \"\"\"---___ ; ; ; ;|.\"\"")
611 print(" : \": \"\"\"----. .-------. ; ; ; ;:")
612 print(" \\ \'--__ \\ \\ \\ / | ; ;;")
613 print(" \'-.. \"\"\"\"---___ : .______..\\ __/..-\"\"| ; ; ;")
614 print(" \"\"--.. \"\"\"--\" m l s . \". . ;")
615 print(" \"\"------... ..--\"\" \" :")
616 print(" \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\" \\ /")
619 print(" Drive your simulation properly with SALOME!")
623 # Add the following two methods since logger is not pickable
624 # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
625 def __getstate__(self):
626 d = dict(self.__dict__)
627 if hasattr(self, '_logger'):
631 def __setstate__(self, d):
632 self.__dict__.update(d) # I *think* this is a safe way to do it
634 # Excluding self._logger from pickle operation imply using the following method to access logger
636 if not hasattr(self, '_logger'):
637 self._logger = logging.getLogger(__name__)
638 #self._logger.setLevel(logging.DEBUG)
639 #self._logger.setLevel(logging.WARNING)
640 self._logger.setLevel(logging.ERROR)
644 if __name__ == "__main__":
645 if len(sys.argv) == 3:
646 context = pickle.loads(sys.argv[1].encode())
647 args = pickle.loads(sys.argv[2].encode())
649 status = context._startSalome(args)