blob: 92ea4d230e0417a3983a1b35aeb06de91c39d722 [file] [log] [blame]
#!/usr/bin/env python
# Copyright 2012 Google Inc. All Rights Reserved.
"""Script for building Bruno images.
NOTE NOTE NOTE NOTE NOTE!
Please consider carefully whether you really want to add stuff to this script.
Almost everything you might want in this script should be added as a
buildroot package instead. Otherwise people who want to do a quick rebuild
of a particular package using 'make' won't be able to do so.
This script already does too much stuff. Most of its existing features
should be migrated out into packages eventually. You almost certainly don't
want to add more.
Think of the goal for this script as running ./configure in an autoconf
program. Everybody needs to do it to set their options, but not every
time you run an incremental build.
"""
import errno
import glob
import os
import subprocess
import sys
import time
import options
__author__ = 'kedong@google.com (Ke Dong)'
optspec = """
builder.py [options...] [output-directory]
--
C,config= buildroot config file [gftv100_defconfig]
v,verbose Increase verbosity
f,fresh,force Force rebuild (once=remove stamps, twice=make clean)
x,platform-only Build less stuff into the app (no webkit, netflix, etc.)
r,production Use production signing keys and license
j,jobs= Number of parallel jobs for make to use (make -j) [12]
k,key-suffix= Suffix for signing keys
u,unsigned Skip the image signing
openbox Use openbox bootloader (forces --no-production)
no-build Don't build, just configure
no-source Don't pre-extract sources
no-ccache Disable use of ccache for this build
"""
RED = '\033[1;31m'
GREEN = '\033[1;32m'
YELLOW = '\033[1;33m'
OFF = '\033[0m'
def _Log(color, fmt, args):
sys.stderr.flush()
if args:
print color + (fmt % args) + OFF
else:
print color + str(fmt) + OFF
sys.stdout.flush()
def Info(fmt, *args):
_Log(GREEN, fmt, args)
def Warn(fmt, *args):
_Log(YELLOW, fmt, args)
def Error(fmt, *args):
_Log(RED, fmt, args)
class SubprocError(Exception):
pass
def PopenAndRead(args, **kwargs):
nkwargs = dict(stdout=subprocess.PIPE)
nkwargs.update(kwargs)
p = subprocess.Popen(args, **nkwargs)
data = p.stdout.read()
retval = p.wait()
if retval:
raise SubprocError('%r returned exit code %d' % (args, retval))
return data.strip()
class BuildError(Exception):
pass
def Makedirs(dirname):
try:
os.makedirs(dirname)
except OSError, e:
if e.errno == errno.EEXIST:
pass
else:
raise
def Unlink(filename):
try:
os.unlink(filename)
except OSError, e:
if e.errno == errno.ENOENT:
pass
else:
raise
def UnlinkGlob(g):
for filename in glob.glob(g):
Unlink(filename)
class BuildRootBuilder(object):
"""Builder class to wrap up buildroot."""
def __init__(self, base_dir, opt):
self.top_dir = os.path.abspath('.')
self.base_dir = base_dir
self.opt = opt
def Build(self):
"""Run the build."""
starttime = time.time()
try:
self.PrintOptions()
Makedirs(self._Path('images'))
self.BuildAppFs()
finally:
endtime = time.time()
elapsed = endtime - starttime
Info('Time executed: %d:%02d', elapsed / 60, elapsed % 60)
def _LogStart(self, info):
Info('##### Start %s', info)
def _LogDone(self, info):
Info('##### Done %s', info)
def PrintOptions(self):
"""Print the currently-selected options."""
print '=========================================================='
print 'CONFIG :', self.opt.config
print 'VERBOSE :', self.opt.verbose
print 'FRESH :', self.opt.fresh
print 'PRODUCTION :', self.opt.production
print 'OPENBOX :', self.opt.openbox
print 'BUILDROOT PATH :', self.top_dir
print 'BUILD PATH :', self.base_dir
print 'KEY SUFFIX :', self.opt.key_suffix
print 'UNSIGNED :', self.opt.unsigned
print 'NOCCACHE :', self.opt.no_ccache
print '=========================================================='
sys.stdout.flush()
def PopenAt(self, cwd, args, **kwargs):
"""Execute Popen in arbitrary dir."""
try:
p = subprocess.Popen(args, cwd=cwd, **kwargs)
except OSError, e:
raise BuildError('%r: %s' % (args, e))
retval = p.wait()
if retval:
raise BuildError('%r returned exit code %r' % (args, retval))
def _Path(self, *paths):
return os.path.abspath(os.path.join(self.base_dir, *paths))
def Make(self, targets, parallel):
"""Execute make for buildroot.
Args:
targets: which targets to ask make to build ([] means default)
parallel: true if you want make to run a parallel build.
"""
cmd = ['make', 'O=%s' % self._Path()] + targets
if self.opt.verbose:
cmd += ['V=1']
if parallel:
cmd += ['-j%d' % self.opt.jobs, '-l%d' % self.opt.jobs]
self.PopenAt(self.top_dir, cmd)
def CleanOutputDir(self):
d = self._Path()
Info('Cleaning %r...', d)
self.Make(['clean'], parallel=True)
def BuildConfig(self, filename, **extra):
"""Generate a config file for the given set of options."""
opts = dict(BR2_PACKAGE_GOOGLE_PROD=self.opt.production,
BR2_PACKAGE_GOOGLE_OPENBOX=self.opt.openbox,
BR2_PACKAGE_GOOGLE_UNSIGNED=self.opt.unsigned)
# Disable ccache for production builds.
if self.opt.production or self.opt.no_ccache:
opts['BR2_CCACHE'] = 0
opts.update(extra)
# We append to the file because the user might have added (unrelated)
# custom entries, but later lines override earlier ones, so it's
# safe to just re-append forever.
localcfg = open(self._Path('.localconfig'), 'a')
for key, value in opts.iteritems():
localcfg.write('%s=%s\n' % (key, value and 'y' or 'n'))
if self.opt.key_suffix:
localcfg.write('BR2_PACKAGE_GOOGLE_KEY_SUFFIX="%s"\n' %
self.opt.key_suffix)
localcfg.close()
# Actually generate the config file
Info('Config file: %r', filename)
self.Make([filename + '_rebuild'], parallel=False)
def RemoveStamps(self):
Info('Cleaning up install stamps...')
self.Make(['remove-stamps'], parallel=True)
def BuildAppFs(self):
"""Build the kernel + simpleramfs + squashfs."""
if self.opt.fresh >= 2:
self.CleanOutputDir()
self._LogStart('Building app')
Info('app: config file is %r', self.opt.config)
if self.opt.platform_only:
self.BuildConfig(self.opt.config,
BR2_PACKAGE_GOOGLE_PLATFORM_ONLY=True)
else:
self.BuildConfig(self.opt.config)
if self.opt.fresh >= 1:
self.RemoveStamps()
if self.opt.build:
self.Make([], parallel=True)
elif self.opt.source:
self.Make(['worldsetup'], parallel=True)
else:
self.Make(['preworldsetup'], parallel=True)
self._LogDone('Building app')
def main():
os.chdir(os.path.dirname(sys.argv[0]) + '/..')
o = options.Options(optspec)
(opt, _, extra) = o.parse(sys.argv[1:])
if '/' in opt.config:
o.fatal('--config must be a filename in %s/configs' % os.getcwd())
if not os.path.exists(os.path.join('configs', opt.config)):
o.fatal('--config %r does not exist' % opt.config)
if not opt.platform_only and not os.path.exists('../vendor/sagetv'):
o.fatal('../vendor/sagetv not available; you probably need to use -x')
if extra:
if len(extra) > 1:
o.fatal('at most one output directory expected')
base_dir = os.path.abspath(extra[0])
else:
base_dir = os.path.abspath('../out')
Warn('Default output dir: %s', base_dir)
if not base_dir or base_dir[-1] in ('-', '.', ',') or '$' in base_dir:
o.fatal('weird output dir %r; check your scripts.' % base_dir)
builder = BuildRootBuilder(base_dir, opt)
builder.Build()
if __name__ == '__main__':
main()