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) (Python >= 3.9)
246 str = value + separator
247 if env.startswith(str):
249 # env = env.removesuffix(separator + value) (Python >= 3.9)
250 str = separator + value
251 if env.endswith(str):
252 env = env[:-len(str)]
253 env = env.replace(separator + value + separator, ':')
255 os.environ[name] = env
258 ###################################
259 # This begins the private section #
260 ###################################
262 def __parseArguments(self, args):
263 if len(args) == 0 or args[0].startswith("-"):
269 availableCommands = {
270 'start' : '_sessionless',
271 'withsession' : '_runAppli',
272 'context' : '_setContext',
273 'shell' : '_runSession',
274 'remote' : '_runRemote',
275 'connect' : '_runConsole',
277 'killall' : '_killAll',
278 'test' : '_runTests',
279 'info' : '_showInfo',
282 'coffee' : '_makeCoffee',
286 if command not in availableCommands:
290 return availableCommands[command], options
295 Args consist in a mandatory command followed by optional parameters.
296 See usage for details on commands.
298 def _startSalome(self, args):
302 from setenv import add_path
303 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
304 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome"))
305 add_path(path, "PYTHONPATH")
306 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome", "appliskel"))
307 add_path(path, "PYTHONPATH")
312 command, options = self.__parseArguments(args)
316 if args and args[0] in ["-h","--help","help"]:
319 # try to default to "start" command
320 command = "_sessionless"
323 res = getattr(self, command)(options) # run appropriate method
325 except SystemExit as ex:
327 self.getLogger().error("SystemExit %s in method %s.", ex.code, command)
329 except SalomeContextException as e:
330 self.getLogger().error(e)
333 self.getLogger().error("Unexpected error:")
335 traceback.print_exc()
339 def __setContextFromConfigFile(self, filename, reserved=None):
340 mesa_root_dir = "MESA_ROOT_DIR"
344 configInfo = parseConfigFile(filename, reserved)
345 unsetVars = configInfo.unsetVariables
346 configVars = configInfo.outputVariables
347 reservedDict = configInfo.reservedValues
348 defaultValues = configInfo.defaultValues
349 except SalomeContextException as e:
351 self.getLogger().error(msg)
355 for var in unsetVars:
356 self.unsetVariable(var)
359 if "MESA_GL_VERSION_OVERRIDE" in os.environ:
360 configVarsDict = {k:v for (k,v) in configVars}
361 if mesa_root_dir in configVarsDict:
362 path_to_mesa_lib = os.path.join(configVarsDict[mesa_root_dir],"lib")
363 if os.name == "posix":
364 self.addToVariable("LD_LIBRARY_PATH",path_to_mesa_lib)
366 self.addToVariable("PATH",path_to_mesa_lib)
369 for reserved in reservedDict:
370 a = [_f for _f in reservedDict[reserved] if _f] # remove empty elements
371 a = [ os.path.realpath(x) for x in a ]
372 reformattedVals = os.pathsep.join(a)
373 if reserved in ["INCLUDE", "LIBPATH"]:
374 self.addToVariable(reserved, reformattedVals, separator=' ')
376 self.addToVariable(reserved, reformattedVals)
380 for key,val in configVars:
381 self.setVariable(key, val, overwrite=True)
384 for key,val in defaultValues:
385 self.setDefaultValue(key, val)
388 pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
389 pythonpath = [ os.path.realpath(x) for x in pythonpath ]
390 sys.path[:0] = pythonpath
393 def _runAppli(self, args=None):
396 # Initialize SALOME environment
397 sys.argv = ['runSalomeOld'] + args
399 setenv.main(True, exeName="salome withsession")
402 runSalomeOld.runSalome()
406 def _sessionless(self, args=None):
409 sys.argv = ['runSalome'] + args
411 setenv.main(True, exeName="salome withsession")
414 runSalome.runSalome()
418 def _setContext(self, args=None):
419 salome_context_set = os.getenv("SALOME_CONTEXT_SET")
420 if salome_context_set:
422 print("*** SALOME context has already been set.")
423 print("*** Enter 'exit' (only once!) to leave SALOME context.")
427 os.environ["SALOME_CONTEXT_SET"] = "yes"
429 print("*** SALOME context is now set.")
430 print("*** Enter 'exit' (only once!) to leave SALOME context.")
433 if sys.platform == 'win32':
437 proc = subprocess.Popen(cmd, shell=False, close_fds=True)
439 return proc.returncode
442 def _runSession(self, args=None):
445 sys.argv = ['runSession'] + args
447 params, args = runSession.configureSession(args, exe="salome shell")
449 sys.argv = ['runSession'] + args
453 return runSession.runSession(params, args)
456 def _runRemote(self, args=None):
459 # complete salome environment
460 sys.argv = ['runRemote']
465 return runRemote.runRemote(args)
468 def _runConsole(self, args=None):
471 # Initialize SALOME environment
472 sys.argv = ['runConsole']
477 return runConsole.connect(args)
480 def _kill(self, args=None):
485 print("Port number(s) not provided to command: salome kill <port(s)>")
492 if os.getenv("NSHOST") == "no_host":
493 os.unsetenv("NSHOST")
495 if sys.platform == "win32":
496 proc = subprocess.Popen([os.getenv("PYTHONBIN"), "-m", "killSalomeWithPort", str(port)])
498 proc = subprocess.Popen(["killSalomeWithPort.py", str(port)])
503 def _killAll(self, unused=None):
504 sys.argv = ['killAll']
507 if os.getenv("NSHOST") == "no_host":
508 os.unsetenv("NSHOST")
510 import PortManager # mandatory
512 ports = PortManager.getBusyPorts()['this']
516 if sys.platform == "win32":
517 proc = subprocess.Popen([os.getenv("PYTHONBIN"), "-m", "killSalomeWithPort", str(port)])
519 proc = subprocess.Popen(["killSalomeWithPort.py", str(port)])
522 # :TODO: should be declared obsolete
523 from killSalome import killAllPorts
526 from addToKillList import killList
531 def _runTests(self, args=None):
534 sys.argv = ['runTests']
539 return runTests.runTests(args, exe="salome test")
542 def _showSoftwareVersions(self, softwares=None):
543 config = configparser.SafeConfigParser()
544 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
545 filename = os.path.join(absoluteAppliPath, "sha1_collections.txt")
548 with open(filename) as f:
551 software, version, sha1 = line.split()
552 versions[software.upper()] = version
553 if len(software) > max_len:
554 max_len = len(software)
560 for soft in softwares:
561 if soft.upper() in versions:
562 print(soft.upper().rjust(max_len), versions[soft.upper()])
565 od = collections.OrderedDict(sorted(versions.items()))
566 for name, version in od.items():
567 print(name.rjust(max_len), versions[name])
570 def _showInfo(self, args=None):
574 usage = "Usage: salome info [options]"
576 Display some information about SALOME.\n
577 Available options are:
578 -p,--ports Show the list of busy ports (running SALOME instances).
579 -s,--softwares [software(s)] Show the list and versions of SALOME softwares.
580 Software names must be separated by blank characters.
581 If no software is given, show version of all softwares.
582 -v,--version Show running SALOME version.
583 -h,--help Show this message.
588 if "-h" in args or "--help" in args:
589 print(usage + epilog)
592 if "-p" in args or "--ports" in args:
594 ports = PortManager.getBusyPorts()
595 this_ports = ports['this']
596 other_ports = ports['other']
597 if this_ports or other_ports:
598 print("SALOME instances are running on the following ports:")
600 print(" This application:", this_ports)
602 print(" No SALOME instances of this application")
604 print(" Other applications:", other_ports)
606 print(" No SALOME instances of other applications")
608 print("No SALOME instances are running")
610 if "-s" in args or "--softwares" in args:
612 index = args.index("-s")
614 index = args.index("--softwares")
616 while indexEnd < len(args) and args[indexEnd][0] != "-":
617 indexEnd = indexEnd + 1
618 self._showSoftwareVersions(softwares=args[index+1:indexEnd])
620 if "-v" in args or "--version" in args:
621 print("Running with python", platform.python_version())
622 return self._sessionless(["--version"])
627 def _showDoc(self, args=None):
633 print("Module(s) not provided to command: salome doc <module(s)>")
636 appliPath = os.getenv("ABSOLUTE_APPLI_PATH")
638 raise SalomeContextException("Unable to find application path. Please check that the variable ABSOLUTE_APPLI_PATH is set.")
639 baseDir = os.path.join(appliPath, "share", "doc", "salome")
640 for module in modules:
641 docfile = os.path.join(baseDir, "gui", module.upper(), "index.html")
642 if not os.path.isfile(docfile):
643 docfile = os.path.join(baseDir, "tui", module.upper(), "index.html")
644 if not os.path.isfile(docfile):
645 docfile = os.path.join(baseDir, "dev", module.upper(), "index.html")
646 if os.path.isfile(docfile):
647 out, err = subprocess.Popen(["xdg-open", docfile]).communicate()
649 print("Online documentation is not accessible for module:", module)
651 def _usage(self, unused=None):
655 def _makeCoffee(self, unused=None):
658 print(" ___...(-------)-....___")
659 print(" .-\"\" ) ( \"\"-.")
660 print(" .-\'``\'|-._ ) _.-|")
661 print(" / .--.| `\"\"---...........---\"\"` |")
665 print(" `\\ `\\ | |")
666 print(" `\\ `| SALOME |")
667 print(" _/ /\\ 4 EVER /")
668 print(" (__/ \\ <3 /")
669 print(" _..---\"\"` \\ /`\"\"---.._")
670 print(" .-\' \\ / \'-.")
671 print(" : `-.__ __.-\' :")
672 print(" : ) \"\"---...---\"\" ( :")
673 print(" \'._ `\"--...___...--\"` _.\'")
674 print(" \\\"\"--..__ __..--\"\"/")
675 print(" \'._ \"\"\"----.....______.....----\"\"\" _.\'")
676 print(" `\"\"--..,,_____ _____,,..--\"\"`")
677 print(" `\"\"\"----\"\"\"`")
679 print(" SALOME is working for you; what else?")
683 def _getCar(self, unused=None):
684 print(" _____________")
685 print(" ..---:::::::-----------. ::::;;.")
686 print(" .\'\"\"\"\"\"\" ;; \\ \":.")
687 print(" .\'\' ; \\ \"\\__.")
688 print(" .\' ;; ; \\\\\";")
689 print(" .\' ; _____; \\\\/")
690 print(" .\' :; ;\" \\ ___:\'.")
691 print(" .\'--........................... : = ____:\" \\ \\")
692 print(" ..-\"\" \"\"\"\' o\"\"\" ; ; :")
693 print(" .--\"\" .----- ..----... _.- --. ..-\" ; ; ; ;")
694 print(" .\"\"_- \"--\"\"-----\'\"\" _-\" .-\"\" ; ; .-.")
695 print(" .\' .\' SALOME .\" .\" ; ; /. |")
696 print(" /-./\' 4 EVER <3 .\" / _.. ; ; ;;;|")
697 print(" : ;-.______ / _________==. /_ \\ ; ; ;;;;")
698 print(" ; / | \"\"\"\"\"\"\"\"\"\"\".---.\"\"\"\"\"\"\" : /\" \". |; ; _; ;;;")
699 print(" /\"-/ | / / / / ;|; ;-\" | ;\';")
700 print(":- : \"\"\"----______ / / ____. . .\"\'. ;; .-\"..T\" .")
701 print("\'. \" ___ \"\": \'\"\"\"\"\"\"\"\"\"\"\"\"\"\" . ; ; ;; ;.\" .\" \'--\"")
702 print(" \", __ \"\"\" \"\"---... :- - - - - - - - - \' \' ; ; ; ;;\" .\"")
703 print(" /. ; \"\"\"---___ ; ; ; ;|.\"\"")
704 print(" : \": \"\"\"----. .-------. ; ; ; ;:")
705 print(" \\ \'--__ \\ \\ \\ / | ; ;;")
706 print(" \'-.. \"\"\"\"---___ : .______..\\ __/..-\"\"| ; ; ;")
707 print(" \"\"--.. \"\"\"--\" m l s . \". . ;")
708 print(" \"\"------... ..--\"\" \" :")
709 print(" \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\" \\ /")
712 print(" Drive your simulation properly with SALOME!")
716 # Add the following two methods since logger is not pickable
717 # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
718 def __getstate__(self):
719 d = dict(self.__dict__)
720 if hasattr(self, '_logger'):
724 def __setstate__(self, d):
725 self.__dict__.update(d) # I *think* this is a safe way to do it
727 # Excluding self._logger from pickle operation imply using the following method to access logger
729 if not hasattr(self, '_logger'):
730 self._logger = logging.getLogger(__name__)
731 #self._logger.setLevel(logging.DEBUG)
732 #self._logger.setLevel(logging.WARNING)
733 self._logger.setLevel(logging.ERROR)
737 if __name__ == "__main__":
738 if len(sys.argv) == 3:
739 context = pickle.loads(sys.argv[1].encode('latin1'))
740 args = pickle.loads(sys.argv[2].encode('latin1'))
742 status = context._startSalome(args)