Mercurial > bitten > bitten-test
changeset 57:ef78d71667ad
Added simple helper class for executing commandline programs.
author | cmlenz |
---|---|
date | Mon, 27 Jun 2005 11:19:54 +0000 |
parents | 033366d81def |
children | 02db4eabf0a9 |
files | bitten/build/ctools.py bitten/build/pythontools.py bitten/slave.py bitten/util/cmdline.py |
diffstat | 4 files changed, 159 insertions(+), 36 deletions(-) [+] |
line wrap: on
line diff
--- a/bitten/build/ctools.py +++ b/bitten/build/ctools.py @@ -18,21 +18,17 @@ # # Author: Christopher Lenz <cmlenz@gmx.de> -from popen2 import Popen3 +from bitten.util.cmdline import Commandline def make(basedir, target='all'): """Execute a Makefile target.""" - cmdline = 'make %s' % target - pipe = Popen3(cmdline, capturestderr=True) # FIXME: Windows compatibility - while True: - retval = pipe.poll() - if retval != -1: - break - line = pipe.fromchild.readline() - if line: - print '[make] %s' % line.rstrip() - line = pipe.childerr.readline() - if line: - print '[make] %s' % line.rstrip() - if retval != 0: - raise BuildError, "Executing distutils failed (%s)" % retval + cmdline = Commandline('make', ['-C', basedir, target]) + for out, err in cmdline.execute(timeout=100.0): + if out: + for line in out.splitlines(): + print '[make] %s' % line + if err: + for line in err.splitlines(): + print '[make] %s' % err + if cmdline.returncode != 0: + raise BuildError, "Executing make failed (%s)" % retval
--- a/bitten/build/pythontools.py +++ b/bitten/build/pythontools.py @@ -19,29 +19,19 @@ # Author: Christopher Lenz <cmlenz@gmx.de> import re -from popen2 import Popen3 from bitten import BuildError +from bitten.util.cmdline import Commandline def distutils(basedir, command='build'): """Execute a `distutils` command.""" - cmdline = 'python setup.py %s' % command - pipe = Popen3(cmdline, capturestderr=True) # FIXME: Windows compatibility - while True: - retval = pipe.poll() - while True: - line = pipe.fromchild.readline() - if not line: - break - print '[distutils] %s' % line.rstrip() - while True: - line = pipe.childerr.readline() - if not line: - break - print '[distutils] %s' % line.rstrip() - if retval != -1: - break - if retval != 0: + cmdline = Commandline('python', ['setup.py', command], cwd=basedir) + for out, err in cmdline.execute(timeout=100.0): + if out: + print '[distutils] %s' % out + if err: + print '[distutils] %s' % err + if cmdline.returncode != 0: raise BuildError, "Executing distutils failed (%s)" % retval def pylint(basedir, file=None): @@ -60,7 +50,7 @@ # TODO: emit to build master def trace(basedir, summary=None, coverdir=None, include=None, exclude=None): - """Extract data from a `trac.py` run.""" + """Extract data from a `trace.py` run.""" assert summary, 'Missing required attribute "summary"' assert coverdir, 'Missing required attribute "coverdir"'
--- a/bitten/slave.py +++ b/bitten/slave.py @@ -108,7 +108,7 @@ # Unpack the archive prefix = archive.unpack(archive_path, workdir) path = os.path.join(workdir, prefix) - logging.info('Unpacked snapshot to %s' % path) + logging.debug('Unpacked snapshot to %s' % path) # Fix permissions for root, dirs, files in os.walk(workdir, topdown=False): @@ -134,7 +134,6 @@ for function, args in step: logging.debug('Executing command "%s"', function) function(recipe.basedir, **args) - xml = xmlio.Element('step', id=step.id, result='success', description=step.description) self.channel.send_ans(msgno, beep.MIMEMessage(xml))
new file mode 100644 --- /dev/null +++ b/bitten/util/cmdline.py @@ -0,0 +1,138 @@ +# -*- coding: iso8859-1 -*- +# +# Copyright (C) 2005 Christopher Lenz <cmlenz@gmx.de> +# +# Bitten is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# Trac is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# Author: Christopher Lenz <cmlenz@gmx.de> + +import os +import os.path + + +class TimeoutError(Exception): + """Exception raised when the execution of a command times out.""" + + +class Commandline(object): + """Simple helper for executing subprocesses.""" + # TODO: Use 'subprocess' module if available (Python >= 2.4) + # TODO: Windows implementation based on temporary files + + def __init__(self, executable, args, cwd=None): + """Initialize the Commandline object. + + @param executable The name of the program to execute + @param args A list of arguments to pass to the executable + @param cwd The working directory to change to before executing the + command + """ + self.executable = executable + self.arguments = args or [] + self.cwd = cwd + if self.cwd: + assert os.path.isdir(self.cwd) + self.returncode = None + + if os.name == 'nt': # windows + def execute(self, timeout=None): + raise NotImplementedError + + else: # posix + def execute(self, timeout=None): + import fcntl, popen2, select + if self.cwd: + os.chdir(self.cwd) + pipe = popen2.Popen3([self.executable] + self.arguments, + capturestderr=True) + pipe.tochild.close() + + def make_non_blocking(fd): + fl = fcntl.fcntl(fd, fcntl.F_GETFL) + try: + fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NDELAY) + except AttributeError: + fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.FNDELAY) + + out_file, err_file = pipe.fromchild, pipe.childerr + map(make_non_blocking, [out_file.fileno(), err_file.fileno()]) + out_data, err_data = [], [] + out_eof = err_eof = False + while True: + to_check = [out_file] * (not out_eof) + [err_file] * (not err_eof) + ready = select.select(to_check, [], [], timeout) + if not ready[0]: + raise TimeoutError, 'Command %s timed out' % self.executable + if out_file in ready[0]: + data = out_file.read() + if data: + out_data.append(data) + else: + out_eof = True + if err_file in ready[0]: + data = err_file.read() + if data: + err_data.append(data) + else: + err_eof = True + + out_lines = self._extract_lines(out_data) + err_lines = self._extract_lines(err_data) + for out_line, err_line in self._combine(out_lines, err_lines): + yield out_line, err_line + + if out_eof and err_eof: + break + self.returncode = pipe.wait() + + def _combine(self, *iterables): + iterables = [iter(iterable) for iterable in iterables] + size = len(iterables) + while [iterable for iterable in iterables if iterable is not None]: + to_yield = [None] * size + for idx, iterable in enumerate(iterables): + if iterable is None: + continue + try: + to_yield[idx] = iterable.next() + except StopIteration: + iterables[idx] = None + yield tuple(to_yield) + + def _extract_lines(self, data): + def endswith_linesep(string): + for linesep in ('\n', '\r\n', '\r'): + if lines[0].endswith(linesep): + return True + extracted = [] + while data: + chunk = data[0] + lines = chunk.splitlines(True) + if len(lines) > 1: + extracted += lines[:-1] + if endswith_linesep(lines[-1]): + extracted.append(lines[-1]) + data.pop(0) + else: + data[0] = lines[-1] + elif endswith_linesep(chunk): + extracted.append(chunk) + data.pop(0) + elif len(data) > 2: + data[1] = data[0] + data[1] + data.pop(0) + else: + break + return [line.rstrip() for line in extracted]