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
35 from addToKillList import killList
39 Usage: salome [command] [options] [--config=<file,folder,...>]
43 start Start a new SALOME instance. Start a single SALOME_Session_Server_No_Server
44 process with environment relevant to the application and hosting all servants in it.
45 context Initialize SALOME context. Current environment is extended.
46 shell Initialize SALOME context, attached to the last created SALOME
47 instance if any, and executes scripts passed as command arguments.
48 User works in a Shell terminal. SALOME environment is set but
49 application is not started.
50 test Run SALOME tests.
51 info Display some information about SALOME.
52 doc <module(s)> Show online module documentation (if available).
53 Module names must be separated by blank characters.
54 help Show this message.
55 remote run command in SALOME environment from remote call, ssh or rsh.
56 withsession Start a new SWS SALOME instance with multiple servers hosting all servants.
57 connect In SWS context, Connect a Python console to the active SALOME instance.
58 kill <port(s)> In SWS context, Terminate SALOME instances running on given ports for current user.
59 Port numbers must be separated by blank characters.
60 killall Terminate *all* SALOME running SWS instances for current user.
61 Do not start a new one.
63 If no command is given, default is start.
67 Use salome <command> --help to show help on command. Available for the
68 following commands: start, shell, connect, test, info.
70 --config=<file,folder,...>
71 ==========================
72 Initialize SALOME context from a list of context files and/or a list
73 of folders containing context files. The list is comma-separated, without
81 The SalomeContext class in an API to configure SALOME context then
82 start SALOME using a single python command.
87 Initialize context from a list of configuration files
88 identified by their names.
89 These files should be in appropriate .cfg format.
91 def __init__(self, configFileNames=0):
92 self.getLogger().setLevel(logging.INFO)
93 #it could be None explicitly (if user use multiples setVariable...for standalone)
94 if configFileNames is None:
96 configFileNames = configFileNames or []
97 if len(configFileNames) == 0:
98 raise SalomeContextException("No configuration files given")
100 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']
101 for filename in configFileNames:
102 basename, extension = os.path.splitext(filename)
103 if extension == ".cfg":
104 self.__setContextFromConfigFile(filename, reserved)
106 self.getLogger().error("Unrecognized extension for configuration file: %s", filename)
109 def __loadEnvModules(self, env_modules):
110 modulecmd = os.getenv('LMOD_CMD')
112 raise SalomeContextException("Module environment not present")
115 out, err = subprocess.Popen([modulecmd, "python", "load"] + env_modules, stdout=subprocess.PIPE).communicate()
116 exec(out) # define specific environment variables
118 raise SalomeContextException("Failed to load env modules: %s ..." % ' '.join(env_modules))
122 def runSalome(self, args):
124 # Run this module as a script, in order to use appropriate Python interpreter
125 # according to current path (initialized from context files).
126 env_modules_option = "--with-env-modules="
127 env_modules_l = [x for x in args if x.startswith(env_modules_option)]
129 env_modules = env_modules_l[-1][len(env_modules_option):].split(',')
130 self.__loadEnvModules(env_modules)
131 args = [x for x in args if not x.startswith(env_modules_option)]
133 env_modules = os.getenv("SALOME_ENV_MODULES", None)
135 self.__loadEnvModules(env_modules.split(','))
137 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
138 env_copy = os.environ.copy()
139 selfBytes= pickle.dumps(self, protocol=0)
140 argsBytes= pickle.dumps(args, protocol=0)
141 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)
142 out, err = proc.communicate()
143 return out, err, proc.returncode
146 """Append value to PATH environment variable"""
147 def addToPath(self, value):
148 self.addToVariable('PATH', value)
151 """Append value to LD_LIBRARY_PATH environment variable"""
152 def addToLdLibraryPath(self, value):
153 if sys.platform == 'win32':
154 self.addToVariable('PATH', value)
155 elif sys.platform == 'darwin':
156 if "LAPACK" in value:
157 self.addToVariable('DYLD_FALLBACK_LIBRARY_PATH', value)
159 self.addToVariable('DYLD_LIBRARY_PATH', value)
161 self.addToVariable('LD_LIBRARY_PATH', value)
164 """Append value to DYLD_LIBRARY_PATH environment variable"""
165 def addToDyldLibraryPath(self, value):
166 self.addToVariable('DYLD_LIBRARY_PATH', value)
169 """Append value to PYTHONPATH environment variable"""
170 def addToPythonPath(self, value):
171 self.addToVariable('PYTHONPATH', value)
174 """Set environment variable to value"""
175 def setVariable(self, name, value, overwrite=False):
176 env = os.getenv(name, '')
177 if env and not overwrite:
178 self.getLogger().error("Environment variable already existing (and not overwritten): %s=%s", name, value)
182 self.getLogger().debug("Overwriting environment variable: %s=%s", name, value)
184 value = os.path.expandvars(value) # expand environment variables
185 self.getLogger().debug("Set environment variable: %s=%s", name, value)
186 os.environ[name] = value
189 def setDefaultValue(self, name, value):
190 """ Set environment variable only if it is undefined."""
191 env = os.getenv(name, '')
193 value = os.path.expandvars(value) # expand environment variables
194 self.getLogger().debug("Set environment variable: %s=%s", name, value)
195 os.environ[name] = value
197 """Unset environment variable"""
198 def unsetVariable(self, name):
199 if os.environ.has_key(name):
203 """Append value to environment variable"""
204 def addToVariable(self, name, value, separator=os.pathsep):
208 value = os.path.expandvars(value) # expand environment variables
209 self.getLogger().debug("Add to %s: %s", name, value)
210 env = os.getenv(name, None)
212 os.environ[name] = value
214 os.environ[name] = value + separator + env
217 ###################################
218 # This begins the private section #
219 ###################################
221 def __parseArguments(self, args):
222 if len(args) == 0 or args[0].startswith("-"):
228 availableCommands = {
229 'start' : '_sessionless',
230 'withsession' : '_runAppli',
231 'context' : '_setContext',
232 'shell' : '_runSession',
233 'remote' : '_runRemote',
234 'connect' : '_runConsole',
236 'killall' : '_killAll',
237 'test' : '_runTests',
238 'info' : '_showInfo',
241 'coffee' : '_makeCoffee',
245 if command not in availableCommands:
249 return availableCommands[command], options
254 Args consist in a mandatory command followed by optional parameters.
255 See usage for details on commands.
257 def _startSalome(self, args):
261 from setenv import add_path
262 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
263 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome"))
264 add_path(path, "PYTHONPATH")
265 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome", "appliskel"))
266 add_path(path, "PYTHONPATH")
271 command, options = self.__parseArguments(args)
275 if args and args[0] in ["-h","--help","help"]:
278 # try to default to "start" command
279 command = "_sessionless"
282 res = getattr(self, command)(options) # run appropriate method
284 except SystemExit as ex:
286 self.getLogger().error("SystemExit %s in method %s.", ex.code, command)
288 except SalomeContextException as e:
289 self.getLogger().error(e)
292 self.getLogger().error("Unexpected error:")
294 traceback.print_exc()
298 def __setContextFromConfigFile(self, filename, reserved=None):
299 mesa_root_dir = "MESA_ROOT_DIR"
303 configInfo = parseConfigFile(filename, reserved)
304 unsetVars = configInfo.unsetVariables
305 configVars = configInfo.outputVariables
306 reservedDict = configInfo.reservedValues
307 defaultValues = configInfo.defaultValues
308 except SalomeContextException as e:
310 self.getLogger().error(msg)
314 for var in unsetVars:
315 self.unsetVariable(var)
318 if "MESA_GL_VERSION_OVERRIDE" in os.environ:
319 configVarsDict = {k:v for (k,v) in configVars}
320 if mesa_root_dir in configVarsDict:
321 path_to_mesa_lib = os.path.join(configVarsDict[mesa_root_dir],"lib")
322 if os.name == "posix":
323 self.addToVariable("LD_LIBRARY_PATH",path_to_mesa_lib)
325 self.addToVariable("PATH",path_to_mesa_lib)
328 for reserved in reservedDict:
329 a = [_f for _f in reservedDict[reserved] if _f] # remove empty elements
330 a = [ os.path.realpath(x) for x in a ]
331 reformattedVals = os.pathsep.join(a)
332 if reserved in ["INCLUDE", "LIBPATH"]:
333 self.addToVariable(reserved, reformattedVals, separator=' ')
335 self.addToVariable(reserved, reformattedVals)
339 for key,val in configVars:
340 self.setVariable(key, val, overwrite=True)
343 for key,val in defaultValues:
344 self.setDefaultValue(key, val)
347 pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
348 pythonpath = [ os.path.realpath(x) for x in pythonpath ]
349 sys.path[:0] = pythonpath
352 def _runAppli(self, args=None):
355 # Initialize SALOME environment
356 sys.argv = ['runSalomeOld'] + args
358 setenv.main(True, exeName="salome withsession")
361 runSalomeOld.runSalome()
365 def _sessionless(self, args=None):
368 sys.argv = ['runSalome'] + args
369 import runSalomeNoServer
370 runSalomeNoServer.main()
373 def _setContext(self, args=None):
374 salome_context_set = os.getenv("SALOME_CONTEXT_SET")
375 if salome_context_set:
377 print("*** SALOME context has already been set.")
378 print("*** Enter 'exit' (only once!) to leave SALOME context.")
382 os.environ["SALOME_CONTEXT_SET"] = "yes"
384 print("*** SALOME context is now set.")
385 print("*** Enter 'exit' (only once!) to leave SALOME context.")
388 if sys.platform == 'win32':
392 proc = subprocess.Popen(cmd, shell=False, close_fds=True)
394 return proc.returncode
397 def _runSession(self, args=None):
400 sys.argv = ['runSession'] + args
402 params, args = runSession.configureSession(args, exe="salome shell")
404 sys.argv = ['runSession'] + args
408 return runSession.runSession(params, args)
411 def _runRemote(self, args=None):
414 # complete salome environment
415 sys.argv = ['runRemote']
420 return runRemote.runRemote(args)
423 def _runConsole(self, args=None):
426 # Initialize SALOME environment
427 sys.argv = ['runConsole']
432 return runConsole.connect(args)
435 def _kill(self, args=None):
440 print("Port number(s) not provided to command: salome kill <port(s)>")
447 if os.getenv("NSHOST") == "no_host":
448 os.unsetenv("NSHOST")
450 if sys.platform == "win32":
451 proc = subprocess.Popen([os.getenv("PYTHONBIN"), "-m", "killSalomeWithPort", str(port)])
453 proc = subprocess.Popen(["killSalomeWithPort.py", str(port)])
458 def _killAll(self, unused=None):
459 sys.argv = ['killAll']
462 if os.getenv("NSHOST") == "no_host":
463 os.unsetenv("NSHOST")
465 import PortManager # mandatory
467 ports = PortManager.getBusyPorts()['this']
471 if sys.platform == "win32":
472 proc = subprocess.Popen([os.getenv("PYTHONBIN"), "-m", "killSalomeWithPort", str(port)])
474 proc = subprocess.Popen(["killSalomeWithPort.py", str(port)])
477 # :TODO: should be declared obsolete
478 from killSalome import killAllPorts
485 def _runTests(self, args=None):
488 sys.argv = ['runTests']
493 return runTests.runTests(args, exe="salome test")
496 def _showSoftwareVersions(self, softwares=None):
497 config = configparser.SafeConfigParser()
498 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
499 filename = os.path.join(absoluteAppliPath, "sha1_collections.txt")
502 with open(filename) as f:
505 software, version, sha1 = line.split()
506 versions[software.upper()] = version
507 if len(software) > max_len:
508 max_len = len(software)
514 for soft in softwares:
515 if soft.upper() in versions:
516 print(soft.upper().rjust(max_len), versions[soft.upper()])
519 od = collections.OrderedDict(sorted(versions.items()))
520 for name, version in od.items():
521 print(name.rjust(max_len), versions[name])
524 def _showInfo(self, args=None):
528 usage = "Usage: salome info [options]"
530 Display some information about SALOME.\n
531 Available options are:
532 -p,--ports Show the list of busy ports (running SALOME instances).
533 -s,--softwares [software(s)] Show the list and versions of SALOME softwares.
534 Software names must be separated by blank characters.
535 If no software is given, show version of all softwares.
536 -v,--version Show running SALOME version.
537 -h,--help Show this message.
542 if "-h" in args or "--help" in args:
543 print(usage + epilog)
546 if "-p" in args or "--ports" in args:
548 ports = PortManager.getBusyPorts()
549 this_ports = ports['this']
550 other_ports = ports['other']
551 if this_ports or other_ports:
552 print("SALOME instances are running on the following ports:")
554 print(" This application:", this_ports)
556 print(" No SALOME instances of this application")
558 print(" Other applications:", other_ports)
560 print(" No SALOME instances of other applications")
562 print("No SALOME instances are running")
564 if "-s" in args or "--softwares" in args:
566 index = args.index("-s")
568 index = args.index("--softwares")
570 while indexEnd < len(args) and args[indexEnd][0] != "-":
571 indexEnd = indexEnd + 1
572 self._showSoftwareVersions(softwares=args[index+1:indexEnd])
574 if "-v" in args or "--version" in args:
575 print("Running with python", platform.python_version())
576 return self._sessionless(["--version"])
581 def _showDoc(self, args=None):
587 print("Module(s) not provided to command: salome doc <module(s)>")
590 appliPath = os.getenv("ABSOLUTE_APPLI_PATH")
592 raise SalomeContextException("Unable to find application path. Please check that the variable ABSOLUTE_APPLI_PATH is set.")
593 baseDir = os.path.join(appliPath, "share", "doc", "salome")
594 for module in modules:
595 docfile = os.path.join(baseDir, "gui", module.upper(), "index.html")
596 if not os.path.isfile(docfile):
597 docfile = os.path.join(baseDir, "tui", module.upper(), "index.html")
598 if not os.path.isfile(docfile):
599 docfile = os.path.join(baseDir, "dev", module.upper(), "index.html")
600 if os.path.isfile(docfile):
601 out, err = subprocess.Popen(["xdg-open", docfile]).communicate()
603 print("Online documentation is not accessible for module:", module)
605 def _usage(self, unused=None):
609 def _makeCoffee(self, unused=None):
612 print(" ___...(-------)-....___")
613 print(" .-\"\" ) ( \"\"-.")
614 print(" .-\'``\'|-._ ) _.-|")
615 print(" / .--.| `\"\"---...........---\"\"` |")
619 print(" `\\ `\\ | |")
620 print(" `\\ `| SALOME |")
621 print(" _/ /\\ 4 EVER /")
622 print(" (__/ \\ <3 /")
623 print(" _..---\"\"` \\ /`\"\"---.._")
624 print(" .-\' \\ / \'-.")
625 print(" : `-.__ __.-\' :")
626 print(" : ) \"\"---...---\"\" ( :")
627 print(" \'._ `\"--...___...--\"` _.\'")
628 print(" \\\"\"--..__ __..--\"\"/")
629 print(" \'._ \"\"\"----.....______.....----\"\"\" _.\'")
630 print(" `\"\"--..,,_____ _____,,..--\"\"`")
631 print(" `\"\"\"----\"\"\"`")
633 print(" SALOME is working for you; what else?")
637 def _getCar(self, unused=None):
638 print(" _____________")
639 print(" ..---:::::::-----------. ::::;;.")
640 print(" .\'\"\"\"\"\"\" ;; \\ \":.")
641 print(" .\'\' ; \\ \"\\__.")
642 print(" .\' ;; ; \\\\\";")
643 print(" .\' ; _____; \\\\/")
644 print(" .\' :; ;\" \\ ___:\'.")
645 print(" .\'--........................... : = ____:\" \\ \\")
646 print(" ..-\"\" \"\"\"\' o\"\"\" ; ; :")
647 print(" .--\"\" .----- ..----... _.- --. ..-\" ; ; ; ;")
648 print(" .\"\"_- \"--\"\"-----\'\"\" _-\" .-\"\" ; ; .-.")
649 print(" .\' .\' SALOME .\" .\" ; ; /. |")
650 print(" /-./\' 4 EVER <3 .\" / _.. ; ; ;;;|")
651 print(" : ;-.______ / _________==. /_ \\ ; ; ;;;;")
652 print(" ; / | \"\"\"\"\"\"\"\"\"\"\".---.\"\"\"\"\"\"\" : /\" \". |; ; _; ;;;")
653 print(" /\"-/ | / / / / ;|; ;-\" | ;\';")
654 print(":- : \"\"\"----______ / / ____. . .\"\'. ;; .-\"..T\" .")
655 print("\'. \" ___ \"\": \'\"\"\"\"\"\"\"\"\"\"\"\"\"\" . ; ; ;; ;.\" .\" \'--\"")
656 print(" \", __ \"\"\" \"\"---... :- - - - - - - - - \' \' ; ; ; ;;\" .\"")
657 print(" /. ; \"\"\"---___ ; ; ; ;|.\"\"")
658 print(" : \": \"\"\"----. .-------. ; ; ; ;:")
659 print(" \\ \'--__ \\ \\ \\ / | ; ;;")
660 print(" \'-.. \"\"\"\"---___ : .______..\\ __/..-\"\"| ; ; ;")
661 print(" \"\"--.. \"\"\"--\" m l s . \". . ;")
662 print(" \"\"------... ..--\"\" \" :")
663 print(" \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\" \\ /")
666 print(" Drive your simulation properly with SALOME!")
670 # Add the following two methods since logger is not pickable
671 # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
672 def __getstate__(self):
673 d = dict(self.__dict__)
674 if hasattr(self, '_logger'):
678 def __setstate__(self, d):
679 self.__dict__.update(d) # I *think* this is a safe way to do it
681 # Excluding self._logger from pickle operation imply using the following method to access logger
683 if not hasattr(self, '_logger'):
684 self._logger = logging.getLogger(__name__)
685 #self._logger.setLevel(logging.DEBUG)
686 #self._logger.setLevel(logging.WARNING)
687 self._logger.setLevel(logging.ERROR)
691 if __name__ == "__main__":
692 if len(sys.argv) == 3:
693 context = pickle.loads(sys.argv[1].encode('latin1'))
694 args = pickle.loads(sys.argv[2].encode('latin1'))
696 status = context._startSalome(args)