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]
Copyright (C) 2012-2017 Edgewall Software