# HG changeset patch # User cmlenz # Date 1124711890 0 # Node ID d72d68471c10170ba0343d13eaf364a972126b99 # Parent 3ab91c56d7bc08f0b4422ad5a46d778505cd13b6 Improvements to program execution from recipe commands. Also, adds a `` command, but that doesn't really work yet (doesn't get input from stdin). Related to #34. diff --git a/bitten/build/pythontools.py b/bitten/build/pythontools.py --- a/bitten/build/pythontools.py +++ b/bitten/build/pythontools.py @@ -64,13 +64,9 @@ ctxt.error('Cannot execute Python module %s: %s' % (module, e)) return - if args: - args = file_ + ' ' + args - else: - args = file_ - from bitten.build import shtools - shtools.exec_(ctxt, file_='python', output=output, args=args) + shtools.exec_(ctxt, executable='python', file_=file_, output=output, + args=args) def pylint(ctxt, file_=None): """Extract data from a `pylint` run written to a file.""" diff --git a/bitten/build/shtools.py b/bitten/build/shtools.py --- a/bitten/build/shtools.py +++ b/bitten/build/shtools.py @@ -27,22 +27,28 @@ log = logging.getLogger('bitten.build.shtools') -def exec_(ctxt, file_=None, output=None, args=None): +def exec_(ctxt, executable=None, file_=None, output=None, args=None): """Execute a shell script.""" - assert file_, 'Missing required attribute "file"' + assert executable or file_, \ + 'Either "executable" or "file" attribute required' if args: args = shlex.split(args) else: args = [] + if executable is None: + executable = file_ + elif file_: + args[:0] = [file_] + output_file = None if output: output = ctxt.resolve(output) output_file = file(output, 'w') try: - cmdline = Commandline(file_, args, cwd=ctxt.basedir) + cmdline = Commandline(executable, args, cwd=ctxt.basedir) log_elem = xmlio.Fragment() for out, err in cmdline.execute(): if out is not None: @@ -61,4 +67,55 @@ output_file.close() if cmdline.returncode != 0: - ctxt.error('exec failed (%s)' % cmdline.returncode) + ctxt.error('Executing %s failed (%s)' % (file_, cmdline.returncode)) + +def pipe(ctxt, executable=None, file_=None, input_=None, output=None, + args=None): + """Pipe the contents of a file through a script.""" + assert file_, 'Missing required attribute "file"' + assert input_, 'Missing required attribute "file"' + + if args: + args = shlex.split(args) + else: + args = [] + + if os.path.isfile(ctxt.resolve(file_)): + file_ = ctxt.resolve(file_) + + if executable is None: + executable = file_ + elif file_: + args[:0] = [file_] + + input_file = file(ctxt.resolve(input_), 'r') + + output_file = None + if output: + output = ctxt.resolve(output) + output_file = file(output, 'w') + + try: + cmdline = Commandline(executable, args, input=input_file, + cwd=ctxt.basedir) + log_elem = xmlio.Fragment() + for out, err in cmdline.execute(): + if out is not None: + log.info(out) + xmlio.SubElement(log_elem, 'message', level='info')[out] + if output: + output_file.write(out + os.linesep) + if err is not None: + log.error(err) + xmlio.SubElement(log_elem, 'message', level='error')[err] + if output: + output_file.write(err + os.linesep) + ctxt.log(log_elem) + finally: + input_file.close() + if output: + output_file.close() + + if cmdline.returncode != 0: + ctxt.error('Piping through %s failed (%s)' % (file_, + cmdline.returncode)) diff --git a/bitten/util/cmdline.py b/bitten/util/cmdline.py --- a/bitten/util/cmdline.py +++ b/bitten/util/cmdline.py @@ -20,7 +20,8 @@ import logging import os -import os.path +import shlex +import shutil import time log = logging.getLogger('bitten.cmdline') @@ -34,7 +35,7 @@ """Simple helper for executing subprocesses.""" # TODO: Use 'subprocess' module if available (Python >= 2.4) - def __init__(self, executable, args, cwd=None): + def __init__(self, executable, args, input=None, cwd=None): """Initialize the Commandline object. @param executable The name of the program to execute @@ -44,11 +45,32 @@ """ self.executable = executable self.arguments = [str(arg) for arg in args] + self.input = input self.cwd = cwd if self.cwd: assert os.path.isdir(self.cwd) self.returncode = None + # TODO: On windows, map file name extension to application + if os.name == 'nt': + pass + + # Shebang support for Posix systems + if os.path.isfile(self.executable): + executable_file = file(self.executable, 'r') + try: + for line in executable_file: + if line.startswith('#!'): + parts = shlex.split(line[2:]) + if len(parts) > 1: + self.arguments[:0] = parts[1:] + [self.executable] + else: + self.arguments[:0] = [self.executable] + self.executable = parts[0] + break + finally: + executable_file.close() + if os.name == 'nt': # windows def execute(self, timeout=None): @@ -59,6 +81,7 @@ log.debug('Executing %s', args) if self.cwd: + old_cwd = os.getcwd() os.chdir(self.cwd) import tempfile @@ -78,16 +101,25 @@ yield out_line and out_line.rstrip(), \ err_line and err_line.rstrip() + if self.cwd: + os.chdir(old_cwd) + else: # posix def execute(self, timeout=None): import fcntl, popen2, select if self.cwd: + old_cwd = os.getcwd() os.chdir(self.cwd) log.debug('Executing %s', [self.executable] + self.arguments) pipe = popen2.Popen3([self.executable] + self.arguments, capturestderr=True) + if self.input: + if isinstance(self.input, basestring): + pipe.tochild.write(self.input) + else: + shutil.copyfileobj(self.input, pipe.tochild) pipe.tochild.close() def make_non_blocking(fd): @@ -125,7 +157,11 @@ yield out_line, err_line time.sleep(.1) self.returncode = pipe.wait() - log.debug('Exited with code %s', self.returncode) + log.debug('%s exited with code %s', self.executable, + self.returncode) + + if self.cwd: + os.chdir(old_cwd) def _combine(self, *iterables): iterables = [iter(iterable) for iterable in iterables]