Salome HOME
remove tmp_package directory if success sat package
[tools/sat.git] / salomeTools.py
index 33288fb863c314cb7e628357c1ec6be82e6c030d..84429e4d3f5bf98be045c6a3763f879848cfa984 100755 (executable)
 #  You should have received a copy of the GNU Lesser General Public
 #  License along with this library; if not, write to the Free Software
 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
-
 '''This file is the main entry file to salomeTools
 '''
 
 # python imports
 import os
 import sys
+import re
+import tempfile
 import imp
 import types
 import gettext
+import traceback
 
 # salomeTools imports
 import src
@@ -44,6 +46,12 @@ import config
 #es.install()
 gettext.install('salomeTools', os.path.join(satdir, 'src', 'i18n'))
 
+# The possible hooks : 
+# pre is for hooks to be executed before commands
+# post is for hooks to be executed after commands
+C_PRE_HOOK = "pre"
+C_POST_HOOK = "post"
+
 def find_command_list(dirPath):
     ''' Parse files in dirPath that end with .py : it gives commands list
     
@@ -63,24 +71,35 @@ lCommand = find_command_list(cmdsdir)
 
 # Define all possible option for salomeTools command :  sat <option> <args>
 parser = src.options.Options()
-parser.add_option('h', 'help', 'boolean', 'help', _("shows global help or help on a specific command."))
-parser.add_option('o', 'overwrite', 'list', "overwrite", _("overwrites a configuration parameters."))
-parser.add_option('g', 'debug', 'boolean', 'debug_mode', _("run salomeTools in debug mode."))
-parser.add_option('l', 'level', 'int', "output_level", _("change output level (default is 3)."))
-parser.add_option('s', 'silent', 'boolean', 'silent', _("do not write log or show errors."))
+parser.add_option('h', 'help', 'boolean', 'help', 
+                  _("shows global help or help on a specific command."))
+parser.add_option('o', 'overwrite', 'list', "overwrite", 
+                  _("overwrites a configuration parameters."))
+parser.add_option('g', 'debug', 'boolean', 'debug_mode', 
+                  _("run salomeTools in debug mode."))
+parser.add_option('v', 'verbose', 'int', "output_verbose_level", 
+                  _("change output verbose level (default is 3)."))
+parser.add_option('b', 'batch', 'boolean', "batch", 
+                  _("batch mode (no question)."))
+parser.add_option('t', 'all_in_terminal', 'boolean', "all_in_terminal", 
+                  _("All traces in the terminal (for example compilation logs)."))
+parser.add_option('l', 'logs_paths_in_file', 'string', "logs_paths_in_file", 
+                  _("Put the command result and paths to log files in ."))
 
 class Sat(object):
     '''The main class that stores all the commands of salomeTools
     '''
-    def __init__(self, opt='', dataDir=None):
+    def __init__(self, opt='', datadir=None):
         '''Initialization
         
         :param opt str: The sat options 
-        :param: dataDir str : the directory that contain all the external data (like software pyconf and software scripts)
+        :param: datadir str : the directory that contain all the external 
+                              data (like software pyconf and software scripts)
         '''
-        # Read the salomeTools options (the list of possible options is at the beginning of this file)
+        # Read the salomeTools options (the list of possible options is 
+        # at the beginning of this file)
         try:
-            (options, argus) = parser.parse_args(opt.split(' '))
+            (options, argus) = parser.parse_args(opt)
         except Exception as exc:
             write_exception(exc)
             sys.exit(-1)
@@ -90,9 +109,9 @@ class Sat(object):
         self.cfg = None # the config that will be read using pyconf module
         self.arguments = opt
         self.options = options # the options passed to salomeTools
-        self.dataDir = dataDir # default value will be <salomeTools root>/data
+        self.datadir = datadir # default value will be <salomeTools root>/data
         # set the commands by calling the dedicated function
-        self.__setCommands__(cmdsdir)
+        self._setCommands(cmdsdir)
         
         # if the help option has been called, print help and exit
         if options.help:
@@ -104,7 +123,8 @@ class Sat(object):
                 sys.exit(1)
 
     def __getattr__(self, name):
-        ''' overwrite of __getattr__ function in order to display a customized message in case of a wrong call
+        ''' overwrite of __getattr__ function in order to display 
+            a customized message in case of a wrong call
         
         :param name str: The name of the attribute 
         '''
@@ -113,65 +133,288 @@ class Sat(object):
         else:
             raise AttributeError(name + _(" is not a valid command"))
     
-    def __setCommands__(self, dirPath):
-        '''set class attributes corresponding to all commands that are in the dirPath directory
+    def _setCommands(self, dirPath):
+        '''set class attributes corresponding to all commands that are 
+           in the dirPath directory
         
         :param dirPath str: The directory path containing the commands 
         '''
         # loop on the commands name
         for nameCmd in lCommand:
+            
+            # Exception for the jobs command that requires the paramiko module
+            if nameCmd == "jobs":
+                try:
+                    saveout = sys.stderr
+                    ff = tempfile.TemporaryFile()
+                    sys.stderr = ff
+                    import paramiko
+                    sys.stderr = saveout
+                except:
+                    sys.stderr = saveout
+                    continue
+
             # load the module that has name nameCmd in dirPath
             (file_, pathname, description) = imp.find_module(nameCmd, [dirPath])
             module = imp.load_module(nameCmd, file_, pathname, description)
             
-            def run_command(args='', logger=None):
+            def run_command(args='',
+                            options=None,
+                            batch = False,
+                            verbose = -1,
+                            logger_add_link = None):
                 '''The function that will load the configuration (all pyconf)
                 and return the function run of the command corresponding to module
                 
-                :param args str: The directory path containing the commands 
+                :param args str: The arguments of the command 
                 '''
-                argv = args.split(" ")
+                # Make sure the internationalization is available
+                gettext.install('salomeTools', os.path.join(satdir, 'src', 'i18n'))
                 
+                # Get the arguments in a list and remove the empty elements
+                if type(args) == type(''):
+                    # split by spaces without considering spaces in quotes
+                    argv_0 = re.findall(r'(?:"[^"]*"|[^\s"])+', args)
+                else:
+                    argv_0 = args
+                
+                if argv_0 != ['']:
+                    while "" in argv_0: argv_0.remove("")
+                
+                # Format the argv list in order to prevent strings 
+                # that contain a blank to be separated
+                argv = []
+                elem_old = ""
+                for elem in argv_0:
+                    if argv == [] or elem_old.startswith("-") or elem.startswith("-"):
+                        argv.append(elem)
+                    else:
+                        argv[-1] += " " + elem
+                    elem_old = elem
+                           
                 # if it is provided by the command line, get the application
                 appliToLoad = None
-                if argv != [''] and argv[0][0] != "-":
+                if argv not in [[''], []] and argv[0][0] != "-":
                     appliToLoad = argv[0].rstrip('*')
                     argv = argv[1:]
                 
+                # Check if the global options of salomeTools have to be changed
+                if options:
+                    options_save = self.options
+                    self.options = options  
+
                 # read the configuration from all the pyconf files    
                 cfgManager = config.ConfigManager()
-                self.cfg = cfgManager.getConfig(dataDir=self.dataDir, application=appliToLoad, options=self.options, command=__nameCmd__)
-                    
+                self.cfg = cfgManager.get_config(datadir=self.datadir, 
+                                                 application=appliToLoad, 
+                                                 options=self.options, 
+                                                 command=__nameCmd__)
+                               
+                # Set the verbose mode if called
+                if verbose > -1:
+                    verbose_save = self.options.output_verbose_level
+                    self.options.__setattr__("output_verbose_level", verbose)    
+
+                # Set batch mode if called
+                if batch:
+                    batch_save = self.options.batch
+                    self.options.__setattr__("batch", True)
+
                 # set output level
-                if self.options.output_level:
-                    self.cfg.USER.output_level = self.options.output_level
-                if self.cfg.USER.output_level < 1:
-                    self.cfg.USER.output_level = 1
-
-                # create log file, unless the command is called with a logger as parameter
-                logger_command = src.logger.Logger(self.cfg, silent_sysstd=self.options.silent)
-                if logger:
-                    logger_command = logger
+                if self.options.output_verbose_level is not None:
+                    self.cfg.USER.output_verbose_level = self.options.output_verbose_level
+                if self.cfg.USER.output_verbose_level < 1:
+                    self.cfg.USER.output_verbose_level = 0
+                silent = (self.cfg.USER.output_verbose_level == 0)
+
+                # create log file
+                micro_command = False
+                if logger_add_link:
+                    micro_command = True
+                logger_command = src.logger.Logger(self.cfg, 
+                                   silent_sysstd=silent,
+                                   all_in_terminal=self.options.all_in_terminal,
+                                   micro_command=micro_command)
+                
+                # Check that the path given by the logs_paths_in_file option
+                # is a file path that can be written
+                if self.options.logs_paths_in_file and not micro_command:
+                    try:
+                        self.options.logs_paths_in_file = os.path.abspath(
+                                                self.options.logs_paths_in_file)
+                        dir_file = os.path.dirname(self.options.logs_paths_in_file)
+                        if not os.path.exists(dir_file):
+                            os.makedirs(dir_file)
+                        if os.path.exists(self.options.logs_paths_in_file):
+                            os.remove(self.options.logs_paths_in_file)
+                        file_test = open(self.options.logs_paths_in_file, "w")
+                        file_test.close()
+                    except Exception as e:
+                        msg = _("WARNING: the logs_paths_in_file option will "
+                                "not be taken into account.\nHere is the error:")
+                        logger_command.write("%s\n%s\n\n" % (
+                                             src.printcolors.printcWarning(msg),
+                                             str(e)))
+                        self.options.logs_paths_in_file = None
                 
+                options_launched = ""
+                res = None
                 try:
-                    # Execute the run method of the command
+                    # Execute the hooks (if there is any) 
+                    # and run method of the command
+                    self.run_hook(__nameCmd__, C_PRE_HOOK, logger_command)
                     res = __module__.run(argv, self, logger_command)
+                    self.run_hook(__nameCmd__, C_POST_HOOK, logger_command)
+                    if res is None:
+                        res = 0
+                        
+                except Exception as e:
+                    # Get error
+                    logger_command.write("\n***** ", 1)
+                    logger_command.write(src.printcolors.printcError(
+                                                       "salomeTools ERROR:"), 1)
+                    logger_command.write("\n" + str(e) + "\n\n", 1)
+                    # get stack
+                    __, __, exc_traceback = sys.exc_info()
+                    fp = tempfile.TemporaryFile()
+                    traceback.print_tb(exc_traceback, file=fp)
+                    fp.seek(0)
+                    stack = fp.read()
+                    verbosity = 5
+                    if self.options.debug_mode:
+                        verbosity = 1
+                    logger_command.write("TRACEBACK: %s" % stack.replace('"',"'"),
+                                         verbosity)
                 finally:
-                    # put final attributes in xml log file (end time, total time, ...) and write it
-                    launchedCommand = ' '.join([self.cfg.VARS.salometoolsway + os.path.sep + 'sat', self.arguments.split(' ')[0], args])
-                    logger_command.endWrite({"launchedCommand" : launchedCommand})
+                    # set res if it is not set in the command
+                    if res is None:
+                        res = 1
+                                            
+                    # come back to the original global options
+                    if options:
+                        options_launched = get_text_from_options(self.options)
+                        self.options = options_save
+                    
+                    # come back in the original batch mode if 
+                    # batch argument was called
+                    if batch:
+                        self.options.__setattr__("batch", batch_save)
+
+                    # come back in the original verbose mode if 
+                    # verbose argument was called                        
+                    if verbose > -1:
+                        self.options.__setattr__("output_verbose_level", 
+                                                 verbose_save)
+                    # put final attributes in xml log file 
+                    # (end time, total time, ...) and write it
+                    launchedCommand = ' '.join([self.cfg.VARS.salometoolsway +
+                                                os.path.sep +
+                                                'sat',
+                                                options_launched,
+                                                __nameCmd__, 
+                                                ' '.join(argv_0)])
+                    launchedCommand = launchedCommand.replace('"', "'")
+                    
+                    # Add a link to the parent command      
+                    if logger_add_link is not None:
+                        logger_add_link.add_link(logger_command.logFileName,
+                                                 __nameCmd__,
+                                                 res,
+                                                 launchedCommand)
+                        logger_add_link.l_logFiles += logger_command.l_logFiles
+                                            
+                    # Put the final attributes corresponding to end time and
+                    # Write the file to the hard drive
+                    logger_command.end_write(
+                                        {"launchedCommand" : launchedCommand})
+                    
+                    if res != 0:
+                        res = 1
+                        
+                    # print the log file path if 
+                    # the maximum verbose mode is invoked
+                    if not micro_command:
+                        logger_command.write("\nPath to the xml log file :\n",
+                                             5)
+                        logger_command.write("%s\n\n" % src.printcolors.printcInfo(
+                                                logger_command.logFilePath), 5)
+
+                    # If the logs_paths_in_file was called, write the result
+                    # and log files in the given file path
+                    if self.options.logs_paths_in_file and not micro_command:
+                        file_res = open(self.options.logs_paths_in_file, "w")
+                        file_res.write(str(res) + "\n")
+                        for i, filepath in enumerate(logger_command.l_logFiles):
+                            file_res.write(filepath)
+                            if i < len(logger_command.l_logFiles):
+                                file_res.write("\n")
+                                file_res.flush()
                 
                 return res
 
-            # Make sure that run_command will be redefined at each iteration of the loop
+            # Make sure that run_command will be redefined 
+            # at each iteration of the loop
             globals_up = {}
             globals_up.update(run_command.__globals__)
             globals_up.update({'__nameCmd__': nameCmd, '__module__' : module})
-            func = types.FunctionType(run_command.__code__, globals_up, run_command.__name__,run_command.__defaults__, run_command.__closure__)
+            func = types.FunctionType(run_command.__code__,
+                                      globals_up,
+                                      run_command.__name__,
+                                      run_command.__defaults__,
+                                      run_command.__closure__)
 
             # set the attribute corresponding to the command
             self.__setattr__(nameCmd, func)
 
+    def run_hook(self, cmd_name, hook_type, logger):
+        '''Execute a hook file for a given command regarding the fact 
+           it is pre or post
+        
+        :param cmd_name str: The the command on which execute the hook
+        :param hook_type str: pre or post
+        :param logger Logger: the logging instance to use for the prints
+        '''
+        # The hooks must be defined in the application pyconf
+        # So, if there is no application, do not do anything
+        if not src.config_has_application(self.cfg):
+            return
+
+        # The hooks must be defined in the application pyconf in the
+        # APPLICATION section, hook : { command : 'script_path.py'}
+        if "hook" not in self.cfg.APPLICATION \
+                    or cmd_name not in self.cfg.APPLICATION.hook:
+            return
+
+        # Get the hook_script path and verify that it exists
+        hook_script_path = self.cfg.APPLICATION.hook[cmd_name]
+        if not os.path.exists(hook_script_path):
+            raise src.SatException(_("Hook script not found: %s") % 
+                                   hook_script_path)
+        
+        # Try to execute the script, catch the exception if it fails
+        try:
+            # import the module (in the sense of python)
+            pymodule = imp.load_source(cmd_name, hook_script_path)
+            
+            # format a message to be printed at hook execution
+            msg = src.printcolors.printcWarning(_("Run hook script"))
+            msg = "%s: %s\n" % (msg, 
+                                src.printcolors.printcInfo(hook_script_path))
+            
+            # run the function run_pre_hook if this function is called 
+            # before the command, run_post_hook if it is called after
+            if hook_type == C_PRE_HOOK and "run_pre_hook" in dir(pymodule):
+                logger.write(msg, 1)
+                pymodule.run_pre_hook(self.cfg, logger)
+            elif hook_type == C_POST_HOOK and "run_post_hook" in dir(pymodule):
+                logger.write(msg, 1)
+                pymodule.run_post_hook(self.cfg, logger)
+
+        except Exception as exc:
+            msg = _("Unable to run hook script: %s") % hook_script_path
+            msg += "\n" + str(exc)
+            raise src.SatException(msg)
+
     def print_help(self, opt):
         '''Prints help for a command. Function called when "sat -h <command>"
         
@@ -183,9 +426,9 @@ class Sat(object):
             return
         # get command name
         command = opt[0]
-        # read the configuration from all the pyconf files    
+        # read the configuration from all the pyconf files
         cfgManager = config.ConfigManager()
-        self.cfg = cfgManager.getConfig(dataDir=self.dataDir)
+        self.cfg = cfgManager.get_config(datadir=self.datadir)
 
         # Check if this command exists
         if not hasattr(self, command):
@@ -219,15 +462,31 @@ class Sat(object):
         (file_, pathname, description) = imp.find_module(module, [cmdsdir])
         module = imp.load_module(module, file_, pathname, description)
         return module
+
+def get_text_from_options(options):
+    text_options = ""
+    for attr in dir(options):
+        if attr.startswith("__"):
+            continue
+        if options.__getattr__(attr) != None:
+            option_contain = options.__getattr__(attr)
+            if type(option_contain)==type([]):
+                option_contain = ",".join(option_contain)
+            if type(option_contain)==type(True):
+                option_contain = ""
+            text_options+= "--%s %s " % (attr, option_contain)
+    return text_options
+                
+
 def print_version():
     '''prints salomeTools version (in src/internal_config/salomeTools.pyconf)
     '''
     # read the config 
     cfgManager = config.ConfigManager()
-    cfg = cfgManager.getConfig()
+    cfg = cfgManager.get_config()
     # print the key corresponding to salomeTools version
-    print(src.printcolors.printcHeader( _("Version: ") ) + cfg.INTERNAL.sat_version + '\n')
+    print(src.printcolors.printcHeader( _("Version: ") ) + 
+          cfg.INTERNAL.sat_version + '\n')
 
 
 def print_help():
@@ -237,7 +496,8 @@ def print_help():
     '''
     print_version()
     
-    print(src.printcolors.printcHeader( _("Usage: ") ) + "sat [sat_options] <command> [product] [command_options]\n")
+    print(src.printcolors.printcHeader( _("Usage: ") ) + 
+          "sat [sat_options] <command> [product] [command_options]\n")
 
     parser.print_help()
 
@@ -247,7 +507,8 @@ def print_help():
         print(" - %s" % (command))
         
     # Explain how to get the help for a specific command
-    print(src.printcolors.printcHeader(_("\nGetting the help for a specific command: ")) + "sat --help <command>\n")
+    print(src.printcolors.printcHeader(_("\nGetting the help for a specific"
+                                    " command: ")) + "sat --help <command>\n")
 
 def write_exception(exc):
     '''write exception in case of error in a command
@@ -272,22 +533,13 @@ if __name__ == "__main__":
         sys.exit(0)
     
     # instantiate the salomeTools class with correct options
-    sat = Sat(' '.join(sys.argv[1:]))
+    sat = Sat(sys.argv[1:])
     # the command called
     command = args[0]
     # get dynamically the command function to call
     fun_command = sat.__getattr__(command)
-    # call the command with two cases : mode debug or not
-    if options.debug_mode:
-        # call classically the command and if it fails, show exception and stack (usual python mode)
-        code = fun_command(' '.join(args[1:]))
-    else:
-        # catch exception in order to show less verbose but elegant message
-        try:
-            code = fun_command(' '.join(args[1:]))
-        except Exception as exc:
-            code = 1
-            write_exception(exc)
+    # Run the command using the arguments
+    code = fun_command(args[1:])
     
     # exit salomeTools with the right code (0 if no errors, else 1)
     if code is None: code = 0