]> SALOME platform Git repositories - modules/yacs.git/blob - bin/salomeContext.py
Salome HOME
Merge branch 'V9_4_BR'
[modules/yacs.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 import platform
33
34 from salomeContextUtils import SalomeContextException
35
36 def usage():
37   msg = '''\
38 Usage: salome [command] [options] [--config=<file,folder,...>]
39
40 Commands:
41 =========
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.
59
60 If no command is given, default is start.
61
62 Command options:
63 ================
64     Use salome <command> --help to show help on command. Available for the
65     following commands: start, shell, connect, test, info.
66
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
71     any blank characters.
72 '''
73
74   print(msg)
75 #
76
77 """
78 The SalomeContext class in an API to configure SALOME context then
79 start SALOME using a single python command.
80
81 """
82 class SalomeContext:
83   """
84   Initialize context from a list of configuration files
85   identified by their names.
86   These files should be in appropriate .cfg format.
87   """
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:
92        return
93     configFileNames = configFileNames or []
94     if len(configFileNames) == 0:
95       raise SalomeContextException("No configuration files given")
96
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)
102       else:
103         self.getLogger().error("Unrecognized extension for configuration file: %s", filename)
104   #
105
106   def __loadEnvModules(self, env_modules):
107     modulecmd = os.getenv('LMOD_CMD')
108     if not modulecmd:
109       raise SalomeContextException("Module environment not present")
110       return
111     try:
112       out, err = subprocess.Popen([modulecmd, "python", "load"] + env_modules, stdout=subprocess.PIPE).communicate()
113       exec(out)  # define specific environment variables
114     except:
115       raise SalomeContextException("Failed to load env modules: %s ..." % ' '.join(env_modules))
116       pass
117   #
118
119   def runSalome(self, args):
120     import os
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)]
125     if env_modules_l:
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)]
129     else:
130       env_modules = os.getenv("SALOME_ENV_MODULES", None)
131       if env_modules:
132         self.__loadEnvModules(env_modules.split(','))
133
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(), argsBytes.decode()], shell=False, close_fds=True, env=env_copy)
139     out, err = proc.communicate()
140     return out, err, proc.returncode
141   #
142
143   """Append value to PATH environment variable"""
144   def addToPath(self, value):
145     self.addToVariable('PATH', value)
146   #
147
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)
155       else:
156         self.addToVariable('DYLD_LIBRARY_PATH', value)
157     else:
158       self.addToVariable('LD_LIBRARY_PATH', value)
159   #
160
161   """Append value to DYLD_LIBRARY_PATH environment variable"""
162   def addToDyldLibraryPath(self, value):
163     self.addToVariable('DYLD_LIBRARY_PATH', value)
164   #
165
166   """Append value to PYTHONPATH environment variable"""
167   def addToPythonPath(self, value):
168     self.addToVariable('PYTHONPATH', value)
169   #
170
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)
176       return
177
178     if env:
179       self.getLogger().debug("Overwriting environment variable: %s=%s", name, value)
180
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
184   #
185
186   """Unset environment variable"""
187   def unsetVariable(self, name):
188     if os.environ.has_key(name):
189       del os.environ[name]
190   #
191
192   """Append value to environment variable"""
193   def addToVariable(self, name, value, separator=os.pathsep):
194     if value == '':
195       return
196
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)
200     if env is None:
201       os.environ[name] = value
202     else:
203       os.environ[name] = value + separator + env
204   #
205
206   ###################################
207   # This begins the private section #
208   ###################################
209
210   def __parseArguments(self, args):
211     if len(args) == 0 or args[0].startswith("-"):
212       return None, args
213
214     command = args[0]
215     options = args[1:]
216
217     availableCommands = {
218       'start'   : '_runAppli',
219       'context' : '_setContext',
220       'shell'   : '_runSession',
221       'remote'  : '_runRemote',
222       'connect' : '_runConsole',
223       'kill'    : '_kill',
224       'killall' : '_killAll',
225       'test'    : '_runTests',
226       'info'    : '_showInfo',
227       'doc'     : '_showDoc',
228       'help'    : '_usage',
229       'coffee'  : '_makeCoffee',
230       'car'     : '_getCar',
231       }
232
233     if command not in availableCommands:
234       command = "start"
235       options = args
236
237     return availableCommands[command], options
238   #
239
240   """
241   Run SALOME!
242   Args consist in a mandatory command followed by optional parameters.
243   See usage for details on commands.
244   """
245   def _startSalome(self, args):
246     import os
247     import sys
248     try:
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")
255
256     except:
257       pass
258
259     command, options = self.__parseArguments(args)
260     sys.argv = options
261
262     if command is None:
263       if args and args[0] in ["-h","--help","help"]:
264         usage()
265         return 0
266       # try to default to "start" command
267       command = "_runAppli"
268
269     try:
270       res = getattr(self, command)(options) # run appropriate method
271       return res or 0
272     except SystemExit as ex:
273       if ex.code != 0:
274         self.getLogger().error("SystemExit %s in method %s.", ex.code, command)
275       return ex.code
276     except SalomeContextException as e:
277       self.getLogger().error(e)
278       return 1
279     except Exception:
280       self.getLogger().error("Unexpected error:")
281       import traceback
282       traceback.print_exc()
283       return 1
284   #
285
286   def __setContextFromConfigFile(self, filename, reserved=None):
287     if reserved is None:
288       reserved = []
289     try:
290       unsetVars, configVars, reservedDict = parseConfigFile(filename, reserved)
291     except SalomeContextException as e:
292       msg = "%s"%e
293       self.getLogger().error(msg)
294       return 1
295
296     # unset variables
297     for var in unsetVars:
298       self.unsetVariable(var)
299
300     # set context
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=' ')
307       else:
308         self.addToVariable(reserved, reformattedVals)
309       pass
310
311     for key,val in configVars:
312       self.setVariable(key, val, overwrite=True)
313       pass
314
315     pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
316     pythonpath = [ os.path.realpath(x) for x in pythonpath ]
317     sys.path[:0] = pythonpath
318   #
319
320   def _runAppli(self, args=None):
321     if args is None:
322       args = []
323     # Initialize SALOME environment
324     sys.argv = ['runSalome'] + args
325     import setenv
326     setenv.main(True, exeName="salome start")
327
328     import runSalome
329     runSalome.runSalome()
330     return 0
331   #
332
333   def _setContext(self, args=None):
334     salome_context_set = os.getenv("SALOME_CONTEXT_SET")
335     if salome_context_set:
336       print("***")
337       print("*** SALOME context has already been set.")
338       print("*** Enter 'exit' (only once!) to leave SALOME context.")
339       print("***")
340       return 0
341
342     os.environ["SALOME_CONTEXT_SET"] = "yes"
343     print("***")
344     print("*** SALOME context is now set.")
345     print("*** Enter 'exit' (only once!) to leave SALOME context.")
346     print("***")
347
348     if sys.platform == 'win32':
349       cmd = ['cmd.exe']
350     else:
351       cmd = ["/bin/bash"]
352     proc = subprocess.Popen(cmd, shell=False, close_fds=True)
353     proc.communicate()
354     return proc.returncode
355   #
356
357   def _runSession(self, args=None):
358     if args is None:
359       args = []
360     sys.argv = ['runSession'] + args
361     import runSession
362     params, args = runSession.configureSession(args, exe="salome shell")
363
364     sys.argv = ['runSession'] + args
365     import setenv
366     setenv.main(True)
367
368     return runSession.runSession(params, args)
369   #
370
371   def _runRemote(self, args=None):
372     if args is None:
373       args = []
374 #   complete salome environment 
375     sys.argv = ['runRemote']
376     import setenv
377     setenv.main(True)
378
379     import runRemote
380     return runRemote.runRemote(args)
381   #
382
383   def _runConsole(self, args=None):
384     if args is None:
385       args = []
386     # Initialize SALOME environment
387     sys.argv = ['runConsole']
388     import setenv
389     setenv.main(True)
390
391     import runConsole
392     return runConsole.connect(args)
393   #
394
395   def _kill(self, args=None):
396     if args is None:
397       args = []
398     ports = args
399     if not ports:
400       print("Port number(s) not provided to command: salome kill <port(s)>")
401       return 1
402
403     import subprocess
404     sys.argv = ['kill']
405     import setenv
406     setenv.main(True)
407     if os.getenv("NSHOST") == "no_host":
408       os.unsetenv("NSHOST")
409     for port in ports:
410       proc = subprocess.Popen(["killSalomeWithPort.py", port])
411       proc.communicate()
412
413     return 0
414   #
415
416   def _killAll(self, unused=None):
417     sys.argv = ['killAll']
418     import setenv
419     setenv.main(True)
420     if os.getenv("NSHOST") == "no_host":
421       os.unsetenv("NSHOST")
422     try:
423       import PortManager # mandatory
424       import subprocess
425       ports = PortManager.getBusyPorts()['this']
426
427       if ports:
428         for port in ports:
429           proc = subprocess.Popen(["killSalomeWithPort.py", str(port)])
430           proc.communicate()
431     except ImportError:
432       # :TODO: should be declared obsolete
433       from killSalome import killAllPorts
434       killAllPorts()
435       pass
436     return 0
437   #
438
439   def _runTests(self, args=None):
440     if args is None:
441       args = []
442     sys.argv = ['runTests']
443     import setenv
444     setenv.main(True)
445
446     import runTests
447     return runTests.runTests(args, exe="salome test")
448   #
449
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")
454     versions = {}
455     max_len = 0
456     with open(filename) as f:
457       for line in f:
458         try:
459           software, version, sha1 = line.split()
460           versions[software.upper()] = version
461           if len(software) > max_len:
462             max_len = len(software)
463         except:
464           pass
465         pass
466       pass
467     if softwares:
468       for soft in softwares:
469         if soft.upper() in versions:
470           print(soft.upper().rjust(max_len), versions[soft.upper()])
471     else:
472       import collections
473       od = collections.OrderedDict(sorted(versions.items()))
474       for name, version in od.items():
475         print(name.rjust(max_len), versions[name])
476     pass
477
478   def _showInfo(self, args=None):
479     if args is None:
480       args = []
481
482     usage = "Usage: salome info [options]"
483     epilog  = """\n
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.
492 """
493     if not args:
494       args = ["--version"]
495
496     if "-h" in args or "--help" in args:
497       print(usage + epilog)
498       return 0
499
500     if "-p" in args or "--ports" in args:
501       import PortManager
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:")
507           if this_ports:
508               print("   This application:", this_ports)
509           else:
510               print("   No SALOME instances of this application")
511           if other_ports:
512               print("   Other applications:", other_ports)
513           else:
514               print("   No SALOME instances of other applications")
515       else:
516           print("No SALOME instances are running")
517
518     if "-s" in args or "--softwares" in args:
519       if "-s" in args:
520         index = args.index("-s")
521       else:
522         index = args.index("--softwares")
523       indexEnd=index+1
524       while indexEnd < len(args) and args[indexEnd][0] != "-":
525         indexEnd = indexEnd + 1
526       self._showSoftwareVersions(softwares=args[index+1:indexEnd])
527
528     if "-v" in args or "--version" in args:
529       print("Running with python", platform.python_version())
530       return self._runAppli(["--version"])
531
532     return 0
533   #
534
535   def _showDoc(self, args=None):
536     if args is None:
537       args = []
538
539     modules = args
540     if not modules:
541       print("Module(s) not provided to command: salome doc <module(s)>")
542       return 1
543
544     appliPath = os.getenv("ABSOLUTE_APPLI_PATH")
545     if not appliPath:
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()
556       else:
557         print("Online documentation is not accessible for module:", module)
558
559   def _usage(self, unused=None):
560     usage()
561   #
562
563   def _makeCoffee(self, unused=None):
564     print("                        (")
565     print("                          )     (")
566     print("                   ___...(-------)-....___")
567     print("               .-\"\"       )    (          \"\"-.")
568     print("         .-\'``\'|-._             )         _.-|")
569     print("        /  .--.|   `\"\"---...........---\"\"`   |")
570     print("       /  /    |                             |")
571     print("       |  |    |                             |")
572     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("                        `\"\"\"----\"\"\"`")
586     print("")
587     print("                    SALOME is working for you; what else?")
588     print("")
589   #
590
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("                        \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"    \\        /")
618     print("                                               \"------\"")
619     print("")
620     print("                                Drive your simulation properly with SALOME!")
621     print("")
622   #
623
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'):
629       del d['_logger']
630     return d
631   #
632   def __setstate__(self, d):
633     self.__dict__.update(d) # I *think* this is a safe way to do it
634   #
635   # Excluding self._logger from pickle operation imply using the following method to access logger
636   def getLogger(self):
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)
642     return self._logger
643   #
644
645 if __name__ == "__main__":
646   if len(sys.argv) == 3:
647     context = pickle.loads(sys.argv[1].encode())
648     args = pickle.loads(sys.argv[2].encode())
649
650     status = context._startSalome(args)
651     sys.exit(status)
652   else:
653     usage()
654 #