blob: 8a632208ae2654a8cea3c55b477a1782dacdca7e [file] [log] [blame]
David Rochberg7c79a812011-01-19 14:24:45 -05001#!/usr/bin/env python
Ryan Cairnsdd1ceb82010-03-02 21:35:01 -08002
David Rochberg7c79a812011-01-19 14:24:45 -05003# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
Ryan Cairnsdd1ceb82010-03-02 21:35:01 -08004# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
David Rochberg7c79a812011-01-19 14:24:45 -05007"""Build packages on a host machine, then install them on the local target.
Chris Sosa136418c2010-11-10 16:27:14 -08008
David Rochberg7c79a812011-01-19 14:24:45 -05009Contacts a devserver (trunk/src/platform/dev/devserver.py) and
10requests that it build a package, then performs a binary install of
11that package on the local machine.
12"""
Chris Sosa4a1e8192010-12-13 14:22:41 -080013
David Rochberg7c79a812011-01-19 14:24:45 -050014import optparse
15import os
16import shutil
17import subprocess
18import sys
19import urllib
20import urllib2
Chris Sosa4c9b2f92010-12-13 16:09:06 -080021
Ryan Cairnsdd1ceb82010-03-02 21:35:01 -080022
David Rochberg7c79a812011-01-19 14:24:45 -050023class GMerger(object):
24 """emerges a package from the devserver.
Chris Sosa136418c2010-11-10 16:27:14 -080025
David Rochberg7c79a812011-01-19 14:24:45 -050026 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 Sosa4a1e8192010-12-13 14:22:41 -080030
David Rochberg7c79a812011-01-19 14:24:45 -050031 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)
jglasgowcc71f3a2010-03-12 14:30:21 -050038
David Rochberg7c79a812011-01-19 14:24:45 -050039 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 Sosa605fe882010-04-22 17:01:32 -070049
David Rochberg7c79a812011-01-19 14:24:45 -050050 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 Cairnsdd1ceb82010-03-02 21:35:01 -080055
David Rochberg7c79a812011-01-19 14:24:45 -050056 def SetupPortageEnvironment(self, environ):
57 """Setup portage to use stateful partition and fetch from dev server."""
David Jamesed079b12011-05-17 14:53:15 -070058 binhost_prefix = '%s/static/pkgroot/%s' % (self.devkit_url, self.board_name)
David Jamese4f73a42011-05-19 12:18:33 -070059 binhost = '%s/packages' % binhost_prefix
60 if not FLAGS.include_masked_files:
61 binhost += ' %s/gmerge-packages' % binhost_prefix
David Rochberg7c79a812011-01-19 14:24:45 -050062 environ.update({
63 'PORTDIR': '/usr/local/portage',
64 'PKGDIR': '/usr/local/portage',
65 'DISTDIR': '/usr/local/portage/distfiles',
David Jamese4f73a42011-05-19 12:18:33 -070066 'PORTAGE_BINHOST': binhost,
David Rochberg7c79a812011-01-19 14:24:45 -050067 'PORTAGE_TMPDIR': '/tmp',
68 'CONFIG_PROTECT': '-*',
69 'FEATURES': '-sandbox',
70 'ACCEPT_KEYWORDS': 'arm x86 ~arm ~x86',
David Jamesff339442011-05-19 14:08:35 -070071 'ROOT': os.environ.get('ROOT', '/'),
72 'PORTAGE_CONFIGROOT': '/usr/local'
David Rochberg7c79a812011-01-19 14:24:45 -050073 })
Ryan Cairnsdd1ceb82010-03-02 21:35:01 -080074
David Rochberg7c79a812011-01-19 14:24:45 -050075 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 James3556d222011-05-20 15:58:41 -070078 'deep': FLAGS.deep or '',
David Rochberg7c79a812011-01-19 14:24:45 -050079 'pkg': package_name,
David Jamesed079b12011-05-17 14:53:15 -070080 'features': os.environ.get('FEATURES'),
81 'use': os.environ.get('USE'),
82 'accept_stable': FLAGS.accept_stable or '',
83 'usepkg': FLAGS.usepkg or '',
David Rochberg7c79a812011-01-19 14:24:45 -050084 }
85 post_data = dict([(key, value) for (key, value) in post_data.iteritems()
David Jamesed079b12011-05-17 14:53:15 -070086 if value is not None])
David Rochberg7c79a812011-01-19 14:24:45 -050087 return urllib.urlencode(post_data)
Frank Swiderskidc130812010-10-08 15:42:28 -070088
David Rochberg7c79a812011-01-19 14:24:45 -050089 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 Bainesea6b7a52010-08-17 14:03:57 -070096
David Rochberg7c79a812011-01-19 14:24:45 -050097 except urllib2.HTTPError, e:
98 # The exception includes the content, which is the error mesage
99 sys.exit(e.read())
David James3556d222011-05-20 15:58:41 -0700100 except urllib2.URLError, e:
101 sys.exit('Could not reach devserver. Reason: %s' % e.reason)
Ryan Cairnsdd1ceb82010-03-02 21:35:01 -0800102
Ryan Cairnsdd1ceb82010-03-02 21:35:01 -0800103
David Rochberg7c79a812011-01-19 14:24:45 -0500104def 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 Jamese4f73a42011-05-19 12:18:33 -0700111 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 Jamesed079b12011-05-17 14:53:15 -0700115 parser.add_option('-n', '--usepkg',
116 action='store_true', dest='usepkg', default=False,
David James3556d222011-05-20 15:58:41 -0700117 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 Rochberg7c79a812011-01-19 14:24:45 -0500124
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 James3556d222011-05-20 15:58:41 -0700130 # 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 Rochberg7c79a812011-01-19 14:24:45 -0500136 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 Jamesed079b12011-05-17 14:53:15 -0700141 merger.RequestPackageBuild(package_name)
David Rochberg7c79a812011-01-19 14:24:45 -0500142
143 print 'Emerging ', package_name
Chris Sosadda923d2011-04-13 13:12:01 -0700144 merger.SetupPortageEnvironment(os.environ)
145 merger.RemountOrChangeRoot(os.environ)
David James0e242d62011-11-16 17:16:26 -0800146 subprocess.check_call(['rm', '-rf', '/usr/local/portage',
147 '/var/cache/edb/binhost'])
David James3556d222011-05-20 15:58:41 -0700148 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 Rochberg7c79a812011-01-19 14:24:45 -0500155 finally:
156 subprocess.call(['mount', '-o', 'remount,noexec', '/tmp'])
157
158
159if __name__ == '__main__':
160 main()