1 #! /usr/bin/env python3
2 # Copyright (C) 2013-2023 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 ###################################
235 # This begins the private section #
236 ###################################
238 def __parseArguments(self, args):
239 if len(args) == 0 or args[0].startswith("-"):
245 availableCommands = {
246 'start' : '_sessionless',
247 'withsession' : '_runAppli',
248 'context' : '_setContext',
249 'shell' : '_runSession',
250 'remote' : '_runRemote',
251 'connect' : '_runConsole',
253 'killall' : '_killAll',
254 'test' : '_runTests',
255 'info' : '_showInfo',
258 'coffee' : '_makeCoffee',
262 if command not in availableCommands:
266 return availableCommands[command], options
271 Args consist in a mandatory command followed by optional parameters.
272 See usage for details on commands.
274 def _startSalome(self, args):
278 from setenv import add_path
279 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
280 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome"))
281 add_path(path, "PYTHONPATH")
282 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome", "appliskel"))
283 add_path(path, "PYTHONPATH")
288 command, options = self.__parseArguments(args)
292 if args and args[0] in ["-h","--help","help"]:
295 # try to default to "start" command
296 command = "_sessionless"
299 res = getattr(self, command)(options) # run appropriate method
301 except SystemExit as ex:
303 self.getLogger().error("SystemExit %s in method %s.", ex.code, command)
305 except SalomeContextException as e:
306 self.getLogger().error(e)
309 self.getLogger().error("Unexpected error:")
311 traceback.print_exc()
315 def __setContextFromConfigFile(self, filename, reserved=None):
316 mesa_root_dir = "MESA_ROOT_DIR"
320 configInfo = parseConfigFile(filename, reserved)
321 unsetVars = configInfo.unsetVariables
322 configVars = configInfo.outputVariables
323 reservedDict = configInfo.reservedValues
324 defaultValues = configInfo.defaultValues
325 except SalomeContextException as e:
327 self.getLogger().error(msg)
331 for var in unsetVars:
332 self.unsetVariable(var)
335 if "MESA_GL_VERSION_OVERRIDE" in os.environ:
336 configVarsDict = {k:v for (k,v) in configVars}
337 if mesa_root_dir in configVarsDict:
338 path_to_mesa_lib = os.path.join(configVarsDict[mesa_root_dir],"lib")
339 if os.name == "posix":
340 self.addToVariable("LD_LIBRARY_PATH",path_to_mesa_lib)
342 self.addToVariable("PATH",path_to_mesa_lib)
345 for reserved in reservedDict:
346 a = [_f for _f in reservedDict[reserved] if _f] # remove empty elements
347 a = [ os.path.realpath(x) for x in a ]
348 reformattedVals = os.pathsep.join(a)
349 if reserved in ["INCLUDE", "LIBPATH"]:
350 self.addToVariable(reserved, reformattedVals, separator=' ')
352 self.addToVariable(reserved, reformattedVals)
356 for key,val in configVars:
357 self.setVariable(key, val, overwrite=True)
360 for key,val in defaultValues:
361 self.setDefaultValue(key, val)
364 pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
365 pythonpath = [ os.path.realpath(x) for x in pythonpath ]
366 sys.path[:0] = pythonpath
369 def _runAppli(self, args=None):
372 # Initialize SALOME environment
373 sys.argv = ['runSalomeOld'] + args
375 setenv.main(True, exeName="salome withsession")
378 runSalomeOld.runSalome()
382 def _sessionless(self, args=None):
385 sys.argv = ['runSalome'] + args
387 setenv.main(True, exeName="salome withsession")
390 runSalome.runSalome()
394 def _setContext(self, args=None):
395 salome_context_set = os.getenv("SALOME_CONTEXT_SET")
396 if salome_context_set:
398 print("*** SALOME context has already been set.")
399 print("*** Enter 'exit' (only once!) to leave SALOME context.")
403 os.environ["SALOME_CONTEXT_SET"] = "yes"
405 print("*** SALOME context is now set.")
406 print("*** Enter 'exit' (only once!) to leave SALOME context.")
409 if sys.platform == 'win32':
413 proc = subprocess.Popen(cmd, shell=False, close_fds=True)
415 return proc.returncode
418 def _runSession(self, args=None):
421 sys.argv = ['runSession'] + args
423 params, args = runSession.configureSession(args, exe="salome shell")
425 sys.argv = ['runSession'] + args
429 return runSession.runSession(params, args)
432 def _runRemote(self, args=None):
435 # complete salome environment
436 sys.argv = ['runRemote']
441 return runRemote.runRemote(args)
444 def _runConsole(self, args=None):
447 # Initialize SALOME environment
448 sys.argv = ['runConsole']
453 return runConsole.connect(args)
456 def _kill(self, args=None):
461 print("Port number(s) not provided to command: salome kill <port(s)>")
468 if os.getenv("NSHOST") == "no_host":
469 os.unsetenv("NSHOST")
471 if sys.platform == "win32":
472 proc = subprocess.Popen([os.getenv("PYTHONBIN"), "-m", "killSalomeWithPort", str(port)])
474 proc = subprocess.Popen(["killSalomeWithPort.py", str(port)])
479 def _killAll(self, unused=None):
480 sys.argv = ['killAll']
483 if os.getenv("NSHOST") == "no_host":
484 os.unsetenv("NSHOST")
486 import PortManager # mandatory
488 ports = PortManager.getBusyPorts()['this']
492 if sys.platform == "win32":
493 proc = subprocess.Popen([os.getenv("PYTHONBIN"), "-m", "killSalomeWithPort", str(port)])
495 proc = subprocess.Popen(["killSalomeWithPort.py", str(port)])
498 # :TODO: should be declared obsolete
499 from killSalome import killAllPorts
502 from addToKillList import killList
507 def _runTests(self, args=None):
510 sys.argv = ['runTests']
515 return runTests.runTests(args, exe="salome test")
518 def _showSoftwareVersions(self, softwares=None):
519 config = configparser.SafeConfigParser()
520 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
521 filename = os.path.join(absoluteAppliPath, "sha1_collections.txt")
524 with open(filename) as f:
527 software, version, sha1 = line.split()
528 versions[software.upper()] = version
529 if len(software) > max_len:
530 max_len = len(software)
536 for soft in softwares:
537 if soft.upper() in versions:
538 print(soft.upper().rjust(max_len), versions[soft.upper()])
541 od = collections.OrderedDict(sorted(versions.items()))
542 for name, version in od.items():
543 print(name.rjust(max_len), versions[name])
546 def _showInfo(self, args=None):
550 usage = "Usage: salome info [options]"
552 Display some information about SALOME.\n
553 Available options are:
554 -p,--ports Show the list of busy ports (running SALOME instances).
555 -s,--softwares [software(s)] Show the list and versions of SALOME softwares.
556 Software names must be separated by blank characters.
557 If no software is given, show version of all softwares.
558 -v,--version Show running SALOME version.
559 -h,--help Show this message.
564 if "-h" in args or "--help" in args:
565 print(usage + epilog)
568 if "-p" in args or "--ports" in args:
570 ports = PortManager.getBusyPorts()
571 this_ports = ports['this']
572 other_ports = ports['other']
573 if this_ports or other_ports:
574 print("SALOME instances are running on the following ports:")
576 print(" This application:", this_ports)
578 print(" No SALOME instances of this application")
580 print(" Other applications:", other_ports)
582 print(" No SALOME instances of other applications")
584 print("No SALOME instances are running")
586 if "-s" in args or "--softwares" in args:
588 index = args.index("-s")
590 index = args.index("--softwares")
592 while indexEnd < len(args) and args[indexEnd][0] != "-":
593 indexEnd = indexEnd + 1
594 self._showSoftwareVersions(softwares=args[index+1:indexEnd])
596 if "-v" in args or "--version" in args:
597 print("Running with python", platform.python_version())
598 return self._sessionless(["--version"])
603 def _showDoc(self, args=None):
609 print("Module(s) not provided to command: salome doc <module(s)>")
612 appliPath = os.getenv("ABSOLUTE_APPLI_PATH")
614 raise SalomeContextException("Unable to find application path. Please check that the variable ABSOLUTE_APPLI_PATH is set.")
615 baseDir = os.path.join(appliPath, "share", "doc", "salome")
616 for module in modules:
617 docfile = os.path.join(baseDir, "gui", module.upper(), "index.html")
618 if not os.path.isfile(docfile):
619 docfile = os.path.join(baseDir, "tui", module.upper(), "index.html")
620 if not os.path.isfile(docfile):
621 docfile = os.path.join(baseDir, "dev", module.upper(), "index.html")
622 if os.path.isfile(docfile):
623 out, err = subprocess.Popen(["xdg-open", docfile]).communicate()
625 print("Online documentation is not accessible for module:", module)
627 def _usage(self, unused=None):
631 def _makeCoffee(self, unused=None):
634 print(" ___...(-------)-....___")
635 print(" .-\"\" ) ( \"\"-.")
636 print(" .-\'``\'|-._ ) _.-|")
637 print(" / .--.| `\"\"---...........---\"\"` |")
641 print(" `\\ `\\ | |")
642 print(" `\\ `| SALOME |")
643 print(" _/ /\\ 4 EVER /")
644 print(" (__/ \\ <3 /")
645 print(" _..---\"\"` \\ /`\"\"---.._")
646 print(" .-\' \\ / \'-.")
647 print(" : `-.__ __.-\' :")
648 print(" : ) \"\"---...---\"\" ( :")
649 print(" \'._ `\"--...___...--\"` _.\'")
650 print(" \\\"\"--..__ __..--\"\"/")
651 print(" \'._ \"\"\"----.....______.....----\"\"\" _.\'")
652 print(" `\"\"--..,,_____ _____,,..--\"\"`")
653 print(" `\"\"\"----\"\"\"`")
655 print(" SALOME is working for you; what else?")
659 def _getCar(self, unused=None):
660 print(" _____________")
661 print(" ..---:::::::-----------. ::::;;.")
662 print(" .\'\"\"\"\"\"\" ;; \\ \":.")
663 print(" .\'\' ; \\ \"\\__.")
664 print(" .\' ;; ; \\\\\";")
665 print(" .\' ; _____; \\\\/")
666 print(" .\' :; ;\" \\ ___:\'.")
667 print(" .\'--........................... : = ____:\" \\ \\")
668 print(" ..-\"\" \"\"\"\' o\"\"\" ; ; :")
669 print(" .--\"\" .----- ..----... _.- --. ..-\" ; ; ; ;")
670 print(" .\"\"_- \"--\"\"-----\'\"\" _-\" .-\"\" ; ; .-.")
671 print(" .\' .\' SALOME .\" .\" ; ; /. |")
672 print(" /-./\' 4 EVER <3 .\" / _.. ; ; ;;;|")
673 print(" : ;-.______ / _________==. /_ \\ ; ; ;;;;")
674 print(" ; / | \"\"\"\"\"\"\"\"\"\"\".---.\"\"\"\"\"\"\" : /\" \". |; ; _; ;;;")
675 print(" /\"-/ | / / / / ;|; ;-\" | ;\';")
676 print(":- : \"\"\"----______ / / ____. . .\"\'. ;; .-\"..T\" .")
677 print("\'. \" ___ \"\": \'\"\"\"\"\"\"\"\"\"\"\"\"\"\" . ; ; ;; ;.\" .\" \'--\"")
678 print(" \", __ \"\"\" \"\"---... :- - - - - - - - - \' \' ; ; ; ;;\" .\"")
679 print(" /. ; \"\"\"---___ ; ; ; ;|.\"\"")
680 print(" : \": \"\"\"----. .-------. ; ; ; ;:")
681 print(" \\ \'--__ \\ \\ \\ / | ; ;;")
682 print(" \'-.. \"\"\"\"---___ : .______..\\ __/..-\"\"| ; ; ;")
683 print(" \"\"--.. \"\"\"--\" m l s . \". . ;")
684 print(" \"\"------... ..--\"\" \" :")
685 print(" \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\" \\ /")
688 print(" Drive your simulation properly with SALOME!")
692 # Add the following two methods since logger is not pickable
693 # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
694 def __getstate__(self):
695 d = dict(self.__dict__)
696 if hasattr(self, '_logger'):
700 def __setstate__(self, d):
701 self.__dict__.update(d) # I *think* this is a safe way to do it
703 # Excluding self._logger from pickle operation imply using the following method to access logger
705 if not hasattr(self, '_logger'):
706 self._logger = logging.getLogger(__name__)
707 #self._logger.setLevel(logging.DEBUG)
708 #self._logger.setLevel(logging.WARNING)
709 self._logger.setLevel(logging.ERROR)
713 if __name__ == "__main__":
714 if len(sys.argv) == 3:
715 context = pickle.loads(sys.argv[1].encode('latin1'))
716 args = pickle.loads(sys.argv[2].encode('latin1'))
718 status = context._startSalome(args)