1 #! /usr/bin/env python3
2 # Copyright (C) 2013-2021 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
34 from salomeContextUtils import SalomeContextException
38 Usage: salome [command] [options] [--config=<file,folder,...>]
42 start Start a new SALOME instance.
43 context Initialize SALOME context. Current environment is extended.
44 shell Initialize SALOME context, attached to the last created SALOME
45 instance if any, and executes scripts passed as command arguments.
46 User works in a Shell terminal. SALOME environment is set but
47 application is not started.
48 connect Connect a Python console to the active SALOME instance.
49 remote run command in SALOME environment from remote call, ssh or rsh.
50 kill <port(s)> Terminate SALOME instances running on given ports for current user.
51 Port numbers must be separated by blank characters.
52 killall Terminate *all* SALOME running instances for current user.
53 Do not start a new one.
54 test Run SALOME tests.
55 info Display some information about SALOME.
56 doc <module(s)> Show online module documentation (if available).
57 Module names must be separated by blank characters.
58 help Show this message.
60 If no command is given, default is start.
64 Use salome <command> --help to show help on command. Available for the
65 following commands: start, shell, connect, test, info.
67 --config=<file,folder,...>
68 ==========================
69 Initialize SALOME context from a list of context files and/or a list
70 of folders containing context files. The list is comma-separated, without
78 The SalomeContext class in an API to configure SALOME context then
79 start SALOME using a single python command.
84 Initialize context from a list of configuration files
85 identified by their names.
86 These files should be in appropriate .cfg format.
88 def __init__(self, configFileNames=0):
89 self.getLogger().setLevel(logging.INFO)
90 #it could be None explicitly (if user use multiples setVariable...for standalone)
91 if configFileNames is None:
93 configFileNames = configFileNames or []
94 if len(configFileNames) == 0:
95 raise SalomeContextException("No configuration files given")
97 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']
98 for filename in configFileNames:
99 basename, extension = os.path.splitext(filename)
100 if extension == ".cfg":
101 self.__setContextFromConfigFile(filename, reserved)
103 self.getLogger().error("Unrecognized extension for configuration file: %s", filename)
106 def __loadEnvModules(self, env_modules):
107 modulecmd = os.getenv('LMOD_CMD')
109 raise SalomeContextException("Module environment not present")
112 out, err = subprocess.Popen([modulecmd, "python", "load"] + env_modules, stdout=subprocess.PIPE).communicate()
113 exec(out) # define specific environment variables
115 raise SalomeContextException("Failed to load env modules: %s ..." % ' '.join(env_modules))
119 def runSalome(self, args):
121 # Run this module as a script, in order to use appropriate Python interpreter
122 # according to current path (initialized from context files).
123 env_modules_option = "--with-env-modules="
124 env_modules_l = [x for x in args if x.startswith(env_modules_option)]
126 env_modules = env_modules_l[-1][len(env_modules_option):].split(',')
127 self.__loadEnvModules(env_modules)
128 args = [x for x in args if not x.startswith(env_modules_option)]
130 env_modules = os.getenv("SALOME_ENV_MODULES", None)
132 self.__loadEnvModules(env_modules.split(','))
134 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
135 env_copy = os.environ.copy()
136 selfBytes= pickle.dumps(self, protocol=0)
137 argsBytes= pickle.dumps(args, protocol=0)
138 proc = subprocess.Popen(['python3', os.path.join(absoluteAppliPath,"bin","salome","salomeContext.py"), selfBytes.decode('latin1'), argsBytes.decode('latin1')], shell=False, close_fds=True, env=env_copy)
139 out, err = proc.communicate()
140 return out, err, proc.returncode
143 """Append value to PATH environment variable"""
144 def addToPath(self, value):
145 self.addToVariable('PATH', value)
148 """Append value to LD_LIBRARY_PATH environment variable"""
149 def addToLdLibraryPath(self, value):
150 if sys.platform == 'win32':
151 self.addToVariable('PATH', value)
152 elif sys.platform == 'darwin':
153 if "LAPACK" in value:
154 self.addToVariable('DYLD_FALLBACK_LIBRARY_PATH', value)
156 self.addToVariable('DYLD_LIBRARY_PATH', value)
158 self.addToVariable('LD_LIBRARY_PATH', value)
161 """Append value to DYLD_LIBRARY_PATH environment variable"""
162 def addToDyldLibraryPath(self, value):
163 self.addToVariable('DYLD_LIBRARY_PATH', value)
166 """Append value to PYTHONPATH environment variable"""
167 def addToPythonPath(self, value):
168 self.addToVariable('PYTHONPATH', value)
171 """Set environment variable to value"""
172 def setVariable(self, name, value, overwrite=False):
173 env = os.getenv(name, '')
174 if env and not overwrite:
175 self.getLogger().error("Environment variable already existing (and not overwritten): %s=%s", name, value)
179 self.getLogger().debug("Overwriting environment variable: %s=%s", name, value)
181 value = os.path.expandvars(value) # expand environment variables
182 self.getLogger().debug("Set environment variable: %s=%s", name, value)
183 os.environ[name] = value
186 def setDefaultValue(self, name, value):
187 """ Set environment variable only if it is undefined."""
188 env = os.getenv(name, '')
190 value = os.path.expandvars(value) # expand environment variables
191 self.getLogger().debug("Set environment variable: %s=%s", name, value)
192 os.environ[name] = value
194 """Unset environment variable"""
195 def unsetVariable(self, name):
196 if os.environ.has_key(name):
200 """Append value to environment variable"""
201 def addToVariable(self, name, value, separator=os.pathsep):
205 value = os.path.expandvars(value) # expand environment variables
206 self.getLogger().debug("Add to %s: %s", name, value)
207 env = os.getenv(name, None)
209 os.environ[name] = value
211 os.environ[name] = value + separator + env
214 ###################################
215 # This begins the private section #
216 ###################################
218 def __parseArguments(self, args):
219 if len(args) == 0 or args[0].startswith("-"):
225 availableCommands = {
226 'start' : '_runAppli',
227 'context' : '_setContext',
228 'shell' : '_runSession',
229 'remote' : '_runRemote',
230 'connect' : '_runConsole',
232 'killall' : '_killAll',
233 'test' : '_runTests',
234 'info' : '_showInfo',
237 'coffee' : '_makeCoffee',
241 if command not in availableCommands:
245 return availableCommands[command], options
250 Args consist in a mandatory command followed by optional parameters.
251 See usage for details on commands.
253 def _startSalome(self, args):
257 from setenv import add_path
258 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
259 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome"))
260 add_path(path, "PYTHONPATH")
261 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome", "appliskel"))
262 add_path(path, "PYTHONPATH")
267 command, options = self.__parseArguments(args)
271 if args and args[0] in ["-h","--help","help"]:
274 # try to default to "start" command
275 command = "_runAppli"
278 res = getattr(self, command)(options) # run appropriate method
280 except SystemExit as ex:
282 self.getLogger().error("SystemExit %s in method %s.", ex.code, command)
284 except SalomeContextException as e:
285 self.getLogger().error(e)
288 self.getLogger().error("Unexpected error:")
290 traceback.print_exc()
294 def __setContextFromConfigFile(self, filename, reserved=None):
295 mesa_root_dir = "MESA_ROOT_DIR"
299 configInfo = parseConfigFile(filename, reserved)
300 unsetVars = configInfo.unsetVariables
301 configVars = configInfo.outputVariables
302 reservedDict = configInfo.reservedValues
303 defaultValues = configInfo.defaultValues
304 except SalomeContextException as e:
306 self.getLogger().error(msg)
310 for var in unsetVars:
311 self.unsetVariable(var)
314 if "MESA_GL_VERSION_OVERRIDE" in os.environ:
315 configVarsDict = {k:v for (k,v) in configVars}
316 if mesa_root_dir in configVarsDict:
317 path_to_mesa_lib = os.path.join(configVarsDict[mesa_root_dir],"lib")
318 if os.name == "posix":
319 self.addToVariable("LD_LIBRARY_PATH",path_to_mesa_lib)
321 self.addToVariable("PATH",path_to_mesa_lib)
324 for reserved in reservedDict:
325 a = [_f for _f in reservedDict[reserved] if _f] # remove empty elements
326 a = [ os.path.realpath(x) for x in a ]
327 reformattedVals = os.pathsep.join(a)
328 if reserved in ["INCLUDE", "LIBPATH"]:
329 self.addToVariable(reserved, reformattedVals, separator=' ')
331 self.addToVariable(reserved, reformattedVals)
335 for key,val in configVars:
336 self.setVariable(key, val, overwrite=True)
339 for key,val in defaultValues:
340 self.setDefaultValue(key, val)
343 pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
344 pythonpath = [ os.path.realpath(x) for x in pythonpath ]
345 sys.path[:0] = pythonpath
348 def _runAppli(self, args=None):
351 # Initialize SALOME environment
352 sys.argv = ['runSalome'] + args
354 setenv.main(True, exeName="salome start")
357 runSalome.runSalome()
361 def _setContext(self, args=None):
362 salome_context_set = os.getenv("SALOME_CONTEXT_SET")
363 if salome_context_set:
365 print("*** SALOME context has already been set.")
366 print("*** Enter 'exit' (only once!) to leave SALOME context.")
370 os.environ["SALOME_CONTEXT_SET"] = "yes"
372 print("*** SALOME context is now set.")
373 print("*** Enter 'exit' (only once!) to leave SALOME context.")
376 if sys.platform == 'win32':
380 proc = subprocess.Popen(cmd, shell=False, close_fds=True)
382 return proc.returncode
385 def _runSession(self, args=None):
388 sys.argv = ['runSession'] + args
390 params, args = runSession.configureSession(args, exe="salome shell")
392 sys.argv = ['runSession'] + args
396 return runSession.runSession(params, args)
399 def _runRemote(self, args=None):
402 # complete salome environment
403 sys.argv = ['runRemote']
408 return runRemote.runRemote(args)
411 def _runConsole(self, args=None):
414 # Initialize SALOME environment
415 sys.argv = ['runConsole']
420 return runConsole.connect(args)
423 def _kill(self, args=None):
428 print("Port number(s) not provided to command: salome kill <port(s)>")
435 if os.getenv("NSHOST") == "no_host":
436 os.unsetenv("NSHOST")
438 proc = subprocess.Popen(["killSalomeWithPort.py", port])
444 def _killAll(self, unused=None):
445 sys.argv = ['killAll']
448 if os.getenv("NSHOST") == "no_host":
449 os.unsetenv("NSHOST")
451 import PortManager # mandatory
453 ports = PortManager.getBusyPorts()['this']
457 proc = subprocess.Popen(["killSalomeWithPort.py", str(port)])
460 # :TODO: should be declared obsolete
461 from killSalome import killAllPorts
467 def _runTests(self, args=None):
470 sys.argv = ['runTests']
475 return runTests.runTests(args, exe="salome test")
478 def _showSoftwareVersions(self, softwares=None):
479 config = configparser.SafeConfigParser()
480 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
481 filename = os.path.join(absoluteAppliPath, "sha1_collections.txt")
484 with open(filename) as f:
487 software, version, sha1 = line.split()
488 versions[software.upper()] = version
489 if len(software) > max_len:
490 max_len = len(software)
496 for soft in softwares:
497 if soft.upper() in versions:
498 print(soft.upper().rjust(max_len), versions[soft.upper()])
501 od = collections.OrderedDict(sorted(versions.items()))
502 for name, version in od.items():
503 print(name.rjust(max_len), versions[name])
506 def _showInfo(self, args=None):
510 usage = "Usage: salome info [options]"
512 Display some information about SALOME.\n
513 Available options are:
514 -p,--ports Show the list of busy ports (running SALOME instances).
515 -s,--softwares [software(s)] Show the list and versions of SALOME softwares.
516 Software names must be separated by blank characters.
517 If no software is given, show version of all softwares.
518 -v,--version Show running SALOME version.
519 -h,--help Show this message.
524 if "-h" in args or "--help" in args:
525 print(usage + epilog)
528 if "-p" in args or "--ports" in args:
530 ports = PortManager.getBusyPorts()
531 this_ports = ports['this']
532 other_ports = ports['other']
533 if this_ports or other_ports:
534 print("SALOME instances are running on the following ports:")
536 print(" This application:", this_ports)
538 print(" No SALOME instances of this application")
540 print(" Other applications:", other_ports)
542 print(" No SALOME instances of other applications")
544 print("No SALOME instances are running")
546 if "-s" in args or "--softwares" in args:
548 index = args.index("-s")
550 index = args.index("--softwares")
552 while indexEnd < len(args) and args[indexEnd][0] != "-":
553 indexEnd = indexEnd + 1
554 self._showSoftwareVersions(softwares=args[index+1:indexEnd])
556 if "-v" in args or "--version" in args:
557 print("Running with python", platform.python_version())
558 return self._runAppli(["--version"])
563 def _showDoc(self, args=None):
569 print("Module(s) not provided to command: salome doc <module(s)>")
572 appliPath = os.getenv("ABSOLUTE_APPLI_PATH")
574 raise SalomeContextException("Unable to find application path. Please check that the variable ABSOLUTE_APPLI_PATH is set.")
575 baseDir = os.path.join(appliPath, "share", "doc", "salome")
576 for module in modules:
577 docfile = os.path.join(baseDir, "gui", module.upper(), "index.html")
578 if not os.path.isfile(docfile):
579 docfile = os.path.join(baseDir, "tui", module.upper(), "index.html")
580 if not os.path.isfile(docfile):
581 docfile = os.path.join(baseDir, "dev", module.upper(), "index.html")
582 if os.path.isfile(docfile):
583 out, err = subprocess.Popen(["xdg-open", docfile]).communicate()
585 print("Online documentation is not accessible for module:", module)
587 def _usage(self, unused=None):
591 def _makeCoffee(self, unused=None):
594 print(" ___...(-------)-....___")
595 print(" .-\"\" ) ( \"\"-.")
596 print(" .-\'``\'|-._ ) _.-|")
597 print(" / .--.| `\"\"---...........---\"\"` |")
601 print(" `\\ `\\ | |")
602 print(" `\\ `| SALOME |")
603 print(" _/ /\\ 4 EVER /")
604 print(" (__/ \\ <3 /")
605 print(" _..---\"\"` \\ /`\"\"---.._")
606 print(" .-\' \\ / \'-.")
607 print(" : `-.__ __.-\' :")
608 print(" : ) \"\"---...---\"\" ( :")
609 print(" \'._ `\"--...___...--\"` _.\'")
610 print(" \\\"\"--..__ __..--\"\"/")
611 print(" \'._ \"\"\"----.....______.....----\"\"\" _.\'")
612 print(" `\"\"--..,,_____ _____,,..--\"\"`")
613 print(" `\"\"\"----\"\"\"`")
615 print(" SALOME is working for you; what else?")
619 def _getCar(self, unused=None):
620 print(" _____________")
621 print(" ..---:::::::-----------. ::::;;.")
622 print(" .\'\"\"\"\"\"\" ;; \\ \":.")
623 print(" .\'\' ; \\ \"\\__.")
624 print(" .\' ;; ; \\\\\";")
625 print(" .\' ; _____; \\\\/")
626 print(" .\' :; ;\" \\ ___:\'.")
627 print(" .\'--........................... : = ____:\" \\ \\")
628 print(" ..-\"\" \"\"\"\' o\"\"\" ; ; :")
629 print(" .--\"\" .----- ..----... _.- --. ..-\" ; ; ; ;")
630 print(" .\"\"_- \"--\"\"-----\'\"\" _-\" .-\"\" ; ; .-.")
631 print(" .\' .\' SALOME .\" .\" ; ; /. |")
632 print(" /-./\' 4 EVER <3 .\" / _.. ; ; ;;;|")
633 print(" : ;-.______ / _________==. /_ \\ ; ; ;;;;")
634 print(" ; / | \"\"\"\"\"\"\"\"\"\"\".---.\"\"\"\"\"\"\" : /\" \". |; ; _; ;;;")
635 print(" /\"-/ | / / / / ;|; ;-\" | ;\';")
636 print(":- : \"\"\"----______ / / ____. . .\"\'. ;; .-\"..T\" .")
637 print("\'. \" ___ \"\": \'\"\"\"\"\"\"\"\"\"\"\"\"\"\" . ; ; ;; ;.\" .\" \'--\"")
638 print(" \", __ \"\"\" \"\"---... :- - - - - - - - - \' \' ; ; ; ;;\" .\"")
639 print(" /. ; \"\"\"---___ ; ; ; ;|.\"\"")
640 print(" : \": \"\"\"----. .-------. ; ; ; ;:")
641 print(" \\ \'--__ \\ \\ \\ / | ; ;;")
642 print(" \'-.. \"\"\"\"---___ : .______..\\ __/..-\"\"| ; ; ;")
643 print(" \"\"--.. \"\"\"--\" m l s . \". . ;")
644 print(" \"\"------... ..--\"\" \" :")
645 print(" \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\" \\ /")
648 print(" Drive your simulation properly with SALOME!")
652 # Add the following two methods since logger is not pickable
653 # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
654 def __getstate__(self):
655 d = dict(self.__dict__)
656 if hasattr(self, '_logger'):
660 def __setstate__(self, d):
661 self.__dict__.update(d) # I *think* this is a safe way to do it
663 # Excluding self._logger from pickle operation imply using the following method to access logger
665 if not hasattr(self, '_logger'):
666 self._logger = logging.getLogger(__name__)
667 #self._logger.setLevel(logging.DEBUG)
668 #self._logger.setLevel(logging.WARNING)
669 self._logger.setLevel(logging.ERROR)
673 if __name__ == "__main__":
674 if len(sys.argv) == 3:
675 context = pickle.loads(sys.argv[1].encode('latin1'))
676 args = pickle.loads(sys.argv[2].encode('latin1'))
678 status = context._startSalome(args)