1 #! /usr/bin/env python3
2 # Copyright (C) 2013-2022 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. Start a single SALOME_Session_Server_No_Server
43 process with environment relevant to the application and hosting all servants in it.
44 context Initialize SALOME context. Current environment is extended.
45 shell Initialize SALOME context, attached to the last created SALOME
46 instance if any, and executes scripts passed as command arguments.
47 User works in a Shell terminal. SALOME environment is set but
48 application is not started.
49 test Run SALOME tests.
50 info Display some information about SALOME.
51 doc <module(s)> Show online module documentation (if available).
52 Module names must be separated by blank characters.
53 help Show this message.
54 remote run command in SALOME environment from remote call, ssh or rsh.
55 withsession Start a new SWS SALOME instance with multiple servers hosting all servants.
56 connect In SWS context, Connect a Python console to the active SALOME instance.
57 kill <port(s)> In SWS context, Terminate SALOME instances running on given ports for current user.
58 Port numbers must be separated by blank characters.
59 killall Terminate *all* SALOME running SWS instances for current user.
60 Do not start a new one.
62 If no command is given, default is start.
66 Use salome <command> --help to show help on command. Available for the
67 following commands: start, shell, connect, test, info.
69 --config=<file,folder,...>
70 ==========================
71 Initialize SALOME context from a list of context files and/or a list
72 of folders containing context files. The list is comma-separated, without
80 The SalomeContext class in an API to configure SALOME context then
81 start SALOME using a single python command.
86 Initialize context from a list of configuration files
87 identified by their names.
88 These files should be in appropriate .cfg format.
90 def __init__(self, configFileNames=0):
91 self.getLogger().setLevel(logging.INFO)
92 #it could be None explicitly (if user use multiples setVariable...for standalone)
93 if configFileNames is None:
95 configFileNames = configFileNames or []
96 if len(configFileNames) == 0:
97 raise SalomeContextException("No configuration files given")
99 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']
100 for filename in configFileNames:
101 basename, extension = os.path.splitext(filename)
102 if extension == ".cfg":
103 self.__setContextFromConfigFile(filename, reserved)
105 self.getLogger().error("Unrecognized extension for configuration file: %s", filename)
108 def __loadEnvModules(self, env_modules):
109 modulecmd = os.getenv('LMOD_CMD')
111 raise SalomeContextException("Module environment not present")
114 out, err = subprocess.Popen([modulecmd, "python", "load"] + env_modules, stdout=subprocess.PIPE).communicate()
115 exec(out) # define specific environment variables
117 raise SalomeContextException("Failed to load env modules: %s ..." % ' '.join(env_modules))
121 def runSalome(self, args):
123 # Run this module as a script, in order to use appropriate Python interpreter
124 # according to current path (initialized from context files).
125 env_modules_option = "--with-env-modules="
126 env_modules_l = [x for x in args if x.startswith(env_modules_option)]
128 env_modules = env_modules_l[-1][len(env_modules_option):].split(',')
129 self.__loadEnvModules(env_modules)
130 args = [x for x in args if not x.startswith(env_modules_option)]
132 env_modules = os.getenv("SALOME_ENV_MODULES", None)
134 self.__loadEnvModules(env_modules.split(','))
136 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
137 env_copy = os.environ.copy()
138 selfBytes= pickle.dumps(self, protocol=0)
139 argsBytes= pickle.dumps(args, protocol=0)
140 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)
141 out, err = proc.communicate()
142 return out, err, proc.returncode
145 """Append value to PATH environment variable"""
146 def addToPath(self, value):
147 self.addToVariable('PATH', value)
150 """Append value to LD_LIBRARY_PATH environment variable"""
151 def addToLdLibraryPath(self, value):
152 if sys.platform == 'win32':
153 self.addToVariable('PATH', value)
154 elif sys.platform == 'darwin':
155 if "LAPACK" in value:
156 self.addToVariable('DYLD_FALLBACK_LIBRARY_PATH', value)
158 self.addToVariable('DYLD_LIBRARY_PATH', value)
160 self.addToVariable('LD_LIBRARY_PATH', value)
163 """Append value to DYLD_LIBRARY_PATH environment variable"""
164 def addToDyldLibraryPath(self, value):
165 self.addToVariable('DYLD_LIBRARY_PATH', value)
168 """Append value to PYTHONPATH environment variable"""
169 def addToPythonPath(self, value):
170 self.addToVariable('PYTHONPATH', value)
173 """Set environment variable to value"""
174 def setVariable(self, name, value, overwrite=False):
175 env = os.getenv(name, '')
176 if env and not overwrite:
177 self.getLogger().error("Environment variable already existing (and not overwritten): %s=%s", name, value)
181 self.getLogger().debug("Overwriting environment variable: %s=%s", name, value)
183 value = os.path.expandvars(value) # expand environment variables
184 self.getLogger().debug("Set environment variable: %s=%s", name, value)
185 os.environ[name] = value
188 def setDefaultValue(self, name, value):
189 """ Set environment variable only if it is undefined."""
190 env = os.getenv(name, '')
192 value = os.path.expandvars(value) # expand environment variables
193 self.getLogger().debug("Set environment variable: %s=%s", name, value)
194 os.environ[name] = value
196 """Unset environment variable"""
197 def unsetVariable(self, name):
198 if os.environ.has_key(name):
202 """Append value to environment variable"""
203 def addToVariable(self, name, value, separator=os.pathsep):
207 value = os.path.expandvars(value) # expand environment variables
208 self.getLogger().debug("Add to %s: %s", name, value)
209 env = os.getenv(name, None)
211 os.environ[name] = value
213 os.environ[name] = value + separator + env
216 ###################################
217 # This begins the private section #
218 ###################################
220 def __parseArguments(self, args):
221 if len(args) == 0 or args[0].startswith("-"):
227 availableCommands = {
228 'start' : '_sessionless',
229 'withsession' : '_runAppli',
230 'context' : '_setContext',
231 'shell' : '_runSession',
232 'remote' : '_runRemote',
233 'connect' : '_runConsole',
235 'killall' : '_killAll',
236 'test' : '_runTests',
237 'info' : '_showInfo',
240 'coffee' : '_makeCoffee',
244 if command not in availableCommands:
248 return availableCommands[command], options
253 Args consist in a mandatory command followed by optional parameters.
254 See usage for details on commands.
256 def _startSalome(self, args):
260 from setenv import add_path
261 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
262 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome"))
263 add_path(path, "PYTHONPATH")
264 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome", "appliskel"))
265 add_path(path, "PYTHONPATH")
270 command, options = self.__parseArguments(args)
274 if args and args[0] in ["-h","--help","help"]:
277 # try to default to "start" command
278 command = "_sessionless"
281 res = getattr(self, command)(options) # run appropriate method
283 except SystemExit as ex:
285 self.getLogger().error("SystemExit %s in method %s.", ex.code, command)
287 except SalomeContextException as e:
288 self.getLogger().error(e)
291 self.getLogger().error("Unexpected error:")
293 traceback.print_exc()
297 def __setContextFromConfigFile(self, filename, reserved=None):
298 mesa_root_dir = "MESA_ROOT_DIR"
302 configInfo = parseConfigFile(filename, reserved)
303 unsetVars = configInfo.unsetVariables
304 configVars = configInfo.outputVariables
305 reservedDict = configInfo.reservedValues
306 defaultValues = configInfo.defaultValues
307 except SalomeContextException as e:
309 self.getLogger().error(msg)
313 for var in unsetVars:
314 self.unsetVariable(var)
317 if "MESA_GL_VERSION_OVERRIDE" in os.environ:
318 configVarsDict = {k:v for (k,v) in configVars}
319 if mesa_root_dir in configVarsDict:
320 path_to_mesa_lib = os.path.join(configVarsDict[mesa_root_dir],"lib")
321 if os.name == "posix":
322 self.addToVariable("LD_LIBRARY_PATH",path_to_mesa_lib)
324 self.addToVariable("PATH",path_to_mesa_lib)
327 for reserved in reservedDict:
328 a = [_f for _f in reservedDict[reserved] if _f] # remove empty elements
329 a = [ os.path.realpath(x) for x in a ]
330 reformattedVals = os.pathsep.join(a)
331 if reserved in ["INCLUDE", "LIBPATH"]:
332 self.addToVariable(reserved, reformattedVals, separator=' ')
334 self.addToVariable(reserved, reformattedVals)
338 for key,val in configVars:
339 self.setVariable(key, val, overwrite=True)
342 for key,val in defaultValues:
343 self.setDefaultValue(key, val)
346 pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
347 pythonpath = [ os.path.realpath(x) for x in pythonpath ]
348 sys.path[:0] = pythonpath
351 def _runAppli(self, args=None):
354 # Initialize SALOME environment
355 sys.argv = ['runSalomeOld'] + args
357 setenv.main(True, exeName="salome withsession")
360 runSalomeOld.runSalome()
364 def _sessionless(self, args=None):
367 sys.argv = ['runSalome'] + args
369 setenv.main(True, exeName="salome withsession")
372 runSalome.runSalome()
376 def _setContext(self, args=None):
377 salome_context_set = os.getenv("SALOME_CONTEXT_SET")
378 if salome_context_set:
380 print("*** SALOME context has already been set.")
381 print("*** Enter 'exit' (only once!) to leave SALOME context.")
385 os.environ["SALOME_CONTEXT_SET"] = "yes"
387 print("*** SALOME context is now set.")
388 print("*** Enter 'exit' (only once!) to leave SALOME context.")
391 if sys.platform == 'win32':
395 proc = subprocess.Popen(cmd, shell=False, close_fds=True)
397 return proc.returncode
400 def _runSession(self, args=None):
403 sys.argv = ['runSession'] + args
405 params, args = runSession.configureSession(args, exe="salome shell")
407 sys.argv = ['runSession'] + args
411 return runSession.runSession(params, args)
414 def _runRemote(self, args=None):
417 # complete salome environment
418 sys.argv = ['runRemote']
423 return runRemote.runRemote(args)
426 def _runConsole(self, args=None):
429 # Initialize SALOME environment
430 sys.argv = ['runConsole']
435 return runConsole.connect(args)
438 def _kill(self, args=None):
443 print("Port number(s) not provided to command: salome kill <port(s)>")
450 if os.getenv("NSHOST") == "no_host":
451 os.unsetenv("NSHOST")
453 if sys.platform == "win32":
454 proc = subprocess.Popen([os.getenv("PYTHONBIN"), "-m", "killSalomeWithPort", str(port)])
456 proc = subprocess.Popen(["killSalomeWithPort.py", str(port)])
461 def _killAll(self, unused=None):
462 sys.argv = ['killAll']
465 if os.getenv("NSHOST") == "no_host":
466 os.unsetenv("NSHOST")
468 import PortManager # mandatory
470 ports = PortManager.getBusyPorts()['this']
474 if sys.platform == "win32":
475 proc = subprocess.Popen([os.getenv("PYTHONBIN"), "-m", "killSalomeWithPort", str(port)])
477 proc = subprocess.Popen(["killSalomeWithPort.py", str(port)])
480 # :TODO: should be declared obsolete
481 from killSalome import killAllPorts
484 from addToKillList import killList
489 def _runTests(self, args=None):
492 sys.argv = ['runTests']
497 return runTests.runTests(args, exe="salome test")
500 def _showSoftwareVersions(self, softwares=None):
501 config = configparser.SafeConfigParser()
502 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
503 filename = os.path.join(absoluteAppliPath, "sha1_collections.txt")
506 with open(filename) as f:
509 software, version, sha1 = line.split()
510 versions[software.upper()] = version
511 if len(software) > max_len:
512 max_len = len(software)
518 for soft in softwares:
519 if soft.upper() in versions:
520 print(soft.upper().rjust(max_len), versions[soft.upper()])
523 od = collections.OrderedDict(sorted(versions.items()))
524 for name, version in od.items():
525 print(name.rjust(max_len), versions[name])
528 def _showInfo(self, args=None):
532 usage = "Usage: salome info [options]"
534 Display some information about SALOME.\n
535 Available options are:
536 -p,--ports Show the list of busy ports (running SALOME instances).
537 -s,--softwares [software(s)] Show the list and versions of SALOME softwares.
538 Software names must be separated by blank characters.
539 If no software is given, show version of all softwares.
540 -v,--version Show running SALOME version.
541 -h,--help Show this message.
546 if "-h" in args or "--help" in args:
547 print(usage + epilog)
550 if "-p" in args or "--ports" in args:
552 ports = PortManager.getBusyPorts()
553 this_ports = ports['this']
554 other_ports = ports['other']
555 if this_ports or other_ports:
556 print("SALOME instances are running on the following ports:")
558 print(" This application:", this_ports)
560 print(" No SALOME instances of this application")
562 print(" Other applications:", other_ports)
564 print(" No SALOME instances of other applications")
566 print("No SALOME instances are running")
568 if "-s" in args or "--softwares" in args:
570 index = args.index("-s")
572 index = args.index("--softwares")
574 while indexEnd < len(args) and args[indexEnd][0] != "-":
575 indexEnd = indexEnd + 1
576 self._showSoftwareVersions(softwares=args[index+1:indexEnd])
578 if "-v" in args or "--version" in args:
579 print("Running with python", platform.python_version())
580 return self._sessionless(["--version"])
585 def _showDoc(self, args=None):
591 print("Module(s) not provided to command: salome doc <module(s)>")
594 appliPath = os.getenv("ABSOLUTE_APPLI_PATH")
596 raise SalomeContextException("Unable to find application path. Please check that the variable ABSOLUTE_APPLI_PATH is set.")
597 baseDir = os.path.join(appliPath, "share", "doc", "salome")
598 for module in modules:
599 docfile = os.path.join(baseDir, "gui", module.upper(), "index.html")
600 if not os.path.isfile(docfile):
601 docfile = os.path.join(baseDir, "tui", module.upper(), "index.html")
602 if not os.path.isfile(docfile):
603 docfile = os.path.join(baseDir, "dev", module.upper(), "index.html")
604 if os.path.isfile(docfile):
605 out, err = subprocess.Popen(["xdg-open", docfile]).communicate()
607 print("Online documentation is not accessible for module:", module)
609 def _usage(self, unused=None):
613 def _makeCoffee(self, unused=None):
616 print(" ___...(-------)-....___")
617 print(" .-\"\" ) ( \"\"-.")
618 print(" .-\'``\'|-._ ) _.-|")
619 print(" / .--.| `\"\"---...........---\"\"` |")
623 print(" `\\ `\\ | |")
624 print(" `\\ `| SALOME |")
625 print(" _/ /\\ 4 EVER /")
626 print(" (__/ \\ <3 /")
627 print(" _..---\"\"` \\ /`\"\"---.._")
628 print(" .-\' \\ / \'-.")
629 print(" : `-.__ __.-\' :")
630 print(" : ) \"\"---...---\"\" ( :")
631 print(" \'._ `\"--...___...--\"` _.\'")
632 print(" \\\"\"--..__ __..--\"\"/")
633 print(" \'._ \"\"\"----.....______.....----\"\"\" _.\'")
634 print(" `\"\"--..,,_____ _____,,..--\"\"`")
635 print(" `\"\"\"----\"\"\"`")
637 print(" SALOME is working for you; what else?")
641 def _getCar(self, unused=None):
642 print(" _____________")
643 print(" ..---:::::::-----------. ::::;;.")
644 print(" .\'\"\"\"\"\"\" ;; \\ \":.")
645 print(" .\'\' ; \\ \"\\__.")
646 print(" .\' ;; ; \\\\\";")
647 print(" .\' ; _____; \\\\/")
648 print(" .\' :; ;\" \\ ___:\'.")
649 print(" .\'--........................... : = ____:\" \\ \\")
650 print(" ..-\"\" \"\"\"\' o\"\"\" ; ; :")
651 print(" .--\"\" .----- ..----... _.- --. ..-\" ; ; ; ;")
652 print(" .\"\"_- \"--\"\"-----\'\"\" _-\" .-\"\" ; ; .-.")
653 print(" .\' .\' SALOME .\" .\" ; ; /. |")
654 print(" /-./\' 4 EVER <3 .\" / _.. ; ; ;;;|")
655 print(" : ;-.______ / _________==. /_ \\ ; ; ;;;;")
656 print(" ; / | \"\"\"\"\"\"\"\"\"\"\".---.\"\"\"\"\"\"\" : /\" \". |; ; _; ;;;")
657 print(" /\"-/ | / / / / ;|; ;-\" | ;\';")
658 print(":- : \"\"\"----______ / / ____. . .\"\'. ;; .-\"..T\" .")
659 print("\'. \" ___ \"\": \'\"\"\"\"\"\"\"\"\"\"\"\"\"\" . ; ; ;; ;.\" .\" \'--\"")
660 print(" \", __ \"\"\" \"\"---... :- - - - - - - - - \' \' ; ; ; ;;\" .\"")
661 print(" /. ; \"\"\"---___ ; ; ; ;|.\"\"")
662 print(" : \": \"\"\"----. .-------. ; ; ; ;:")
663 print(" \\ \'--__ \\ \\ \\ / | ; ;;")
664 print(" \'-.. \"\"\"\"---___ : .______..\\ __/..-\"\"| ; ; ;")
665 print(" \"\"--.. \"\"\"--\" m l s . \". . ;")
666 print(" \"\"------... ..--\"\" \" :")
667 print(" \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\" \\ /")
670 print(" Drive your simulation properly with SALOME!")
674 # Add the following two methods since logger is not pickable
675 # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
676 def __getstate__(self):
677 d = dict(self.__dict__)
678 if hasattr(self, '_logger'):
682 def __setstate__(self, d):
683 self.__dict__.update(d) # I *think* this is a safe way to do it
685 # Excluding self._logger from pickle operation imply using the following method to access logger
687 if not hasattr(self, '_logger'):
688 self._logger = logging.getLogger(__name__)
689 #self._logger.setLevel(logging.DEBUG)
690 #self._logger.setLevel(logging.WARNING)
691 self._logger.setLevel(logging.ERROR)
695 if __name__ == "__main__":
696 if len(sys.argv) == 3:
697 context = pickle.loads(sys.argv[1].encode('latin1'))
698 args = pickle.loads(sys.argv[2].encode('latin1'))
700 status = context._startSalome(args)