David Rochberg | 7c79a81 | 2011-01-19 14:24:45 -0500 | [diff] [blame] | 1 | #!/usr/bin/env python |
Ryan Cairns | dd1ceb8 | 2010-03-02 21:35:01 -0800 | [diff] [blame] | 2 | |
David Rochberg | 7c79a81 | 2011-01-19 14:24:45 -0500 | [diff] [blame] | 3 | # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
Ryan Cairns | dd1ceb8 | 2010-03-02 21:35:01 -0800 | [diff] [blame] | 4 | # Use of this source code is governed by a BSD-style license that can be |
| 5 | # found in the LICENSE file. |
| 6 | |
David Rochberg | 7c79a81 | 2011-01-19 14:24:45 -0500 | [diff] [blame] | 7 | """Build packages on a host machine, then install them on the local target. |
Chris Sosa | 136418c | 2010-11-10 16:27:14 -0800 | [diff] [blame] | 8 | |
David Rochberg | 7c79a81 | 2011-01-19 14:24:45 -0500 | [diff] [blame] | 9 | Contacts a devserver (trunk/src/platform/dev/devserver.py) and |
| 10 | requests that it build a package, then performs a binary install of |
| 11 | that package on the local machine. |
| 12 | """ |
Chris Sosa | 4a1e819 | 2010-12-13 14:22:41 -0800 | [diff] [blame] | 13 | |
David Rochberg | 7c79a81 | 2011-01-19 14:24:45 -0500 | [diff] [blame] | 14 | import optparse |
| 15 | import os |
| 16 | import shutil |
| 17 | import subprocess |
| 18 | import sys |
| 19 | import urllib |
| 20 | import urllib2 |
Chris Sosa | 4c9b2f9 | 2010-12-13 16:09:06 -0800 | [diff] [blame] | 21 | |
Ryan Cairns | dd1ceb8 | 2010-03-02 21:35:01 -0800 | [diff] [blame] | 22 | |
David Rochberg | 7c79a81 | 2011-01-19 14:24:45 -0500 | [diff] [blame] | 23 | class GMerger(object): |
| 24 | """emerges a package from the devserver. |
Chris Sosa | 136418c | 2010-11-10 16:27:14 -0800 | [diff] [blame] | 25 | |
David Rochberg | 7c79a81 | 2011-01-19 14:24:45 -0500 | [diff] [blame] | 26 | NB: Must be instantiated using with, e.g.: |
| 27 | with GMerger(open('/etc/lsb-release').readlines()) as merger: |
| 28 | in order to remount /tmp as executable. |
| 29 | """ |
Chris Sosa | 4a1e819 | 2010-12-13 14:22:41 -0800 | [diff] [blame] | 30 | |
David Rochberg | 7c79a81 | 2011-01-19 14:24:45 -0500 | [diff] [blame] | 31 | def __init__(self, lsb_release_lines): |
| 32 | self.lsb_release = self.ParseLsbRelease(lsb_release_lines) |
| 33 | try: |
| 34 | self.devkit_url = self.lsb_release['CHROMEOS_DEVSERVER'] |
| 35 | self.board_name = self.lsb_release['CHROMEOS_RELEASE_BOARD'] |
| 36 | except KeyError, e: |
| 37 | sys.exit('Could not find /etc/lsb_release value: ' + e) |
jglasgow | cc71f3a | 2010-03-12 14:30:21 -0500 | [diff] [blame] | 38 | |
David Rochberg | 7c79a81 | 2011-01-19 14:24:45 -0500 | [diff] [blame] | 39 | def RemountOrChangeRoot(self, environ): |
| 40 | """Remount the root filesystem rw; install into /usr/local if this fails.""" |
| 41 | rc = subprocess.call(['mount', '-o', 'remount,rw', '/']) |
| 42 | if rc == 0: |
| 43 | return |
| 44 | answer = raw_input( |
| 45 | 'Could not mount / as writable. Install into /usr/local? (Y/n)') |
| 46 | if answer[0] not in 'Yy': |
| 47 | sys.exit('Better safe than sorry.') |
| 48 | environ['ROOT'] = '/usr/local' |
Chris Sosa | 605fe88 | 2010-04-22 17:01:32 -0700 | [diff] [blame] | 49 | |
David Rochberg | 7c79a81 | 2011-01-19 14:24:45 -0500 | [diff] [blame] | 50 | def ParseLsbRelease(self, lsb_release_lines): |
| 51 | """Convert a list of KEY=VALUE lines to a dictionary.""" |
| 52 | partitioned_lines = [line.rstrip().partition('=') |
| 53 | for line in lsb_release_lines] |
| 54 | return dict([(fields[0], fields[2]) for fields in partitioned_lines]) |
Ryan Cairns | dd1ceb8 | 2010-03-02 21:35:01 -0800 | [diff] [blame] | 55 | |
David Rochberg | 7c79a81 | 2011-01-19 14:24:45 -0500 | [diff] [blame] | 56 | def SetupPortageEnvironment(self, environ): |
| 57 | """Setup portage to use stateful partition and fetch from dev server.""" |
David James | ed079b1 | 2011-05-17 14:53:15 -0700 | [diff] [blame] | 58 | binhost_prefix = '%s/static/pkgroot/%s' % (self.devkit_url, self.board_name) |
David James | e4f73a4 | 2011-05-19 12:18:33 -0700 | [diff] [blame] | 59 | binhost = '%s/packages' % binhost_prefix |
| 60 | if not FLAGS.include_masked_files: |
| 61 | binhost += ' %s/gmerge-packages' % binhost_prefix |
David Rochberg | 7c79a81 | 2011-01-19 14:24:45 -0500 | [diff] [blame] | 62 | environ.update({ |
| 63 | 'PORTDIR': '/usr/local/portage', |
| 64 | 'PKGDIR': '/usr/local/portage', |
| 65 | 'DISTDIR': '/usr/local/portage/distfiles', |
David James | e4f73a4 | 2011-05-19 12:18:33 -0700 | [diff] [blame] | 66 | 'PORTAGE_BINHOST': binhost, |
David Rochberg | 7c79a81 | 2011-01-19 14:24:45 -0500 | [diff] [blame] | 67 | 'PORTAGE_TMPDIR': '/tmp', |
| 68 | 'CONFIG_PROTECT': '-*', |
| 69 | 'FEATURES': '-sandbox', |
| 70 | 'ACCEPT_KEYWORDS': 'arm x86 ~arm ~x86', |
David James | ff33944 | 2011-05-19 14:08:35 -0700 | [diff] [blame] | 71 | 'ROOT': os.environ.get('ROOT', '/'), |
| 72 | 'PORTAGE_CONFIGROOT': '/usr/local' |
David Rochberg | 7c79a81 | 2011-01-19 14:24:45 -0500 | [diff] [blame] | 73 | }) |
Ryan Cairns | dd1ceb8 | 2010-03-02 21:35:01 -0800 | [diff] [blame] | 74 | |
David Rochberg | 7c79a81 | 2011-01-19 14:24:45 -0500 | [diff] [blame] | 75 | def GeneratePackageRequest(self, package_name): |
| 76 | """Build the POST string that conveys our options to the devserver.""" |
| 77 | post_data = {'board': self.board_name, |
David James | 3556d22 | 2011-05-20 15:58:41 -0700 | [diff] [blame] | 78 | 'deep': FLAGS.deep or '', |
David Rochberg | 7c79a81 | 2011-01-19 14:24:45 -0500 | [diff] [blame] | 79 | 'pkg': package_name, |
David James | ed079b1 | 2011-05-17 14:53:15 -0700 | [diff] [blame] | 80 | 'features': os.environ.get('FEATURES'), |
| 81 | 'use': os.environ.get('USE'), |
| 82 | 'accept_stable': FLAGS.accept_stable or '', |
| 83 | 'usepkg': FLAGS.usepkg or '', |
David Rochberg | 7c79a81 | 2011-01-19 14:24:45 -0500 | [diff] [blame] | 84 | } |
| 85 | post_data = dict([(key, value) for (key, value) in post_data.iteritems() |
David James | ed079b1 | 2011-05-17 14:53:15 -0700 | [diff] [blame] | 86 | if value is not None]) |
David Rochberg | 7c79a81 | 2011-01-19 14:24:45 -0500 | [diff] [blame] | 87 | return urllib.urlencode(post_data) |
Frank Swiderski | dc13081 | 2010-10-08 15:42:28 -0700 | [diff] [blame] | 88 | |
David Rochberg | 7c79a81 | 2011-01-19 14:24:45 -0500 | [diff] [blame] | 89 | def RequestPackageBuild(self, package_name): |
| 90 | """Contacts devserver to request a build.""" |
| 91 | try: |
| 92 | result = urllib2.urlopen(self.devkit_url + '/build', |
| 93 | data=self.GeneratePackageRequest(package_name)) |
| 94 | print result.read() |
| 95 | result.close() |
Mandeep Singh Baines | ea6b7a5 | 2010-08-17 14:03:57 -0700 | [diff] [blame] | 96 | |
David Rochberg | 7c79a81 | 2011-01-19 14:24:45 -0500 | [diff] [blame] | 97 | except urllib2.HTTPError, e: |
| 98 | # The exception includes the content, which is the error mesage |
| 99 | sys.exit(e.read()) |
David James | 3556d22 | 2011-05-20 15:58:41 -0700 | [diff] [blame] | 100 | except urllib2.URLError, e: |
| 101 | sys.exit('Could not reach devserver. Reason: %s' % e.reason) |
Ryan Cairns | dd1ceb8 | 2010-03-02 21:35:01 -0800 | [diff] [blame] | 102 | |
Ryan Cairns | dd1ceb8 | 2010-03-02 21:35:01 -0800 | [diff] [blame] | 103 | |
David Rochberg | 7c79a81 | 2011-01-19 14:24:45 -0500 | [diff] [blame] | 104 | def main(): |
| 105 | global FLAGS |
| 106 | parser = optparse.OptionParser(usage='usage: %prog [options] package_name') |
| 107 | parser.add_option('--accept_stable', |
| 108 | action='store_true', dest='accept_stable', default=False, |
| 109 | help=('Build even if a cros_workon package is not ' |
| 110 | 'using the live package')) |
David James | e4f73a4 | 2011-05-19 12:18:33 -0700 | [diff] [blame] | 111 | parser.add_option('--include_masked_files', |
| 112 | action='store_true', dest='include_masked_files', |
| 113 | default=False, help=('Include masked files in package ' |
| 114 | '(e.g. debug symbols)')) |
David James | ed079b1 | 2011-05-17 14:53:15 -0700 | [diff] [blame] | 115 | parser.add_option('-n', '--usepkg', |
| 116 | action='store_true', dest='usepkg', default=False, |
David James | 3556d22 | 2011-05-20 15:58:41 -0700 | [diff] [blame] | 117 | help='Use currently built binary packages on server.') |
| 118 | parser.add_option('-D', '--deep', |
| 119 | action='store_true', dest='deep', default=False, |
| 120 | help='Update package and all dependencies ' |
| 121 | '(requires --usepkg).') |
| 122 | parser.add_option('-x', '--extra', dest='extra', default='', |
| 123 | help='Extra arguments to pass to emerge command.') |
David Rochberg | 7c79a81 | 2011-01-19 14:24:45 -0500 | [diff] [blame] | 124 | |
| 125 | (FLAGS, remaining_arguments) = parser.parse_args() |
| 126 | if len(remaining_arguments) != 1: |
| 127 | parser.print_help() |
| 128 | sys.exit('Need exactly one package name') |
| 129 | |
David James | 3556d22 | 2011-05-20 15:58:41 -0700 | [diff] [blame] | 130 | # TODO(davidjames): Should we allow --deep without --usepkg? Not sure what |
| 131 | # the desired behavior should be in this case, so disabling the combo for |
| 132 | # now. |
| 133 | if FLAGS.deep and not FLAGS.usepkg: |
| 134 | sys.exit('If using --deep, --usepkg must also be enabled.') |
| 135 | |
David Rochberg | 7c79a81 | 2011-01-19 14:24:45 -0500 | [diff] [blame] | 136 | package_name = remaining_arguments[0] |
| 137 | |
| 138 | try: |
| 139 | subprocess.check_call(['mount', '-o', 'remount,exec', '/tmp']) |
| 140 | merger = GMerger(open('/etc/lsb-release').readlines()) |
David James | ed079b1 | 2011-05-17 14:53:15 -0700 | [diff] [blame] | 141 | merger.RequestPackageBuild(package_name) |
David Rochberg | 7c79a81 | 2011-01-19 14:24:45 -0500 | [diff] [blame] | 142 | |
| 143 | print 'Emerging ', package_name |
Chris Sosa | dda923d | 2011-04-13 13:12:01 -0700 | [diff] [blame] | 144 | merger.SetupPortageEnvironment(os.environ) |
| 145 | merger.RemountOrChangeRoot(os.environ) |
David James | 0e242d6 | 2011-11-16 17:16:26 -0800 | [diff] [blame] | 146 | subprocess.check_call(['rm', '-rf', '/usr/local/portage', |
| 147 | '/var/cache/edb/binhost']) |
David James | 3556d22 | 2011-05-20 15:58:41 -0700 | [diff] [blame] | 148 | emerge_args = 'emerge --getbinpkgonly --usepkgonly --verbose' |
| 149 | if FLAGS.deep: |
| 150 | emerge_args += ' --update --deep' |
| 151 | if FLAGS.extra: |
| 152 | emerge_args += ' ' + FLAGS.extra |
| 153 | emerge_args += ' ' + package_name |
| 154 | subprocess.check_call(emerge_args, shell=True) |
David Rochberg | 7c79a81 | 2011-01-19 14:24:45 -0500 | [diff] [blame] | 155 | finally: |
| 156 | subprocess.call(['mount', '-o', 'remount,noexec', '/tmp']) |
| 157 | |
| 158 | |
| 159 | if __name__ == '__main__': |
| 160 | main() |