| #!/usr/bin/env python |
| |
| # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Build packages on a host machine, then install them on the local target. |
| |
| Contacts a devserver (trunk/src/platform/dev/devserver.py) and |
| requests that it build a package, then performs a binary install of |
| that package on the local machine. |
| """ |
| |
| import optparse |
| import os |
| import shutil |
| import subprocess |
| import sys |
| import urllib |
| import urllib2 |
| |
| |
| class GMerger(object): |
| """emerges a package from the devserver. |
| |
| NB: Must be instantiated using with, e.g.: |
| with GMerger(open('/etc/lsb-release').readlines()) as merger: |
| in order to remount /tmp as executable. |
| """ |
| |
| def __init__(self, lsb_release_lines): |
| self.lsb_release = self.ParseLsbRelease(lsb_release_lines) |
| try: |
| self.devkit_url = self.lsb_release['CHROMEOS_DEVSERVER'] |
| self.board_name = self.lsb_release['CHROMEOS_RELEASE_BOARD'] |
| except KeyError, e: |
| sys.exit('Could not find /etc/lsb_release value: ' + e) |
| |
| def RemountOrChangeRoot(self, environ): |
| """Remount the root filesystem rw; install into /usr/local if this fails.""" |
| rc = subprocess.call(['mount', '-o', 'remount,rw', '/']) |
| if rc == 0: |
| return |
| answer = raw_input( |
| 'Could not mount / as writable. Install into /usr/local? (Y/n)') |
| if answer[0] not in 'Yy': |
| sys.exit('Better safe than sorry.') |
| environ['ROOT'] = '/usr/local' |
| |
| def ParseLsbRelease(self, lsb_release_lines): |
| """Convert a list of KEY=VALUE lines to a dictionary.""" |
| partitioned_lines = [line.rstrip().partition('=') |
| for line in lsb_release_lines] |
| return dict([(fields[0], fields[2]) for fields in partitioned_lines]) |
| |
| def SetupPortageEnvironment(self, environ): |
| """Setup portage to use stateful partition and fetch from dev server.""" |
| binhost_prefix = '%s/static/pkgroot/%s' % (self.devkit_url, self.board_name) |
| binhost = '%s/packages' % binhost_prefix |
| if not FLAGS.include_masked_files: |
| binhost += ' %s/gmerge-packages' % binhost_prefix |
| environ.update({ |
| 'PORTDIR': '/usr/local/portage', |
| 'PKGDIR': '/usr/local/portage', |
| 'DISTDIR': '/usr/local/portage/distfiles', |
| 'PORTAGE_BINHOST': binhost, |
| 'PORTAGE_TMPDIR': '/tmp', |
| 'CONFIG_PROTECT': '-*', |
| 'FEATURES': '-sandbox', |
| 'ACCEPT_KEYWORDS': 'arm x86 ~arm ~x86', |
| 'ROOT': os.environ.get('ROOT', '/'), |
| 'PORTAGE_CONFIGROOT': '/usr/local' |
| }) |
| |
| def GeneratePackageRequest(self, package_name): |
| """Build the POST string that conveys our options to the devserver.""" |
| post_data = {'board': self.board_name, |
| 'deep': FLAGS.deep or '', |
| 'pkg': package_name, |
| 'features': os.environ.get('FEATURES'), |
| 'use': os.environ.get('USE'), |
| 'accept_stable': FLAGS.accept_stable or '', |
| 'usepkg': FLAGS.usepkg or '', |
| } |
| post_data = dict([(key, value) for (key, value) in post_data.iteritems() |
| if value is not None]) |
| return urllib.urlencode(post_data) |
| |
| def RequestPackageBuild(self, package_name): |
| """Contacts devserver to request a build.""" |
| try: |
| result = urllib2.urlopen(self.devkit_url + '/build', |
| data=self.GeneratePackageRequest(package_name)) |
| print result.read() |
| result.close() |
| |
| except urllib2.HTTPError, e: |
| # The exception includes the content, which is the error mesage |
| sys.exit(e.read()) |
| except urllib2.URLError, e: |
| sys.exit('Could not reach devserver. Reason: %s' % e.reason) |
| |
| |
| def main(): |
| global FLAGS |
| parser = optparse.OptionParser(usage='usage: %prog [options] package_name') |
| parser.add_option('--accept_stable', |
| action='store_true', dest='accept_stable', default=False, |
| help=('Build even if a cros_workon package is not ' |
| 'using the live package')) |
| parser.add_option('--include_masked_files', |
| action='store_true', dest='include_masked_files', |
| default=False, help=('Include masked files in package ' |
| '(e.g. debug symbols)')) |
| parser.add_option('-n', '--usepkg', |
| action='store_true', dest='usepkg', default=False, |
| help='Use currently built binary packages on server.') |
| parser.add_option('-D', '--deep', |
| action='store_true', dest='deep', default=False, |
| help='Update package and all dependencies ' |
| '(requires --usepkg).') |
| parser.add_option('-x', '--extra', dest='extra', default='', |
| help='Extra arguments to pass to emerge command.') |
| |
| (FLAGS, remaining_arguments) = parser.parse_args() |
| if len(remaining_arguments) != 1: |
| parser.print_help() |
| sys.exit('Need exactly one package name') |
| |
| # TODO(davidjames): Should we allow --deep without --usepkg? Not sure what |
| # the desired behavior should be in this case, so disabling the combo for |
| # now. |
| if FLAGS.deep and not FLAGS.usepkg: |
| sys.exit('If using --deep, --usepkg must also be enabled.') |
| |
| package_name = remaining_arguments[0] |
| |
| try: |
| subprocess.check_call(['mount', '-o', 'remount,exec', '/tmp']) |
| merger = GMerger(open('/etc/lsb-release').readlines()) |
| merger.RequestPackageBuild(package_name) |
| |
| print 'Emerging ', package_name |
| merger.SetupPortageEnvironment(os.environ) |
| merger.RemountOrChangeRoot(os.environ) |
| subprocess.check_call(['rm', '-rf', '/usr/local/portage']) |
| emerge_args = 'emerge --getbinpkgonly --usepkgonly --verbose' |
| if FLAGS.deep: |
| emerge_args += ' --update --deep' |
| if FLAGS.extra: |
| emerge_args += ' ' + FLAGS.extra |
| emerge_args += ' ' + package_name |
| subprocess.check_call(emerge_args, shell=True) |
| finally: |
| subprocess.call(['mount', '-o', 'remount,noexec', '/tmp']) |
| |
| |
| if __name__ == '__main__': |
| main() |