Salome HOME
Fix for "23677: [CEA 13186] cannot save study to h5 format" issue.
[modules/kernel.git] / bin / salomeContext.py
1 # Copyright (C) 2013-2017  CEA/DEN, EDF R&D, OPEN CASCADE
2 #
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.
7 #
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.
12 #
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
16 #
17 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 #
19
20 import os
21 import sys
22 import logging
23 import configparser
24
25 from parseConfigFile import parseConfigFile
26
27 import tempfile
28 import pickle
29 import subprocess
30 import platform
31
32 from salomeContextUtils import SalomeContextException
33
34 def usage():
35   msg = '''\
36 Usage: salome [command] [options] [--config=<file,folder,...>]
37
38 Commands:
39 =========
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.
56
57 If no command is given, default is start.
58
59 Command options:
60 ================
61     Use salome <command> --help to show help on command. Available for the
62     following commands: start, shell, connect, test, info.
63
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
68     any blank characters.
69 '''
70
71   print(msg)
72 #
73
74 """
75 The SalomeContext class in an API to configure SALOME context then
76 start SALOME using a single python command.
77
78 """
79 class SalomeContext:
80   """
81   Initialize context from a list of configuration files
82   identified by their names.
83   These files should be in appropriate .cfg format.
84   """
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:
89        return
90     configFileNames = configFileNames or []
91     if len(configFileNames) == 0:
92       raise SalomeContextException("No configuration files given")
93
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)
99       else:
100         self.getLogger().error("Unrecognized extension for configuration file: %s", filename)
101   #
102
103   def __loadEnvModules(self, env_modules):
104     print("Trying to load env modules: %s ..." % ' '.join(env_modules))
105     modulecmd = os.getenv('LMOD_CMD')
106     if not modulecmd:
107       print('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       print("OK")
113     except:
114       print("** Failed **")
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 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)
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       'connect' : '_runConsole',
221       'kill'    : '_kill',
222       'killall' : '_killAll',
223       'test'    : '_runTests',
224       'info'    : '_showInfo',
225       'doc'     : '_showDoc',
226       'help'    : '_usage',
227       'coffee'  : '_makeCoffee',
228       'car'     : '_getCar',
229       }
230
231     if command not in availableCommands:
232       command = "start"
233       options = args
234
235     return availableCommands[command], options
236   #
237
238   """
239   Run SALOME!
240   Args consist in a mandatory command followed by optional parameters.
241   See usage for details on commands.
242   """
243   def _startSalome(self, args):
244     import os
245     import sys
246     try:
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")
253
254     except:
255       pass
256
257     command, options = self.__parseArguments(args)
258     sys.argv = options
259
260     if command is None:
261       if args and args[0] in ["-h","--help","help"]:
262         usage()
263         return 0
264       # try to default to "start" command
265       command = "_runAppli"
266
267     try:
268       res = getattr(self, command)(options) # run appropriate method
269       return res or 0
270     except SystemExit as ex:
271       if ex.code != 0:
272         self.getLogger().error("SystemExit %s in method %s.", ex.code, command)
273       return ex.code
274     except SalomeContextException as e:
275       self.getLogger().error(e)
276       return 1
277     except Exception:
278       self.getLogger().error("Unexpected error:")
279       import traceback
280       traceback.print_exc()
281       return 1
282   #
283
284   def __setContextFromConfigFile(self, filename, reserved=None):
285     if reserved is None:
286       reserved = []
287     try:
288       unsetVars, configVars, reservedDict = parseConfigFile(filename, reserved)
289     except SalomeContextException as e:
290       msg = "%s"%e
291       self.getLogger().error(msg)
292       return 1
293
294     # unset variables
295     for var in unsetVars:
296       self.unsetVariable(var)
297
298     # set context
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=' ')
305       else:
306         self.addToVariable(reserved, reformattedVals)
307       pass
308
309     for key,val in configVars:
310       self.setVariable(key, val, overwrite=True)
311       pass
312
313     pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
314     pythonpath = [ os.path.realpath(x) for x in pythonpath ]
315     sys.path[:0] = pythonpath
316   #
317
318   def _runAppli(self, args=None):
319     if args is None:
320       args = []
321     # Initialize SALOME environment
322     sys.argv = ['runSalome'] + args
323     import setenv
324     setenv.main(True, exeName="salome start")
325
326     import runSalome
327     runSalome.runSalome()
328     return 0
329   #
330
331   def _setContext(self, args=None):
332     salome_context_set = os.getenv("SALOME_CONTEXT_SET")
333     if salome_context_set:
334       print("***")
335       print("*** SALOME context has already been set.")
336       print("*** Enter 'exit' (only once!) to leave SALOME context.")
337       print("***")
338       return 0
339
340     os.environ["SALOME_CONTEXT_SET"] = "yes"
341     print("***")
342     print("*** SALOME context is now set.")
343     print("*** Enter 'exit' (only once!) to leave SALOME context.")
344     print("***")
345
346     cmd = ["/bin/bash"]
347     proc = subprocess.Popen(cmd, shell=False, close_fds=True)
348     proc.communicate()
349     return proc.returncode
350   #
351
352   def _runSession(self, args=None):
353     if args is None:
354       args = []
355     sys.argv = ['runSession'] + args
356     import runSession
357     params, args = runSession.configureSession(args, exe="salome shell")
358
359     sys.argv = ['runSession'] + args
360     import setenv
361     setenv.main(True)
362
363     return runSession.runSession(params, args)
364   #
365
366   def _runConsole(self, args=None):
367     if args is None:
368       args = []
369     # Initialize SALOME environment
370     sys.argv = ['runConsole']
371     import setenv
372     setenv.main(True)
373
374     import runConsole
375     return runConsole.connect(args)
376   #
377
378   def _kill(self, args=None):
379     if args is None:
380       args = []
381     ports = args
382     if not ports:
383       print("Port number(s) not provided to command: salome kill <port(s)>")
384       return 1
385
386     from multiprocessing import Process
387     from killSalomeWithPort import killMyPort
388     import tempfile
389     for port in ports:
390       with tempfile.NamedTemporaryFile():
391         p = Process(target = killMyPort, args=(port,))
392         p.start()
393         p.join()
394     return 0
395   #
396
397   def _killAll(self, unused=None):
398     try:
399       import PortManager # mandatory
400       from multiprocessing import Process
401       from killSalomeWithPort import killMyPort
402       ports = PortManager.getBusyPorts()['this']
403
404       if ports:
405         import tempfile
406         for port in ports:
407           with tempfile.NamedTemporaryFile():
408             p = Process(target = killMyPort, args=(port,))
409             p.start()
410             p.join()
411     except ImportError:
412       # :TODO: should be declared obsolete
413       from killSalome import killAllPorts
414       killAllPorts()
415       pass
416     return 0
417   #
418
419   def _runTests(self, args=None):
420     if args is None:
421       args = []
422     sys.argv = ['runTests']
423     import setenv
424     setenv.main(True)
425
426     import runTests
427     return runTests.runTests(args, exe="salome test")
428   #
429
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")
434     versions = {}
435     max_len = 0
436     with open(filename) as f:
437       for line in f:
438         try:
439           software, version, sha1 = line.split()
440           versions[software.upper()] = version
441           if len(software) > max_len:
442             max_len = len(software)
443         except:
444           pass
445         pass
446       pass
447     if softwares:
448       for soft in softwares:
449         if soft.upper() in versions:
450           print(soft.upper().rjust(max_len), versions[soft.upper()])
451     else:
452       import collections
453       od = collections.OrderedDict(sorted(versions.items()))
454       for name, version in od.items():
455         print(name.rjust(max_len), versions[name])
456     pass
457
458   def _showInfo(self, args=None):
459     if args is None:
460       args = []
461
462     usage = "Usage: salome info [options]"
463     epilog  = """\n
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.
472 """
473     if not args:
474       args = ["--version"]
475
476     if "-h" in args or "--help" in args:
477       print(usage + epilog)
478       return 0
479
480     if "-p" in args or "--ports" in args:
481       import PortManager
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:")
487           if this_ports:
488               print("   This application:", this_ports)
489           else:
490               print("   No SALOME instances of this application")
491           if other_ports:
492               print("   Other applications:", other_ports)
493           else:
494               print("   No SALOME instances of other applications")
495       else:
496           print("No SALOME instances are running")
497
498     if "-s" in args or "--softwares" in args:
499       if "-s" in args:
500         index = args.index("-s")
501       else:
502         index = args.index("--softwares")
503       indexEnd=index+1
504       while indexEnd < len(args) and args[indexEnd][0] != "-":
505         indexEnd = indexEnd + 1
506       self._showSoftwareVersions(softwares=args[index+1:indexEnd])
507
508     if "-v" in args or "--version" in args:
509       print("Running with python", platform.python_version())
510       return self._runAppli(["--version"])
511
512     return 0
513   #
514
515   def _showDoc(self, args=None):
516     if args is None:
517       args = []
518
519     modules = args
520     if not modules:
521       print("Module(s) not provided to command: salome doc <module(s)>")
522       return 1
523
524     appliPath = os.getenv("ABSOLUTE_APPLI_PATH")
525     if not appliPath:
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()
536       else:
537         print("Online documentation is not accessible for module:", module)
538
539   def _usage(self, unused=None):
540     usage()
541   #
542
543   def _makeCoffee(self, unused=None):
544     print("                        (")
545     print("                          )     (")
546     print("                   ___...(-------)-....___")
547     print("               .-\"\"       )    (          \"\"-.")
548     print("         .-\'``\'|-._             )         _.-|")
549     print("        /  .--.|   `\"\"---...........---\"\"`   |")
550     print("       /  /    |                             |")
551     print("       |  |    |                             |")
552     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("                        `\"\"\"----\"\"\"`")
566     print("")
567     print("                    SALOME is working for you; what else?")
568     print("")
569   #
570
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("                        \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"    \\        /")
598     print("                                               \"------\"")
599     print("")
600     print("                                Drive your simulation properly with SALOME!")
601     print("")
602   #
603
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'):
609       del d['_logger']
610     return d
611   #
612   def __setstate__(self, d):
613     self.__dict__.update(d) # I *think* this is a safe way to do it
614   #
615   # Excluding self._logger from pickle operation imply using the following method to access logger
616   def getLogger(self):
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)
622     return self._logger
623   #
624
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())
629
630     status = context._startSalome(args)
631     sys.exit(status)
632   else:
633     usage()
634 #