blob: 2a321d7634e8562ed8fd3be9d0f6e45175043316 [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
Chris Sosa6c922142013-02-15 17:37:49 -080022LSB_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-02 21:35:01 -080029
David Rochberg7c79a812011-01-19 14:24:45 -050030class GMerger(object):
Chris Sosa6c922142013-02-15 17:37:49 -080031 """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-10 16:27:14 -080035
Chris Sosa6c922142013-02-15 17:37:49 -080036 @staticmethod
37 def RemountOrChangeRoot(environ):
38 """Remount the root filesystem rw; install into /usr/local if this fails.
Chris Sosa4a1e8192010-12-13 14:22:41 -080039
Chris Sosa6c922142013-02-15 17:37:49 -080040 Args:
41 environ: The environment dictionary.
42 """
David Rochberg7c79a812011-01-19 14:24:45 -050043 rc = subprocess.call(['mount', '-o', 'remount,rw', '/'])
44 if rc == 0:
45 return
46 answer = raw_input(
47 'Could not mount / as writable. Install into /usr/local? (Y/n)')
Chris Sosa6c922142013-02-15 17:37:49 -080048 if answer and answer[0] not in 'Yy':
David Rochberg7c79a812011-01-19 14:24:45 -050049 sys.exit('Better safe than sorry.')
Chris Sosa6c922142013-02-15 17:37:49 -080050
David Rochberg7c79a812011-01-19 14:24:45 -050051 environ['ROOT'] = '/usr/local'
Chris Sosa605fe882010-04-22 17:01:32 -070052
David Rochberg7c79a812011-01-19 14:24:45 -050053 def ParseLsbRelease(self, lsb_release_lines):
Chris Sosa6c922142013-02-15 17:37:49 -080054 """Parses LSB release and set out internal variables accordingly
Ryan Cairnsdd1ceb82010-03-02 21:35:01 -080055
Chris Sosa6c922142013-02-15 17:37:49 -080056 Args:
57 lsb_release_lines: a list of key=val lines e.g. the output of opening
58 /etc/lsb-release and using readlines().
59 """
60 lsb_release = dict((k, v) for k, _, v in [line.rstrip().partition('=')
61 for line in lsb_release_lines])
62
63 parsing_msg = ('%(variable)s not set. Please set by using a command line '
64 'option or overriding in ' + STATEFUL_LSB_RELEASE_PATH)
65 if not self.devserver_url:
66 self.devserver_url = lsb_release['CHROMEOS_DEVSERVER']
67 if not self.devserver_url:
68 raise GMergeParsingException(parsing_msg % dict(
69 variable='CHROMEOS_DEVSERVER'))
70
71 if not self.board_name:
72 self.board_name = lsb_release['CHROMEOS_RELEASE_BOARD']
73 if not self.board_name:
74 raise GMergeParsingException(parsing_msg % dict(
75 variable='CHROMEOS_RELEASE_BOARD'))
76
77 def SetupPortageEnvironment(self, environ, include_masked_files):
78 """Setup portage to use stateful partition and fetch from dev server.
79
80 Args:
81 environ: The environment dictionary to setup.
82 include_masked_files: If true, include masked files in package
83 (e.g. debug symbols).
84 """
85 binhost_prefix = '%s/static/pkgroot/%s' % (self.devserver_url,
86 self.board_name)
David Jamese4f73a42011-05-19 12:18:33 -070087 binhost = '%s/packages' % binhost_prefix
Chris Sosa6c922142013-02-15 17:37:49 -080088 if not include_masked_files:
David Jamese4f73a42011-05-19 12:18:33 -070089 binhost += ' %s/gmerge-packages' % binhost_prefix
Chris Sosa6c922142013-02-15 17:37:49 -080090
David Rochberg7c79a812011-01-19 14:24:45 -050091 environ.update({
92 'PORTDIR': '/usr/local/portage',
93 'PKGDIR': '/usr/local/portage',
94 'DISTDIR': '/usr/local/portage/distfiles',
David Jamese4f73a42011-05-19 12:18:33 -070095 'PORTAGE_BINHOST': binhost,
David Rochberg7c79a812011-01-19 14:24:45 -050096 'PORTAGE_TMPDIR': '/tmp',
97 'CONFIG_PROTECT': '-*',
Chris Sosa6c922142013-02-15 17:37:49 -080098 'FEATURES': '-sandbox -usersandbox',
99 'ACCEPT_KEYWORDS': 'arm x86 amd64 ~arm ~x86 ~amd64',
David Jamesff339442011-05-19 14:08:35 -0700100 'ROOT': os.environ.get('ROOT', '/'),
101 'PORTAGE_CONFIGROOT': '/usr/local'
David Rochberg7c79a812011-01-19 14:24:45 -0500102 })
Ryan Cairnsdd1ceb82010-03-02 21:35:01 -0800103
Chris Sosa6c922142013-02-15 17:37:49 -0800104 def RequestPackageBuild(self, package_name, deep, accept_stable, usepkg):
105 """Contacts devserver to request a build.
106
107 Args:
108 package_name: The name of the package to build.
109 deep: Update package and all dependencies.
110 accept_stable: Allow non-workon packages.
111 usepkg: Use currently built binary packages on server.
112 """
113 def GeneratePackageRequest():
114 """Build the POST string that conveys our options to the devserver."""
115 post_data = {'board': self.board_name,
116 'deep': deep or '',
David Rochberg7c79a812011-01-19 14:24:45 -0500117 'pkg': package_name,
David Jamesed079b12011-05-17 14:53:15 -0700118 'features': os.environ.get('FEATURES'),
119 'use': os.environ.get('USE'),
Chris Sosa6c922142013-02-15 17:37:49 -0800120 'accept_stable': accept_stable or '',
121 'usepkg': usepkg or '',
David Rochberg7c79a812011-01-19 14:24:45 -0500122 }
Chris Sosa6c922142013-02-15 17:37:49 -0800123 post_data = dict([(key, value) for (key, value) in post_data.iteritems()
David Jamesed079b12011-05-17 14:53:15 -0700124 if value is not None])
Chris Sosa6c922142013-02-15 17:37:49 -0800125 return urllib.urlencode(post_data)
Frank Swiderskidc130812010-10-08 15:42:28 -0700126
Chris Sosa6c922142013-02-15 17:37:49 -0800127 print 'Sending build request to', self.devserver_url
David Rochberg7c79a812011-01-19 14:24:45 -0500128 try:
Chris Sosa6c922142013-02-15 17:37:49 -0800129 result = urllib2.urlopen(
130 self.devserver_url + '/build',
131 data=GeneratePackageRequest())
David Rochberg7c79a812011-01-19 14:24:45 -0500132 print result.read()
133 result.close()
Mandeep Singh Bainesea6b7a52010-08-17 14:03:57 -0700134
David Rochberg7c79a812011-01-19 14:24:45 -0500135 except urllib2.HTTPError, e:
136 # The exception includes the content, which is the error mesage
137 sys.exit(e.read())
David James3556d222011-05-20 15:58:41 -0700138 except urllib2.URLError, e:
139 sys.exit('Could not reach devserver. Reason: %s' % e.reason)
Ryan Cairnsdd1ceb82010-03-02 21:35:01 -0800140
Chris Sosa6c922142013-02-15 17:37:49 -0800141 @staticmethod
142 def EmergePackage(package_name, deep, extra):
143 """Emerges the package from the binhost.
144
145 Args:
146 package_name: The name of the package to build.
147 deep: Update package and all dependencies.
148 extra: Extra arguments to emerge.
149 """
150 # In case the version is the same as the one that's installed, don't re-use
151 # it.
152 print 'Emerging ', package_name
153 shutil.rmtree('/usr/local/portage', ignore_errors=True)
154 shutil.rmtree('/var/cache/edb/binhost', ignore_errors=True)
155
156 emerge_args = ['emerge', '--getbinpkgonly', '--usepkgonly', '--verbose']
157 if deep:
158 emerge_args.extend(['--update', '--deep'])
159
160 if extra:
161 emerge_args.extend(extra.split())
162
163 emerge_args.append(package_name)
164 subprocess.check_call(emerge_args)
165
Ryan Cairnsdd1ceb82010-03-02 21:35:01 -0800166
David Rochberg7c79a812011-01-19 14:24:45 -0500167def main():
David Rochberg7c79a812011-01-19 14:24:45 -0500168 parser = optparse.OptionParser(usage='usage: %prog [options] package_name')
169 parser.add_option('--accept_stable',
Chris Sosa6c922142013-02-15 17:37:49 -0800170 action='store_true', default=False,
David Rochberg7c79a812011-01-19 14:24:45 -0500171 help=('Build even if a cros_workon package is not '
172 'using the live package'))
Chris Sosa6c922142013-02-15 17:37:49 -0800173 parser.add_option('-b', '--board', default=None,
174 help='Specify a different board to use when building.')
175 parser.add_option('-d', '--devserver_url', default=None,
176 help='Specify a different devserver(binhost) url to use'
177 'to build and download packages.')
David Jamese4f73a42011-05-19 12:18:33 -0700178 parser.add_option('--include_masked_files',
Chris Sosa6c922142013-02-15 17:37:49 -0800179 action='store_true',
David Jamese4f73a42011-05-19 12:18:33 -0700180 default=False, help=('Include masked files in package '
181 '(e.g. debug symbols)'))
David Jamesed079b12011-05-17 14:53:15 -0700182 parser.add_option('-n', '--usepkg',
Chris Sosa6c922142013-02-15 17:37:49 -0800183 action='store_true', default=False,
David James3556d222011-05-20 15:58:41 -0700184 help='Use currently built binary packages on server.')
185 parser.add_option('-D', '--deep',
Chris Sosa6c922142013-02-15 17:37:49 -0800186 action='store_true', default=False,
David James3556d222011-05-20 15:58:41 -0700187 help='Update package and all dependencies '
188 '(requires --usepkg).')
Chris Sosa6c922142013-02-15 17:37:49 -0800189 parser.add_option('-x', '--extra', default='',
David James3556d222011-05-20 15:58:41 -0700190 help='Extra arguments to pass to emerge command.')
David Rochberg7c79a812011-01-19 14:24:45 -0500191
Chris Sosa6c922142013-02-15 17:37:49 -0800192 options, remaining_arguments = parser.parse_args()
David Rochberg7c79a812011-01-19 14:24:45 -0500193 if len(remaining_arguments) != 1:
194 parser.print_help()
195 sys.exit('Need exactly one package name')
196
Chris Sosa6c922142013-02-15 17:37:49 -0800197
David James3556d222011-05-20 15:58:41 -0700198 # TODO(davidjames): Should we allow --deep without --usepkg? Not sure what
199 # the desired behavior should be in this case, so disabling the combo for
200 # now.
Chris Sosa6c922142013-02-15 17:37:49 -0800201 if options.deep and not options.usepkg:
David James3556d222011-05-20 15:58:41 -0700202 sys.exit('If using --deep, --usepkg must also be enabled.')
203
David Rochberg7c79a812011-01-19 14:24:45 -0500204 package_name = remaining_arguments[0]
205
Chris Sosa6c922142013-02-15 17:37:49 -0800206 subprocess.check_call(['mount', '-o', 'remount,exec', '/tmp'])
David Rochberg7c79a812011-01-19 14:24:45 -0500207 try:
Chris Sosa6c922142013-02-15 17:37:49 -0800208 etc_lsb_release_lines = open(LSB_RELEASE_PATH).readlines()
209 # Allow overrides from the stateful partition.
210 if os.path.exists(STATEFUL_LSB_RELEASE_PATH):
211 etc_lsb_release_lines += open(STATEFUL_LSB_RELEASE_PATH).readlines()
212 print 'Stateful lsb release file found', STATEFUL_LSB_RELEASE_PATH
David Rochberg7c79a812011-01-19 14:24:45 -0500213
Chris Sosa6c922142013-02-15 17:37:49 -0800214 merger = GMerger(options.devserver_url, options.board)
215 merger.ParseLsbRelease(etc_lsb_release_lines)
216 merger.RequestPackageBuild(package_name, options.deep,
217 options.accept_stable, options.usepkg)
218
219 merger.SetupPortageEnvironment(os.environ, options.include_masked_files)
Chris Sosadda923d2011-04-13 13:12:01 -0700220 merger.RemountOrChangeRoot(os.environ)
Chris Sosa6c922142013-02-15 17:37:49 -0800221 merger.EmergePackage(package_name, options.deep, options.extra)
David Rochberg7c79a812011-01-19 14:24:45 -0500222 finally:
223 subprocess.call(['mount', '-o', 'remount,noexec', '/tmp'])
224
225
226if __name__ == '__main__':
227 main()