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
33 from salomeContextUtils import SalomeContextException
37 Usage: salome [command] [options] [--config=<file,folder,...>]
41 start Start a new SALOME instance.
42 context Initialize SALOME context. Current environment is extended.
43 shell Initialize SALOME context, attached to the last created SALOME
44 instance if any, and executes scripts passed as command arguments.
45 User works in a Shell terminal. SALOME environment is set but
46 application is not started.
47 connect Connect a Python console to the active SALOME instance.
48 kill <port(s)> Terminate SALOME instances running on given ports for current user.
49 Port numbers must be separated by blank characters.
50 killall Terminate *all* SALOME running instances for current user.
51 Do not start a new one.
52 test Run SALOME tests.
53 info Display some information about SALOME.
54 doc <module(s)> Show online module documentation (if available).
55 Module names must be separated by blank characters.
56 help Show this message.
58 If no command is given, default is start.
62 Use salome <command> --help to show help on command. Available for the
63 following commands: start, shell, connect, test, info.
65 --config=<file,folder,...>
66 ==========================
67 Initialize SALOME context from a list of context files and/or a list
68 of folders containing context files. The list is comma-separated, without
76 The SalomeContext class in an API to configure SALOME context then
77 start SALOME using a single python command.
82 Initialize context from a list of configuration files
83 identified by their names.
84 These files should be in appropriate .cfg format.
86 def __init__(self, configFileNames=0):
87 self.getLogger().setLevel(logging.INFO)
88 #it could be None explicitly (if user use multiples setVariable...for standalone)
89 if configFileNames is None:
91 configFileNames = configFileNames or []
92 if len(configFileNames) == 0:
93 raise SalomeContextException("No configuration files given")
95 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']
96 for filename in configFileNames:
97 basename, extension = os.path.splitext(filename)
98 if extension == ".cfg":
99 self.__setContextFromConfigFile(filename, reserved)
101 self.getLogger().error("Unrecognized extension for configuration file: %s", filename)
104 def __loadEnvModules(self, env_modules):
105 modulecmd = os.getenv('LMOD_CMD')
107 raise SalomeContextException("Module environment not present")
110 out, err = subprocess.Popen([modulecmd, "python", "load"] + env_modules, stdout=subprocess.PIPE).communicate()
111 exec(out) # define specific environment variables
113 raise SalomeContextException("Failed to load env modules: %s ..." % ' '.join(env_modules))
117 def runSalome(self, args):
119 # Run this module as a script, in order to use appropriate Python interpreter
120 # according to current path (initialized from context files).
121 env_modules_option = "--with-env-modules="
122 env_modules_l = [x for x in args if x.startswith(env_modules_option)]
124 env_modules = env_modules_l[-1][len(env_modules_option):].split(',')
125 self.__loadEnvModules(env_modules)
126 args = [x for x in args if not x.startswith(env_modules_option)]
128 env_modules = os.getenv("SALOME_ENV_MODULES", None)
130 self.__loadEnvModules(env_modules.split(','))
132 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
133 env_copy = os.environ.copy()
134 selfBytes= pickle.dumps(self, protocol=0)
135 argsBytes= pickle.dumps(args, protocol=0)
136 proc = subprocess.Popen(['python3', os.path.join(absoluteAppliPath,"bin","salome","salomeContext.py"), selfBytes.decode(), argsBytes.decode()], shell=False, close_fds=True, env=env_copy)
137 out, err = proc.communicate()
138 return out, err, proc.returncode
141 """Append value to PATH environment variable"""
142 def addToPath(self, value):
143 self.addToVariable('PATH', value)
146 """Append value to LD_LIBRARY_PATH environment variable"""
147 def addToLdLibraryPath(self, value):
148 if platform.system() == 'Windows':
149 self.addToVariable('PATH', value)
150 elif platform.system() == 'Darwin':
151 if "LAPACK" in value:
152 self.addToVariable('DYLD_FALLBACK_LIBRARY_PATH', value)
154 self.addToVariable('DYLD_LIBRARY_PATH', value)
156 self.addToVariable('LD_LIBRARY_PATH', value)
159 """Append value to DYLD_LIBRARY_PATH environment variable"""
160 def addToDyldLibraryPath(self, value):
161 self.addToVariable('DYLD_LIBRARY_PATH', value)
164 """Append value to PYTHONPATH environment variable"""
165 def addToPythonPath(self, value):
166 self.addToVariable('PYTHONPATH', value)
169 """Set environment variable to value"""
170 def setVariable(self, name, value, overwrite=False):
171 env = os.getenv(name, '')
172 if env and not overwrite:
173 self.getLogger().error("Environment variable already existing (and not overwritten): %s=%s", name, value)
177 self.getLogger().debug("Overwriting environment variable: %s=%s", name, value)
179 value = os.path.expandvars(value) # expand environment variables
180 self.getLogger().debug("Set environment variable: %s=%s", name, value)
181 os.environ[name] = value
184 """Unset environment variable"""
185 def unsetVariable(self, name):
186 if os.environ.has_key(name):
190 """Append value to environment variable"""
191 def addToVariable(self, name, value, separator=os.pathsep):
195 value = os.path.expandvars(value) # expand environment variables
196 self.getLogger().debug("Add to %s: %s", name, value)
197 env = os.getenv(name, None)
199 os.environ[name] = value
201 os.environ[name] = value + separator + env
204 ###################################
205 # This begins the private section #
206 ###################################
208 def __parseArguments(self, args):
209 if len(args) == 0 or args[0].startswith("-"):
215 availableCommands = {
216 'start' : '_runAppli',
217 'context' : '_setContext',
218 'shell' : '_runSession',
219 'connect' : '_runConsole',
221 'killall' : '_killAll',
222 'test' : '_runTests',
223 'info' : '_showInfo',
226 'coffee' : '_makeCoffee',
230 if command not in availableCommands:
234 return availableCommands[command], options
239 Args consist in a mandatory command followed by optional parameters.
240 See usage for details on commands.
242 def _startSalome(self, args):
246 from setenv import add_path
247 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
248 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome"))
249 add_path(path, "PYTHONPATH")
250 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome", "appliskel"))
251 add_path(path, "PYTHONPATH")
256 command, options = self.__parseArguments(args)
260 if args and args[0] in ["-h","--help","help"]:
263 # try to default to "start" command
264 command = "_runAppli"
267 res = getattr(self, command)(options) # run appropriate method
269 except SystemExit as ex:
271 self.getLogger().error("SystemExit %s in method %s.", ex.code, command)
273 except SalomeContextException as e:
274 self.getLogger().error(e)
277 self.getLogger().error("Unexpected error:")
279 traceback.print_exc()
283 def __setContextFromConfigFile(self, filename, reserved=None):
287 unsetVars, configVars, reservedDict = parseConfigFile(filename, reserved)
288 except SalomeContextException as e:
290 self.getLogger().error(msg)
294 for var in unsetVars:
295 self.unsetVariable(var)
298 for reserved in reservedDict:
299 a = [_f for _f in reservedDict[reserved] if _f] # remove empty elements
300 a = [ os.path.realpath(x) for x in a ]
301 reformattedVals = os.pathsep.join(a)
302 if reserved in ["INCLUDE", "LIBPATH"]:
303 self.addToVariable(reserved, reformattedVals, separator=' ')
305 self.addToVariable(reserved, reformattedVals)
308 for key,val in configVars:
309 self.setVariable(key, val, overwrite=True)
312 pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
313 pythonpath = [ os.path.realpath(x) for x in pythonpath ]
314 sys.path[:0] = pythonpath
317 def _runAppli(self, args=None):
320 # Initialize SALOME environment
321 sys.argv = ['runSalome'] + args
323 setenv.main(True, exeName="salome start")
326 runSalome.runSalome()
330 def _setContext(self, args=None):
331 salome_context_set = os.getenv("SALOME_CONTEXT_SET")
332 if salome_context_set:
334 print("*** SALOME context has already been set.")
335 print("*** Enter 'exit' (only once!) to leave SALOME context.")
339 os.environ["SALOME_CONTEXT_SET"] = "yes"
341 print("*** SALOME context is now set.")
342 print("*** Enter 'exit' (only once!) to leave SALOME context.")
346 proc = subprocess.Popen(cmd, shell=False, close_fds=True)
348 return proc.returncode
351 def _runSession(self, args=None):
354 sys.argv = ['runSession'] + args
356 params, args = runSession.configureSession(args, exe="salome shell")
358 sys.argv = ['runSession'] + args
362 return runSession.runSession(params, args)
365 def _runConsole(self, args=None):
368 # Initialize SALOME environment
369 sys.argv = ['runConsole']
374 return runConsole.connect(args)
377 def _kill(self, args=None):
382 print("Port number(s) not provided to command: salome kill <port(s)>")
385 from multiprocessing import Process
386 from killSalomeWithPort import killMyPort
389 with tempfile.NamedTemporaryFile():
390 p = Process(target = killMyPort, args=(port,))
396 def _killAll(self, unused=None):
398 import PortManager # mandatory
399 from multiprocessing import Process
400 from killSalomeWithPort import killMyPort
401 ports = PortManager.getBusyPorts()['this']
406 with tempfile.NamedTemporaryFile():
407 p = Process(target = killMyPort, args=(port,))
411 # :TODO: should be declared obsolete
412 from killSalome import killAllPorts
418 def _runTests(self, args=None):
421 sys.argv = ['runTests']
426 return runTests.runTests(args, exe="salome test")
429 def _showSoftwareVersions(self, softwares=None):
430 config = configparser.SafeConfigParser()
431 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
432 filename = os.path.join(absoluteAppliPath, "sha1_collections.txt")
435 with open(filename) as f:
438 software, version, sha1 = line.split()
439 versions[software.upper()] = version
440 if len(software) > max_len:
441 max_len = len(software)
447 for soft in softwares:
448 if soft.upper() in versions:
449 print(soft.upper().rjust(max_len), versions[soft.upper()])
452 od = collections.OrderedDict(sorted(versions.items()))
453 for name, version in od.items():
454 print(name.rjust(max_len), versions[name])
457 def _showInfo(self, args=None):
461 usage = "Usage: salome info [options]"
463 Display some information about SALOME.\n
464 Available options are:
465 -p,--ports Show the list of busy ports (running SALOME instances).
466 -s,--softwares [software(s)] Show the list and versions of SALOME softwares.
467 Software names must be separated by blank characters.
468 If no software is given, show version of all softwares.
469 -v,--version Show running SALOME version.
470 -h,--help Show this message.
475 if "-h" in args or "--help" in args:
476 print(usage + epilog)
479 if "-p" in args or "--ports" in args:
481 ports = PortManager.getBusyPorts()
482 this_ports = ports['this']
483 other_ports = ports['other']
484 if this_ports or other_ports:
485 print("SALOME instances are running on the following ports:")
487 print(" This application:", this_ports)
489 print(" No SALOME instances of this application")
491 print(" Other applications:", other_ports)
493 print(" No SALOME instances of other applications")
495 print("No SALOME instances are running")
497 if "-s" in args or "--softwares" in args:
499 index = args.index("-s")
501 index = args.index("--softwares")
503 while indexEnd < len(args) and args[indexEnd][0] != "-":
504 indexEnd = indexEnd + 1
505 self._showSoftwareVersions(softwares=args[index+1:indexEnd])
507 if "-v" in args or "--version" in args:
508 print("Running with python", platform.python_version())
509 return self._runAppli(["--version"])
514 def _showDoc(self, args=None):
520 print("Module(s) not provided to command: salome doc <module(s)>")
523 appliPath = os.getenv("ABSOLUTE_APPLI_PATH")
525 raise SalomeContextException("Unable to find application path. Please check that the variable ABSOLUTE_APPLI_PATH is set.")
526 baseDir = os.path.join(appliPath, "share", "doc", "salome")
527 for module in modules:
528 docfile = os.path.join(baseDir, "gui", module.upper(), "index.html")
529 if not os.path.isfile(docfile):
530 docfile = os.path.join(baseDir, "tui", module.upper(), "index.html")
531 if not os.path.isfile(docfile):
532 docfile = os.path.join(baseDir, "dev", module.upper(), "index.html")
533 if os.path.isfile(docfile):
534 out, err = subprocess.Popen(["xdg-open", docfile]).communicate()
536 print("Online documentation is not accessible for module:", module)
538 def _usage(self, unused=None):
542 def _makeCoffee(self, unused=None):
545 print(" ___...(-------)-....___")
546 print(" .-\"\" ) ( \"\"-.")
547 print(" .-\'``\'|-._ ) _.-|")
548 print(" / .--.| `\"\"---...........---\"\"` |")
552 print(" `\\ `\\ | |")
553 print(" `\\ `| SALOME |")
554 print(" _/ /\\ 4 EVER /")
555 print(" (__/ \\ <3 /")
556 print(" _..---\"\"` \\ /`\"\"---.._")
557 print(" .-\' \\ / \'-.")
558 print(" : `-.__ __.-\' :")
559 print(" : ) \"\"---...---\"\" ( :")
560 print(" \'._ `\"--...___...--\"` _.\'")
561 print(" \\\"\"--..__ __..--\"\"/")
562 print(" \'._ \"\"\"----.....______.....----\"\"\" _.\'")
563 print(" `\"\"--..,,_____ _____,,..--\"\"`")
564 print(" `\"\"\"----\"\"\"`")
566 print(" SALOME is working for you; what else?")
570 def _getCar(self, unused=None):
571 print(" _____________")
572 print(" ..---:::::::-----------. ::::;;.")
573 print(" .\'\"\"\"\"\"\" ;; \\ \":.")
574 print(" .\'\' ; \\ \"\\__.")
575 print(" .\' ;; ; \\\\\";")
576 print(" .\' ; _____; \\\\/")
577 print(" .\' :; ;\" \\ ___:\'.")
578 print(" .\'--........................... : = ____:\" \\ \\")
579 print(" ..-\"\" \"\"\"\' o\"\"\" ; ; :")
580 print(" .--\"\" .----- ..----... _.- --. ..-\" ; ; ; ;")
581 print(" .\"\"_- \"--\"\"-----\'\"\" _-\" .-\"\" ; ; .-.")
582 print(" .\' .\' SALOME .\" .\" ; ; /. |")
583 print(" /-./\' 4 EVER <3 .\" / _.. ; ; ;;;|")
584 print(" : ;-.______ / _________==. /_ \\ ; ; ;;;;")
585 print(" ; / | \"\"\"\"\"\"\"\"\"\"\".---.\"\"\"\"\"\"\" : /\" \". |; ; _; ;;;")
586 print(" /\"-/ | / / / / ;|; ;-\" | ;\';")
587 print(":- : \"\"\"----______ / / ____. . .\"\'. ;; .-\"..T\" .")
588 print("\'. \" ___ \"\": \'\"\"\"\"\"\"\"\"\"\"\"\"\"\" . ; ; ;; ;.\" .\" \'--\"")
589 print(" \", __ \"\"\" \"\"---... :- - - - - - - - - \' \' ; ; ; ;;\" .\"")
590 print(" /. ; \"\"\"---___ ; ; ; ;|.\"\"")
591 print(" : \": \"\"\"----. .-------. ; ; ; ;:")
592 print(" \\ \'--__ \\ \\ \\ / | ; ;;")
593 print(" \'-.. \"\"\"\"---___ : .______..\\ __/..-\"\"| ; ; ;")
594 print(" \"\"--.. \"\"\"--\" m l s . \". . ;")
595 print(" \"\"------... ..--\"\" \" :")
596 print(" \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\" \\ /")
599 print(" Drive your simulation properly with SALOME!")
603 # Add the following two methods since logger is not pickable
604 # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
605 def __getstate__(self):
606 d = dict(self.__dict__)
607 if hasattr(self, '_logger'):
611 def __setstate__(self, d):
612 self.__dict__.update(d) # I *think* this is a safe way to do it
614 # Excluding self._logger from pickle operation imply using the following method to access logger
616 if not hasattr(self, '_logger'):
617 self._logger = logging.getLogger(__name__)
618 #self._logger.setLevel(logging.DEBUG)
619 #self._logger.setLevel(logging.WARNING)
620 self._logger.setLevel(logging.ERROR)
624 if __name__ == "__main__":
625 if len(sys.argv) == 3:
626 context = pickle.loads(sys.argv[1].encode())
627 args = pickle.loads(sys.argv[2].encode())
629 status = context._startSalome(args)