blob: 8df098da718b6de593afeb8b17f66f702b3ceec5 [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
Chris Sosaee1e9722013-03-06 11:04:31 -080046 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-15 17:37:49 -080053 if answer and answer[0] not in 'Yy':
David Rochberg7c79a812011-01-19 14:24:45 -050054 sys.exit('Better safe than sorry.')
Chris Sosa6c922142013-02-15 17:37:49 -080055
David Rochberg7c79a812011-01-19 14:24:45 -050056 environ['ROOT'] = '/usr/local'
Chris Sosa605fe882010-04-22 17:01:32 -070057
David Rochberg7c79a812011-01-19 14:24:45 -050058 def ParseLsbRelease(self, lsb_release_lines):
Chris Sosa6c922142013-02-15 17:37:49 -080059 """Parses LSB release and set out internal variables accordingly
Ryan Cairnsdd1ceb82010-03-02 21:35:01 -080060
Chris Sosa6c922142013-02-15 17:37:49 -080061 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 12:18:33 -070092 binhost = '%s/packages' % binhost_prefix
Chris Sosa6c922142013-02-15 17:37:49 -080093 if not include_masked_files:
David Jamese4f73a42011-05-19 12:18:33 -070094 binhost += ' %s/gmerge-packages' % binhost_prefix
Chris Sosa6c922142013-02-15 17:37:49 -080095
David Rochberg7c79a812011-01-19 14:24:45 -050096 environ.update({
97 'PORTDIR': '/usr/local/portage',
98 'PKGDIR': '/usr/local/portage',
99 'DISTDIR': '/usr/local/portage/distfiles',
David Jamese4f73a42011-05-19 12:18:33 -0700100 'PORTAGE_BINHOST': binhost,
David Rochberg7c79a812011-01-19 14:24:45 -0500101 'PORTAGE_TMPDIR': '/tmp',
102 'CONFIG_PROTECT': '-*',
Chris Sosa6c922142013-02-15 17:37:49 -0800103 'FEATURES': '-sandbox -usersandbox',
104 'ACCEPT_KEYWORDS': 'arm x86 amd64 ~arm ~x86 ~amd64',
David Jamesff339442011-05-19 14:08:35 -0700105 'ROOT': os.environ.get('ROOT', '/'),
106 'PORTAGE_CONFIGROOT': '/usr/local'
David Rochberg7c79a812011-01-19 14:24:45 -0500107 })
Ryan Cairnsdd1ceb82010-03-02 21:35:01 -0800108
Chris Sosa6c922142013-02-15 17:37:49 -0800109 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 14:24:45 -0500122 'pkg': package_name,
David Jamesed079b12011-05-17 14:53:15 -0700123 'features': os.environ.get('FEATURES'),
124 'use': os.environ.get('USE'),
Chris Sosa6c922142013-02-15 17:37:49 -0800125 'accept_stable': accept_stable or '',
126 'usepkg': usepkg or '',
David Rochberg7c79a812011-01-19 14:24:45 -0500127 }
Chris Sosa6c922142013-02-15 17:37:49 -0800128 post_data = dict([(key, value) for (key, value) in post_data.iteritems()
David Jamesed079b12011-05-17 14:53:15 -0700129 if value is not None])
Chris Sosa6c922142013-02-15 17:37:49 -0800130 return urllib.urlencode(post_data)
Frank Swiderskidc130812010-10-08 15:42:28 -0700131
Chris Sosa6c922142013-02-15 17:37:49 -0800132 print 'Sending build request to', self.devserver_url
David Rochberg7c79a812011-01-19 14:24:45 -0500133 try:
Chris Sosa6c922142013-02-15 17:37:49 -0800134 result = urllib2.urlopen(
135 self.devserver_url + '/build',
136 data=GeneratePackageRequest())
David Rochberg7c79a812011-01-19 14:24:45 -0500137 print result.read()
138 result.close()
Mandeep Singh Bainesea6b7a52010-08-17 14:03:57 -0700139
David Rochberg7c79a812011-01-19 14:24:45 -0500140 except urllib2.HTTPError, e:
141 # The exception includes the content, which is the error mesage
142 sys.exit(e.read())
David James3556d222011-05-20 15:58:41 -0700143 except urllib2.URLError, e:
144 sys.exit('Could not reach devserver. Reason: %s' % e.reason)
Ryan Cairnsdd1ceb82010-03-02 21:35:01 -0800145
Chris Sosa6c922142013-02-15 17:37:49 -0800146 @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)
159 shutil.rmtree('/var/cache/edb/binhost', ignore_errors=True)
160
161 emerge_args = ['emerge', '--getbinpkgonly', '--usepkgonly', '--verbose']
162 if deep:
163 emerge_args.extend(['--update', '--deep'])
164
165 if extra:
166 emerge_args.extend(extra.split())
167
168 emerge_args.append(package_name)
169 subprocess.check_call(emerge_args)
170
Ryan Cairnsdd1ceb82010-03-02 21:35:01 -0800171
David Rochberg7c79a812011-01-19 14:24:45 -0500172def main():
David Rochberg7c79a812011-01-19 14:24:45 -0500173 parser = optparse.OptionParser(usage='usage: %prog [options] package_name')
174 parser.add_option('--accept_stable',
Chris Sosa6c922142013-02-15 17:37:49 -0800175 action='store_true', default=False,
David Rochberg7c79a812011-01-19 14:24:45 -0500176 help=('Build even if a cros_workon package is not '
177 'using the live package'))
Chris Sosa6c922142013-02-15 17:37:49 -0800178 parser.add_option('-b', '--board', default=None,
179 help='Specify a different board to use when building.')
180 parser.add_option('-d', '--devserver_url', default=None,
181 help='Specify a different devserver(binhost) url to use'
182 'to build and download packages.')
David Jamese4f73a42011-05-19 12:18:33 -0700183 parser.add_option('--include_masked_files',
Chris Sosa6c922142013-02-15 17:37:49 -0800184 action='store_true',
David Jamese4f73a42011-05-19 12:18:33 -0700185 default=False, help=('Include masked files in package '
186 '(e.g. debug symbols)'))
David Jamesed079b12011-05-17 14:53:15 -0700187 parser.add_option('-n', '--usepkg',
Chris Sosa6c922142013-02-15 17:37:49 -0800188 action='store_true', default=False,
David James3556d222011-05-20 15:58:41 -0700189 help='Use currently built binary packages on server.')
190 parser.add_option('-D', '--deep',
Chris Sosa6c922142013-02-15 17:37:49 -0800191 action='store_true', default=False,
David James3556d222011-05-20 15:58:41 -0700192 help='Update package and all dependencies '
193 '(requires --usepkg).')
Chris Sosa6c922142013-02-15 17:37:49 -0800194 parser.add_option('-x', '--extra', default='',
David James3556d222011-05-20 15:58:41 -0700195 help='Extra arguments to pass to emerge command.')
David Rochberg7c79a812011-01-19 14:24:45 -0500196
Chris Sosa6c922142013-02-15 17:37:49 -0800197 options, remaining_arguments = parser.parse_args()
David Rochberg7c79a812011-01-19 14:24:45 -0500198 if len(remaining_arguments) != 1:
199 parser.print_help()
200 sys.exit('Need exactly one package name')
201
Chris Sosa6c922142013-02-15 17:37:49 -0800202
David James3556d222011-05-20 15:58:41 -0700203 # TODO(davidjames): Should we allow --deep without --usepkg? Not sure what
204 # the desired behavior should be in this case, so disabling the combo for
205 # now.
Chris Sosa6c922142013-02-15 17:37:49 -0800206 if options.deep and not options.usepkg:
David James3556d222011-05-20 15:58:41 -0700207 sys.exit('If using --deep, --usepkg must also be enabled.')
208
David Rochberg7c79a812011-01-19 14:24:45 -0500209 package_name = remaining_arguments[0]
210
Chris Sosa6c922142013-02-15 17:37:49 -0800211 subprocess.check_call(['mount', '-o', 'remount,exec', '/tmp'])
David Rochberg7c79a812011-01-19 14:24:45 -0500212 try:
Chris Sosa6c922142013-02-15 17:37:49 -0800213 etc_lsb_release_lines = open(LSB_RELEASE_PATH).readlines()
214 # Allow overrides from the stateful partition.
215 if os.path.exists(STATEFUL_LSB_RELEASE_PATH):
216 etc_lsb_release_lines += open(STATEFUL_LSB_RELEASE_PATH).readlines()
217 print 'Stateful lsb release file found', STATEFUL_LSB_RELEASE_PATH
David Rochberg7c79a812011-01-19 14:24:45 -0500218
Chris Sosa6c922142013-02-15 17:37:49 -0800219 merger = GMerger(options.devserver_url, options.board)
220 merger.ParseLsbRelease(etc_lsb_release_lines)
221 merger.RequestPackageBuild(package_name, options.deep,
222 options.accept_stable, options.usepkg)
223
224 merger.SetupPortageEnvironment(os.environ, options.include_masked_files)
Chris Sosadda923d2011-04-13 13:12:01 -0700225 merger.RemountOrChangeRoot(os.environ)
Chris Sosa6c922142013-02-15 17:37:49 -0800226 merger.EmergePackage(package_name, options.deep, options.extra)
David Rochberg7c79a812011-01-19 14:24:45 -0500227 finally:
228 subprocess.call(['mount', '-o', 'remount,noexec', '/tmp'])
229
230
231if __name__ == '__main__':
232 main()