1 #! /usr/bin/env python3
2 # Copyright (C) 2013-2020 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.
43 context Initialize SALOME context. Current environment is extended.
44 shell Initialize SALOME context, attached to the last created SALOME
45 instance if any, and executes scripts passed as command arguments.
46 User works in a Shell terminal. SALOME environment is set but
47 application is not started.
48 connect Connect a Python console to the active SALOME instance.
49 remote run command in SALOME environment from remote call, ssh or rsh.
50 kill <port(s)> Terminate SALOME instances running on given ports for current user.
51 Port numbers must be separated by blank characters.
52 killall Terminate *all* SALOME running instances for current user.
53 Do not start a new one.
54 test Run SALOME tests.
55 info Display some information about SALOME.
56 doc <module(s)> Show online module documentation (if available).
57 Module names must be separated by blank characters.
58 help Show this message.
60 If no command is given, default is start.
64 Use salome <command> --help to show help on command. Available for the
65 following commands: start, shell, connect, test, info.
67 --config=<file,folder,...>
68 ==========================
69 Initialize SALOME context from a list of context files and/or a list
70 of folders containing context files. The list is comma-separated, without
78 The SalomeContext class in an API to configure SALOME context then
79 start SALOME using a single python command.
84 Initialize context from a list of configuration files
85 identified by their names.
86 These files should be in appropriate .cfg format.
88 def __init__(self, configFileNames=0):
89 self.getLogger().setLevel(logging.INFO)
90 #it could be None explicitly (if user use multiples setVariable...for standalone)
91 if configFileNames is None:
93 configFileNames = configFileNames or []
94 if len(configFileNames) == 0:
95 raise SalomeContextException("No configuration files given")
97 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']
98 for filename in configFileNames:
99 basename, extension = os.path.splitext(filename)
100 if extension == ".cfg":
101 self.__setContextFromConfigFile(filename, reserved)
103 self.getLogger().error("Unrecognized extension for configuration file: %s", filename)
106 def __loadEnvModules(self, env_modules):
107 modulecmd = os.getenv('LMOD_CMD')
109 raise SalomeContextException("Module environment not present")
112 out, err = subprocess.Popen([modulecmd, "python", "load"] + env_modules, stdout=subprocess.PIPE).communicate()
113 exec(out) # define specific environment variables
115 raise SalomeContextException("Failed to load env modules: %s ..." % ' '.join(env_modules))
119 def runSalome(self, args):
121 # Run this module as a script, in order to use appropriate Python interpreter
122 # according to current path (initialized from context files).
123 env_modules_option = "--with-env-modules="
124 env_modules_l = [x for x in args if x.startswith(env_modules_option)]
126 env_modules = env_modules_l[-1][len(env_modules_option):].split(',')
127 self.__loadEnvModules(env_modules)
128 args = [x for x in args if not x.startswith(env_modules_option)]
130 env_modules = os.getenv("SALOME_ENV_MODULES", None)
132 self.__loadEnvModules(env_modules.split(','))
134 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
135 env_copy = os.environ.copy()
136 selfBytes= pickle.dumps(self, protocol=0)
137 argsBytes= pickle.dumps(args, protocol=0)
138 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)
139 out, err = proc.communicate()
140 return out, err, proc.returncode
143 """Append value to PATH environment variable"""
144 def addToPath(self, value):
145 self.addToVariable('PATH', value)
148 """Append value to LD_LIBRARY_PATH environment variable"""
149 def addToLdLibraryPath(self, value):
150 if sys.platform == 'win32':
151 self.addToVariable('PATH', value)
152 elif sys.platform == 'darwin':
153 if "LAPACK" in value:
154 self.addToVariable('DYLD_FALLBACK_LIBRARY_PATH', value)
156 self.addToVariable('DYLD_LIBRARY_PATH', value)
158 self.addToVariable('LD_LIBRARY_PATH', value)
161 """Append value to DYLD_LIBRARY_PATH environment variable"""
162 def addToDyldLibraryPath(self, value):
163 self.addToVariable('DYLD_LIBRARY_PATH', value)
166 """Append value to PYTHONPATH environment variable"""
167 def addToPythonPath(self, value):
168 self.addToVariable('PYTHONPATH', value)
171 """Set environment variable to value"""
172 def setVariable(self, name, value, overwrite=False):
173 env = os.getenv(name, '')
174 if env and not overwrite:
175 self.getLogger().error("Environment variable already existing (and not overwritten): %s=%s", name, value)
179 self.getLogger().debug("Overwriting environment variable: %s=%s", name, value)
181 value = os.path.expandvars(value) # expand environment variables
182 self.getLogger().debug("Set environment variable: %s=%s", name, value)
183 os.environ[name] = value
186 def setDefaultValue(self, name, value):
187 """ Set environment variable only if it is undefined."""
188 env = os.getenv(name, '')
190 value = os.path.expandvars(value) # expand environment variables
191 self.getLogger().debug("Set environment variable: %s=%s", name, value)
192 os.environ[name] = value
194 """Unset environment variable"""
195 def unsetVariable(self, name):
196 if os.environ.has_key(name):
200 """Append value to environment variable"""
201 def addToVariable(self, name, value, separator=os.pathsep):
205 value = os.path.expandvars(value) # expand environment variables
206 self.getLogger().debug("Add to %s: %s", name, value)
207 env = os.getenv(name, None)
209 os.environ[name] = value
211 os.environ[name] = value + separator + env
214 ###################################
215 # This begins the private section #
216 ###################################
218 def __parseArguments(self, args):
219 if len(args) == 0 or args[0].startswith("-"):
225 availableCommands = {
226 'start' : '_runAppli',
227 'context' : '_setContext',
228 'shell' : '_runSession',
229 'remote' : '_runRemote',
230 'connect' : '_runConsole',
232 'killall' : '_killAll',
233 'test' : '_runTests',
234 'info' : '_showInfo',
237 'coffee' : '_makeCoffee',
241 if command not in availableCommands:
245 return availableCommands[command], options
250 Args consist in a mandatory command followed by optional parameters.
251 See usage for details on commands.
253 def _startSalome(self, args):
257 from setenv import add_path
258 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
259 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome"))
260 add_path(path, "PYTHONPATH")
261 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome", "appliskel"))
262 add_path(path, "PYTHONPATH")
267 command, options = self.__parseArguments(args)
271 if args and args[0] in ["-h","--help","help"]:
274 # try to default to "start" command
275 command = "_runAppli"
278 res = getattr(self, command)(options) # run appropriate method
280 except SystemExit as ex:
282 self.getLogger().error("SystemExit %s in method %s.", ex.code, command)
284 except SalomeContextException as e:
285 self.getLogger().error(e)
288 self.getLogger().error("Unexpected error:")
290 traceback.print_exc()
294 def __setContextFromConfigFile(self, filename, reserved=None):
298 configInfo = parseConfigFile(filename, reserved)
299 unsetVars = configInfo.unsetVariables
300 configVars = configInfo.outputVariables
301 reservedDict = configInfo.reservedValues
302 defaultValues = configInfo.defaultValues
303 except SalomeContextException as e:
305 self.getLogger().error(msg)
309 for var in unsetVars:
310 self.unsetVariable(var)
313 for reserved in reservedDict:
314 a = [_f for _f in reservedDict[reserved] if _f] # remove empty elements
315 a = [ os.path.realpath(x) for x in a ]
316 reformattedVals = os.pathsep.join(a)
317 if reserved in ["INCLUDE", "LIBPATH"]:
318 self.addToVariable(reserved, reformattedVals, separator=' ')
320 self.addToVariable(reserved, reformattedVals)
323 for key,val in configVars:
324 self.setVariable(key, val, overwrite=True)
327 for key,val in defaultValues:
328 self.setDefaultValue(key, val)
331 pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
332 pythonpath = [ os.path.realpath(x) for x in pythonpath ]
333 sys.path[:0] = pythonpath
336 def _runAppli(self, args=None):
339 # Initialize SALOME environment
340 sys.argv = ['runSalome'] + args
342 setenv.main(True, exeName="salome start")
345 runSalome.runSalome()
349 def _setContext(self, args=None):
350 salome_context_set = os.getenv("SALOME_CONTEXT_SET")
351 if salome_context_set:
353 print("*** SALOME context has already been set.")
354 print("*** Enter 'exit' (only once!) to leave SALOME context.")
358 os.environ["SALOME_CONTEXT_SET"] = "yes"
360 print("*** SALOME context is now set.")
361 print("*** Enter 'exit' (only once!) to leave SALOME context.")
364 if sys.platform == 'win32':
368 proc = subprocess.Popen(cmd, shell=False, close_fds=True)
370 return proc.returncode
373 def _runSession(self, args=None):
376 sys.argv = ['runSession'] + args
378 params, args = runSession.configureSession(args, exe="salome shell")
380 sys.argv = ['runSession'] + args
384 return runSession.runSession(params, args)
387 def _runRemote(self, args=None):
390 # complete salome environment
391 sys.argv = ['runRemote']
396 return runRemote.runRemote(args)
399 def _runConsole(self, args=None):
402 # Initialize SALOME environment
403 sys.argv = ['runConsole']
408 return runConsole.connect(args)
411 def _kill(self, args=None):
416 print("Port number(s) not provided to command: salome kill <port(s)>")
423 if os.getenv("NSHOST") == "no_host":
424 os.unsetenv("NSHOST")
426 proc = subprocess.Popen(["killSalomeWithPort.py", port])
432 def _killAll(self, unused=None):
433 sys.argv = ['killAll']
436 if os.getenv("NSHOST") == "no_host":
437 os.unsetenv("NSHOST")
439 import PortManager # mandatory
441 ports = PortManager.getBusyPorts()['this']
445 proc = subprocess.Popen(["killSalomeWithPort.py", str(port)])
448 # :TODO: should be declared obsolete
449 from killSalome import killAllPorts
455 def _runTests(self, args=None):
458 sys.argv = ['runTests']
463 return runTests.runTests(args, exe="salome test")
466 def _showSoftwareVersions(self, softwares=None):
467 config = configparser.SafeConfigParser()
468 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
469 filename = os.path.join(absoluteAppliPath, "sha1_collections.txt")
472 with open(filename) as f:
475 software, version, sha1 = line.split()
476 versions[software.upper()] = version
477 if len(software) > max_len:
478 max_len = len(software)
484 for soft in softwares:
485 if soft.upper() in versions:
486 print(soft.upper().rjust(max_len), versions[soft.upper()])
489 od = collections.OrderedDict(sorted(versions.items()))
490 for name, version in od.items():
491 print(name.rjust(max_len), versions[name])
494 def _showInfo(self, args=None):
498 usage = "Usage: salome info [options]"
500 Display some information about SALOME.\n
501 Available options are:
502 -p,--ports Show the list of busy ports (running SALOME instances).
503 -s,--softwares [software(s)] Show the list and versions of SALOME softwares.
504 Software names must be separated by blank characters.
505 If no software is given, show version of all softwares.
506 -v,--version Show running SALOME version.
507 -h,--help Show this message.
512 if "-h" in args or "--help" in args:
513 print(usage + epilog)
516 if "-p" in args or "--ports" in args:
518 ports = PortManager.getBusyPorts()
519 this_ports = ports['this']
520 other_ports = ports['other']
521 if this_ports or other_ports:
522 print("SALOME instances are running on the following ports:")
524 print(" This application:", this_ports)
526 print(" No SALOME instances of this application")
528 print(" Other applications:", other_ports)
530 print(" No SALOME instances of other applications")
532 print("No SALOME instances are running")
534 if "-s" in args or "--softwares" in args:
536 index = args.index("-s")
538 index = args.index("--softwares")
540 while indexEnd < len(args) and args[indexEnd][0] != "-":
541 indexEnd = indexEnd + 1
542 self._showSoftwareVersions(softwares=args[index+1:indexEnd])
544 if "-v" in args or "--version" in args:
545 print("Running with python", platform.python_version())
546 return self._runAppli(["--version"])
551 def _showDoc(self, args=None):
557 print("Module(s) not provided to command: salome doc <module(s)>")
560 appliPath = os.getenv("ABSOLUTE_APPLI_PATH")
562 raise SalomeContextException("Unable to find application path. Please check that the variable ABSOLUTE_APPLI_PATH is set.")
563 baseDir = os.path.join(appliPath, "share", "doc", "salome")
564 for module in modules:
565 docfile = os.path.join(baseDir, "gui", module.upper(), "index.html")
566 if not os.path.isfile(docfile):
567 docfile = os.path.join(baseDir, "tui", module.upper(), "index.html")
568 if not os.path.isfile(docfile):
569 docfile = os.path.join(baseDir, "dev", module.upper(), "index.html")
570 if os.path.isfile(docfile):
571 out, err = subprocess.Popen(["xdg-open", docfile]).communicate()
573 print("Online documentation is not accessible for module:", module)
575 def _usage(self, unused=None):
579 def _makeCoffee(self, unused=None):
582 print(" ___...(-------)-....___")
583 print(" .-\"\" ) ( \"\"-.")
584 print(" .-\'``\'|-._ ) _.-|")
585 print(" / .--.| `\"\"---...........---\"\"` |")
589 print(" `\\ `\\ | |")
590 print(" `\\ `| SALOME |")
591 print(" _/ /\\ 4 EVER /")
592 print(" (__/ \\ <3 /")
593 print(" _..---\"\"` \\ /`\"\"---.._")
594 print(" .-\' \\ / \'-.")
595 print(" : `-.__ __.-\' :")
596 print(" : ) \"\"---...---\"\" ( :")
597 print(" \'._ `\"--...___...--\"` _.\'")
598 print(" \\\"\"--..__ __..--\"\"/")
599 print(" \'._ \"\"\"----.....______.....----\"\"\" _.\'")
600 print(" `\"\"--..,,_____ _____,,..--\"\"`")
601 print(" `\"\"\"----\"\"\"`")
603 print(" SALOME is working for you; what else?")
607 def _getCar(self, unused=None):
608 print(" _____________")
609 print(" ..---:::::::-----------. ::::;;.")
610 print(" .\'\"\"\"\"\"\" ;; \\ \":.")
611 print(" .\'\' ; \\ \"\\__.")
612 print(" .\' ;; ; \\\\\";")
613 print(" .\' ; _____; \\\\/")
614 print(" .\' :; ;\" \\ ___:\'.")
615 print(" .\'--........................... : = ____:\" \\ \\")
616 print(" ..-\"\" \"\"\"\' o\"\"\" ; ; :")
617 print(" .--\"\" .----- ..----... _.- --. ..-\" ; ; ; ;")
618 print(" .\"\"_- \"--\"\"-----\'\"\" _-\" .-\"\" ; ; .-.")
619 print(" .\' .\' SALOME .\" .\" ; ; /. |")
620 print(" /-./\' 4 EVER <3 .\" / _.. ; ; ;;;|")
621 print(" : ;-.______ / _________==. /_ \\ ; ; ;;;;")
622 print(" ; / | \"\"\"\"\"\"\"\"\"\"\".---.\"\"\"\"\"\"\" : /\" \". |; ; _; ;;;")
623 print(" /\"-/ | / / / / ;|; ;-\" | ;\';")
624 print(":- : \"\"\"----______ / / ____. . .\"\'. ;; .-\"..T\" .")
625 print("\'. \" ___ \"\": \'\"\"\"\"\"\"\"\"\"\"\"\"\"\" . ; ; ;; ;.\" .\" \'--\"")
626 print(" \", __ \"\"\" \"\"---... :- - - - - - - - - \' \' ; ; ; ;;\" .\"")
627 print(" /. ; \"\"\"---___ ; ; ; ;|.\"\"")
628 print(" : \": \"\"\"----. .-------. ; ; ; ;:")
629 print(" \\ \'--__ \\ \\ \\ / | ; ;;")
630 print(" \'-.. \"\"\"\"---___ : .______..\\ __/..-\"\"| ; ; ;")
631 print(" \"\"--.. \"\"\"--\" m l s . \". . ;")
632 print(" \"\"------... ..--\"\" \" :")
633 print(" \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\" \\ /")
636 print(" Drive your simulation properly with SALOME!")
640 # Add the following two methods since logger is not pickable
641 # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
642 def __getstate__(self):
643 d = dict(self.__dict__)
644 if hasattr(self, '_logger'):
648 def __setstate__(self, d):
649 self.__dict__.update(d) # I *think* this is a safe way to do it
651 # Excluding self._logger from pickle operation imply using the following method to access logger
653 if not hasattr(self, '_logger'):
654 self._logger = logging.getLogger(__name__)
655 #self._logger.setLevel(logging.DEBUG)
656 #self._logger.setLevel(logging.WARNING)
657 self._logger.setLevel(logging.ERROR)
661 if __name__ == "__main__":
662 if len(sys.argv) == 3:
663 context = pickle.loads(sys.argv[1].encode('latin1'))
664 args = pickle.loads(sys.argv[2].encode('latin1'))
666 status = context._startSalome(args)