1 # Copyright (C) 2013-2017 CEA/DEN, EDF R&D, OPEN CASCADE
3 # This library is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU Lesser General Public
5 # License as published by the Free Software Foundation; either
6 # version 2.1 of the License, or (at your option) any later version.
8 # This library is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 # Lesser General Public License for more details.
13 # You should have received a copy of the GNU Lesser General Public
14 # License along with this library; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
25 from parseConfigFile import parseConfigFile
32 from salomeContextUtils import SalomeContextException
36 Usage: salome [command] [options] [--config=<file,folder,...>]
40 start Start a new SALOME instance.
41 context Initialize SALOME context. Current environment is extended.
42 shell Initialize SALOME context, attached to the last created SALOME
43 instance if any, and executes scripts passed as command arguments.
44 User works in a Shell terminal. SALOME environment is set but
45 application is not started.
46 connect Connect a Python console to the active SALOME instance.
47 kill <port(s)> Terminate SALOME instances running on given ports for current user.
48 Port numbers must be separated by blank characters.
49 killall Terminate *all* SALOME running instances for current user.
50 Do not start a new one.
51 test Run SALOME tests.
52 info Display some information about SALOME.
53 doc <module(s)> Show online module documentation (if available).
54 Module names must be separated by blank characters.
55 help Show this message.
57 If no command is given, default is start.
61 Use salome <command> --help to show help on command. Available for the
62 following commands: start, shell, connect, test, info.
64 --config=<file,folder,...>
65 ==========================
66 Initialize SALOME context from a list of context files and/or a list
67 of folders containing context files. The list is comma-separated, without
75 The SalomeContext class in an API to configure SALOME context then
76 start SALOME using a single python command.
81 Initialize context from a list of configuration files
82 identified by their names.
83 These files should be in appropriate .cfg format.
85 def __init__(self, configFileNames=0):
86 self.getLogger().setLevel(logging.INFO)
87 #it could be None explicitly (if user use multiples setVariable...for standalone)
88 if configFileNames is None:
90 configFileNames = configFileNames or []
91 if len(configFileNames) == 0:
92 raise SalomeContextException("No configuration files given")
94 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']
95 for filename in configFileNames:
96 basename, extension = os.path.splitext(filename)
97 if extension == ".cfg":
98 self.__setContextFromConfigFile(filename, reserved)
100 self.getLogger().error("Unrecognized extension for configuration file: %s", filename)
103 def __loadEnvModules(self, env_modules):
104 print("Trying to load env modules: %s ..." % ' '.join(env_modules))
105 modulecmd = os.getenv('LMOD_CMD')
107 print('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
114 print("** Failed **")
118 def runSalome(self, args):
120 # Run this module as a script, in order to use appropriate Python interpreter
121 # according to current path (initialized from context files).
122 env_modules_option = "--with-env-modules="
123 env_modules_l = [x for x in args if x.startswith(env_modules_option)]
125 env_modules = env_modules_l[-1][len(env_modules_option):].split(',')
126 self.__loadEnvModules(env_modules)
127 args = [x for x in args if not x.startswith(env_modules_option)]
129 env_modules = os.getenv("SALOME_ENV_MODULES", None)
131 self.__loadEnvModules(env_modules.split(','))
133 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
134 env_copy = os.environ.copy()
135 selfBytes= pickle.dumps(self, protocol=0)
136 argsBytes= pickle.dumps(args, protocol=0)
137 proc = subprocess.Popen(['python3', os.path.join(absoluteAppliPath,"bin","salome","salomeContext.py"), selfBytes.decode(), argsBytes.decode()], shell=False, close_fds=True, env=env_copy)
138 out, err = proc.communicate()
139 return out, err, proc.returncode
142 """Append value to PATH environment variable"""
143 def addToPath(self, value):
144 self.addToVariable('PATH', value)
147 """Append value to LD_LIBRARY_PATH environment variable"""
148 def addToLdLibraryPath(self, value):
149 if platform.system() == 'Windows':
150 self.addToVariable('PATH', value)
151 elif platform.system() == 'Darwin':
152 if "LAPACK" in value:
153 self.addToVariable('DYLD_FALLBACK_LIBRARY_PATH', value)
155 self.addToVariable('DYLD_LIBRARY_PATH', value)
157 self.addToVariable('LD_LIBRARY_PATH', value)
160 """Append value to DYLD_LIBRARY_PATH environment variable"""
161 def addToDyldLibraryPath(self, value):
162 self.addToVariable('DYLD_LIBRARY_PATH', value)
165 """Append value to PYTHONPATH environment variable"""
166 def addToPythonPath(self, value):
167 self.addToVariable('PYTHONPATH', value)
170 """Set environment variable to value"""
171 def setVariable(self, name, value, overwrite=False):
172 env = os.getenv(name, '')
173 if env and not overwrite:
174 self.getLogger().error("Environment variable already existing (and not overwritten): %s=%s", name, value)
178 self.getLogger().debug("Overwriting environment variable: %s=%s", name, value)
180 value = os.path.expandvars(value) # expand environment variables
181 self.getLogger().debug("Set environment variable: %s=%s", name, value)
182 os.environ[name] = value
185 """Unset environment variable"""
186 def unsetVariable(self, name):
187 if os.environ.has_key(name):
191 """Append value to environment variable"""
192 def addToVariable(self, name, value, separator=os.pathsep):
196 value = os.path.expandvars(value) # expand environment variables
197 self.getLogger().debug("Add to %s: %s", name, value)
198 env = os.getenv(name, None)
200 os.environ[name] = value
202 os.environ[name] = value + separator + env
205 ###################################
206 # This begins the private section #
207 ###################################
209 def __parseArguments(self, args):
210 if len(args) == 0 or args[0].startswith("-"):
216 availableCommands = {
217 'start' : '_runAppli',
218 'context' : '_setContext',
219 'shell' : '_runSession',
220 'connect' : '_runConsole',
222 'killall' : '_killAll',
223 'test' : '_runTests',
224 'info' : '_showInfo',
227 'coffee' : '_makeCoffee',
231 if command not in availableCommands:
235 return availableCommands[command], options
240 Args consist in a mandatory command followed by optional parameters.
241 See usage for details on commands.
243 def _startSalome(self, args):
247 from setenv import add_path
248 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
249 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome"))
250 add_path(path, "PYTHONPATH")
251 path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome", "appliskel"))
252 add_path(path, "PYTHONPATH")
257 command, options = self.__parseArguments(args)
261 if args and args[0] in ["-h","--help","help"]:
264 # try to default to "start" command
265 command = "_runAppli"
268 res = getattr(self, command)(options) # run appropriate method
270 except SystemExit as ex:
272 self.getLogger().error("SystemExit %s in method %s.", ex.code, command)
274 except SalomeContextException as e:
275 self.getLogger().error(e)
278 self.getLogger().error("Unexpected error:")
280 traceback.print_exc()
284 def __setContextFromConfigFile(self, filename, reserved=None):
288 unsetVars, configVars, reservedDict = parseConfigFile(filename, reserved)
289 except SalomeContextException as e:
291 self.getLogger().error(msg)
295 for var in unsetVars:
296 self.unsetVariable(var)
299 for reserved in reservedDict:
300 a = [_f for _f in reservedDict[reserved] if _f] # remove empty elements
301 a = [ os.path.realpath(x) for x in a ]
302 reformattedVals = os.pathsep.join(a)
303 if reserved in ["INCLUDE", "LIBPATH"]:
304 self.addToVariable(reserved, reformattedVals, separator=' ')
306 self.addToVariable(reserved, reformattedVals)
309 for key,val in configVars:
310 self.setVariable(key, val, overwrite=True)
313 pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
314 pythonpath = [ os.path.realpath(x) for x in pythonpath ]
315 sys.path[:0] = pythonpath
318 def _runAppli(self, args=None):
321 # Initialize SALOME environment
322 sys.argv = ['runSalome'] + args
324 setenv.main(True, exeName="salome start")
327 runSalome.runSalome()
331 def _setContext(self, args=None):
332 salome_context_set = os.getenv("SALOME_CONTEXT_SET")
333 if salome_context_set:
335 print("*** SALOME context has already been set.")
336 print("*** Enter 'exit' (only once!) to leave SALOME context.")
340 os.environ["SALOME_CONTEXT_SET"] = "yes"
342 print("*** SALOME context is now set.")
343 print("*** Enter 'exit' (only once!) to leave SALOME context.")
347 proc = subprocess.Popen(cmd, shell=False, close_fds=True)
349 return proc.returncode
352 def _runSession(self, args=None):
355 sys.argv = ['runSession'] + args
357 params, args = runSession.configureSession(args, exe="salome shell")
359 sys.argv = ['runSession'] + args
363 return runSession.runSession(params, args)
366 def _runConsole(self, args=None):
369 # Initialize SALOME environment
370 sys.argv = ['runConsole']
375 return runConsole.connect(args)
378 def _kill(self, args=None):
383 print("Port number(s) not provided to command: salome kill <port(s)>")
386 from multiprocessing import Process
387 from killSalomeWithPort import killMyPort
390 with tempfile.NamedTemporaryFile():
391 p = Process(target = killMyPort, args=(port,))
397 def _killAll(self, unused=None):
399 import PortManager # mandatory
400 from multiprocessing import Process
401 from killSalomeWithPort import killMyPort
402 ports = PortManager.getBusyPorts()['this']
407 with tempfile.NamedTemporaryFile():
408 p = Process(target = killMyPort, args=(port,))
412 # :TODO: should be declared obsolete
413 from killSalome import killAllPorts
419 def _runTests(self, args=None):
422 sys.argv = ['runTests']
427 return runTests.runTests(args, exe="salome test")
430 def _showSoftwareVersions(self, softwares=None):
431 config = configparser.SafeConfigParser()
432 absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
433 filename = os.path.join(absoluteAppliPath, "sha1_collections.txt")
436 with open(filename) as f:
439 software, version, sha1 = line.split()
440 versions[software.upper()] = version
441 if len(software) > max_len:
442 max_len = len(software)
448 for soft in softwares:
449 if soft.upper() in versions:
450 print(soft.upper().rjust(max_len), versions[soft.upper()])
453 od = collections.OrderedDict(sorted(versions.items()))
454 for name, version in od.items():
455 print(name.rjust(max_len), versions[name])
458 def _showInfo(self, args=None):
462 usage = "Usage: salome info [options]"
464 Display some information about SALOME.\n
465 Available options are:
466 -p,--ports Show the list of busy ports (running SALOME instances).
467 -s,--softwares [software(s)] Show the list and versions of SALOME softwares.
468 Software names must be separated by blank characters.
469 If no software is given, show version of all softwares.
470 -v,--version Show running SALOME version.
471 -h,--help Show this message.
476 if "-h" in args or "--help" in args:
477 print(usage + epilog)
480 if "-p" in args or "--ports" in args:
482 ports = PortManager.getBusyPorts()
483 this_ports = ports['this']
484 other_ports = ports['other']
485 if this_ports or other_ports:
486 print("SALOME instances are running on the following ports:")
488 print(" This application:", this_ports)
490 print(" No SALOME instances of this application")
492 print(" Other applications:", other_ports)
494 print(" No SALOME instances of other applications")
496 print("No SALOME instances are running")
498 if "-s" in args or "--softwares" in args:
500 index = args.index("-s")
502 index = args.index("--softwares")
504 while indexEnd < len(args) and args[indexEnd][0] != "-":
505 indexEnd = indexEnd + 1
506 self._showSoftwareVersions(softwares=args[index+1:indexEnd])
508 if "-v" in args or "--version" in args:
509 print("Running with python", platform.python_version())
510 return self._runAppli(["--version"])
515 def _showDoc(self, args=None):
521 print("Module(s) not provided to command: salome doc <module(s)>")
524 appliPath = os.getenv("ABSOLUTE_APPLI_PATH")
526 raise SalomeContextException("Unable to find application path. Please check that the variable ABSOLUTE_APPLI_PATH is set.")
527 baseDir = os.path.join(appliPath, "share", "doc", "salome")
528 for module in modules:
529 docfile = os.path.join(baseDir, "gui", module.upper(), "index.html")
530 if not os.path.isfile(docfile):
531 docfile = os.path.join(baseDir, "tui", module.upper(), "index.html")
532 if not os.path.isfile(docfile):
533 docfile = os.path.join(baseDir, "dev", module.upper(), "index.html")
534 if os.path.isfile(docfile):
535 out, err = subprocess.Popen(["xdg-open", docfile]).communicate()
537 print("Online documentation is not accessible for module:", module)
539 def _usage(self, unused=None):
543 def _makeCoffee(self, unused=None):
546 print(" ___...(-------)-....___")
547 print(" .-\"\" ) ( \"\"-.")
548 print(" .-\'``\'|-._ ) _.-|")
549 print(" / .--.| `\"\"---...........---\"\"` |")
553 print(" `\\ `\\ | |")
554 print(" `\\ `| SALOME |")
555 print(" _/ /\\ 4 EVER /")
556 print(" (__/ \\ <3 /")
557 print(" _..---\"\"` \\ /`\"\"---.._")
558 print(" .-\' \\ / \'-.")
559 print(" : `-.__ __.-\' :")
560 print(" : ) \"\"---...---\"\" ( :")
561 print(" \'._ `\"--...___...--\"` _.\'")
562 print(" \\\"\"--..__ __..--\"\"/")
563 print(" \'._ \"\"\"----.....______.....----\"\"\" _.\'")
564 print(" `\"\"--..,,_____ _____,,..--\"\"`")
565 print(" `\"\"\"----\"\"\"`")
567 print(" SALOME is working for you; what else?")
571 def _getCar(self, unused=None):
572 print(" _____________")
573 print(" ..---:::::::-----------. ::::;;.")
574 print(" .\'\"\"\"\"\"\" ;; \\ \":.")
575 print(" .\'\' ; \\ \"\\__.")
576 print(" .\' ;; ; \\\\\";")
577 print(" .\' ; _____; \\\\/")
578 print(" .\' :; ;\" \\ ___:\'.")
579 print(" .\'--........................... : = ____:\" \\ \\")
580 print(" ..-\"\" \"\"\"\' o\"\"\" ; ; :")
581 print(" .--\"\" .----- ..----... _.- --. ..-\" ; ; ; ;")
582 print(" .\"\"_- \"--\"\"-----\'\"\" _-\" .-\"\" ; ; .-.")
583 print(" .\' .\' SALOME .\" .\" ; ; /. |")
584 print(" /-./\' 4 EVER <3 .\" / _.. ; ; ;;;|")
585 print(" : ;-.______ / _________==. /_ \\ ; ; ;;;;")
586 print(" ; / | \"\"\"\"\"\"\"\"\"\"\".---.\"\"\"\"\"\"\" : /\" \". |; ; _; ;;;")
587 print(" /\"-/ | / / / / ;|; ;-\" | ;\';")
588 print(":- : \"\"\"----______ / / ____. . .\"\'. ;; .-\"..T\" .")
589 print("\'. \" ___ \"\": \'\"\"\"\"\"\"\"\"\"\"\"\"\"\" . ; ; ;; ;.\" .\" \'--\"")
590 print(" \", __ \"\"\" \"\"---... :- - - - - - - - - \' \' ; ; ; ;;\" .\"")
591 print(" /. ; \"\"\"---___ ; ; ; ;|.\"\"")
592 print(" : \": \"\"\"----. .-------. ; ; ; ;:")
593 print(" \\ \'--__ \\ \\ \\ / | ; ;;")
594 print(" \'-.. \"\"\"\"---___ : .______..\\ __/..-\"\"| ; ; ;")
595 print(" \"\"--.. \"\"\"--\" m l s . \". . ;")
596 print(" \"\"------... ..--\"\" \" :")
597 print(" \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\" \\ /")
600 print(" Drive your simulation properly with SALOME!")
604 # Add the following two methods since logger is not pickable
605 # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
606 def __getstate__(self):
607 d = dict(self.__dict__)
608 if hasattr(self, '_logger'):
612 def __setstate__(self, d):
613 self.__dict__.update(d) # I *think* this is a safe way to do it
615 # Excluding self._logger from pickle operation imply using the following method to access logger
617 if not hasattr(self, '_logger'):
618 self._logger = logging.getLogger(__name__)
619 #self._logger.setLevel(logging.DEBUG)
620 #self._logger.setLevel(logging.WARNING)
621 self._logger.setLevel(logging.ERROR)
625 if __name__ == "__main__":
626 if len(sys.argv) == 3:
627 context = pickle.loads(sys.argv[1].encode())
628 args = pickle.loads(sys.argv[2].encode())
630 status = context._startSalome(args)