Salome HOME
Ban noisy messages
[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 platform
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     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.
57
58 If no command is given, default is start.
59
60 Command options:
61 ================
62     Use salome <command> --help to show help on command. Available for the
63     following commands: start, shell, connect, test, info.
64
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
69     any blank characters.
70 '''
71
72   print(msg)
73 #
74
75 """
76 The SalomeContext class in an API to configure SALOME context then
77 start SALOME using a single python command.
78
79 """
80 class SalomeContext:
81   """
82   Initialize context from a list of configuration files
83   identified by their names.
84   These files should be in appropriate .cfg format.
85   """
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:
90        return
91     configFileNames = configFileNames or []
92     if len(configFileNames) == 0:
93       raise SalomeContextException("No configuration files given")
94
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)
100       else:
101         self.getLogger().error("Unrecognized extension for configuration file: %s", filename)
102   #
103
104   def __loadEnvModules(self, env_modules):
105     modulecmd = os.getenv('LMOD_CMD')
106     if not modulecmd:
107       raise SalomeContextException("Module environment not present")
108       return
109     try:
110       out, err = subprocess.Popen([modulecmd, "python", "load"] + env_modules, stdout=subprocess.PIPE).communicate()
111       exec(out)  # define specific environment variables
112     except:
113       raise SalomeContextException("Failed to load env modules: %s ..." % ' '.join(env_modules))
114       pass
115   #
116
117   def runSalome(self, args):
118     import os
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)]
123     if env_modules_l:
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)]
127     else:
128       env_modules = os.getenv("SALOME_ENV_MODULES", None)
129       if env_modules:
130         self.__loadEnvModules(env_modules.split(','))
131
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
139   #
140
141   """Append value to PATH environment variable"""
142   def addToPath(self, value):
143     self.addToVariable('PATH', value)
144   #
145
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)
153       else:
154         self.addToVariable('DYLD_LIBRARY_PATH', value)
155     else:
156       self.addToVariable('LD_LIBRARY_PATH', value)
157   #
158
159   """Append value to DYLD_LIBRARY_PATH environment variable"""
160   def addToDyldLibraryPath(self, value):
161     self.addToVariable('DYLD_LIBRARY_PATH', value)
162   #
163
164   """Append value to PYTHONPATH environment variable"""
165   def addToPythonPath(self, value):
166     self.addToVariable('PYTHONPATH', value)
167   #
168
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)
174       return
175
176     if env:
177       self.getLogger().debug("Overwriting environment variable: %s=%s", name, value)
178
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
182   #
183
184   """Unset environment variable"""
185   def unsetVariable(self, name):
186     if os.environ.has_key(name):
187       del os.environ[name]
188   #
189
190   """Append value to environment variable"""
191   def addToVariable(self, name, value, separator=os.pathsep):
192     if value == '':
193       return
194
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)
198     if env is None:
199       os.environ[name] = value
200     else:
201       os.environ[name] = value + separator + env
202   #
203
204   ###################################
205   # This begins the private section #
206   ###################################
207
208   def __parseArguments(self, args):
209     if len(args) == 0 or args[0].startswith("-"):
210       return None, args
211
212     command = args[0]
213     options = args[1:]
214
215     availableCommands = {
216       'start'   : '_runAppli',
217       'context' : '_setContext',
218       'shell'   : '_runSession',
219       'connect' : '_runConsole',
220       'kill'    : '_kill',
221       'killall' : '_killAll',
222       'test'    : '_runTests',
223       'info'    : '_showInfo',
224       'doc'     : '_showDoc',
225       'help'    : '_usage',
226       'coffee'  : '_makeCoffee',
227       'car'     : '_getCar',
228       }
229
230     if command not in availableCommands:
231       command = "start"
232       options = args
233
234     return availableCommands[command], options
235   #
236
237   """
238   Run SALOME!
239   Args consist in a mandatory command followed by optional parameters.
240   See usage for details on commands.
241   """
242   def _startSalome(self, args):
243     import os
244     import sys
245     try:
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")
252
253     except:
254       pass
255
256     command, options = self.__parseArguments(args)
257     sys.argv = options
258
259     if command is None:
260       if args and args[0] in ["-h","--help","help"]:
261         usage()
262         return 0
263       # try to default to "start" command
264       command = "_runAppli"
265
266     try:
267       res = getattr(self, command)(options) # run appropriate method
268       return res or 0
269     except SystemExit as ex:
270       if ex.code != 0:
271         self.getLogger().error("SystemExit %s in method %s.", ex.code, command)
272       return ex.code
273     except SalomeContextException as e:
274       self.getLogger().error(e)
275       return 1
276     except Exception:
277       self.getLogger().error("Unexpected error:")
278       import traceback
279       traceback.print_exc()
280       return 1
281   #
282
283   def __setContextFromConfigFile(self, filename, reserved=None):
284     if reserved is None:
285       reserved = []
286     try:
287       unsetVars, configVars, reservedDict = parseConfigFile(filename, reserved)
288     except SalomeContextException as e:
289       msg = "%s"%e
290       self.getLogger().error(msg)
291       return 1
292
293     # unset variables
294     for var in unsetVars:
295       self.unsetVariable(var)
296
297     # set context
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=' ')
304       else:
305         self.addToVariable(reserved, reformattedVals)
306       pass
307
308     for key,val in configVars:
309       self.setVariable(key, val, overwrite=True)
310       pass
311
312     pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
313     pythonpath = [ os.path.realpath(x) for x in pythonpath ]
314     sys.path[:0] = pythonpath
315   #
316
317   def _runAppli(self, args=None):
318     if args is None:
319       args = []
320     # Initialize SALOME environment
321     sys.argv = ['runSalome'] + args
322     import setenv
323     setenv.main(True, exeName="salome start")
324
325     import runSalome
326     runSalome.runSalome()
327     return 0
328   #
329
330   def _setContext(self, args=None):
331     salome_context_set = os.getenv("SALOME_CONTEXT_SET")
332     if salome_context_set:
333       print("***")
334       print("*** SALOME context has already been set.")
335       print("*** Enter 'exit' (only once!) to leave SALOME context.")
336       print("***")
337       return 0
338
339     os.environ["SALOME_CONTEXT_SET"] = "yes"
340     print("***")
341     print("*** SALOME context is now set.")
342     print("*** Enter 'exit' (only once!) to leave SALOME context.")
343     print("***")
344
345     cmd = ["/bin/bash"]
346     proc = subprocess.Popen(cmd, shell=False, close_fds=True)
347     proc.communicate()
348     return proc.returncode
349   #
350
351   def _runSession(self, args=None):
352     if args is None:
353       args = []
354     sys.argv = ['runSession'] + args
355     import runSession
356     params, args = runSession.configureSession(args, exe="salome shell")
357
358     sys.argv = ['runSession'] + args
359     import setenv
360     setenv.main(True)
361
362     return runSession.runSession(params, args)
363   #
364
365   def _runConsole(self, args=None):
366     if args is None:
367       args = []
368     # Initialize SALOME environment
369     sys.argv = ['runConsole']
370     import setenv
371     setenv.main(True)
372
373     import runConsole
374     return runConsole.connect(args)
375   #
376
377   def _kill(self, args=None):
378     if args is None:
379       args = []
380     ports = args
381     if not ports:
382       print("Port number(s) not provided to command: salome kill <port(s)>")
383       return 1
384
385     from multiprocessing import Process
386     from killSalomeWithPort import killMyPort
387     import tempfile
388     for port in ports:
389       with tempfile.NamedTemporaryFile():
390         p = Process(target = killMyPort, args=(port,))
391         p.start()
392         p.join()
393     return 0
394   #
395
396   def _killAll(self, unused=None):
397     try:
398       import PortManager # mandatory
399       from multiprocessing import Process
400       from killSalomeWithPort import killMyPort
401       ports = PortManager.getBusyPorts()['this']
402
403       if ports:
404         import tempfile
405         for port in ports:
406           with tempfile.NamedTemporaryFile():
407             p = Process(target = killMyPort, args=(port,))
408             p.start()
409             p.join()
410     except ImportError:
411       # :TODO: should be declared obsolete
412       from killSalome import killAllPorts
413       killAllPorts()
414       pass
415     return 0
416   #
417
418   def _runTests(self, args=None):
419     if args is None:
420       args = []
421     sys.argv = ['runTests']
422     import setenv
423     setenv.main(True)
424
425     import runTests
426     return runTests.runTests(args, exe="salome test")
427   #
428
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")
433     versions = {}
434     max_len = 0
435     with open(filename) as f:
436       for line in f:
437         try:
438           software, version, sha1 = line.split()
439           versions[software.upper()] = version
440           if len(software) > max_len:
441             max_len = len(software)
442         except:
443           pass
444         pass
445       pass
446     if softwares:
447       for soft in softwares:
448         if soft.upper() in versions:
449           print(soft.upper().rjust(max_len), versions[soft.upper()])
450     else:
451       import collections
452       od = collections.OrderedDict(sorted(versions.items()))
453       for name, version in od.items():
454         print(name.rjust(max_len), versions[name])
455     pass
456
457   def _showInfo(self, args=None):
458     if args is None:
459       args = []
460
461     usage = "Usage: salome info [options]"
462     epilog  = """\n
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.
471 """
472     if not args:
473       args = ["--version"]
474
475     if "-h" in args or "--help" in args:
476       print(usage + epilog)
477       return 0
478
479     if "-p" in args or "--ports" in args:
480       import PortManager
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:")
486           if this_ports:
487               print("   This application:", this_ports)
488           else:
489               print("   No SALOME instances of this application")
490           if other_ports:
491               print("   Other applications:", other_ports)
492           else:
493               print("   No SALOME instances of other applications")
494       else:
495           print("No SALOME instances are running")
496
497     if "-s" in args or "--softwares" in args:
498       if "-s" in args:
499         index = args.index("-s")
500       else:
501         index = args.index("--softwares")
502       indexEnd=index+1
503       while indexEnd < len(args) and args[indexEnd][0] != "-":
504         indexEnd = indexEnd + 1
505       self._showSoftwareVersions(softwares=args[index+1:indexEnd])
506
507     if "-v" in args or "--version" in args:
508       print("Running with python", platform.python_version())
509       return self._runAppli(["--version"])
510
511     return 0
512   #
513
514   def _showDoc(self, args=None):
515     if args is None:
516       args = []
517
518     modules = args
519     if not modules:
520       print("Module(s) not provided to command: salome doc <module(s)>")
521       return 1
522
523     appliPath = os.getenv("ABSOLUTE_APPLI_PATH")
524     if not appliPath:
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()
535       else:
536         print("Online documentation is not accessible for module:", module)
537
538   def _usage(self, unused=None):
539     usage()
540   #
541
542   def _makeCoffee(self, unused=None):
543     print("                        (")
544     print("                          )     (")
545     print("                   ___...(-------)-....___")
546     print("               .-\"\"       )    (          \"\"-.")
547     print("         .-\'``\'|-._             )         _.-|")
548     print("        /  .--.|   `\"\"---...........---\"\"`   |")
549     print("       /  /    |                             |")
550     print("       |  |    |                             |")
551     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("                        `\"\"\"----\"\"\"`")
565     print("")
566     print("                    SALOME is working for you; what else?")
567     print("")
568   #
569
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("                        \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"    \\        /")
597     print("                                               \"------\"")
598     print("")
599     print("                                Drive your simulation properly with SALOME!")
600     print("")
601   #
602
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'):
608       del d['_logger']
609     return d
610   #
611   def __setstate__(self, d):
612     self.__dict__.update(d) # I *think* this is a safe way to do it
613   #
614   # Excluding self._logger from pickle operation imply using the following method to access logger
615   def getLogger(self):
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)
621     return self._logger
622   #
623
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())
628
629     status = context._startSalome(args)
630     sys.exit(status)
631   else:
632     usage()
633 #