1 #! /usr/bin/env python3
2 # Copyright (C) 2013-2024 CEA, EDF, 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,...>] [--with-env-modules=<env_module1,env_module2,...>]
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
75 --with-env-modules=<env_module1,env_module2,...>
76 ================================================
77 Initialize SALOME context with the provided additional environment modules.
78 The list is comma-separated, without any blank characters.
85 The SalomeContext class in an API to configure SALOME context then
86 start SALOME using a single python command.
91 Initialize context from a list of configuration files
92 identified by their names.
93 These files should be in appropriate .cfg format.
95 def __init__(self, configFileNames=0):
96 self.getLogger().setLevel(logging.INFO)
97 #it could be None explicitly (if user use multiples setVariable...for standalone)
98 if configFileNames is None:
100 configFileNames = configFileNames or []
101 if len(configFileNames) == 0:
102 raise SalomeContextException("No configuration files given")
104 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']
105 for filename in configFileNames:
106 basename, extension = os.path.splitext(filename)
107 if extension == ".cfg":
108 self.__setContextFromConfigFile(filename, reserved)
110 self.getLogger().error("Unrecognized extension for configuration file: %s", filename)
113 def __loadEnvModules(self, env_modules):
114 modulecmd = os.getenv('LMOD_CMD')
116 raise SalomeContextException("Module environment not present")
119 out, err = subprocess.Popen([modulecmd, "python", "try-load"] + env_modules, stdout=subprocess.PIPE).communicate()
120 exec(out) # define specific environment variables
122 raise SalomeContextException("Failed to load env modules: %s ..." % ' '.join(env_modules))
126 def runSalome(self, args):
128 # Run this module as a script, in order to use appropriate Python interpreter
129 # according to current path (initialized from context files).
130 env_modules_option = "--with-env-modules="
131 env_modules_l = [x for x in args if x.startswith(env_modules_option)]
133 env_modules = env_modules_l[-1][len(env_modules_option):].split(',')
134 self.__loadEnvModules(env_modules)
135 args = [x for x in args if not x.startswith(env_modules_option)]
137 env_modules = os.getenv("SALOME_ENV_MODULES", None)
139 self.__loadEnvModules(env_modules.split(','))
141 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
142 env_copy = os.environ.copy()
143 selfBytes= pickle.dumps(self, protocol=0)
144 argsBytes= pickle.dumps(args, protocol=0)
145 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)
146 out, err = proc.communicate()
147 return out, err, proc.returncode
150 """Append value to PATH environment variable"""
151 def addToPath(self, value):
152 self.addToVariable('PATH', value)
155 """Append value to LD_LIBRARY_PATH environment variable"""
156 def addToLdLibraryPath(self, value):
157 if sys.platform == 'win32':
158 self.addToVariable('PATH', value)
159 elif sys.platform == 'darwin':
160 if "LAPACK" in value:
161 self.addToVariable('DYLD_FALLBACK_LIBRARY_PATH', value)
163 self.addToVariable('DYLD_LIBRARY_PATH', value)
165 self.addToVariable('LD_LIBRARY_PATH', value)
168 """Append value to DYLD_LIBRARY_PATH environment variable"""
169 def addToDyldLibraryPath(self, value):
170 self.addToVariable('DYLD_LIBRARY_PATH', value)
173 """Append value to PYTHONPATH environment variable"""
174 def addToPythonPath(self, value):
175 self.addToVariable('PYTHONPATH', value)
178 """Set environment variable to value"""
179 def setVariable(self, name, value, overwrite=False):
180 env = os.getenv(name, '')
181 if env and not overwrite:
182 self.getLogger().error("Environment variable already existing (and not overwritten): %s=%s", name, value)
186 self.getLogger().debug("Overwriting environment variable: %s=%s", name, value)
188 value = os.path.expandvars(value) # expand environment variables
189 self.getLogger().debug("Set environment variable: %s=%s", name, value)
190 os.environ[name] = value
193 def setDefaultValue(self, name, value):
194 """ Set environment variable only if it is undefined."""
195 env = os.getenv(name, '')
197 value = os.path.expandvars(value) # expand environment variables
198 self.getLogger().debug("Set environment variable: %s=%s", name, value)
199 os.environ[name] = value
201 """Unset environment variable"""
202 def unsetVariable(self, name):
203 if name in os.environ:
207 """Prepend value to environment variable"""
208 def addToVariable(self, name, value, separator=os.pathsep):
212 value = os.path.expandvars(value) # expand environment variables
213 self.getLogger().debug("Add to %s: %s", name, value)
214 env = os.getenv(name, None)
216 os.environ[name] = value
218 os.environ[name] = value + separator + env
221 """Append a variable"""
222 def appendVariable(self, name, value, separator=os.pathsep):
226 value = os.path.expandvars(value) # expand environment variables
227 env = os.getenv(name, None)
229 os.environ[name] = value
231 os.environ[name] = env + separator + value
234 """Remove value from environment variable"""
235 def removeFromVariable(self, name, value, separator=os.pathsep):
239 value = os.path.expandvars(value) # expand environment variables
240 self.getLogger().debug("Remove from %s: %s", name, value)
241 env = os.getenv(name, None)
245 env = env.removeprefix(value + separator)
246 env = env.removesuffix(separator + value)
247 env = env.replace(separator + value + separator, ':')
249 os.environ[name] = env
252 ###################################
253 # This begins the private section #
254 ###################################
256 def __parseArguments(self, args):
257 if len(args) == 0 or args[0].startswith("-"):
263 availableCommands = {
264 'start' : '_sessionless',
265 'withsession' : '_runAppli',
266 'context' : '_setContext',
267 'shell' : '_runSession',
268 'remote' : '_runRemote',
269 'connect' : '_runConsole',
271 'killall' : '_killAll',
272 'test' : '_runTests',
273 'info' : '_showInfo',
276 'coffee' : '_makeCoffee',
280 if command not in availableCommands:
284 return availableCommands[command], options
289 Args consist in a mandatory command followed by optional parameters.
290 See usage for details on commands.
292 def _startSalome(self, args):
296 from setenv import add_path
297 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
298 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome"))
299 add_path(path, "PYTHONPATH")
300 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome", "appliskel"))
301 add_path(path, "PYTHONPATH")
306 command, options = self.__parseArguments(args)
310 if args and args[0] in ["-h","--help","help"]:
313 # try to default to "start" command
314 command = "_sessionless"
317 res = getattr(self, command)(options) # run appropriate method
319 except SystemExit as ex:
321 self.getLogger().error("SystemExit %s in method %s.", ex.code, command)
323 except SalomeContextException as e:
324 self.getLogger().error(e)
327 self.getLogger().error("Unexpected error:")
329 traceback.print_exc()
333 def __setContextFromConfigFile(self, filename, reserved=None):
334 mesa_root_dir = "MESA_ROOT_DIR"
338 configInfo = parseConfigFile(filename, reserved)
339 unsetVars = configInfo.unsetVariables
340 configVars = configInfo.outputVariables
341 reservedDict = configInfo.reservedValues
342 defaultValues = configInfo.defaultValues
343 except SalomeContextException as e:
345 self.getLogger().error(msg)
349 for var in unsetVars:
350 self.unsetVariable(var)
353 if "MESA_GL_VERSION_OVERRIDE" in os.environ:
354 configVarsDict = {k:v for (k,v) in configVars}
355 if mesa_root_dir in configVarsDict:
356 path_to_mesa_lib = os.path.join(configVarsDict[mesa_root_dir],"lib")
357 if os.name == "posix":
358 self.addToVariable("LD_LIBRARY_PATH",path_to_mesa_lib)
360 self.addToVariable("PATH",path_to_mesa_lib)
363 for reserved in reservedDict:
364 a = [_f for _f in reservedDict[reserved] if _f] # remove empty elements
365 a = [ os.path.realpath(x) for x in a ]
366 reformattedVals = os.pathsep.join(a)
367 if reserved in ["INCLUDE", "LIBPATH"]:
368 self.addToVariable(reserved, reformattedVals, separator=' ')
370 self.addToVariable(reserved, reformattedVals)
374 for key,val in configVars:
375 self.setVariable(key, val, overwrite=True)
378 for key,val in defaultValues:
379 self.setDefaultValue(key, val)
382 pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
383 pythonpath = [ os.path.realpath(x) for x in pythonpath ]
384 sys.path[:0] = pythonpath
387 def _runAppli(self, args=None):
390 # Initialize SALOME environment
391 sys.argv = ['runSalomeOld'] + args
393 setenv.main(True, exeName="salome withsession")
396 runSalomeOld.runSalome()
400 def _sessionless(self, args=None):
403 sys.argv = ['runSalome'] + args
405 setenv.main(True, exeName="salome withsession")
408 runSalome.runSalome()
412 def _setContext(self, args=None):
413 salome_context_set = os.getenv("SALOME_CONTEXT_SET")
414 if salome_context_set:
416 print("*** SALOME context has already been set.")
417 print("*** Enter 'exit' (only once!) to leave SALOME context.")
421 os.environ["SALOME_CONTEXT_SET"] = "yes"
423 print("*** SALOME context is now set.")
424 print("*** Enter 'exit' (only once!) to leave SALOME context.")
427 if sys.platform == 'win32':
431 proc = subprocess.Popen(cmd, shell=False, close_fds=True)
433 return proc.returncode
436 def _runSession(self, args=None):
439 sys.argv = ['runSession'] + args
441 params, args = runSession.configureSession(args, exe="salome shell")
443 sys.argv = ['runSession'] + args
447 return runSession.runSession(params, args)
450 def _runRemote(self, args=None):
453 # complete salome environment
454 sys.argv = ['runRemote']
459 return runRemote.runRemote(args)
462 def _runConsole(self, args=None):
465 # Initialize SALOME environment
466 sys.argv = ['runConsole']
471 return runConsole.connect(args)
474 def _kill(self, args=None):
479 print("Port number(s) not provided to command: salome kill <port(s)>")
486 if os.getenv("NSHOST") == "no_host":
487 os.unsetenv("NSHOST")
489 if sys.platform == "win32":
490 proc = subprocess.Popen([os.getenv("PYTHONBIN"), "-m", "killSalomeWithPort", str(port)])
492 proc = subprocess.Popen(["killSalomeWithPort.py", str(port)])
497 def _killAll(self, unused=None):
498 sys.argv = ['killAll']
501 if os.getenv("NSHOST") == "no_host":
502 os.unsetenv("NSHOST")
504 import PortManager # mandatory
506 ports = PortManager.getBusyPorts()['this']
510 if sys.platform == "win32":
511 proc = subprocess.Popen([os.getenv("PYTHONBIN"), "-m", "killSalomeWithPort", str(port)])
513 proc = subprocess.Popen(["killSalomeWithPort.py", str(port)])
516 # :TODO: should be declared obsolete
517 from killSalome import killAllPorts
520 from addToKillList import killList
525 def _runTests(self, args=None):
528 sys.argv = ['runTests']
533 return runTests.runTests(args, exe="salome test")
536 def _showSoftwareVersions(self, softwares=None):
537 config = configparser.SafeConfigParser()
538 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
539 filename = os.path.join(absoluteAppliPath, "sha1_collections.txt")
542 with open(filename) as f:
545 software, version, sha1 = line.split()
546 versions[software.upper()] = version
547 if len(software) > max_len:
548 max_len = len(software)
554 for soft in softwares:
555 if soft.upper() in versions:
556 print(soft.upper().rjust(max_len), versions[soft.upper()])
559 od = collections.OrderedDict(sorted(versions.items()))
560 for name, version in od.items():
561 print(name.rjust(max_len), versions[name])
564 def _showInfo(self, args=None):
568 usage = "Usage: salome info [options]"
570 Display some information about SALOME.\n
571 Available options are:
572 -p,--ports Show the list of busy ports (running SALOME instances).
573 -s,--softwares [software(s)] Show the list and versions of SALOME softwares.
574 Software names must be separated by blank characters.
575 If no software is given, show version of all softwares.
576 -v,--version Show running SALOME version.
577 -h,--help Show this message.
582 if "-h" in args or "--help" in args:
583 print(usage + epilog)
586 if "-p" in args or "--ports" in args:
588 ports = PortManager.getBusyPorts()
589 this_ports = ports['this']
590 other_ports = ports['other']
591 if this_ports or other_ports:
592 print("SALOME instances are running on the following ports:")
594 print(" This application:", this_ports)
596 print(" No SALOME instances of this application")
598 print(" Other applications:", other_ports)
600 print(" No SALOME instances of other applications")
602 print("No SALOME instances are running")
604 if "-s" in args or "--softwares" in args:
606 index = args.index("-s")
608 index = args.index("--softwares")
610 while indexEnd < len(args) and args[indexEnd][0] != "-":
611 indexEnd = indexEnd + 1
612 self._showSoftwareVersions(softwares=args[index+1:indexEnd])
614 if "-v" in args or "--version" in args:
615 print("Running with python", platform.python_version())
616 return self._sessionless(["--version"])
621 def _showDoc(self, args=None):
627 print("Module(s) not provided to command: salome doc <module(s)>")
630 appliPath = os.getenv("ABSOLUTE_APPLI_PATH")
632 raise SalomeContextException("Unable to find application path. Please check that the variable ABSOLUTE_APPLI_PATH is set.")
633 baseDir = os.path.join(appliPath, "share", "doc", "salome")
634 for module in modules:
635 docfile = os.path.join(baseDir, "gui", module.upper(), "index.html")
636 if not os.path.isfile(docfile):
637 docfile = os.path.join(baseDir, "tui", module.upper(), "index.html")
638 if not os.path.isfile(docfile):
639 docfile = os.path.join(baseDir, "dev", module.upper(), "index.html")
640 if os.path.isfile(docfile):
641 out, err = subprocess.Popen(["xdg-open", docfile]).communicate()
643 print("Online documentation is not accessible for module:", module)
645 def _usage(self, unused=None):
649 def _makeCoffee(self, unused=None):
652 print(" ___...(-------)-....___")
653 print(" .-\"\" ) ( \"\"-.")
654 print(" .-\'``\'|-._ ) _.-|")
655 print(" / .--.| `\"\"---...........---\"\"` |")
659 print(" `\\ `\\ | |")
660 print(" `\\ `| SALOME |")
661 print(" _/ /\\ 4 EVER /")
662 print(" (__/ \\ <3 /")
663 print(" _..---\"\"` \\ /`\"\"---.._")
664 print(" .-\' \\ / \'-.")
665 print(" : `-.__ __.-\' :")
666 print(" : ) \"\"---...---\"\" ( :")
667 print(" \'._ `\"--...___...--\"` _.\'")
668 print(" \\\"\"--..__ __..--\"\"/")
669 print(" \'._ \"\"\"----.....______.....----\"\"\" _.\'")
670 print(" `\"\"--..,,_____ _____,,..--\"\"`")
671 print(" `\"\"\"----\"\"\"`")
673 print(" SALOME is working for you; what else?")
677 def _getCar(self, unused=None):
678 print(" _____________")
679 print(" ..---:::::::-----------. ::::;;.")
680 print(" .\'\"\"\"\"\"\" ;; \\ \":.")
681 print(" .\'\' ; \\ \"\\__.")
682 print(" .\' ;; ; \\\\\";")
683 print(" .\' ; _____; \\\\/")
684 print(" .\' :; ;\" \\ ___:\'.")
685 print(" .\'--........................... : = ____:\" \\ \\")
686 print(" ..-\"\" \"\"\"\' o\"\"\" ; ; :")
687 print(" .--\"\" .----- ..----... _.- --. ..-\" ; ; ; ;")
688 print(" .\"\"_- \"--\"\"-----\'\"\" _-\" .-\"\" ; ; .-.")
689 print(" .\' .\' SALOME .\" .\" ; ; /. |")
690 print(" /-./\' 4 EVER <3 .\" / _.. ; ; ;;;|")
691 print(" : ;-.______ / _________==. /_ \\ ; ; ;;;;")
692 print(" ; / | \"\"\"\"\"\"\"\"\"\"\".---.\"\"\"\"\"\"\" : /\" \". |; ; _; ;;;")
693 print(" /\"-/ | / / / / ;|; ;-\" | ;\';")
694 print(":- : \"\"\"----______ / / ____. . .\"\'. ;; .-\"..T\" .")
695 print("\'. \" ___ \"\": \'\"\"\"\"\"\"\"\"\"\"\"\"\"\" . ; ; ;; ;.\" .\" \'--\"")
696 print(" \", __ \"\"\" \"\"---... :- - - - - - - - - \' \' ; ; ; ;;\" .\"")
697 print(" /. ; \"\"\"---___ ; ; ; ;|.\"\"")
698 print(" : \": \"\"\"----. .-------. ; ; ; ;:")
699 print(" \\ \'--__ \\ \\ \\ / | ; ;;")
700 print(" \'-.. \"\"\"\"---___ : .______..\\ __/..-\"\"| ; ; ;")
701 print(" \"\"--.. \"\"\"--\" m l s . \". . ;")
702 print(" \"\"------... ..--\"\" \" :")
703 print(" \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\" \\ /")
706 print(" Drive your simulation properly with SALOME!")
710 # Add the following two methods since logger is not pickable
711 # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
712 def __getstate__(self):
713 d = dict(self.__dict__)
714 if hasattr(self, '_logger'):
718 def __setstate__(self, d):
719 self.__dict__.update(d) # I *think* this is a safe way to do it
721 # Excluding self._logger from pickle operation imply using the following method to access logger
723 if not hasattr(self, '_logger'):
724 self._logger = logging.getLogger(__name__)
725 #self._logger.setLevel(logging.DEBUG)
726 #self._logger.setLevel(logging.WARNING)
727 self._logger.setLevel(logging.ERROR)
731 if __name__ == "__main__":
732 if len(sys.argv) == 3:
733 context = pickle.loads(sys.argv[1].encode('latin1'))
734 args = pickle.loads(sys.argv[2].encode('latin1'))
736 status = context._startSalome(args)