]> SALOME platform Git repositories - modules/kernel.git/blob - bin/salomeContext.py
Salome HOME
#17978 [CEA] SALOME windows minor corrections : fix for problem 1 of 5
[modules/kernel.git] / bin / salomeContext.py
1 #! /usr/bin/env python3
2 # Copyright (C) 2013-2019  CEA/DEN, EDF R&D, OPEN CASCADE
3 #
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.
8 #
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.
13 #
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
17 #
18 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
19 #
20
21 import os
22 import sys
23 import logging
24 import configparser
25
26 from parseConfigFile import parseConfigFile
27
28 import tempfile
29 import pickle
30 import subprocess
31 import sys
32
33 from salomeContextUtils import SalomeContextException
34
35 def usage():
36   msg = '''\
37 Usage: salome [command] [options] [--config=<file,folder,...>]
38
39 Commands:
40 =========
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     remote          run command in SALOME environment from remote call, ssh or rsh.
49     kill <port(s)>  Terminate SALOME instances running on given ports for current user.
50                     Port numbers must be separated by blank characters.
51     killall         Terminate *all* SALOME running instances for current user.
52                     Do not start a new one.
53     test            Run SALOME tests.
54     info            Display some information about SALOME.
55     doc <module(s)> Show online module documentation (if available).
56                     Module names must be separated by blank characters.
57     help            Show this message.
58
59 If no command is given, default is start.
60
61 Command options:
62 ================
63     Use salome <command> --help to show help on command. Available for the
64     following commands: start, shell, connect, test, info.
65
66 --config=<file,folder,...>
67 ==========================
68     Initialize SALOME context from a list of context files and/or a list
69     of folders containing context files. The list is comma-separated, without
70     any blank characters.
71 '''
72
73   print(msg)
74 #
75
76 """
77 The SalomeContext class in an API to configure SALOME context then
78 start SALOME using a single python command.
79
80 """
81 class SalomeContext:
82   """
83   Initialize context from a list of configuration files
84   identified by their names.
85   These files should be in appropriate .cfg format.
86   """
87   def __init__(self, configFileNames=0):
88     self.getLogger().setLevel(logging.INFO)
89     #it could be None explicitly (if user use multiples setVariable...for standalone)
90     if configFileNames is None:
91        return
92     configFileNames = configFileNames or []
93     if len(configFileNames) == 0:
94       raise SalomeContextException("No configuration files given")
95
96     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']
97     for filename in configFileNames:
98       basename, extension = os.path.splitext(filename)
99       if extension == ".cfg":
100         self.__setContextFromConfigFile(filename, reserved)
101       else:
102         self.getLogger().error("Unrecognized extension for configuration file: %s", filename)
103   #
104
105   def __loadEnvModules(self, env_modules):
106     modulecmd = os.getenv('LMOD_CMD')
107     if not modulecmd:
108       raise SalomeContextException("Module environment not present")
109       return
110     try:
111       out, err = subprocess.Popen([modulecmd, "python", "load"] + env_modules, stdout=subprocess.PIPE).communicate()
112       exec(out)  # define specific environment variables
113     except:
114       raise SalomeContextException("Failed to load env modules: %s ..." % ' '.join(env_modules))
115       pass
116   #
117
118   def runSalome(self, args):
119     import os
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)]
124     if env_modules_l:
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)]
128     else:
129       env_modules = os.getenv("SALOME_ENV_MODULES", None)
130       if env_modules:
131         self.__loadEnvModules(env_modules.split(','))
132
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
140   #
141
142   """Append value to PATH environment variable"""
143   def addToPath(self, value):
144     self.addToVariable('PATH', value)
145   #
146
147   """Append value to LD_LIBRARY_PATH environment variable"""
148   def addToLdLibraryPath(self, value):
149     if sys.platform == 'win32':
150       self.addToVariable('PATH', value)
151     elif  sys.platform == 'darwin':
152       if "LAPACK" in value:
153         self.addToVariable('DYLD_FALLBACK_LIBRARY_PATH', value)
154       else:
155         self.addToVariable('DYLD_LIBRARY_PATH', value)
156     else:
157       self.addToVariable('LD_LIBRARY_PATH', value)
158   #
159
160   """Append value to DYLD_LIBRARY_PATH environment variable"""
161   def addToDyldLibraryPath(self, value):
162     self.addToVariable('DYLD_LIBRARY_PATH', value)
163   #
164
165   """Append value to PYTHONPATH environment variable"""
166   def addToPythonPath(self, value):
167     self.addToVariable('PYTHONPATH', value)
168   #
169
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)
175       return
176
177     if env:
178       self.getLogger().debug("Overwriting environment variable: %s=%s", name, value)
179
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
183   #
184
185   """Unset environment variable"""
186   def unsetVariable(self, name):
187     if os.environ.has_key(name):
188       del os.environ[name]
189   #
190
191   """Append value to environment variable"""
192   def addToVariable(self, name, value, separator=os.pathsep):
193     if value == '':
194       return
195
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)
199     if env is None:
200       os.environ[name] = value
201     else:
202       os.environ[name] = value + separator + env
203   #
204
205   ###################################
206   # This begins the private section #
207   ###################################
208
209   def __parseArguments(self, args):
210     if len(args) == 0 or args[0].startswith("-"):
211       return None, args
212
213     command = args[0]
214     options = args[1:]
215
216     availableCommands = {
217       'start'   : '_runAppli',
218       'context' : '_setContext',
219       'shell'   : '_runSession',
220       'remote'  : '_runRemote',
221       'connect' : '_runConsole',
222       'kill'    : '_kill',
223       'killall' : '_killAll',
224       'test'    : '_runTests',
225       'info'    : '_showInfo',
226       'doc'     : '_showDoc',
227       'help'    : '_usage',
228       'coffee'  : '_makeCoffee',
229       'car'     : '_getCar',
230       }
231
232     if command not in availableCommands:
233       command = "start"
234       options = args
235
236     return availableCommands[command], options
237   #
238
239   """
240   Run SALOME!
241   Args consist in a mandatory command followed by optional parameters.
242   See usage for details on commands.
243   """
244   def _startSalome(self, args):
245     import os
246     import sys
247     try:
248       from setenv import add_path
249       absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
250       path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome"))
251       add_path(path, "PYTHONPATH")
252       path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome", "appliskel"))
253       add_path(path, "PYTHONPATH")
254
255     except:
256       pass
257
258     command, options = self.__parseArguments(args)
259     sys.argv = options
260
261     if command is None:
262       if args and args[0] in ["-h","--help","help"]:
263         usage()
264         return 0
265       # try to default to "start" command
266       command = "_runAppli"
267
268     try:
269       res = getattr(self, command)(options) # run appropriate method
270       return res or 0
271     except SystemExit as ex:
272       if ex.code != 0:
273         self.getLogger().error("SystemExit %s in method %s.", ex.code, command)
274       return ex.code
275     except SalomeContextException as e:
276       self.getLogger().error(e)
277       return 1
278     except Exception:
279       self.getLogger().error("Unexpected error:")
280       import traceback
281       traceback.print_exc()
282       return 1
283   #
284
285   def __setContextFromConfigFile(self, filename, reserved=None):
286     if reserved is None:
287       reserved = []
288     try:
289       unsetVars, configVars, reservedDict = parseConfigFile(filename, reserved)
290     except SalomeContextException as e:
291       msg = "%s"%e
292       self.getLogger().error(msg)
293       return 1
294
295     # unset variables
296     for var in unsetVars:
297       self.unsetVariable(var)
298
299     # set context
300     for reserved in reservedDict:
301       a = [_f for _f in reservedDict[reserved] if _f] # remove empty elements
302       a = [ os.path.realpath(x) for x in a ]
303       reformattedVals = os.pathsep.join(a)
304       if reserved in ["INCLUDE", "LIBPATH"]:
305         self.addToVariable(reserved, reformattedVals, separator=' ')
306       else:
307         self.addToVariable(reserved, reformattedVals)
308       pass
309
310     for key,val in configVars:
311       self.setVariable(key, val, overwrite=True)
312       pass
313
314     pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
315     pythonpath = [ os.path.realpath(x) for x in pythonpath ]
316     sys.path[:0] = pythonpath
317   #
318
319   def _runAppli(self, args=None):
320     if args is None:
321       args = []
322     # Initialize SALOME environment
323     sys.argv = ['runSalome'] + args
324     import setenv
325     setenv.main(True, exeName="salome start")
326
327     import runSalome
328     runSalome.runSalome()
329     return 0
330   #
331
332   def _setContext(self, args=None):
333     salome_context_set = os.getenv("SALOME_CONTEXT_SET")
334     if salome_context_set:
335       print("***")
336       print("*** SALOME context has already been set.")
337       print("*** Enter 'exit' (only once!) to leave SALOME context.")
338       print("***")
339       return 0
340
341     os.environ["SALOME_CONTEXT_SET"] = "yes"
342     print("***")
343     print("*** SALOME context is now set.")
344     print("*** Enter 'exit' (only once!) to leave SALOME context.")
345     print("***")
346
347     if sys.platform == 'win32':
348       cmd = ['cmd.exe']
349     else:
350       cmd = ["/bin/bash"]
351     proc = subprocess.Popen(cmd, shell=False, close_fds=True)
352     proc.communicate()
353     return proc.returncode
354   #
355
356   def _runSession(self, args=None):
357     if args is None:
358       args = []
359     sys.argv = ['runSession'] + args
360     import runSession
361     params, args = runSession.configureSession(args, exe="salome shell")
362
363     sys.argv = ['runSession'] + args
364     import setenv
365     setenv.main(True)
366
367     return runSession.runSession(params, args)
368   #
369
370   def _runRemote(self, args=None):
371     if args is None:
372       args = []
373 #   complete salome environment 
374     sys.argv = ['runRemote']
375     import setenv
376     setenv.main(True)
377
378     import runRemote
379     return runRemote.runRemote(args)
380   #
381
382   def _runConsole(self, args=None):
383     if args is None:
384       args = []
385     # Initialize SALOME environment
386     sys.argv = ['runConsole']
387     import setenv
388     setenv.main(True)
389
390     import runConsole
391     return runConsole.connect(args)
392   #
393
394   def _kill(self, args=None):
395     if args is None:
396       args = []
397     ports = args
398     if not ports:
399       print("Port number(s) not provided to command: salome kill <port(s)>")
400       return 1
401
402     import subprocess
403     sys.argv = ['kill']
404     import setenv
405     setenv.main(True)
406     if os.getenv("NSHOST") == "no_host":
407       os.unsetenv("NSHOST")
408     for port in ports:
409       proc = subprocess.Popen(["killSalomeWithPort.py", port])
410       proc.communicate()
411
412     return 0
413   #
414
415   def _killAll(self, unused=None):
416     sys.argv = ['killAll']
417     import setenv
418     setenv.main(True)
419     if os.getenv("NSHOST") == "no_host":
420       os.unsetenv("NSHOST")
421     try:
422       import PortManager # mandatory
423       import subprocess
424       ports = PortManager.getBusyPorts()['this']
425
426       if ports:
427         for port in ports:
428           proc = subprocess.Popen(["killSalomeWithPort.py", str(port)])
429           proc.communicate()
430     except ImportError:
431       # :TODO: should be declared obsolete
432       from killSalome import killAllPorts
433       killAllPorts()
434       pass
435     return 0
436   #
437
438   def _runTests(self, args=None):
439     if args is None:
440       args = []
441     sys.argv = ['runTests']
442     import setenv
443     setenv.main(True)
444
445     import runTests
446     return runTests.runTests(args, exe="salome test")
447   #
448
449   def _showSoftwareVersions(self, softwares=None):
450     config = configparser.SafeConfigParser()
451     absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
452     filename = os.path.join(absoluteAppliPath, "sha1_collections.txt")
453     versions = {}
454     max_len = 0
455     with open(filename) as f:
456       for line in f:
457         try:
458           software, version, sha1 = line.split()
459           versions[software.upper()] = version
460           if len(software) > max_len:
461             max_len = len(software)
462         except:
463           pass
464         pass
465       pass
466     if softwares:
467       for soft in softwares:
468         if soft.upper() in versions:
469           print(soft.upper().rjust(max_len), versions[soft.upper()])
470     else:
471       import collections
472       od = collections.OrderedDict(sorted(versions.items()))
473       for name, version in od.items():
474         print(name.rjust(max_len), versions[name])
475     pass
476
477   def _showInfo(self, args=None):
478     if args is None:
479       args = []
480
481     usage = "Usage: salome info [options]"
482     epilog  = """\n
483 Display some information about SALOME.\n
484 Available options are:
485     -p,--ports                     Show the list of busy ports (running SALOME instances).
486     -s,--softwares [software(s)]   Show the list and versions of SALOME softwares.
487                                    Software names must be separated by blank characters.
488                                    If no software is given, show version of all softwares.
489     -v,--version                   Show running SALOME version.
490     -h,--help                      Show this message.
491 """
492     if not args:
493       args = ["--version"]
494
495     if "-h" in args or "--help" in args:
496       print(usage + epilog)
497       return 0
498
499     if "-p" in args or "--ports" in args:
500       import PortManager
501       ports = PortManager.getBusyPorts()
502       this_ports = ports['this']
503       other_ports = ports['other']
504       if this_ports or other_ports:
505           print("SALOME instances are running on the following ports:")
506           if this_ports:
507               print("   This application:", this_ports)
508           else:
509               print("   No SALOME instances of this application")
510           if other_ports:
511               print("   Other applications:", other_ports)
512           else:
513               print("   No SALOME instances of other applications")
514       else:
515           print("No SALOME instances are running")
516
517     if "-s" in args or "--softwares" in args:
518       if "-s" in args:
519         index = args.index("-s")
520       else:
521         index = args.index("--softwares")
522       indexEnd=index+1
523       while indexEnd < len(args) and args[indexEnd][0] != "-":
524         indexEnd = indexEnd + 1
525       self._showSoftwareVersions(softwares=args[index+1:indexEnd])
526
527     if "-v" in args or "--version" in args:
528       print("Running with python", platform.python_version())
529       return self._runAppli(["--version"])
530
531     return 0
532   #
533
534   def _showDoc(self, args=None):
535     if args is None:
536       args = []
537
538     modules = args
539     if not modules:
540       print("Module(s) not provided to command: salome doc <module(s)>")
541       return 1
542
543     appliPath = os.getenv("ABSOLUTE_APPLI_PATH")
544     if not appliPath:
545       raise SalomeContextException("Unable to find application path. Please check that the variable ABSOLUTE_APPLI_PATH is set.")
546     baseDir = os.path.join(appliPath, "share", "doc", "salome")
547     for module in modules:
548       docfile = os.path.join(baseDir, "gui", module.upper(), "index.html")
549       if not os.path.isfile(docfile):
550         docfile = os.path.join(baseDir, "tui", module.upper(), "index.html")
551       if not os.path.isfile(docfile):
552         docfile = os.path.join(baseDir, "dev", module.upper(), "index.html")
553       if os.path.isfile(docfile):
554         out, err = subprocess.Popen(["xdg-open", docfile]).communicate()
555       else:
556         print("Online documentation is not accessible for module:", module)
557
558   def _usage(self, unused=None):
559     usage()
560   #
561
562   def _makeCoffee(self, unused=None):
563     print("                        (")
564     print("                          )     (")
565     print("                   ___...(-------)-....___")
566     print("               .-\"\"       )    (          \"\"-.")
567     print("         .-\'``\'|-._             )         _.-|")
568     print("        /  .--.|   `\"\"---...........---\"\"`   |")
569     print("       /  /    |                             |")
570     print("       |  |    |                             |")
571     print("        \\  \\   |                             |")
572     print("         `\\ `\\ |                             |")
573     print("           `\\ `|            SALOME           |")
574     print("           _/ /\\            4 EVER           /")
575     print("          (__/  \\             <3            /")
576     print("       _..---\"\"` \\                         /`\"\"---.._")
577     print("    .-\'           \\                       /          \'-.")
578     print("   :               `-.__             __.-\'              :")
579     print("   :                  ) \"\"---...---\"\" (                 :")
580     print("    \'._               `\"--...___...--\"`              _.\'")
581     print("      \\\"\"--..__                              __..--\"\"/")
582     print("       \'._     \"\"\"----.....______.....----\"\"\"     _.\'")
583     print("          `\"\"--..,,_____            _____,,..--\"\"`")
584     print("                        `\"\"\"----\"\"\"`")
585     print("")
586     print("                    SALOME is working for you; what else?")
587     print("")
588   #
589
590   def _getCar(self, unused=None):
591     print("                                              _____________")
592     print("                                  ..---:::::::-----------. ::::;;.")
593     print("                               .\'\"\"\"\"\"\"                  ;;   \\  \":.")
594     print("                            .\'\'                          ;     \\   \"\\__.")
595     print("                          .\'                            ;;      ;   \\\\\";")
596     print("                        .\'                              ;   _____;   \\\\/")
597     print("                      .\'                               :; ;\"     \\ ___:\'.")
598     print("                    .\'--...........................    : =   ____:\"    \\ \\")
599     print("               ..-\"\"                               \"\"\"\'  o\"\"\"     ;     ; :")
600     print("          .--\"\"  .----- ..----...    _.-    --.  ..-\"     ;       ;     ; ;")
601     print("       .\"\"_-     \"--\"\"-----\'\"\"    _-\"        .-\"\"         ;        ;    .-.")
602     print("    .\'  .\'   SALOME             .\"         .\"              ;       ;   /. |")
603     print("   /-./\'         4 EVER <3    .\"          /           _..  ;       ;   ;;;|")
604     print("  :  ;-.______               /       _________==.    /_  \\ ;       ;   ;;;;")
605     print("  ;  / |      \"\"\"\"\"\"\"\"\"\"\".---.\"\"\"\"\"\"\"          :    /\" \". |;       ; _; ;;;")
606     print(" /\"-/  |                /   /                  /   /     ;|;      ;-\" | ;\';")
607     print(":-  :   \"\"\"----______  /   /              ____.   .  .\"\'. ;;   .-\"..T\"   .")
608     print("\'. \"  ___            \"\":   \'\"\"\"\"\"\"\"\"\"\"\"\"\"\"    .   ; ;    ;; ;.\" .\"   \'--\"")
609     print(" \",   __ \"\"\"  \"\"---... :- - - - - - - - - \' \'  ; ;  ;    ;;\"  .\"")
610     print("  /. ;  \"\"\"---___                             ;  ; ;     ;|.\"\"")
611     print(" :  \":           \"\"\"----.    .-------.       ;   ; ;     ;:")
612     print("  \\  \'--__               \\   \\        \\     /    | ;     ;;")
613     print("   \'-..   \"\"\"\"---___      :   .______..\\ __/..-\"\"|  ;   ; ;")
614     print("       \"\"--..       \"\"\"--\"        m l s         .   \". . ;")
615     print("             \"\"------...                  ..--\"\"      \" :")
616     print("                        \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"    \\        /")
617     print("                                               \"------\"")
618     print("")
619     print("                                Drive your simulation properly with SALOME!")
620     print("")
621   #
622
623   # Add the following two methods since logger is not pickable
624   # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
625   def __getstate__(self):
626     d = dict(self.__dict__)
627     if hasattr(self, '_logger'):
628       del d['_logger']
629     return d
630   #
631   def __setstate__(self, d):
632     self.__dict__.update(d) # I *think* this is a safe way to do it
633   #
634   # Excluding self._logger from pickle operation imply using the following method to access logger
635   def getLogger(self):
636     if not hasattr(self, '_logger'):
637       self._logger = logging.getLogger(__name__)
638       #self._logger.setLevel(logging.DEBUG)
639       #self._logger.setLevel(logging.WARNING)
640       self._logger.setLevel(logging.ERROR)
641     return self._logger
642   #
643
644 if __name__ == "__main__":
645   if len(sys.argv) == 3:
646     context = pickle.loads(sys.argv[1].encode())
647     args = pickle.loads(sys.argv[2].encode())
648
649     status = context._startSalome(args)
650     sys.exit(status)
651   else:
652     usage()
653 #