Mercurial > bitten > bitten-test
view bitten/build/shtools.py @ 752:673ec182679d
Allow ''timeout'' parameter to be set on sh:exec, python:exec and
python:distutils commands, which kills the commands after the given
number of seconds. This makes it much easier to deal with runaway
processes.
Small fix to killing processes-- ''kill'' isn't an attribute of
subprocess. It's an attribute of subprocess.Popen.
This is a good solution for #380-- set limits on the processes running
the steps, and you'll eventually get through the build. Other tickets
for ignoring a build fill in the rest. Closes #380.
Will update http://bitten.edgewall.org/wiki/Documentation/commands.html when
this goes in.
author | wbell |
---|---|
date | Sat, 24 Apr 2010 13:21:29 +0000 |
parents | 0f5456e9d5da |
children | 630b48a53fc6 |
line wrap: on
line source
# -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de> # Copyright (C) 2007 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://bitten.edgewall.org/wiki/License. """Generic recipe commands for executing external processes.""" import codecs import logging import os import shlex from bitten.build import CommandLine from bitten.util import xmlio log = logging.getLogger('bitten.build.shtools') __docformat__ = 'restructuredtext en' def exec_(ctxt, executable=None, file_=None, output=None, args=None, dir_=None, timeout=None): """Execute a program or shell script. :param ctxt: the build context :type ctxt: `Context` :param executable: name of the executable to run :param file\_: name of the script file, relative to the project directory, that should be run :param output: name of the file to which the output of the script should be written :param args: command-line arguments to pass to the script :param timeout: the number of seconds before the external process should be aborted (has same constraints as CommandLine) """ assert executable or file_, \ 'Either "executable" or "file" attribute required' returncode = execute(ctxt, executable=executable, file_=file_, output=output, args=args, dir_=dir_, timeout=timeout) if returncode != 0: ctxt.error('Executing %s failed (error code %s)' % (executable or file_, returncode)) def pipe(ctxt, executable=None, file_=None, input_=None, output=None, args=None, dir_=None): """Pipe the contents of a file through a program or shell script. :param ctxt: the build context :type ctxt: `Context` :param executable: name of the executable to run :param file\_: name of the script file, relative to the project directory, that should be run :param input\_: name of the file containing the data that should be passed to the shell script on its standard input stream :param output: name of the file to which the output of the script should be written :param args: command-line arguments to pass to the script """ assert executable or file_, \ 'Either "executable" or "file" attribute required' assert input_, 'Missing required attribute "input"' returncode = execute(ctxt, executable=executable, file_=file_, input_=input_, output=output, args=args, dir_=dir_) if returncode != 0: ctxt.error('Piping through %s failed (error code %s)' % (executable or file_, returncode)) def execute(ctxt, executable=None, file_=None, input_=None, output=None, args=None, dir_=None, filter_=None, timeout=None): """Generic external program execution. This function is not itself bound to a recipe command, but rather used from other commands. :param ctxt: the build context :type ctxt: `Context` :param executable: name of the executable to run :param file\_: name of the script file, relative to the project directory, that should be run :param input\_: name of the file containing the data that should be passed to the shell script on its standard input stream :param output: name of the file to which the output of the script should be written :param args: command-line arguments to pass to the script :param filter\_: function to filter out messages from the executable stdout :param timeout: the number of seconds before the external process should be aborted (has same constraints as CommandLine) """ if args: if isinstance(args, basestring): args = shlex.split(args) else: args = [] if dir_: def resolve(*args): return ctxt.resolve(dir_, *args) else: resolve = ctxt.resolve if file_ and os.path.isfile(resolve(file_)): file_ = resolve(file_) shell = False if file_ and os.name == 'nt': # Need to execute script files through a shell on Windows shell = True if executable is None: executable = file_ elif file_: args[:0] = [file_] # Support important Windows CMD.EXE built-ins (and it does its own quoting) if os.name == 'nt' and executable.upper() in ['COPY', 'DIR', 'ECHO', 'ERASE', 'DEL', 'MKDIR', 'MD', 'MOVE', 'RMDIR', 'RD', 'TYPE']: shell = True if input_: input_file = codecs.open(resolve(input_), 'r', 'utf-8') else: input_file = None if output: output_file = codecs.open(resolve(output), 'w', 'utf-8') else: output_file = None if dir_ and os.path.isdir(ctxt.resolve(dir_)): dir_ = ctxt.resolve(dir_) else: dir_ = ctxt.basedir if not filter_: filter_=lambda s: s if timeout: timeout = int(timeout) try: cmdline = CommandLine(executable, args, input=input_file, cwd=dir_, shell=shell) log_elem = xmlio.Fragment() for out, err in cmdline.execute(timeout=timeout): if out is not None: log.info(out) info = filter_(out) if info: log_elem.append(xmlio.Element('message', level='info')[ info.replace(ctxt.basedir + os.sep, '') .replace(ctxt.basedir, '') ]) if output: output_file.write(out + os.linesep) if err is not None: log.error(err) log_elem.append(xmlio.Element('message', level='error')[ err.replace(ctxt.basedir + os.sep, '') .replace(ctxt.basedir, '') ]) if output: output_file.write(err + os.linesep) ctxt.log(log_elem) finally: if input_: input_file.close() if output: output_file.close() return cmdline.returncode