1 #! /usr/bin/env python3
2 # Copyright (C) 2013-2019 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 """Unset environment variable"""
187 def unsetVariable(self, name):
188 if os.environ.has_key(name):
192 """Append value to environment variable"""
193 def addToVariable(self, name, value, separator=os.pathsep):
197 value = os.path.expandvars(value) # expand environment variables
198 self.getLogger().debug("Add to %s: %s", name, value)
199 env = os.getenv(name, None)
201 os.environ[name] = value
203 os.environ[name] = value + separator + env
206 ###################################
207 # This begins the private section #
208 ###################################
210 def __parseArguments(self, args):
211 if len(args) == 0 or args[0].startswith("-"):
217 availableCommands = {
218 'start' : '_runAppli',
219 'context' : '_setContext',
220 'shell' : '_runSession',
221 'remote' : '_runRemote',
222 'connect' : '_runConsole',
224 'killall' : '_killAll',
225 'test' : '_runTests',
226 'info' : '_showInfo',
229 'coffee' : '_makeCoffee',
233 if command not in availableCommands:
237 return availableCommands[command], options
242 Args consist in a mandatory command followed by optional parameters.
243 See usage for details on commands.
245 def _startSalome(self, args):
249 from setenv import add_path
250 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
251 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome"))
252 add_path(path, "PYTHONPATH")
253 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome", "appliskel"))
254 add_path(path, "PYTHONPATH")
259 command, options = self.__parseArguments(args)
263 if args and args[0] in ["-h","--help","help"]:
266 # try to default to "start" command
267 command = "_runAppli"
270 res = getattr(self, command)(options) # run appropriate method
272 except SystemExit as ex:
274 self.getLogger().error("SystemExit %s in method %s.", ex.code, command)
276 except SalomeContextException as e:
277 self.getLogger().error(e)
280 self.getLogger().error("Unexpected error:")
282 traceback.print_exc()
286 def __setContextFromConfigFile(self, filename, reserved=None):
290 unsetVars, configVars, reservedDict = parseConfigFile(filename, reserved)
291 except SalomeContextException as e:
293 self.getLogger().error(msg)
297 for var in unsetVars:
298 self.unsetVariable(var)
301 for reserved in reservedDict:
302 a = [_f for _f in reservedDict[reserved] if _f] # remove empty elements
303 a = [ os.path.realpath(x) for x in a ]
304 reformattedVals = os.pathsep.join(a)
305 if reserved in ["INCLUDE", "LIBPATH"]:
306 self.addToVariable(reserved, reformattedVals, separator=' ')
308 self.addToVariable(reserved, reformattedVals)
311 for key,val in configVars:
312 self.setVariable(key, val, overwrite=True)
315 pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
316 pythonpath = [ os.path.realpath(x) for x in pythonpath ]
317 sys.path[:0] = pythonpath
320 def _runAppli(self, args=None):
323 # Initialize SALOME environment
324 sys.argv = ['runSalome'] + args
326 setenv.main(True, exeName="salome start")
329 runSalome.runSalome()
333 def _setContext(self, args=None):
334 salome_context_set = os.getenv("SALOME_CONTEXT_SET")
335 if salome_context_set:
337 print("*** SALOME context has already been set.")
338 print("*** Enter 'exit' (only once!) to leave SALOME context.")
342 os.environ["SALOME_CONTEXT_SET"] = "yes"
344 print("*** SALOME context is now set.")
345 print("*** Enter 'exit' (only once!) to leave SALOME context.")
348 if sys.platform == 'win32':
352 proc = subprocess.Popen(cmd, shell=False, close_fds=True)
354 return proc.returncode
357 def _runSession(self, args=None):
360 sys.argv = ['runSession'] + args
362 params, args = runSession.configureSession(args, exe="salome shell")
364 sys.argv = ['runSession'] + args
368 return runSession.runSession(params, args)
371 def _runRemote(self, args=None):
374 # complete salome environment
375 sys.argv = ['runRemote']
380 return runRemote.runRemote(args)
383 def _runConsole(self, args=None):
386 # Initialize SALOME environment
387 sys.argv = ['runConsole']
392 return runConsole.connect(args)
395 def _kill(self, args=None):
400 print("Port number(s) not provided to command: salome kill <port(s)>")
407 if os.getenv("NSHOST") == "no_host":
408 os.unsetenv("NSHOST")
410 proc = subprocess.Popen(["killSalomeWithPort.py", port])
416 def _killAll(self, unused=None):
417 sys.argv = ['killAll']
420 if os.getenv("NSHOST") == "no_host":
421 os.unsetenv("NSHOST")
423 import PortManager # mandatory
425 ports = PortManager.getBusyPorts()['this']
429 proc = subprocess.Popen(["killSalomeWithPort.py", str(port)])
432 # :TODO: should be declared obsolete
433 from killSalome import killAllPorts
439 def _runTests(self, args=None):
442 sys.argv = ['runTests']
447 return runTests.runTests(args, exe="salome test")
450 def _showSoftwareVersions(self, softwares=None):
451 config = configparser.SafeConfigParser()
452 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
453 filename = os.path.join(absoluteAppliPath, "sha1_collections.txt")
456 with open(filename) as f:
459 software, version, sha1 = line.split()
460 versions[software.upper()] = version
461 if len(software) > max_len:
462 max_len = len(software)
468 for soft in softwares:
469 if soft.upper() in versions:
470 print(soft.upper().rjust(max_len), versions[soft.upper()])
473 od = collections.OrderedDict(sorted(versions.items()))
474 for name, version in od.items():
475 print(name.rjust(max_len), versions[name])
478 def _showInfo(self, args=None):
482 usage = "Usage: salome info [options]"
484 Display some information about SALOME.\n
485 Available options are:
486 -p,--ports Show the list of busy ports (running SALOME instances).
487 -s,--softwares [software(s)] Show the list and versions of SALOME softwares.
488 Software names must be separated by blank characters.
489 If no software is given, show version of all softwares.
490 -v,--version Show running SALOME version.
491 -h,--help Show this message.
496 if "-h" in args or "--help" in args:
497 print(usage + epilog)
500 if "-p" in args or "--ports" in args:
502 ports = PortManager.getBusyPorts()
503 this_ports = ports['this']
504 other_ports = ports['other']
505 if this_ports or other_ports:
506 print("SALOME instances are running on the following ports:")
508 print(" This application:", this_ports)
510 print(" No SALOME instances of this application")
512 print(" Other applications:", other_ports)
514 print(" No SALOME instances of other applications")
516 print("No SALOME instances are running")
518 if "-s" in args or "--softwares" in args:
520 index = args.index("-s")
522 index = args.index("--softwares")
524 while indexEnd < len(args) and args[indexEnd][0] != "-":
525 indexEnd = indexEnd + 1
526 self._showSoftwareVersions(softwares=args[index+1:indexEnd])
528 if "-v" in args or "--version" in args:
529 print("Running with python", platform.python_version())
530 return self._runAppli(["--version"])
535 def _showDoc(self, args=None):
541 print("Module(s) not provided to command: salome doc <module(s)>")
544 appliPath = os.getenv("ABSOLUTE_APPLI_PATH")
546 raise SalomeContextException("Unable to find application path. Please check that the variable ABSOLUTE_APPLI_PATH is set.")
547 baseDir = os.path.join(appliPath, "share", "doc", "salome")
548 for module in modules:
549 docfile = os.path.join(baseDir, "gui", module.upper(), "index.html")
550 if not os.path.isfile(docfile):
551 docfile = os.path.join(baseDir, "tui", module.upper(), "index.html")
552 if not os.path.isfile(docfile):
553 docfile = os.path.join(baseDir, "dev", module.upper(), "index.html")
554 if os.path.isfile(docfile):
555 out, err = subprocess.Popen(["xdg-open", docfile]).communicate()
557 print("Online documentation is not accessible for module:", module)
559 def _usage(self, unused=None):
563 def _makeCoffee(self, unused=None):
566 print(" ___...(-------)-....___")
567 print(" .-\"\" ) ( \"\"-.")
568 print(" .-\'``\'|-._ ) _.-|")
569 print(" / .--.| `\"\"---...........---\"\"` |")
573 print(" `\\ `\\ | |")
574 print(" `\\ `| SALOME |")
575 print(" _/ /\\ 4 EVER /")
576 print(" (__/ \\ <3 /")
577 print(" _..---\"\"` \\ /`\"\"---.._")
578 print(" .-\' \\ / \'-.")
579 print(" : `-.__ __.-\' :")
580 print(" : ) \"\"---...---\"\" ( :")
581 print(" \'._ `\"--...___...--\"` _.\'")
582 print(" \\\"\"--..__ __..--\"\"/")
583 print(" \'._ \"\"\"----.....______.....----\"\"\" _.\'")
584 print(" `\"\"--..,,_____ _____,,..--\"\"`")
585 print(" `\"\"\"----\"\"\"`")
587 print(" SALOME is working for you; what else?")
591 def _getCar(self, unused=None):
592 print(" _____________")
593 print(" ..---:::::::-----------. ::::;;.")
594 print(" .\'\"\"\"\"\"\" ;; \\ \":.")
595 print(" .\'\' ; \\ \"\\__.")
596 print(" .\' ;; ; \\\\\";")
597 print(" .\' ; _____; \\\\/")
598 print(" .\' :; ;\" \\ ___:\'.")
599 print(" .\'--........................... : = ____:\" \\ \\")
600 print(" ..-\"\" \"\"\"\' o\"\"\" ; ; :")
601 print(" .--\"\" .----- ..----... _.- --. ..-\" ; ; ; ;")
602 print(" .\"\"_- \"--\"\"-----\'\"\" _-\" .-\"\" ; ; .-.")
603 print(" .\' .\' SALOME .\" .\" ; ; /. |")
604 print(" /-./\' 4 EVER <3 .\" / _.. ; ; ;;;|")
605 print(" : ;-.______ / _________==. /_ \\ ; ; ;;;;")
606 print(" ; / | \"\"\"\"\"\"\"\"\"\"\".---.\"\"\"\"\"\"\" : /\" \". |; ; _; ;;;")
607 print(" /\"-/ | / / / / ;|; ;-\" | ;\';")
608 print(":- : \"\"\"----______ / / ____. . .\"\'. ;; .-\"..T\" .")
609 print("\'. \" ___ \"\": \'\"\"\"\"\"\"\"\"\"\"\"\"\"\" . ; ; ;; ;.\" .\" \'--\"")
610 print(" \", __ \"\"\" \"\"---... :- - - - - - - - - \' \' ; ; ; ;;\" .\"")
611 print(" /. ; \"\"\"---___ ; ; ; ;|.\"\"")
612 print(" : \": \"\"\"----. .-------. ; ; ; ;:")
613 print(" \\ \'--__ \\ \\ \\ / | ; ;;")
614 print(" \'-.. \"\"\"\"---___ : .______..\\ __/..-\"\"| ; ; ;")
615 print(" \"\"--.. \"\"\"--\" m l s . \". . ;")
616 print(" \"\"------... ..--\"\" \" :")
617 print(" \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\" \\ /")
620 print(" Drive your simulation properly with SALOME!")
624 # Add the following two methods since logger is not pickable
625 # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
626 def __getstate__(self):
627 d = dict(self.__dict__)
628 if hasattr(self, '_logger'):
632 def __setstate__(self, d):
633 self.__dict__.update(d) # I *think* this is a safe way to do it
635 # Excluding self._logger from pickle operation imply using the following method to access logger
637 if not hasattr(self, '_logger'):
638 self._logger = logging.getLogger(__name__)
639 #self._logger.setLevel(logging.DEBUG)
640 #self._logger.setLevel(logging.WARNING)
641 self._logger.setLevel(logging.ERROR)
645 if __name__ == "__main__":
646 if len(sys.argv) == 3:
647 context = pickle.loads(sys.argv[1].encode('latin1'))
648 args = pickle.loads(sys.argv[2].encode('latin1'))
650 status = context._startSalome(args)