# HG changeset patch # User cmlenz # Date 1128264051 0 # Node ID 24e91cbae6e01792ff980e5851b3d081aae4460b # Parent bc7b772360110d9f12b71a5c1ff64290ffd3f07d New recipe command `` for running Ant builds. diff --git a/bitten/build/config.py b/bitten/build/config.py --- a/bitten/build/config.py +++ b/bitten/build/config.py @@ -8,10 +8,13 @@ # are also available at http://bitten.cmlenz.net/wiki/License. from ConfigParser import SafeConfigParser +import logging import os import platform import re +log = logging.getLogger('bitten.config') + class Configuration(object): """Encapsulates the configuration of a build machine. @@ -101,6 +104,22 @@ _VAR_RE = re.compile(r'\$\{(?P\w[\w.]*?\w)(?:\:(?P.+))?\}') + def get_dirpath(self, key): + dirpath = self[key] + if dirpath: + if os.path.isdir(dirpath): + return dirpath + log.warning('Invalid %s: %s is not a directory', key, dirpath) + return None + + def get_filepath(self, key): + filepath = self[key] + if filepath: + if os.path.isfile(filepath): + return filepath + log.warning('Invalid %s: %s is not a file', key, filepath) + return None + def interpolate(self, text): """Interpolate configuration properties into a string. diff --git a/bitten/build/javatools.py b/bitten/build/javatools.py new file mode 100644 --- /dev/null +++ b/bitten/build/javatools.py @@ -0,0 +1,65 @@ +# -*- coding: iso8859-1 -*- +# +# Copyright (C) 2005 Christopher Lenz +# 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.cmlenz.net/wiki/License. + +import logging +import os +import tempfile + +from bitten.build import CommandLine +from bitten.util import xmlio + +log = logging.getLogger('bitten.build.javatools') + +def ant(ctxt, file_=None, target=None, keep_going=False): + """Run an Ant build.""" + executable = 'ant' + ant_home = ctxt.config.get_dirpath('ant.home') + if ant_home: + executable = os.path.join(ant_home, 'bin', 'ant') + + logfile = tempfile.NamedTemporaryFile(prefix='ant_log', suffix='.xml') + args = ['-noinput', '-listener', 'org.apache.tools.ant.XmlLogger', + '-Dant.XmlLogger.stylesheet.uri', '""', + '-DXmlLogger.file', logfile.name] + if file_: + args += ['-buildfile', ctxt.resolve(file_)] + if keep_going: + args.append('-keep-going') + if target: + args.append(target) + + cmdline = CommandLine(executable, args, cwd=ctxt.basedir) + for out, err in cmdline.execute(): + if out is not None: + log.info(out) + if err is not None: + log.error(err) + + log_elem = xmlio.Fragment() + try: + xml_log = xmlio.parse(logfile) + def collect_log_messages(node): + for child in node.children(): + if child.name == 'message': + if child.attr['priority'] == 'debug': + continue + log_elem.append(xmlio.Element('message', + level=child.attr['priority'])[ + child.gettext().replace(ctxt.basedir + os.sep, '') + .replace(ctxt.basedir, '') + ]) + else: + collect_log_messages(child) + collect_log_messages(xml_log) + except xmlio.ParseError, e: + log.warning('Error parsing Ant XML log file (%s)', e) + ctxt.log(log_elem) + + if cmdline.returncode != 0: + ctxt.error('Ant failed (%s)' % cmdline.returncode) diff --git a/bitten/build/pythontools.py b/bitten/build/pythontools.py --- a/bitten/build/pythontools.py +++ b/bitten/build/pythontools.py @@ -28,11 +28,9 @@ is returned; otherwise the path to the current Python interpreter is returned. """ - python_path = ctxt.config['python.path'] + python_path = ctxt.config.get_filepath('python.path') if python_path: - if os.path.isfile(python_path): - return python_path - log.warning('Invalid python.path: %s is not a file') + return python_path return sys.executable def distutils(ctxt, command='build', file_='setup.py'): diff --git a/bitten/build/tests/config.py b/bitten/build/tests/config.py --- a/bitten/build/tests/config.py +++ b/bitten/build/tests/config.py @@ -45,9 +45,8 @@ self.assertEqual('VERSION', config['version']) def test_sysinfo_configfile_override(self): - inifile, ininame = tempfile.mkstemp(prefix='bitten_test') - try: - os.write(inifile, """ + inifile = tempfile.NamedTemporaryFile(prefix='bitten_test') + inifile.write(""" [machine] name = MACHINE processor = PROCESSOR @@ -57,16 +56,14 @@ family = FAMILY version = VERSION """) - os.close(inifile) - config = Configuration(ininame) + inifile.seek(0) + config = Configuration(inifile.name) - self.assertEqual('MACHINE', config['machine']) - self.assertEqual('PROCESSOR', config['processor']) - self.assertEqual('OS', config['os']) - self.assertEqual('FAMILY', config['family']) - self.assertEqual('VERSION', config['version']) - finally: - os.remove(ininame) + self.assertEqual('MACHINE', config['machine']) + self.assertEqual('PROCESSOR', config['processor']) + self.assertEqual('OS', config['os']) + self.assertEqual('FAMILY', config['family']) + self.assertEqual('VERSION', config['version']) def test_package_properties(self): config = Configuration(properties={ @@ -78,21 +75,44 @@ self.assertEqual('2.3.5', config['python.version']) def test_package_configfile(self): - inifile, ininame = tempfile.mkstemp(prefix='bitten_test') - try: - os.write(inifile, """ + inifile = tempfile.NamedTemporaryFile(prefix='bitten_test') + inifile.write(""" [python] path = /usr/local/bin/python2.3 version = 2.3.5 """) - os.close(inifile) - config = Configuration(ininame) + inifile.seek(0) + config = Configuration(inifile.name) - self.assertEqual(True, 'python' in config.packages) - self.assertEqual('/usr/local/bin/python2.3', config['python.path']) - self.assertEqual('2.3.5', config['python.version']) + self.assertEqual(True, 'python' in config.packages) + self.assertEqual('/usr/local/bin/python2.3', config['python.path']) + self.assertEqual('2.3.5', config['python.version']) + + def test_get_dirpath_non_existant(self): + tempdir = tempfile.mkdtemp() + os.rmdir(tempdir) + config = Configuration(properties={'somepkg.home': tempdir}) + self.assertEqual(None, config.get_dirpath('somepkg.home')) + + def test_get_dirpath(self): + tempdir = tempfile.mkdtemp() + try: + config = Configuration(properties={'somepkg.home': tempdir}) + self.assertEqual(tempdir, config.get_dirpath('somepkg.home')) finally: - os.remove(ininame) + os.rmdir(tempdir) + + def test_get_filepath_non_existant(self): + testfile, testname = tempfile.mkstemp(prefix='bitten_test') + os.close(testfile) + os.remove(testname) + config = Configuration(properties={'somepkg.path': testname}) + self.assertEqual(None, config.get_filepath('somepkg.path')) + + def test_get_filepath(self): + testfile = tempfile.NamedTemporaryFile(prefix='bitten_test') + config = Configuration(properties={'somepkg.path': testfile.name}) + self.assertEqual(testfile.name, config.get_filepath('somepkg.path')) def test_interpolate(self): config = Configuration(properties={ diff --git a/scripts/build.py b/scripts/build.py --- a/scripts/build.py +++ b/scripts/build.py @@ -24,6 +24,9 @@ version='%%prog %s' % VERSION) parser.add_option('-f', '--recipe-file', action='store', dest='recipe_file', metavar='FILE', help='read build recipe from FILE') + parser.add_option('--print-logs', action='store_const', + dest='print_logs', const=True, + help='print build logs') parser.add_option('--print-reports', action='store_const', dest='print_reports', const=True, help='print generated reports') @@ -54,6 +57,8 @@ for type, category, generator, output in step.execute(recipe.ctxt): if type == Recipe.ERROR: log.error('Failure in step "%s": %s', step.id, output) + elif type == Recipe.LOG and options.print_logs: + output.write(sys.stdout, newlines=True) elif type == Recipe.REPORT and options.print_reports: output.write(sys.stdout, newlines=True) if step.id in steps_to_run: diff --git a/setup.py b/setup.py --- a/setup.py +++ b/setup.py @@ -46,6 +46,7 @@ NS + 'sh#pipe = bitten.build.shtools:pipe', NS + 'c#configure = bitten.build.ctools:configure', NS + 'c#make = bitten.build.ctools:make', + NS + 'java#ant = bitten.build.javatools:ant', NS + 'python#distutils = bitten.build.pythontools:distutils', NS + 'python#exec = bitten.build.pythontools:exec_', NS + 'python#pylint = bitten.build.pythontools:pylint',