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,...>]
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 name in os.environ:
202 """Prepend 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 """Append a variable"""
217 def appendVariable(self, name, value, separator=os.pathsep):
221 value = os.path.expandvars(value) # expand environment variables
222 env = os.getenv(name, None)
224 os.environ[name] = value
226 os.environ[name] = env + separator + value
229 ###################################
230 # This begins the private section #
231 ###################################
233 def __parseArguments(self, args):
234 if len(args) == 0 or args[0].startswith("-"):
240 availableCommands = {
241 'start' : '_sessionless',
242 'withsession' : '_runAppli',
243 'context' : '_setContext',
244 'shell' : '_runSession',
245 'remote' : '_runRemote',
246 'connect' : '_runConsole',
248 'killall' : '_killAll',
249 'test' : '_runTests',
250 'info' : '_showInfo',
253 'coffee' : '_makeCoffee',
257 if command not in availableCommands:
261 return availableCommands[command], options
266 Args consist in a mandatory command followed by optional parameters.
267 See usage for details on commands.
269 def _startSalome(self, args):
273 from setenv import add_path
274 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
275 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome"))
276 add_path(path, "PYTHONPATH")
277 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome", "appliskel"))
278 add_path(path, "PYTHONPATH")
283 command, options = self.__parseArguments(args)
287 if args and args[0] in ["-h","--help","help"]:
290 # try to default to "start" command
291 command = "_sessionless"
294 res = getattr(self, command)(options) # run appropriate method
296 except SystemExit as ex:
298 self.getLogger().error("SystemExit %s in method %s.", ex.code, command)
300 except SalomeContextException as e:
301 self.getLogger().error(e)
304 self.getLogger().error("Unexpected error:")
306 traceback.print_exc()
310 def __setContextFromConfigFile(self, filename, reserved=None):
311 mesa_root_dir = "MESA_ROOT_DIR"
315 configInfo = parseConfigFile(filename, reserved)
316 unsetVars = configInfo.unsetVariables
317 configVars = configInfo.outputVariables
318 reservedDict = configInfo.reservedValues
319 defaultValues = configInfo.defaultValues
320 except SalomeContextException as e:
322 self.getLogger().error(msg)
326 for var in unsetVars:
327 self.unsetVariable(var)
330 if "MESA_GL_VERSION_OVERRIDE" in os.environ:
331 configVarsDict = {k:v for (k,v) in configVars}
332 if mesa_root_dir in configVarsDict:
333 path_to_mesa_lib = os.path.join(configVarsDict[mesa_root_dir],"lib")
334 if os.name == "posix":
335 self.addToVariable("LD_LIBRARY_PATH",path_to_mesa_lib)
337 self.addToVariable("PATH",path_to_mesa_lib)
340 for reserved in reservedDict:
341 a = [_f for _f in reservedDict[reserved] if _f] # remove empty elements
342 a = [ os.path.realpath(x) for x in a ]
343 reformattedVals = os.pathsep.join(a)
344 if reserved in ["INCLUDE", "LIBPATH"]:
345 self.addToVariable(reserved, reformattedVals, separator=' ')
347 self.addToVariable(reserved, reformattedVals)
351 for key,val in configVars:
352 self.setVariable(key, val, overwrite=True)
355 for key,val in defaultValues:
356 self.setDefaultValue(key, val)
359 pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
360 pythonpath = [ os.path.realpath(x) for x in pythonpath ]
361 sys.path[:0] = pythonpath
364 def _runAppli(self, args=None):
367 # Initialize SALOME environment
368 sys.argv = ['runSalomeOld'] + args
370 setenv.main(True, exeName="salome withsession")
373 runSalomeOld.runSalome()
377 def _sessionless(self, args=None):
380 sys.argv = ['runSalome'] + args
382 setenv.main(True, exeName="salome withsession")
385 runSalome.runSalome()
389 def _setContext(self, args=None):
390 salome_context_set = os.getenv("SALOME_CONTEXT_SET")
391 if salome_context_set:
393 print("*** SALOME context has already been set.")
394 print("*** Enter 'exit' (only once!) to leave SALOME context.")
398 os.environ["SALOME_CONTEXT_SET"] = "yes"
400 print("*** SALOME context is now set.")
401 print("*** Enter 'exit' (only once!) to leave SALOME context.")
404 if sys.platform == 'win32':
408 proc = subprocess.Popen(cmd, shell=False, close_fds=True)
410 return proc.returncode
413 def _runSession(self, args=None):
416 sys.argv = ['runSession'] + args
418 params, args = runSession.configureSession(args, exe="salome shell")
420 sys.argv = ['runSession'] + args
424 return runSession.runSession(params, args)
427 def _runRemote(self, args=None):
430 # complete salome environment
431 sys.argv = ['runRemote']
436 return runRemote.runRemote(args)
439 def _runConsole(self, args=None):
442 # Initialize SALOME environment
443 sys.argv = ['runConsole']
448 return runConsole.connect(args)
451 def _kill(self, args=None):
456 print("Port number(s) not provided to command: salome kill <port(s)>")
463 if os.getenv("NSHOST") == "no_host":
464 os.unsetenv("NSHOST")
466 if sys.platform == "win32":
467 proc = subprocess.Popen([os.getenv("PYTHONBIN"), "-m", "killSalomeWithPort", str(port)])
469 proc = subprocess.Popen(["killSalomeWithPort.py", str(port)])
474 def _killAll(self, unused=None):
475 sys.argv = ['killAll']
478 if os.getenv("NSHOST") == "no_host":
479 os.unsetenv("NSHOST")
481 import PortManager # mandatory
483 ports = PortManager.getBusyPorts()['this']
487 if sys.platform == "win32":
488 proc = subprocess.Popen([os.getenv("PYTHONBIN"), "-m", "killSalomeWithPort", str(port)])
490 proc = subprocess.Popen(["killSalomeWithPort.py", str(port)])
493 # :TODO: should be declared obsolete
494 from killSalome import killAllPorts
497 from addToKillList import killList
502 def _runTests(self, args=None):
505 sys.argv = ['runTests']
510 return runTests.runTests(args, exe="salome test")
513 def _showSoftwareVersions(self, softwares=None):
514 config = configparser.SafeConfigParser()
515 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
516 filename = os.path.join(absoluteAppliPath, "sha1_collections.txt")
519 with open(filename) as f:
522 software, version, sha1 = line.split()
523 versions[software.upper()] = version
524 if len(software) > max_len:
525 max_len = len(software)
531 for soft in softwares:
532 if soft.upper() in versions:
533 print(soft.upper().rjust(max_len), versions[soft.upper()])
536 od = collections.OrderedDict(sorted(versions.items()))
537 for name, version in od.items():
538 print(name.rjust(max_len), versions[name])
541 def _showInfo(self, args=None):
545 usage = "Usage: salome info [options]"
547 Display some information about SALOME.\n
548 Available options are:
549 -p,--ports Show the list of busy ports (running SALOME instances).
550 -s,--softwares [software(s)] Show the list and versions of SALOME softwares.
551 Software names must be separated by blank characters.
552 If no software is given, show version of all softwares.
553 -v,--version Show running SALOME version.
554 -h,--help Show this message.
559 if "-h" in args or "--help" in args:
560 print(usage + epilog)
563 if "-p" in args or "--ports" in args:
565 ports = PortManager.getBusyPorts()
566 this_ports = ports['this']
567 other_ports = ports['other']
568 if this_ports or other_ports:
569 print("SALOME instances are running on the following ports:")
571 print(" This application:", this_ports)
573 print(" No SALOME instances of this application")
575 print(" Other applications:", other_ports)
577 print(" No SALOME instances of other applications")
579 print("No SALOME instances are running")
581 if "-s" in args or "--softwares" in args:
583 index = args.index("-s")
585 index = args.index("--softwares")
587 while indexEnd < len(args) and args[indexEnd][0] != "-":
588 indexEnd = indexEnd + 1
589 self._showSoftwareVersions(softwares=args[index+1:indexEnd])
591 if "-v" in args or "--version" in args:
592 print("Running with python", platform.python_version())
593 return self._sessionless(["--version"])
598 def _showDoc(self, args=None):
604 print("Module(s) not provided to command: salome doc <module(s)>")
607 appliPath = os.getenv("ABSOLUTE_APPLI_PATH")
609 raise SalomeContextException("Unable to find application path. Please check that the variable ABSOLUTE_APPLI_PATH is set.")
610 baseDir = os.path.join(appliPath, "share", "doc", "salome")
611 for module in modules:
612 docfile = os.path.join(baseDir, "gui", module.upper(), "index.html")
613 if not os.path.isfile(docfile):
614 docfile = os.path.join(baseDir, "tui", module.upper(), "index.html")
615 if not os.path.isfile(docfile):
616 docfile = os.path.join(baseDir, "dev", module.upper(), "index.html")
617 if os.path.isfile(docfile):
618 out, err = subprocess.Popen(["xdg-open", docfile]).communicate()
620 print("Online documentation is not accessible for module:", module)
622 def _usage(self, unused=None):
626 def _makeCoffee(self, unused=None):
629 print(" ___...(-------)-....___")
630 print(" .-\"\" ) ( \"\"-.")
631 print(" .-\'``\'|-._ ) _.-|")
632 print(" / .--.| `\"\"---...........---\"\"` |")
636 print(" `\\ `\\ | |")
637 print(" `\\ `| SALOME |")
638 print(" _/ /\\ 4 EVER /")
639 print(" (__/ \\ <3 /")
640 print(" _..---\"\"` \\ /`\"\"---.._")
641 print(" .-\' \\ / \'-.")
642 print(" : `-.__ __.-\' :")
643 print(" : ) \"\"---...---\"\" ( :")
644 print(" \'._ `\"--...___...--\"` _.\'")
645 print(" \\\"\"--..__ __..--\"\"/")
646 print(" \'._ \"\"\"----.....______.....----\"\"\" _.\'")
647 print(" `\"\"--..,,_____ _____,,..--\"\"`")
648 print(" `\"\"\"----\"\"\"`")
650 print(" SALOME is working for you; what else?")
654 def _getCar(self, unused=None):
655 print(" _____________")
656 print(" ..---:::::::-----------. ::::;;.")
657 print(" .\'\"\"\"\"\"\" ;; \\ \":.")
658 print(" .\'\' ; \\ \"\\__.")
659 print(" .\' ;; ; \\\\\";")
660 print(" .\' ; _____; \\\\/")
661 print(" .\' :; ;\" \\ ___:\'.")
662 print(" .\'--........................... : = ____:\" \\ \\")
663 print(" ..-\"\" \"\"\"\' o\"\"\" ; ; :")
664 print(" .--\"\" .----- ..----... _.- --. ..-\" ; ; ; ;")
665 print(" .\"\"_- \"--\"\"-----\'\"\" _-\" .-\"\" ; ; .-.")
666 print(" .\' .\' SALOME .\" .\" ; ; /. |")
667 print(" /-./\' 4 EVER <3 .\" / _.. ; ; ;;;|")
668 print(" : ;-.______ / _________==. /_ \\ ; ; ;;;;")
669 print(" ; / | \"\"\"\"\"\"\"\"\"\"\".---.\"\"\"\"\"\"\" : /\" \". |; ; _; ;;;")
670 print(" /\"-/ | / / / / ;|; ;-\" | ;\';")
671 print(":- : \"\"\"----______ / / ____. . .\"\'. ;; .-\"..T\" .")
672 print("\'. \" ___ \"\": \'\"\"\"\"\"\"\"\"\"\"\"\"\"\" . ; ; ;; ;.\" .\" \'--\"")
673 print(" \", __ \"\"\" \"\"---... :- - - - - - - - - \' \' ; ; ; ;;\" .\"")
674 print(" /. ; \"\"\"---___ ; ; ; ;|.\"\"")
675 print(" : \": \"\"\"----. .-------. ; ; ; ;:")
676 print(" \\ \'--__ \\ \\ \\ / | ; ;;")
677 print(" \'-.. \"\"\"\"---___ : .______..\\ __/..-\"\"| ; ; ;")
678 print(" \"\"--.. \"\"\"--\" m l s . \". . ;")
679 print(" \"\"------... ..--\"\" \" :")
680 print(" \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\" \\ /")
683 print(" Drive your simulation properly with SALOME!")
687 # Add the following two methods since logger is not pickable
688 # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
689 def __getstate__(self):
690 d = dict(self.__dict__)
691 if hasattr(self, '_logger'):
695 def __setstate__(self, d):
696 self.__dict__.update(d) # I *think* this is a safe way to do it
698 # Excluding self._logger from pickle operation imply using the following method to access logger
700 if not hasattr(self, '_logger'):
701 self._logger = logging.getLogger(__name__)
702 #self._logger.setLevel(logging.DEBUG)
703 #self._logger.setLevel(logging.WARNING)
704 self._logger.setLevel(logging.ERROR)
708 if __name__ == "__main__":
709 if len(sys.argv) == 3:
710 context = pickle.loads(sys.argv[1].encode('latin1'))
711 args = pickle.loads(sys.argv[2].encode('latin1'))
713 status = context._startSalome(args)