summaryrefslogtreecommitdiff
path: root/jarmonbuild/commands.py
diff options
context:
space:
mode:
Diffstat (limited to 'jarmonbuild/commands.py')
-rw-r--r--jarmonbuild/commands.py239
1 files changed, 239 insertions, 0 deletions
diff --git a/jarmonbuild/commands.py b/jarmonbuild/commands.py
new file mode 100644
index 0000000..5071a46
--- /dev/null
+++ b/jarmonbuild/commands.py
@@ -0,0 +1,239 @@
+# Copyright (c) 2010 Richard Wall <richard (at) the-moon.net>
+"""
+Functions and Classes for automating the release of Jarmon
+"""
+
+import hashlib
+import logging
+import os
+import shutil
+import sys
+
+from optparse import OptionParser
+from subprocess import check_call, PIPE
+from tempfile import gettempdir
+from urllib2 import urlopen
+from zipfile import ZipFile, ZIP_DEFLATED
+
+import pkg_resources
+
+
+JARMON_PROJECT_TITLE='Jarmon'
+JARMON_PROJECT_URL='http://www.launchpad.net/jarmon'
+
+YUIDOC_URL = 'http://yuilibrary.com/downloads/yuidoc/yuidoc_1.0.0b1.zip'
+YUIDOC_MD5 = 'cd5545d2dec8f7afe3d18e793538162c'
+YUIDOC_DEPENDENCIES = ['setuptools', 'pygments', 'cheetah']
+
+
+class BuildError(Exception):
+ """
+ A base Exception for errors in the build system
+ """
+ pass
+
+
+class BuildCommand(object):
+ def __init__(self, buildversion, log=None):
+ self.buildversion = buildversion
+ if log is not None:
+ self.log = log
+ else:
+ self.log = logging.getLogger(
+ '%s.%s' % (__name__, self.__class__.__name__))
+
+ self.workingbranch_dir = os.path.abspath(
+ os.path.join(os.path.dirname(__file__), '..'))
+
+ # setup working dir
+ self.build_dir = os.path.join(self.workingbranch_dir, 'build')
+
+ if not os.path.isdir(self.build_dir):
+ self.log.debug('Creating build dir: %s' % (self.build_dir,))
+ os.mkdir(self.build_dir)
+ else:
+ self.log.debug('Using build dir: %s' % (self.build_dir,))
+
+
+class BuildApidocsCommand(BuildCommand):
+ """
+ Download YUI Doc and use it to generate apidocs for jarmon
+ """
+
+ def main(self, argv):
+ """
+ The main entry point for the build-apidocs command
+
+ @param argv: The list of arguments passed to the build-apidocs command
+ """
+ tmpdir = gettempdir()
+ workingbranch_dir = self.workingbranch_dir
+ build_dir = self.build_dir
+
+ # Check for yuidoc dependencies
+ for r in pkg_resources.parse_requirements(YUIDOC_DEPENDENCIES):
+ if not pkg_resources.working_set.find(r):
+ raise BuildError('Unsatisfied yuidoc dependency: %r' % (r,))
+
+ # download and cache yuidoc
+ yuizip_path = os.path.join(tmpdir, os.path.basename(YUIDOC_URL))
+ if os.path.exists(yuizip_path):
+ self.log.debug('Using cached YUI doc')
+ def producer():
+ yield open(yuizip_path).read()
+ else:
+ self.log.debug('Downloading YUI Doc')
+ def producer():
+ with open(yuizip_path, 'w') as yuizip:
+ download = urlopen(YUIDOC_URL)
+ while True:
+ bytes = download.read(1024*10)
+ if not bytes:
+ break
+ else:
+ yuizip.write(bytes)
+ yield bytes
+
+ checksum = hashlib.md5()
+ for bytes in producer():
+ checksum.update(bytes)
+
+ actual_md5 = checksum.hexdigest()
+ if actual_md5 != YUIDOC_MD5:
+ raise BuildError(
+ 'YUI Doc checksum error. File: %s, '
+ 'Expected: %s, Got: %s' % (yuizip_path, YUIDOC_MD5, actual_md5))
+ else:
+ self.log.debug('YUI Doc checksum verified')
+
+ # Remove any existing apidocs so that we can track removed files
+ shutil.rmtree(os.path.join(build_dir, 'docs', 'apidocs'), True)
+
+ yuidoc_dir = os.path.join(build_dir, 'yuidoc')
+
+ # extract yuidoc folder from the downloaded zip file
+ self.log.debug(
+ 'Extracting YUI Doc from %s to %s' % (yuizip_path, yuidoc_dir))
+ zip = ZipFile(yuizip_path)
+ zip.extractall(
+ build_dir, (m for m in zip.namelist() if m.startswith('yuidoc')))
+
+ # Use the yuidoc script that we just extracted to generate new docs
+ self.log.debug('Running YUI Doc')
+ check_call((
+ sys.executable,
+ os.path.join(yuidoc_dir, 'bin', 'yuidoc.py'),
+ os.path.join(workingbranch_dir, 'jarmon'),
+ '--parseroutdir=%s' % (
+ os.path.join(build_dir, 'docs', 'apidocs'),),
+ '--outputdir=%s' % (
+ os.path.join(build_dir, 'docs', 'apidocs'),),
+ '--template=%s' % (
+ os.path.join(
+ workingbranch_dir, 'jarmonbuild', 'yuidoc_template'),),
+ '--version=%s' % (self.buildversion,),
+ '--project=%s' % (JARMON_PROJECT_TITLE,),
+ '--projecturl=%s' % (JARMON_PROJECT_URL,)
+ ), stdout=PIPE, stderr=PIPE,)
+
+ shutil.rmtree(yuidoc_dir)
+
+
+class BuildReleaseCommand(BuildCommand):
+ """
+ Export all source files, generate apidocs and create a zip archive for
+ upload to Launchpad.
+ """
+
+ def main(self, argv):
+ workingbranch_dir = self.workingbranch_dir
+ build_dir = self.build_dir
+
+ self.log.debug('Export versioned files to a build folder')
+ from bzrlib.commands import main as bzr_main
+ status = bzr_main(['bzr', 'export', build_dir, workingbranch_dir])
+ if status != 0:
+ raise BuildError('bzr export failure. Status: %r' % (status,))
+
+
+ self.log.debug('Record the branch version')
+ from bzrlib.branch import Branch
+ from bzrlib.version_info_formats import format_python
+ v = format_python.PythonVersionInfoBuilder(
+ Branch.open(workingbranch_dir))
+ versionfile_path = os.path.join(build_dir, 'jarmonbuild', '_version.py')
+ with open(versionfile_path, 'w') as f:
+ v.generate(f)
+
+
+ self.log.debug('Generate apidocs')
+ BuildApidocsCommand(buildversion=self.buildversion).main(argv)
+
+
+ self.log.debug('Generate archive')
+ archive_root = 'jarmon-%s' % (self.buildversion,)
+ prefix_len = len(build_dir) + 1
+ z = ZipFile('%s.zip' % (archive_root,), 'w', ZIP_DEFLATED)
+ try:
+ for root, dirs, files in os.walk(build_dir):
+ for file in files:
+ z.write(
+ os.path.join(root, file),
+ os.path.join(archive_root, root[prefix_len:], file)
+ )
+ finally:
+ z.close()
+
+
+# The available sub commands
+build_commands = {
+ 'apidocs': BuildApidocsCommand,
+ 'release': BuildReleaseCommand,
+}
+
+
+def main(argv=sys.argv[1:]):
+ """
+ The root build command which dispatches to various subcommands for eg
+ building apidocs and release zip files.
+ """
+ parser = OptionParser(usage='%prog [options] SUBCOMMAND [options]')
+ parser.add_option(
+ '-V', '--build-version', dest='buildversion', default='0',
+ metavar='BUILDVERSION', help='Specify the build version')
+ parser.add_option(
+ '-d', '--debug', action='store_true', default=False, dest='debug',
+ help='Print verbose debug log to stderr')
+
+ parser.disable_interspersed_args()
+
+ options, args = parser.parse_args(argv)
+
+ if len(args) < 1:
+ parser.error('Please specify a sub command. '
+ 'Available commands: %r' % (build_commands.keys()))
+
+ # First argument is the name of a subcommand
+ command_name = args.pop(0)
+ command_factory = build_commands.get(command_name)
+ if not command_factory:
+ parser.error('Unrecognised subcommand: %r' % (command_name,))
+
+ # Setup logging
+ log = logging.getLogger(__name__)
+ log.setLevel(logging.INFO)
+ # create console handler and set level to debug
+ ch = logging.StreamHandler()
+ ch.setLevel(logging.INFO)
+ # create formatter
+ formatter = logging.Formatter(
+ '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+ # add formatter to ch
+ ch.setFormatter(formatter)
+ log.addHandler(ch)
+
+ if options.debug:
+ log.setLevel(logging.DEBUG)
+ ch.setLevel(logging.DEBUG)
+
+ command_factory(buildversion=options.buildversion).main(argv=args)