blob: 06b4089bccbe43aeb16b0fe3c52867cb84977b31 [file] [log] [blame]
David Rochberg7c79a812011-01-19 19:24:451#!/usr/bin/env python
Ryan Cairnsdd1ceb82010-03-03 05:35:012
David Rochberg7c79a812011-01-19 19:24:453# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
Ryan Cairnsdd1ceb82010-03-03 05:35:014# 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 19:24:457"""Build packages on a host machine, then install them on the local target.
Chris Sosa136418c2010-11-11 00:27:148
David Rochberg7c79a812011-01-19 19:24:459Contacts 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 22:22:4113
David Rochberg7c79a812011-01-19 19:24:4514import optparse
15import os
16import shutil
17import subprocess
18import sys
19import urllib
20import urllib2
Chris Sosa4c9b2f92010-12-14 00:09:0621
Chris Sosa6c922142013-02-16 01:37:4922LSB_RELEASE_PATH = '/etc/lsb-release'
23STATEFUL_LSB_RELEASE_PATH = '/mnt/stateful_partition/etc/lsb-release'
24
25
26class GMergeParsingException(Exception):
27 """A fatal exception raised when an expected variable is not parsed."""
28
Ryan Cairnsdd1ceb82010-03-03 05:35:0129
David Rochberg7c79a812011-01-19 19:24:4530class GMerger(object):
Chris Sosa6c922142013-02-16 01:37:4931 """emerges a package from the devserver."""
32 def __init__(self, devserver_url, board):
33 self.devserver_url = devserver_url
34 self.board_name = board
Chris Sosa136418c2010-11-11 00:27:1435
Chris Sosa6c922142013-02-16 01:37:4936 @staticmethod
37 def RemountOrChangeRoot(environ):
38 """Remount the root filesystem rw; install into /usr/local if this fails.
Chris Sosa4a1e8192010-12-13 22:22:4139
Chris Sosa6c922142013-02-16 01:37:4940 Args:
41 environ: The environment dictionary.
42 """
David Rochberg7c79a812011-01-19 19:24:4543 rc = subprocess.call(['mount', '-o', 'remount,rw', '/'])
44 if rc == 0:
45 return
Chris Sosaee1e9722013-03-06 19:04:3146 try:
47 answer = raw_input(
48 'Could not mount / as writable. Install into /usr/local? (Y/n)')
49 except EOFError:
50 # Received if stdin is piped through /dev/null.
51 answer = None
52
Chris Sosa6c922142013-02-16 01:37:4953 if answer and answer[0] not in 'Yy':
David Rochberg7c79a812011-01-19 19:24:4554 sys.exit('Better safe than sorry.')
Chris Sosa6c922142013-02-16 01:37:4955
David Rochberg7c79a812011-01-19 19:24:4556 environ['ROOT'] = '/usr/local'
Chris Sosa605fe8852010-04-23 00:01:3257
David Rochberg7c79a812011-01-19 19:24:4558 def ParseLsbRelease(self, lsb_release_lines):
Chris Sosa6c922142013-02-16 01:37:4959 """Parses LSB release and set out internal variables accordingly
Ryan Cairnsdd1ceb82010-03-03 05:35:0160
Chris Sosa6c922142013-02-16 01:37:4961 Args:
62 lsb_release_lines: a list of key=val lines e.g. the output of opening
63 /etc/lsb-release and using readlines().
64 """
65 lsb_release = dict((k, v) for k, _, v in [line.rstrip().partition('=')
66 for line in lsb_release_lines])
67
68 parsing_msg = ('%(variable)s not set. Please set by using a command line '
69 'option or overriding in ' + STATEFUL_LSB_RELEASE_PATH)
70 if not self.devserver_url:
71 self.devserver_url = lsb_release['CHROMEOS_DEVSERVER']
72 if not self.devserver_url:
73 raise GMergeParsingException(parsing_msg % dict(
74 variable='CHROMEOS_DEVSERVER'))
75
76 if not self.board_name:
77 self.board_name = lsb_release['CHROMEOS_RELEASE_BOARD']
78 if not self.board_name:
79 raise GMergeParsingException(parsing_msg % dict(
80 variable='CHROMEOS_RELEASE_BOARD'))
81
82 def SetupPortageEnvironment(self, environ, include_masked_files):
83 """Setup portage to use stateful partition and fetch from dev server.
84
85 Args:
86 environ: The environment dictionary to setup.
87 include_masked_files: If true, include masked files in package
88 (e.g. debug symbols).
89 """
90 binhost_prefix = '%s/static/pkgroot/%s' % (self.devserver_url,
91 self.board_name)
David Jamese4f73a42011-05-19 19:18:3392 binhost = '%s/packages' % binhost_prefix
Chris Sosa6c922142013-02-16 01:37:4993 if not include_masked_files:
David Jamese4f73a42011-05-19 19:18:3394 binhost += ' %s/gmerge-packages' % binhost_prefix
Chris Sosa6c922142013-02-16 01:37:4995
David Rochberg7c79a812011-01-19 19:24:4596 environ.update({
97 'PORTDIR': '/usr/local/portage',
98 'PKGDIR': '/usr/local/portage',
99 'DISTDIR': '/usr/local/portage/distfiles',
David Jamese4f73a42011-05-19 19:18:33100 'PORTAGE_BINHOST': binhost,
David Rochberg7c79a812011-01-19 19:24:45101 'PORTAGE_TMPDIR': '/tmp',
102 'CONFIG_PROTECT': '-*',
Chris Sosa6c922142013-02-16 01:37:49103 'FEATURES': '-sandbox -usersandbox',
104 'ACCEPT_KEYWORDS': 'arm x86 amd64 ~arm ~x86 ~amd64',
David Jamesff339442011-05-19 21:08:35105 'ROOT': os.environ.get('ROOT', '/'),
106 'PORTAGE_CONFIGROOT': '/usr/local'
David Rochberg7c79a812011-01-19 19:24:45107 })
Ryan Cairnsdd1ceb82010-03-03 05:35:01108
Chris Sosa6c922142013-02-16 01:37:49109 def RequestPackageBuild(self, package_name, deep, accept_stable, usepkg):
110 """Contacts devserver to request a build.
111
112 Args:
113 package_name: The name of the package to build.
114 deep: Update package and all dependencies.
115 accept_stable: Allow non-workon packages.
116 usepkg: Use currently built binary packages on server.
117 """
118 def GeneratePackageRequest():
119 """Build the POST string that conveys our options to the devserver."""
120 post_data = {'board': self.board_name,
121 'deep': deep or '',
David Rochberg7c79a812011-01-19 19:24:45122 'pkg': package_name,
David Jamesed079b12011-05-17 21:53:15123 'features': os.environ.get('FEATURES'),
124 'use': os.environ.get('USE'),
Chris Sosa6c922142013-02-16 01:37:49125 'accept_stable': accept_stable or '',
126 'usepkg': usepkg or '',
David Rochberg7c79a812011-01-19 19:24:45127 }
Chris Sosa6c922142013-02-16 01:37:49128 post_data = dict([(key, value) for (key, value) in post_data.iteritems()
David Jamesed079b12011-05-17 21:53:15129 if value is not None])
Chris Sosa6c922142013-02-16 01:37:49130 return urllib.urlencode(post_data)
Frank Swiderskidc130812010-10-08 22:42:28131
Chris Sosa6c922142013-02-16 01:37:49132 print 'Sending build request to', self.devserver_url
David Rochberg7c79a812011-01-19 19:24:45133 try:
Chris Sosa6c922142013-02-16 01:37:49134 result = urllib2.urlopen(
135 self.devserver_url + '/build',
136 data=GeneratePackageRequest())
David Rochberg7c79a812011-01-19 19:24:45137 print result.read()
138 result.close()
Mandeep Singh Bainesea6b7a52010-08-17 21:03:57139
David Rochberg7c79a812011-01-19 19:24:45140 except urllib2.HTTPError, e:
141 # The exception includes the content, which is the error mesage
142 sys.exit(e.read())
David James3556d222011-05-20 22:58:41143 except urllib2.URLError, e:
144 sys.exit('Could not reach devserver. Reason: %s' % e.reason)
Ryan Cairnsdd1ceb82010-03-03 05:35:01145
Chris Sosa6c922142013-02-16 01:37:49146 @staticmethod
147 def EmergePackage(package_name, deep, extra):
148 """Emerges the package from the binhost.
149
150 Args:
151 package_name: The name of the package to build.
152 deep: Update package and all dependencies.
153 extra: Extra arguments to emerge.
154 """
155 # In case the version is the same as the one that's installed, don't re-use
156 # it.
157 print 'Emerging ', package_name
158 shutil.rmtree('/usr/local/portage', ignore_errors=True)
David Jamesf8711242013-07-23 03:22:36159 os.makedirs('/usr/local/portage')
Chris Sosa6c922142013-02-16 01:37:49160 shutil.rmtree('/var/cache/edb/binhost', ignore_errors=True)
161
162 emerge_args = ['emerge', '--getbinpkgonly', '--usepkgonly', '--verbose']
163 if deep:
164 emerge_args.extend(['--update', '--deep'])
165
166 if extra:
167 emerge_args.extend(extra.split())
168
169 emerge_args.append(package_name)
170 subprocess.check_call(emerge_args)
171
Ryan Cairnsdd1ceb82010-03-03 05:35:01172
David Rochberg7c79a812011-01-19 19:24:45173def main():
David Rochberg7c79a812011-01-19 19:24:45174 parser = optparse.OptionParser(usage='usage: %prog [options] package_name')
175 parser.add_option('--accept_stable',
Chris Sosa6c922142013-02-16 01:37:49176 action='store_true', default=False,
David Rochberg7c79a812011-01-19 19:24:45177 help=('Build even if a cros_workon package is not '
178 'using the live package'))
Chris Sosa6c922142013-02-16 01:37:49179 parser.add_option('-b', '--board', default=None,
180 help='Specify a different board to use when building.')
181 parser.add_option('-d', '--devserver_url', default=None,
182 help='Specify a different devserver(binhost) url to use'
183 'to build and download packages.')
David Jamese4f73a42011-05-19 19:18:33184 parser.add_option('--include_masked_files',
Chris Sosa6c922142013-02-16 01:37:49185 action='store_true',
David Jamese4f73a42011-05-19 19:18:33186 default=False, help=('Include masked files in package '
187 '(e.g. debug symbols)'))
David Jamesed079b12011-05-17 21:53:15188 parser.add_option('-n', '--usepkg',
Chris Sosa6c922142013-02-16 01:37:49189 action='store_true', default=False,
David James3556d222011-05-20 22:58:41190 help='Use currently built binary packages on server.')
191 parser.add_option('-D', '--deep',
Chris Sosa6c922142013-02-16 01:37:49192 action='store_true', default=False,
David James3556d222011-05-20 22:58:41193 help='Update package and all dependencies '
194 '(requires --usepkg).')
Chris Sosa6c922142013-02-16 01:37:49195 parser.add_option('-x', '--extra', default='',
David James3556d222011-05-20 22:58:41196 help='Extra arguments to pass to emerge command.')
David Rochberg7c79a812011-01-19 19:24:45197
Chris Sosa6c922142013-02-16 01:37:49198 options, remaining_arguments = parser.parse_args()
David Rochberg7c79a812011-01-19 19:24:45199 if len(remaining_arguments) != 1:
200 parser.print_help()
201 sys.exit('Need exactly one package name')
202
Chris Sosa6c922142013-02-16 01:37:49203
David James3556d222011-05-20 22:58:41204 # TODO(davidjames): Should we allow --deep without --usepkg? Not sure what
205 # the desired behavior should be in this case, so disabling the combo for
206 # now.
Chris Sosa6c922142013-02-16 01:37:49207 if options.deep and not options.usepkg:
David James3556d222011-05-20 22:58:41208 sys.exit('If using --deep, --usepkg must also be enabled.')
209
David Rochberg7c79a812011-01-19 19:24:45210 package_name = remaining_arguments[0]
211
Chris Sosa6c922142013-02-16 01:37:49212 subprocess.check_call(['mount', '-o', 'remount,exec', '/tmp'])
David Rochberg7c79a812011-01-19 19:24:45213 try:
Chris Sosa6c922142013-02-16 01:37:49214 etc_lsb_release_lines = open(LSB_RELEASE_PATH).readlines()
215 # Allow overrides from the stateful partition.
216 if os.path.exists(STATEFUL_LSB_RELEASE_PATH):
217 etc_lsb_release_lines += open(STATEFUL_LSB_RELEASE_PATH).readlines()
218 print 'Stateful lsb release file found', STATEFUL_LSB_RELEASE_PATH
David Rochberg7c79a812011-01-19 19:24:45219
Chris Sosa6c922142013-02-16 01:37:49220 merger = GMerger(options.devserver_url, options.board)
221 merger.ParseLsbRelease(etc_lsb_release_lines)
222 merger.RequestPackageBuild(package_name, options.deep,
223 options.accept_stable, options.usepkg)
224
225 merger.SetupPortageEnvironment(os.environ, options.include_masked_files)
Chris Sosadda923d2011-04-13 20:12:01226 merger.RemountOrChangeRoot(os.environ)
Chris Sosa6c922142013-02-16 01:37:49227 merger.EmergePackage(package_name, options.deep, options.extra)
David Rochberg7c79a812011-01-19 19:24:45228 finally:
229 subprocess.call(['mount', '-o', 'remount,noexec', '/tmp'])
230
231
232if __name__ == '__main__':
233 main()