1 #! /usr/bin/env python3
2 # Copyright (C) 2013-2021 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 'sessionless' : '_sessionless',
228 'context' : '_setContext',
229 'shell' : '_runSession',
230 'remote' : '_runRemote',
231 'connect' : '_runConsole',
233 'killall' : '_killAll',
234 'test' : '_runTests',
235 'info' : '_showInfo',
238 'coffee' : '_makeCoffee',
242 if command not in availableCommands:
246 return availableCommands[command], options
251 Args consist in a mandatory command followed by optional parameters.
252 See usage for details on commands.
254 def _startSalome(self, args):
258 from setenv import add_path
259 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
260 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome"))
261 add_path(path, "PYTHONPATH")
262 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome", "appliskel"))
263 add_path(path, "PYTHONPATH")
268 command, options = self.__parseArguments(args)
272 if args and args[0] in ["-h","--help","help"]:
275 # try to default to "start" command
276 command = "_runAppli"
279 res = getattr(self, command)(options) # run appropriate method
281 except SystemExit as ex:
283 self.getLogger().error("SystemExit %s in method %s.", ex.code, command)
285 except SalomeContextException as e:
286 self.getLogger().error(e)
289 self.getLogger().error("Unexpected error:")
291 traceback.print_exc()
295 def __setContextFromConfigFile(self, filename, reserved=None):
296 mesa_root_dir = "MESA_ROOT_DIR"
300 configInfo = parseConfigFile(filename, reserved)
301 unsetVars = configInfo.unsetVariables
302 configVars = configInfo.outputVariables
303 reservedDict = configInfo.reservedValues
304 defaultValues = configInfo.defaultValues
305 except SalomeContextException as e:
307 self.getLogger().error(msg)
311 for var in unsetVars:
312 self.unsetVariable(var)
315 if "MESA_GL_VERSION_OVERRIDE" in os.environ:
316 configVarsDict = {k:v for (k,v) in configVars}
317 if mesa_root_dir in configVarsDict:
318 path_to_mesa_lib = os.path.join(configVarsDict[mesa_root_dir],"lib")
319 if os.name == "posix":
320 self.addToVariable("LD_LIBRARY_PATH",path_to_mesa_lib)
322 self.addToVariable("PATH",path_to_mesa_lib)
325 for reserved in reservedDict:
326 a = [_f for _f in reservedDict[reserved] if _f] # remove empty elements
327 a = [ os.path.realpath(x) for x in a ]
328 reformattedVals = os.pathsep.join(a)
329 if reserved in ["INCLUDE", "LIBPATH"]:
330 self.addToVariable(reserved, reformattedVals, separator=' ')
332 self.addToVariable(reserved, reformattedVals)
336 for key,val in configVars:
337 self.setVariable(key, val, overwrite=True)
340 for key,val in defaultValues:
341 self.setDefaultValue(key, val)
344 pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
345 pythonpath = [ os.path.realpath(x) for x in pythonpath ]
346 sys.path[:0] = pythonpath
349 def _runAppli(self, args=None):
352 # Initialize SALOME environment
353 sys.argv = ['runSalome'] + args
355 setenv.main(True, exeName="salome start")
358 runSalome.runSalome()
362 def _sessionless(self, args=None):
365 sys.argv = ['runSalome'] + args
366 import runSalomeNoServer
367 runSalomeNoServer.main()
370 def _setContext(self, args=None):
371 salome_context_set = os.getenv("SALOME_CONTEXT_SET")
372 if salome_context_set:
374 print("*** SALOME context has already been set.")
375 print("*** Enter 'exit' (only once!) to leave SALOME context.")
379 os.environ["SALOME_CONTEXT_SET"] = "yes"
381 print("*** SALOME context is now set.")
382 print("*** Enter 'exit' (only once!) to leave SALOME context.")
385 if sys.platform == 'win32':
389 proc = subprocess.Popen(cmd, shell=False, close_fds=True)
391 return proc.returncode
394 def _runSession(self, args=None):
397 sys.argv = ['runSession'] + args
399 params, args = runSession.configureSession(args, exe="salome shell")
401 sys.argv = ['runSession'] + args
405 return runSession.runSession(params, args)
408 def _runRemote(self, args=None):
411 # complete salome environment
412 sys.argv = ['runRemote']
417 return runRemote.runRemote(args)
420 def _runConsole(self, args=None):
423 # Initialize SALOME environment
424 sys.argv = ['runConsole']
429 return runConsole.connect(args)
432 def _kill(self, args=None):
437 print("Port number(s) not provided to command: salome kill <port(s)>")
444 if os.getenv("NSHOST") == "no_host":
445 os.unsetenv("NSHOST")
447 if sys.platform == "win32":
448 proc = subprocess.Popen([os.getenv("PYTHONBIN"), "-m", "killSalomeWithPort", str(port)])
450 proc = subprocess.Popen(["killSalomeWithPort.py", str(port)])
456 def _killAll(self, unused=None):
457 sys.argv = ['killAll']
460 if os.getenv("NSHOST") == "no_host":
461 os.unsetenv("NSHOST")
463 import PortManager # mandatory
465 ports = PortManager.getBusyPorts()['this']
469 if sys.platform == "win32":
470 proc = subprocess.Popen([os.getenv("PYTHONBIN"), "-m", "killSalomeWithPort", str(port)])
472 proc = subprocess.Popen(["killSalomeWithPort.py", str(port)])
475 # :TODO: should be declared obsolete
476 from killSalome import killAllPorts
482 def _runTests(self, args=None):
485 sys.argv = ['runTests']
490 return runTests.runTests(args, exe="salome test")
493 def _showSoftwareVersions(self, softwares=None):
494 config = configparser.SafeConfigParser()
495 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
496 filename = os.path.join(absoluteAppliPath, "sha1_collections.txt")
499 with open(filename) as f:
502 software, version, sha1 = line.split()
503 versions[software.upper()] = version
504 if len(software) > max_len:
505 max_len = len(software)
511 for soft in softwares:
512 if soft.upper() in versions:
513 print(soft.upper().rjust(max_len), versions[soft.upper()])
516 od = collections.OrderedDict(sorted(versions.items()))
517 for name, version in od.items():
518 print(name.rjust(max_len), versions[name])
521 def _showInfo(self, args=None):
525 usage = "Usage: salome info [options]"
527 Display some information about SALOME.\n
528 Available options are:
529 -p,--ports Show the list of busy ports (running SALOME instances).
530 -s,--softwares [software(s)] Show the list and versions of SALOME softwares.
531 Software names must be separated by blank characters.
532 If no software is given, show version of all softwares.
533 -v,--version Show running SALOME version.
534 -h,--help Show this message.
539 if "-h" in args or "--help" in args:
540 print(usage + epilog)
543 if "-p" in args or "--ports" in args:
545 ports = PortManager.getBusyPorts()
546 this_ports = ports['this']
547 other_ports = ports['other']
548 if this_ports or other_ports:
549 print("SALOME instances are running on the following ports:")
551 print(" This application:", this_ports)
553 print(" No SALOME instances of this application")
555 print(" Other applications:", other_ports)
557 print(" No SALOME instances of other applications")
559 print("No SALOME instances are running")
561 if "-s" in args or "--softwares" in args:
563 index = args.index("-s")
565 index = args.index("--softwares")
567 while indexEnd < len(args) and args[indexEnd][0] != "-":
568 indexEnd = indexEnd + 1
569 self._showSoftwareVersions(softwares=args[index+1:indexEnd])
571 if "-v" in args or "--version" in args:
572 print("Running with python", platform.python_version())
573 return self._runAppli(["--version"])
578 def _showDoc(self, args=None):
584 print("Module(s) not provided to command: salome doc <module(s)>")
587 appliPath = os.getenv("ABSOLUTE_APPLI_PATH")
589 raise SalomeContextException("Unable to find application path. Please check that the variable ABSOLUTE_APPLI_PATH is set.")
590 baseDir = os.path.join(appliPath, "share", "doc", "salome")
591 for module in modules:
592 docfile = os.path.join(baseDir, "gui", module.upper(), "index.html")
593 if not os.path.isfile(docfile):
594 docfile = os.path.join(baseDir, "tui", module.upper(), "index.html")
595 if not os.path.isfile(docfile):
596 docfile = os.path.join(baseDir, "dev", module.upper(), "index.html")
597 if os.path.isfile(docfile):
598 out, err = subprocess.Popen(["xdg-open", docfile]).communicate()
600 print("Online documentation is not accessible for module:", module)
602 def _usage(self, unused=None):
606 def _makeCoffee(self, unused=None):
609 print(" ___...(-------)-....___")
610 print(" .-\"\" ) ( \"\"-.")
611 print(" .-\'``\'|-._ ) _.-|")
612 print(" / .--.| `\"\"---...........---\"\"` |")
616 print(" `\\ `\\ | |")
617 print(" `\\ `| SALOME |")
618 print(" _/ /\\ 4 EVER /")
619 print(" (__/ \\ <3 /")
620 print(" _..---\"\"` \\ /`\"\"---.._")
621 print(" .-\' \\ / \'-.")
622 print(" : `-.__ __.-\' :")
623 print(" : ) \"\"---...---\"\" ( :")
624 print(" \'._ `\"--...___...--\"` _.\'")
625 print(" \\\"\"--..__ __..--\"\"/")
626 print(" \'._ \"\"\"----.....______.....----\"\"\" _.\'")
627 print(" `\"\"--..,,_____ _____,,..--\"\"`")
628 print(" `\"\"\"----\"\"\"`")
630 print(" SALOME is working for you; what else?")
634 def _getCar(self, unused=None):
635 print(" _____________")
636 print(" ..---:::::::-----------. ::::;;.")
637 print(" .\'\"\"\"\"\"\" ;; \\ \":.")
638 print(" .\'\' ; \\ \"\\__.")
639 print(" .\' ;; ; \\\\\";")
640 print(" .\' ; _____; \\\\/")
641 print(" .\' :; ;\" \\ ___:\'.")
642 print(" .\'--........................... : = ____:\" \\ \\")
643 print(" ..-\"\" \"\"\"\' o\"\"\" ; ; :")
644 print(" .--\"\" .----- ..----... _.- --. ..-\" ; ; ; ;")
645 print(" .\"\"_- \"--\"\"-----\'\"\" _-\" .-\"\" ; ; .-.")
646 print(" .\' .\' SALOME .\" .\" ; ; /. |")
647 print(" /-./\' 4 EVER <3 .\" / _.. ; ; ;;;|")
648 print(" : ;-.______ / _________==. /_ \\ ; ; ;;;;")
649 print(" ; / | \"\"\"\"\"\"\"\"\"\"\".---.\"\"\"\"\"\"\" : /\" \". |; ; _; ;;;")
650 print(" /\"-/ | / / / / ;|; ;-\" | ;\';")
651 print(":- : \"\"\"----______ / / ____. . .\"\'. ;; .-\"..T\" .")
652 print("\'. \" ___ \"\": \'\"\"\"\"\"\"\"\"\"\"\"\"\"\" . ; ; ;; ;.\" .\" \'--\"")
653 print(" \", __ \"\"\" \"\"---... :- - - - - - - - - \' \' ; ; ; ;;\" .\"")
654 print(" /. ; \"\"\"---___ ; ; ; ;|.\"\"")
655 print(" : \": \"\"\"----. .-------. ; ; ; ;:")
656 print(" \\ \'--__ \\ \\ \\ / | ; ;;")
657 print(" \'-.. \"\"\"\"---___ : .______..\\ __/..-\"\"| ; ; ;")
658 print(" \"\"--.. \"\"\"--\" m l s . \". . ;")
659 print(" \"\"------... ..--\"\" \" :")
660 print(" \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\" \\ /")
663 print(" Drive your simulation properly with SALOME!")
667 # Add the following two methods since logger is not pickable
668 # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
669 def __getstate__(self):
670 d = dict(self.__dict__)
671 if hasattr(self, '_logger'):
675 def __setstate__(self, d):
676 self.__dict__.update(d) # I *think* this is a safe way to do it
678 # Excluding self._logger from pickle operation imply using the following method to access logger
680 if not hasattr(self, '_logger'):
681 self._logger = logging.getLogger(__name__)
682 #self._logger.setLevel(logging.DEBUG)
683 #self._logger.setLevel(logging.WARNING)
684 self._logger.setLevel(logging.ERROR)
688 if __name__ == "__main__":
689 if len(sys.argv) == 3:
690 context = pickle.loads(sys.argv[1].encode('latin1'))
691 args = pickle.loads(sys.argv[2].encode('latin1'))
693 status = context._startSalome(args)