1
# Copyright (c) 2010 Richard Wall <richard (at) the-moon.net>
3
Functions and Classes for automating the release of Jarmon
12
from optparse import OptionParser
13
from subprocess import check_call, PIPE
14
from tempfile import gettempdir
15
from urllib2 import urlopen
16
from zipfile import ZipFile, ZIP_DEFLATED
21
JARMON_PROJECT_TITLE='Jarmon'
22
JARMON_PROJECT_URL='http://www.launchpad.net/jarmon'
24
YUIDOC_URL = 'http://yuilibrary.com/downloads/yuidoc/yuidoc_1.0.0b1.zip'
25
YUIDOC_MD5 = 'cd5545d2dec8f7afe3d18e793538162c'
26
YUIDOC_DEPENDENCIES = ['setuptools', 'pygments', 'cheetah']
29
class BuildError(Exception):
31
A base Exception for errors in the build system
36
class BuildCommand(object):
37
def __init__(self, buildversion, log=None):
38
self.buildversion = buildversion
42
self.log = logging.getLogger(
43
'%s.%s' % (__name__, self.__class__.__name__))
45
self.workingbranch_dir = os.path.abspath(
46
os.path.join(os.path.dirname(__file__), '..'))
49
self.build_dir = os.path.join(self.workingbranch_dir, 'build')
51
if not os.path.isdir(self.build_dir):
52
self.log.debug('Creating build dir: %s' % (self.build_dir,))
53
os.mkdir(self.build_dir)
55
self.log.debug('Using build dir: %s' % (self.build_dir,))
58
class BuildApidocsCommand(BuildCommand):
60
Download YUI Doc and use it to generate apidocs for jarmon
65
The main entry point for the build-apidocs command
67
@param argv: The list of arguments passed to the build-apidocs command
70
workingbranch_dir = self.workingbranch_dir
71
build_dir = self.build_dir
73
# Check for yuidoc dependencies
74
for r in pkg_resources.parse_requirements(YUIDOC_DEPENDENCIES):
75
if not pkg_resources.working_set.find(r):
76
raise BuildError('Unsatisfied yuidoc dependency: %r' % (r,))
78
# download and cache yuidoc
79
yuizip_path = os.path.join(tmpdir, os.path.basename(YUIDOC_URL))
80
if os.path.exists(yuizip_path):
81
self.log.debug('Using cached YUI doc')
83
yield open(yuizip_path).read()
85
self.log.debug('Downloading YUI Doc')
87
with open(yuizip_path, 'w') as yuizip:
88
download = urlopen(YUIDOC_URL)
90
bytes = download.read(1024*10)
97
checksum = hashlib.md5()
98
for bytes in producer():
99
checksum.update(bytes)
101
actual_md5 = checksum.hexdigest()
102
if actual_md5 != YUIDOC_MD5:
104
'YUI Doc checksum error. File: %s, '
105
'Expected: %s, Got: %s' % (yuizip_path, YUIDOC_MD5, actual_md5))
107
self.log.debug('YUI Doc checksum verified')
109
# Remove any existing apidocs so that we can track removed files
110
shutil.rmtree(os.path.join(build_dir, 'docs', 'apidocs'), True)
112
yuidoc_dir = os.path.join(build_dir, 'yuidoc')
114
# extract yuidoc folder from the downloaded zip file
116
'Extracting YUI Doc from %s to %s' % (yuizip_path, yuidoc_dir))
117
zip = ZipFile(yuizip_path)
119
build_dir, (m for m in zip.namelist() if m.startswith('yuidoc')))
121
# Use the yuidoc script that we just extracted to generate new docs
122
self.log.debug('Running YUI Doc')
125
os.path.join(yuidoc_dir, 'bin', 'yuidoc.py'),
126
os.path.join(workingbranch_dir, 'jarmon'),
127
'--parseroutdir=%s' % (
128
os.path.join(build_dir, 'docs', 'apidocs'),),
130
os.path.join(build_dir, 'docs', 'apidocs'),),
133
workingbranch_dir, 'jarmonbuild', 'yuidoc_template'),),
134
'--version=%s' % (self.buildversion,),
135
'--project=%s' % (JARMON_PROJECT_TITLE,),
136
'--projecturl=%s' % (JARMON_PROJECT_URL,)
137
), stdout=PIPE, stderr=PIPE,)
139
shutil.rmtree(yuidoc_dir)
142
class BuildReleaseCommand(BuildCommand):
144
Export all source files, generate apidocs and create a zip archive for
148
def main(self, argv):
149
workingbranch_dir = self.workingbranch_dir
150
build_dir = self.build_dir
152
self.log.debug('Export versioned files to a build folder')
153
from bzrlib.commands import main as bzr_main
154
status = bzr_main(['bzr', 'export', build_dir, workingbranch_dir])
156
raise BuildError('bzr export failure. Status: %r' % (status,))
159
self.log.debug('Record the branch version')
160
from bzrlib.branch import Branch
161
from bzrlib.version_info_formats import format_python
162
v = format_python.PythonVersionInfoBuilder(
163
Branch.open(workingbranch_dir))
164
versionfile_path = os.path.join(build_dir, 'jarmonbuild', '_version.py')
165
with open(versionfile_path, 'w') as f:
169
self.log.debug('Generate apidocs')
170
BuildApidocsCommand(buildversion=self.buildversion).main(argv)
173
self.log.debug('Generate archive')
174
archive_root = 'jarmon-%s' % (self.buildversion,)
175
prefix_len = len(build_dir) + 1
176
z = ZipFile('%s.zip' % (archive_root,), 'w', ZIP_DEFLATED)
178
for root, dirs, files in os.walk(build_dir):
181
os.path.join(root, file),
182
os.path.join(archive_root, root[prefix_len:], file)
188
# The available sub commands
190
'apidocs': BuildApidocsCommand,
191
'release': BuildReleaseCommand,
195
def main(argv=sys.argv[1:]):
197
The root build command which dispatches to various subcommands for eg
198
building apidocs and release zip files.
200
parser = OptionParser(usage='%prog [options] SUBCOMMAND [options]')
202
'-V', '--build-version', dest='buildversion', default='0',
203
metavar='BUILDVERSION', help='Specify the build version')
205
'-d', '--debug', action='store_true', default=False, dest='debug',
206
help='Print verbose debug log to stderr')
208
parser.disable_interspersed_args()
210
options, args = parser.parse_args(argv)
213
parser.error('Please specify a sub command. '
214
'Available commands: %r' % (build_commands.keys()))
216
# First argument is the name of a subcommand
217
command_name = args.pop(0)
218
command_factory = build_commands.get(command_name)
219
if not command_factory:
220
parser.error('Unrecognised subcommand: %r' % (command_name,))
223
log = logging.getLogger(__name__)
224
log.setLevel(logging.INFO)
225
# create console handler and set level to debug
226
ch = logging.StreamHandler()
227
ch.setLevel(logging.INFO)
229
formatter = logging.Formatter(
230
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
231
# add formatter to ch
232
ch.setFormatter(formatter)
236
log.setLevel(logging.DEBUG)
237
ch.setLevel(logging.DEBUG)
239
command_factory(buildversion=options.buildversion).main(argv=args)