]> SALOME platform Git repositories - modules/yacs.git/blob - bin/salomeContext.py
Salome HOME
add salome doc command
[modules/yacs.git] / bin / salomeContext.py
1 # Copyright (C) 2013-2016  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 from parseConfigFile import convertEnvFileToConfigFile
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 to start.
59
60 Command options:
61 ================
62     Use salome <command> --help to show help on command ; available for commands:
63     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, whithout
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 (new .cfg) format.
85   However you can give old .sh environment files; in this case,
86   the SalomeContext class will try to automatically convert them
87   to .cfg format before setting the context.
88   """
89   def __init__(self, configFileNames=0):
90     self.getLogger().setLevel(logging.INFO)
91     #it could be None explicitely (if user use multiples setVariable...for standalone)
92     if configFileNames is None:
93        return
94     configFileNames = configFileNames or []
95     if len(configFileNames) == 0:
96       raise SalomeContextException("No configuration files given")
97
98     reserved=['PATH', 'DYLD_LIBRARY_PATH', 'LD_LIBRARY_PATH', 'PYTHONPATH', 'MANPATH', 'PV_PLUGIN_PATH', 'INCLUDE', 'LIBPATH', 'SALOME_PLUGINS_PATH']
99     for filename in configFileNames:
100       basename, extension = os.path.splitext(filename)
101       if extension == ".cfg":
102         self.__setContextFromConfigFile(filename, reserved)
103       elif extension == ".sh":
104         #new convert procedures, temporary could be use not to be automatically deleted
105         #temp = tempfile.NamedTemporaryFile(suffix='.cfg', delete=False)
106         temp = tempfile.NamedTemporaryFile(suffix='.cfg')
107         try:
108           convertEnvFileToConfigFile(filename, temp.name, reserved)
109           self.__setContextFromConfigFile(temp.name, reserved)
110           temp.close()
111         except (ConfigParser.ParsingError, ValueError) as e:
112           self.getLogger().error("Invalid token found when parsing file: %s\n"%(filename))
113           temp.close()
114           sys.exit(1)
115       else:
116         self.getLogger().warning("Unrecognized extension for configuration file: %s", filename)
117   #
118
119   def __loadMPI(self, module_name):
120     print "Trying to load MPI module: %s..."%module_name,
121     try:
122       out, err = subprocess.Popen(["modulecmd", "python", "load", module_name], stdout=subprocess.PIPE).communicate()
123       exec out # define specific environment variables
124       print " OK"
125     except:
126       print " ** Failed **"
127       pass
128   #
129
130   def runSalome(self, args):
131     import os
132     # Run this module as a script, in order to use appropriate Python interpreter
133     # according to current path (initialized from context files).
134     mpi_module_option = "--with-mpi-module="
135     mpi_module = [x for x in args if x.startswith(mpi_module_option)]
136     if mpi_module:
137       mpi_module = mpi_module[0][len(mpi_module_option):]
138       self.__loadMPI(mpi_module)
139       args = [x for x in args if not x.startswith(mpi_module_option)]
140     else:
141       mpi_module = os.getenv("SALOME_MPI_MODULE_NAME", None)
142       if mpi_module:
143         self.__loadMPI(mpi_module)
144
145     absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
146     env_copy = os.environ.copy()
147     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)
148     out, err = proc.communicate()
149     return out, err, proc.returncode
150   #
151
152   """Append value to PATH environment variable"""
153   def addToPath(self, value):
154     self.addToVariable('PATH', value)
155   #
156
157   """Append value to LD_LIBRARY_PATH environment variable"""
158   def addToLdLibraryPath(self, value):
159     if platform.system() == 'Windows':
160       self.addToVariable('PATH', value)
161     else:
162       self.addToVariable('LD_LIBRARY_PATH', value)
163   #
164
165   """Append value to DYLD_LIBRARY_PATH environment variable"""
166   def addToDyldLibraryPath(self, value):
167     self.addToVariable('DYLD_LIBRARY_PATH', value)
168   #
169
170   """Append value to PYTHONPATH environment variable"""
171   def addToPythonPath(self, value):
172     self.addToVariable('PYTHONPATH', value)
173   #
174
175   """Set environment variable to value"""
176   def setVariable(self, name, value, overwrite=False):
177     env = os.getenv(name, '')
178     if env and not overwrite:
179       self.getLogger().warning("Environment variable already existing (and not overwritten): %s=%s", name, value)
180       return
181
182     if env:
183       self.getLogger().warning("Overwriting environment variable: %s=%s", name, value)
184
185     value = os.path.expandvars(value) # expand environment variables
186     self.getLogger().debug("Set environment variable: %s=%s", name, value)
187     os.environ[name] = value
188   #
189
190   """Unset environment variable"""
191   def unsetVariable(self, name):
192     if os.environ.has_key(name):
193       del os.environ[name]
194   #
195
196   """Append value to environment variable"""
197   def addToVariable(self, name, value, separator=os.pathsep):
198     if value == '':
199       return
200
201     value = os.path.expandvars(value) # expand environment variables
202     self.getLogger().debug("Add to %s: %s", name, value)
203     env = os.getenv(name, None)
204     if env is None:
205       os.environ[name] = value
206     else:
207       os.environ[name] = value + separator + env
208   #
209
210   ###################################
211   # This begins the private section #
212   ###################################
213
214   def __parseArguments(self, args):
215     if len(args) == 0 or args[0].startswith("-"):
216       return None, args
217
218     command = args[0]
219     options = args[1:]
220
221     availableCommands = {
222       'start'   : '_runAppli',
223       'context' : '_setContext',
224       'shell'   : '_runSession',
225       'connect' : '_runConsole',
226       'kill'    : '_kill',
227       'killall' : '_killAll',
228       'test'    : '_runTests',
229       'info'    : '_showInfo',
230       'doc'     : '_showDoc',
231       'help'    : '_usage',
232       'coffee'  : '_makeCoffee',
233       'car'     : '_getCar',
234       }
235
236     if not command in availableCommands.keys():
237       command = "start"
238       options = args
239
240     return availableCommands[command], options
241   #
242
243   """
244   Run SALOME!
245   Args consist in a mandatory command followed by optionnal parameters.
246   See usage for details on commands.
247   """
248   def _startSalome(self, args):
249     import os
250     import sys
251     try:
252       from setenv import add_path
253       absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
254       path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome"))
255       add_path(path, "PYTHONPATH")
256       path = os.path.realpath(os.path.join(absoluteAppliPath, "bin", "salome", "appliskel"))
257       add_path(path, "PYTHONPATH")
258
259     except:
260       pass
261
262     command, options = self.__parseArguments(args)
263     sys.argv = options
264
265     if command is None:
266       if args and args[0] in ["-h","--help","help"]:
267         usage()
268         sys.exit(0)
269       # try to default to "start" command
270       command = "_runAppli"
271
272     try:
273       res = getattr(self, command)(options) # run appropriate method
274       return res or (None, None)
275     except SystemExit, returncode:
276       if returncode != 0:
277         self.getLogger().warning("SystemExit %s in method %s.", returncode, command)
278       sys.exit(returncode)
279     except StandardError:
280       self.getLogger().error("Unexpected error:")
281       import traceback
282       traceback.print_exc()
283       sys.exit(1)
284     except SalomeContextException, e:
285       self.getLogger().error(e)
286       sys.exit(1)
287   #
288
289   def __setContextFromConfigFile(self, filename, reserved=None):
290     if reserved is None:
291       reserved = []
292     try:
293       unsetVars, configVars, reservedDict = parseConfigFile(filename, reserved)
294     except SalomeContextException, e:
295       msg = "%s"%e
296       file_dir = os.path.dirname(filename)
297       file_base = os.path.basename(filename)
298       base_no_ext, ext = os.path.splitext(file_base)
299       sh_file = os.path.join(file_dir, base_no_ext+'.sh')
300       #if ext == ".cfg" and os.path.isfile(sh_file):
301       if False:
302         msg += "Found similar %s file; trying to parse this one instead..."%(base_no_ext+'.sh')
303         temp = tempfile.NamedTemporaryFile(suffix='.cfg')
304         try:
305           convertEnvFileToConfigFile(sh_file, temp.name, reserved)
306           self.__setContextFromConfigFile(temp.name, reserved)
307           msg += "OK\n"
308           self.getLogger().warning(msg)
309           temp.close()
310           return
311         except (ConfigParser.ParsingError, ValueError) as e:
312           msg += "Invalid token found when parsing file: %s\n"%(sh_file)
313           self.getLogger().error(msg)
314           temp.close()
315           sys.exit(1)
316       else:
317         self.getLogger().error(msg)
318         sys.exit(1)
319
320     # unset variables
321     for var in unsetVars:
322       self.unsetVariable(var)
323
324     # set context
325     for reserved in reservedDict:
326       a = filter(None, reservedDict[reserved]) # remove empty elements
327       a = [ os.path.realpath(x) for x in a ]
328       reformattedVals = os.pathsep.join(a)
329       if reserved in ["INCLUDE", "LIBPATH"]:
330         self.addToVariable(reserved, reformattedVals, separator=' ')
331       else:
332         self.addToVariable(reserved, reformattedVals)
333       pass
334
335     for key,val in configVars:
336       self.setVariable(key, val, overwrite=True)
337       pass
338
339     pythonpath = os.getenv('PYTHONPATH','').split(os.pathsep)
340     pythonpath = [ os.path.realpath(x) for x in pythonpath ]
341     sys.path[:0] = pythonpath
342   #
343
344   def _runAppli(self, args=None):
345     if args is None:
346       args = []
347     # Initialize SALOME environment
348     sys.argv = ['runSalome'] + args
349     import setenv
350     setenv.main(True, exeName="salome start")
351
352     import runSalome
353     runSalome.runSalome()
354   #
355
356   def _setContext(self, args=None):
357     salome_context_set = os.getenv("SALOME_CONTEXT_SET")
358     if salome_context_set:
359       print "***"
360       print "*** SALOME context has already been set."
361       print "*** Enter 'exit' (only once!) to leave SALOME context."
362       print "***"
363       return
364
365     os.environ["SALOME_CONTEXT_SET"] = "yes"
366     print "***"
367     print "*** SALOME context is now set."
368     print "*** Enter 'exit' (only once!) to leave SALOME context."
369     print "***"
370
371     cmd = ["/bin/bash"]
372     proc = subprocess.Popen(cmd, shell=False, close_fds=True)
373     return proc.communicate()
374   #
375
376   def _runSession(self, args=None):
377     if args is None:
378       args = []
379     sys.argv = ['runSession'] + args
380     import runSession
381     params, args = runSession.configureSession(args, exe="salome shell")
382
383     sys.argv = ['runSession'] + args
384     import setenv
385     setenv.main(True)
386
387     return runSession.runSession(params, args)
388   #
389
390   def _runConsole(self, args=None):
391     if args is None:
392       args = []
393     # Initialize SALOME environment
394     sys.argv = ['runConsole']
395     import setenv
396     setenv.main(True)
397
398     import runConsole
399     return runConsole.connect(args)
400   #
401
402   def _kill(self, args=None):
403     if args is None:
404       args = []
405     ports = args
406     if not ports:
407       print "Port number(s) not provided to command: salome kill <port(s)>"
408       return
409
410     from multiprocessing import Process
411     from killSalomeWithPort import killMyPort
412     import tempfile
413     for port in ports:
414       with tempfile.NamedTemporaryFile():
415         p = Process(target = killMyPort, args=(port,))
416         p.start()
417         p.join()
418     pass
419   #
420
421   def _killAll(self, unused=None):
422     try:
423       import PortManager # mandatory
424       from multiprocessing import Process
425       from killSalomeWithPort import killMyPort
426       ports = PortManager.getBusyPorts()
427
428       if ports:
429         import tempfile
430         for port in ports:
431           with tempfile.NamedTemporaryFile():
432             p = Process(target = killMyPort, args=(port,))
433             p.start()
434             p.join()
435     except ImportError:
436       # :TODO: should be declared obsolete
437       from killSalome import killAllPorts
438       killAllPorts()
439       pass
440   #
441
442   def _runTests(self, args=None):
443     if args is None:
444       args = []
445     sys.argv = ['runTests']
446     import setenv
447     setenv.main(True)
448
449     import runTests
450     return runTests.runTests(args, exe="salome test")
451   #
452
453   def _showSoftwareVersions(self, softwares=None):
454     config = ConfigParser.SafeConfigParser()
455     absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH')
456     filename = os.path.join(absoluteAppliPath, "sha1_collections.txt")
457     versions = {}
458     max_len = 0
459     with open(filename) as f:
460       for line in f:
461         try:
462           software, version, sha1 = line.split()
463           versions[software.upper()] = version
464           if len(software) > max_len:
465             max_len = len(software)
466         except:
467           pass
468         pass
469       pass
470     if softwares:
471       for soft in softwares:
472         if versions.has_key(soft.upper()):
473           print soft.upper().rjust(max_len), versions[soft.upper()]
474     else:
475       for name, version in versions.items():
476         print name.rjust(max_len), versions[name]
477     pass
478
479   def _showInfo(self, args=None):
480     if args is None:
481       args = []
482
483     usage = "Usage: salome info [options]"
484     epilog  = """\n
485 Display some information about SALOME.\n
486 Available options are:
487     -p,--ports        Show the list of busy ports (running SALOME instances).
488     -s,--softwares    Show the list and versions of SALOME softwares.
489     -v,--version      Show running SALOME version.
490     -h,--help         Show this message.
491 """
492     if not args:
493       args = ["--version"]
494
495     if "-h" in args or "--help" in args:
496       print usage + epilog
497       return
498
499     if "-p" in args or "--ports" in args:
500       import PortManager
501       ports = PortManager.getBusyPorts()
502       print "SALOME instances are running on ports:", ports
503       if ports:
504         print "Last started instance on port %s"%ports[-1]
505
506     if "-s" in args or "--softwares" in args:
507       self._showSoftwareVersions()
508
509     if "-v" in args or "--version" in args:
510       print "Running with python", platform.python_version()
511       self._runAppli(["--version"])
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
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     sys.exit(0)
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     sys.exit(0)
603   #
604
605   # Add the following two methods since logger is not pickable
606   # Ref: http://stackoverflow.com/questions/2999638/how-to-stop-attributes-from-being-pickled-in-python
607   def __getstate__(self):
608     d = dict(self.__dict__)
609     if hasattr(self, '_logger'):
610       del d['_logger']
611     return d
612   #
613   def __setstate__(self, d):
614     self.__dict__.update(d) # I *think* this is a safe way to do it
615   #
616   # Excluding self._logger from pickle operation imply using the following method to access logger
617   def getLogger(self):
618     if not hasattr(self, '_logger'):
619       self._logger = logging.getLogger(__name__)
620       #self._logger.setLevel(logging.DEBUG)
621       #self._logger.setLevel(logging.WARNING)
622       self._logger.setLevel(logging.ERROR)
623     return self._logger
624   #
625
626 if __name__ == "__main__":
627   if len(sys.argv) == 3:
628     context = pickle.loads(sys.argv[1])
629     args = pickle.loads(sys.argv[2])
630
631     (out, err) = context._startSalome(args)
632     if out:
633       sys.stdout.write(out)
634     if err:
635       sys.stderr.write(err)
636   else:
637     usage()
638 #