Salome HOME
Typo-fix by Kunda
[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     try:
106       out, err = subprocess.Popen(["modulecmd", "python", "load"] + env_modules, stdout=subprocess.PIPE).communicate()
107       exec(out) # define specific environment variables
108       print("OK")
109     except:
110       print("** Failed **")
111       pass
112   #
113
114   def runSalome(self, args):
115     import os
116     # Run this module as a script, in order to use appropriate Python interpreter
117     # according to current path (initialized from context files).
118     env_modules_option = "--with-env-modules="
119     env_modules_l = [x for x in args if x.startswith(env_modules_option)]
120     if env_modules_l:
121       env_modules = env_modules_l[-1][len(env_modules_option):].split(',')
122       self.__loadEnvModules(env_modules)
123       args = [x for x in args if not x.startswith(env_modules_option)]
124     else:
125       env_modules = os.getenv("SALOME_ENV_MODULES", None)
126       if env_modules:
127         self.__loadEnvModules(env_modules.split(','))
128
129     absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
130     env_copy = os.environ.copy()
131     proc = subprocess.Popen(['python', os.path.join(absoluteAppliPath,"bin","salome","salomeContext.py"), pickle.dumps(self), pickle.dumps(args)], shell=False, close_fds=True, env=env_copy)
132     out, err = proc.communicate()
133     return out, err, proc.returncode
134   #
135
136   """Append value to PATH environment variable"""
137   def addToPath(self, value):
138     self.addToVariable('PATH', value)
139   #
140
141   """Append value to LD_LIBRARY_PATH environment variable"""
142   def addToLdLibraryPath(self, value):
143     if platform.system() == 'Windows':
144       self.addToVariable('PATH', value)
145     elif platform.system() == 'Darwin':
146       if "LAPACK" in value:
147         self.addToVariable('DYLD_FALLBACK_LIBRARY_PATH', value)
148       else:
149         self.addToVariable('DYLD_LIBRARY_PATH', value)
150     else:
151       self.addToVariable('LD_LIBRARY_PATH', value)
152   #
153
154   """Append value to DYLD_LIBRARY_PATH environment variable"""
155   def addToDyldLibraryPath(self, value):
156     self.addToVariable('DYLD_LIBRARY_PATH', value)
157   #
158
159   """Append value to PYTHONPATH environment variable"""
160   def addToPythonPath(self, value):
161     self.addToVariable('PYTHONPATH', value)
162   #
163
164   """Set environment variable to value"""
165   def setVariable(self, name, value, overwrite=False):
166     env = os.getenv(name, '')
167     if env and not overwrite:
168       self.getLogger().error("Environment variable already existing (and not overwritten): %s=%s", name, value)
169       return
170
171     if env:
172       self.getLogger().debug("Overwriting environment variable: %s=%s", name, value)
173
174     value = os.path.expandvars(value) # expand environment variables
175     self.getLogger().debug("Set environment variable: %s=%s", name, value)
176     os.environ[name] = value
177   #
178
179   """Unset environment variable"""
180   def unsetVariable(self, name):
181     if os.environ.has_key(name):
182       del os.environ[name]
183   #
184
185   """Append value to environment variable"""
186   def addToVariable(self, name, value, separator=os.pathsep):
187     if value == '':
188       return
189
190     value = os.path.expandvars(value) # expand environment variables
191     self.getLogger().debug("Add to %s: %s", name, value)
192     env = os.getenv(name, None)
193     if env is None:
194       os.environ[name] = value
195     else:
196       os.environ[name] = value + separator + env
197   #
198
199   ###################################
200   # This begins the private section #
201   ###################################
202
203   def __parseArguments(self, args):
204     if len(args) == 0 or args[0].startswith("-"):
205       return None, args
206
207     command = args[0]
208     options = args[1:]
209
210     availableCommands = {
211       'start'   : '_runAppli',
212       'context' : '_setContext',
213       'shell'   : '_runSession',
214       'connect' : '_runConsole',
215       'kill'    : '_kill',
216       'killall' : '_killAll',
217       'test'    : '_runTests',
218       'info'    : '_showInfo',
219       'doc'     : '_showDoc',
220       'help'    : '_usage',
221       'coffee'  : '_makeCoffee',
222       'car'     : '_getCar',
223       }
224
225     if not command in availableCommands.keys():
226       command = "start"
227       options = args
228
229     return availableCommands[command], options
230   #
231
232   """
233   Run SALOME!
234   Args consist in a mandatory command followed by optional parameters.
235   See usage for details on commands.
236   """
237   def _startSalome(self, args):
238     import os
239     import sys
240     try:
241       from setenv import add_path
242       absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
243       path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome"))
244       add_path(path, "PYTHONPATH")
245       path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome", "appliskel"))
246       add_path(path, "PYTHONPATH")
247
248     except:
249       pass
250
251     command, options = self.__parseArguments(args)
252     sys.argv = options
253
254     if command is None:
255       if args and args[0] in ["-h","--help","help"]:
256         usage()
257         return 0
258       # try to default to "start" command
259       command = "_runAppli"
260
261     try:
262       res = getattr(self, command)(options) # run appropriate method
263       return res or 0
264     except SystemExit as ex:
265       if ex.code != 0:
266         self.getLogger().error("SystemExit %s in method %s.", ex.code, command)
267       return ex.code
268     except StandardError:
269       self.getLogger().error("Unexpected error:")
270       import traceback
271       traceback.print_exc()
272       return 1
273     except SalomeContextException, e:
274       self.getLogger().error(e)
275       return 1
276   #
277
278   def __setContextFromConfigFile(self, filename, reserved=None):
279     if reserved is None:
280       reserved = []
281     try:
282       unsetVars, configVars, reservedDict = parseConfigFile(filename, reserved)
283     except SalomeContextException, e:
284       msg = "%s"%e
285       self.getLogger().error(msg)
286       return 1
287
288     # unset variables
289     for var in unsetVars:
290       self.unsetVariable(var)
291
292     # set context
293     for reserved in reservedDict:
294       a = filter(None, reservedDict[reserved]) # remove empty elements
295       a = [ os.path.realpath(x) for x in a ]
296       reformattedVals = os.pathsep.join(a)
297       if reserved in ["INCLUDE", "LIBPATH"]:
298         self.addToVariable(reserved, reformattedVals, separator=' ')
299       else:
300         self.addToVariable(reserved, reformattedVals)
301       pass
302
303     for key,val in configVars:
304       self.setVariable(key, val, overwrite=True)
305       pass
306
307     pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
308     pythonpath = [ os.path.realpath(x) for x in pythonpath ]
309     sys.path[:0] = pythonpath
310   #
311
312   def _runAppli(self, args=None):
313     if args is None:
314       args = []
315     # Initialize SALOME environment
316     sys.argv = ['runSalome'] + args
317     import setenv
318     setenv.main(True, exeName="salome start")
319
320     import runSalome
321     runSalome.runSalome()
322     return 0
323   #
324
325   def _setContext(self, args=None):
326     salome_context_set = os.getenv("SALOME_CONTEXT_SET")
327     if salome_context_set:
328       print "***"
329       print "*** SALOME context has already been set."
330       print "*** Enter 'exit' (only once!) to leave SALOME context."
331       print "***"
332       return 0
333
334     os.environ["SALOME_CONTEXT_SET"] = "yes"
335     print "***"
336     print "*** SALOME context is now set."
337     print "*** Enter 'exit' (only once!) to leave SALOME context."
338     print "***"
339
340     cmd = ["/bin/bash"]
341     proc = subprocess.Popen(cmd, shell=False, close_fds=True)
342     proc.communicate()
343     return proc.returncode
344   #
345
346   def _runSession(self, args=None):
347     if args is None:
348       args = []
349     sys.argv = ['runSession'] + args
350     import runSession
351     params, args = runSession.configureSession(args, exe="salome shell")
352
353     sys.argv = ['runSession'] + args
354     import setenv
355     setenv.main(True)
356
357     return runSession.runSession(params, args)
358   #
359
360   def _runConsole(self, args=None):
361     if args is None:
362       args = []
363     # Initialize SALOME environment
364     sys.argv = ['runConsole']
365     import setenv
366     setenv.main(True)
367
368     import runConsole
369     return runConsole.connect(args)
370   #
371
372   def _kill(self, args=None):
373     if args is None:
374       args = []
375     ports = args
376     if not ports:
377       print "Port number(s) not provided to command: salome kill <port(s)>"
378       return 1
379
380     from multiprocessing import Process
381     from killSalomeWithPort import killMyPort
382     import tempfile
383     for port in ports:
384       with tempfile.NamedTemporaryFile():
385         p = Process(target = killMyPort, args=(port,))
386         p.start()
387         p.join()
388     return 0
389   #
390
391   def _killAll(self, unused=None):
392     try:
393       import PortManager # mandatory
394       from multiprocessing import Process
395       from killSalomeWithPort import killMyPort
396       ports = PortManager.getBusyPorts()['this']
397
398       if ports:
399         import tempfile
400         for port in ports:
401           with tempfile.NamedTemporaryFile():
402             p = Process(target = killMyPort, args=(port,))
403             p.start()
404             p.join()
405     except ImportError:
406       # :TODO: should be declared obsolete
407       from killSalome import killAllPorts
408       killAllPorts()
409       pass
410     return 0
411   #
412
413   def _runTests(self, args=None):
414     if args is None:
415       args = []
416     sys.argv = ['runTests']
417     import setenv
418     setenv.main(True)
419
420     import runTests
421     return runTests.runTests(args, exe="salome test")
422   #
423
424   def _showSoftwareVersions(self, softwares=None):
425     config = ConfigParser.SafeConfigParser()
426     absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
427     filename = os.path.join(absoluteAppliPath, "sha1_collections.txt")
428     versions = {}
429     max_len = 0
430     with open(filename) as f:
431       for line in f:
432         try:
433           software, version, sha1 = line.split()
434           versions[software.upper()] = version
435           if len(software) > max_len:
436             max_len = len(software)
437         except:
438           pass
439         pass
440       pass
441     if softwares:
442       for soft in softwares:
443         if versions.has_key(soft.upper()):
444           print soft.upper().rjust(max_len), versions[soft.upper()]
445     else:
446       import collections
447       od = collections.OrderedDict(sorted(versions.items()))
448       for name, version in od.iteritems():
449         print name.rjust(max_len), versions[name]
450     pass
451
452   def _showInfo(self, args=None):
453     if args is None:
454       args = []
455
456     usage = "Usage: salome info [options]"
457     epilog  = """\n
458 Display some information about SALOME.\n
459 Available options are:
460     -p,--ports                     Show the list of busy ports (running SALOME instances).
461     -s,--softwares [software(s)]   Show the list and versions of SALOME softwares.
462                                    Software names must be separated by blank characters.
463                                    If no software is given, show version of all softwares.
464     -v,--version                   Show running SALOME version.
465     -h,--help                      Show this message.
466 """
467     if not args:
468       args = ["--version"]
469
470     if "-h" in args or "--help" in args:
471       print usage + epilog
472       return 0
473
474     if "-p" in args or "--ports" in args:
475       import PortManager
476       ports = PortManager.getBusyPorts()
477       this_ports = ports['this']
478       other_ports = ports['other']
479       if this_ports or other_ports:
480           print "SALOME instances are running on the following ports:"
481           if this_ports:
482               print "   This application:", this_ports
483           else:
484               print "   No SALOME instances of this application"
485           if other_ports:
486               print "   Other applications:", other_ports
487           else:
488               print "   No SALOME instances of other applications"
489       else:
490           print "No SALOME instances are running"
491
492     if "-s" in args or "--softwares" in args:
493       if "-s" in args:
494         index = args.index("-s")
495       else:
496         index = args.index("--softwares")
497       indexEnd=index+1
498       while indexEnd < len(args) and args[indexEnd][0] != "-":
499         indexEnd = indexEnd + 1
500       self._showSoftwareVersions(softwares=args[index+1:indexEnd])
501
502     if "-v" in args or "--version" in args:
503       print "Running with python", platform.python_version()
504       return self._runAppli(["--version"])
505
506     return 0
507   #
508
509   def _showDoc(self, args=None):
510     if args is None:
511       args = []
512
513     modules = args
514     if not modules:
515       print "Module(s) not provided to command: salome doc <module(s)>"
516       return 1
517
518     appliPath = os.getenv("ABSOLUTE_APPLI_PATH")
519     if not appliPath:
520       raise SalomeContextException("Unable to find application path. Please check that the variable ABSOLUTE_APPLI_PATH is set.")
521     baseDir = os.path.join(appliPath, "share", "doc", "salome")
522     for module in modules:
523       docfile = os.path.join(baseDir, "gui", module.upper(), "index.html")
524       if not os.path.isfile(docfile):
525         docfile = os.path.join(baseDir, "tui", module.upper(), "index.html")
526       if not os.path.isfile(docfile):
527         docfile = os.path.join(baseDir, "dev", module.upper(), "index.html")
528       if os.path.isfile(docfile):
529         out, err = subprocess.Popen(["xdg-open", docfile]).communicate()
530       else:
531         print "Online documentation is not accessible for module:", module
532
533   def _usage(self, unused=None):
534     usage()
535   #
536
537   def _makeCoffee(self, unused=None):
538     print "                        ("
539     print "                          )     ("
540     print "                   ___...(-------)-....___"
541     print "               .-\"\"       )    (          \"\"-."
542     print "         .-\'``\'|-._             )         _.-|"
543     print "        /  .--.|   `\"\"---...........---\"\"`   |"
544     print "       /  /    |                             |"
545     print "       |  |    |                             |"
546     print "        \\  \\   |                             |"
547     print "         `\\ `\\ |                             |"
548     print "           `\\ `|            SALOME           |"
549     print "           _/ /\\            4 EVER           /"
550     print "          (__/  \\             <3            /"
551     print "       _..---\"\"` \\                         /`\"\"---.._"
552     print "    .-\'           \\                       /          \'-."
553     print "   :               `-.__             __.-\'              :"
554     print "   :                  ) \"\"---...---\"\" (                 :"
555     print "    \'._               `\"--...___...--\"`              _.\'"
556     print "      \\\"\"--..__                              __..--\"\"/"
557     print "       \'._     \"\"\"----.....______.....----\"\"\"     _.\'"
558     print "          `\"\"--..,,_____            _____,,..--\"\"`"
559     print "                        `\"\"\"----\"\"\"`"
560     print ""
561     print "                    SALOME is working for you; what else?"
562     print ""
563   #
564
565   def _getCar(self, unused=None):
566     print "                                              _____________"
567     print "                                  ..---:::::::-----------. ::::;;."
568     print "                               .\'\"\"\"\"\"\"                  ;;   \\  \":."
569     print "                            .\'\'                          ;     \\   \"\\__."
570     print "                          .\'                            ;;      ;   \\\\\";"
571     print "                        .\'                              ;   _____;   \\\\/"
572     print "                      .\'                               :; ;\"     \\ ___:\'."
573     print "                    .\'--...........................    : =   ____:\"    \\ \\"
574     print "               ..-\"\"                               \"\"\"\'  o\"\"\"     ;     ; :"
575     print "          .--\"\"  .----- ..----...    _.-    --.  ..-\"     ;       ;     ; ;"
576     print "       .\"\"_-     \"--\"\"-----\'\"\"    _-\"        .-\"\"         ;        ;    .-."
577     print "    .\'  .\'   SALOME             .\"         .\"              ;       ;   /. |"
578     print "   /-./\'         4 EVER <3    .\"          /           _..  ;       ;   ;;;|"
579     print "  :  ;-.______               /       _________==.    /_  \\ ;       ;   ;;;;"
580     print "  ;  / |      \"\"\"\"\"\"\"\"\"\"\".---.\"\"\"\"\"\"\"          :    /\" \". |;       ; _; ;;;"
581     print " /\"-/  |                /   /                  /   /     ;|;      ;-\" | ;\';"
582     print ":-  :   \"\"\"----______  /   /              ____.   .  .\"\'. ;;   .-\"..T\"   ."
583     print "\'. \"  ___            \"\":   \'\"\"\"\"\"\"\"\"\"\"\"\"\"\"    .   ; ;    ;; ;.\" .\"   \'--\""
584     print " \",   __ \"\"\"  \"\"---... :- - - - - - - - - \' \'  ; ;  ;    ;;\"  .\""
585     print "  /. ;  \"\"\"---___                             ;  ; ;     ;|.\"\""
586     print " :  \":           \"\"\"----.    .-------.       ;   ; ;     ;:"
587     print "  \\  \'--__               \\   \\        \\     /    | ;     ;;"
588     print "   \'-..   \"\"\"\"---___      :   .______..\\ __/..-\"\"|  ;   ; ;"
589     print "       \"\"--..       \"\"\"--\"        m l s         .   \". . ;"
590     print "             \"\"------...                  ..--\"\"      \" :"
591     print "                        \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"    \\        /"
592     print "                                               \"------\""
593     print ""
594     print "                                Drive your simulation properly with SALOME!"
595     print ""
596   #
597
598   # Add the following two methods since logger is not pickable
599   # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
600   def __getstate__(self):
601     d = dict(self.__dict__)
602     if hasattr(self, '_logger'):
603       del d['_logger']
604     return d
605   #
606   def __setstate__(self, d):
607     self.__dict__.update(d) # I *think* this is a safe way to do it
608   #
609   # Excluding self._logger from pickle operation imply using the following method to access logger
610   def getLogger(self):
611     if not hasattr(self, '_logger'):
612       self._logger = logging.getLogger(__name__)
613       #self._logger.setLevel(logging.DEBUG)
614       #self._logger.setLevel(logging.WARNING)
615       self._logger.setLevel(logging.ERROR)
616     return self._logger
617   #
618
619 if __name__ == "__main__":
620   if len(sys.argv) == 3:
621     context = pickle.loads(sys.argv[1])
622     args = pickle.loads(sys.argv[2])
623
624     status = context._startSalome(args)
625     sys.exit(status)
626   else:
627     usage()
628 #