blob: 31f81a012f09ae038945b750699d6d078d75b029 [file] [log] [blame]
Brian Harringb938c782012-02-29 15:14:38 -08001#!/usr/bin/env python
Mike Frysinger2de7f042012-07-10 04:45:03 -04002# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Brian Harringb938c782012-02-29 15:14:38 -08003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""This script fetches and prepares an SDK chroot.
7"""
8
Josh Triplett472a4182013-03-08 11:48:57 -08009import glob
Brian Harringb938c782012-02-29 15:14:38 -080010import os
Josh Triplett472a4182013-03-08 11:48:57 -080011import pwd
David James56e6c2c2012-10-24 23:54:41 -070012import sys
Brian Harringb938c782012-02-29 15:14:38 -080013import urlparse
14
15from chromite.buildbot import constants
Brian Harringcfe762a2012-02-29 13:03:53 -080016from chromite.lib import cgroups
Brian Harringb6cf9142012-09-01 20:43:17 -070017from chromite.lib import commandline
Brian Harringb938c782012-02-29 15:14:38 -080018from chromite.lib import cros_build_lib
Brian Harringb938c782012-02-29 15:14:38 -080019from chromite.lib import locking
Josh Triplette759b232013-03-08 13:03:43 -080020from chromite.lib import namespaces
Brian Harringae0a5322012-09-15 01:46:51 -070021from chromite.lib import osutils
Mike Frysinger8e727a32013-01-16 16:57:53 -050022from chromite.lib import toolchain
Brian Harringb938c782012-02-29 15:14:38 -080023
24cros_build_lib.STRICT_SUDO = True
25
26
Zdenek Behanaa52cea2012-05-30 01:31:11 +020027COMPRESSION_PREFERENCE = ('xz', 'bz2')
Zdenek Behanfd0efe42012-04-13 04:36:40 +020028
Brian Harringb938c782012-02-29 15:14:38 -080029# TODO(zbehan): Remove the dependency on these, reimplement them in python
Mike Frysinger648ba2d2013-01-08 14:19:34 -050030MAKE_CHROOT = [os.path.join(constants.SOURCE_ROOT,
31 'src/scripts/sdk_lib/make_chroot.sh')]
32ENTER_CHROOT = [os.path.join(constants.SOURCE_ROOT,
33 'src/scripts/sdk_lib/enter_chroot.sh')]
Brian Harringb938c782012-02-29 15:14:38 -080034
Josh Triplett472a4182013-03-08 11:48:57 -080035# Proxy simulator configuration.
36PROXY_HOST_IP = '192.168.240.1'
37PROXY_PORT = 8080
38PROXY_GUEST_IP = '192.168.240.2'
39PROXY_NETMASK = 30
40PROXY_VETH_PREFIX = 'veth'
41PROXY_CONNECT_PORTS = (80, 443, 9418)
42PROXY_APACHE_FALLBACK_USERS = ('www-data', 'apache', 'nobody')
43PROXY_APACHE_MPMS = ('event', 'worker', 'prefork')
44PROXY_APACHE_FALLBACK_PATH = ':'.join(
45 '/usr/lib/apache2/mpm-%s' % mpm for mpm in PROXY_APACHE_MPMS
46)
47PROXY_APACHE_MODULE_GLOBS = ('/usr/lib*/apache2/modules', '/usr/lib*/apache2')
48
Josh Triplett9a495f62013-03-15 18:06:55 -070049# We need these tools to run. Very common tools (tar,..) are omitted.
Josh Triplette759b232013-03-08 13:03:43 -080050NEEDED_TOOLS = ('curl', 'xz')
Brian Harringb938c782012-02-29 15:14:38 -080051
Josh Triplett472a4182013-03-08 11:48:57 -080052# Tools needed for --proxy-sim only.
53PROXY_NEEDED_TOOLS = ('ip',)
Brian Harringb938c782012-02-29 15:14:38 -080054
Brian Harring1790ac42012-09-23 08:53:33 -070055def GetArchStageTarballs(version):
Brian Harringb938c782012-02-29 15:14:38 -080056 """Returns the URL for a given arch/version"""
Brian Harring1790ac42012-09-23 08:53:33 -070057 extension = {'bz2':'tbz2', 'xz':'tar.xz'}
Mike Frysinger8e727a32013-01-16 16:57:53 -050058 return [toolchain.GetSdkURL(suburl='cros-sdk-%s.%s'
59 % (version, extension[compressor]))
Brian Harring1790ac42012-09-23 08:53:33 -070060 for compressor in COMPRESSION_PREFERENCE]
61
62
63def GetStage3Urls(version):
Mike Frysinger8e727a32013-01-16 16:57:53 -050064 return [toolchain.GetSdkURL(suburl='stage3-amd64-%s.tar.%s' % (version, ext))
Brian Harring1790ac42012-09-23 08:53:33 -070065 for ext in COMPRESSION_PREFERENCE]
Brian Harringb938c782012-02-29 15:14:38 -080066
67
Brian Harringae0a5322012-09-15 01:46:51 -070068def FetchRemoteTarballs(storage_dir, urls):
Mike Frysinger34db8692013-11-11 14:54:08 -050069 """Fetches a tarball given by url, and place it in |storage_dir|.
Zdenek Behanfd0efe42012-04-13 04:36:40 +020070
71 Args:
Mike Frysinger34db8692013-11-11 14:54:08 -050072 storage_dir: Path where to save the tarball.
Zdenek Behanfd0efe42012-04-13 04:36:40 +020073 urls: List of URLs to try to download. Download will stop on first success.
74
75 Returns:
76 Full path to the downloaded file
77 """
Zdenek Behanfd0efe42012-04-13 04:36:40 +020078
Brian Harring1790ac42012-09-23 08:53:33 -070079 # Note we track content length ourselves since certain versions of curl
80 # fail if asked to resume a complete file.
81 # pylint: disable=C0301,W0631
82 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
Zdenek Behanfd0efe42012-04-13 04:36:40 +020083 for url in urls:
Brian Harring1790ac42012-09-23 08:53:33 -070084 # http://www.logilab.org/ticket/8766
85 # pylint: disable=E1101
86 parsed = urlparse.urlparse(url)
87 tarball_name = os.path.basename(parsed.path)
88 if parsed.scheme in ('', 'file'):
89 if os.path.exists(parsed.path):
90 return parsed.path
91 continue
92 content_length = 0
Zdenek Behanfd0efe42012-04-13 04:36:40 +020093 print 'Attempting download: %s' % url
Brian Harring1790ac42012-09-23 08:53:33 -070094 result = cros_build_lib.RunCurl(
95 ['-I', url], redirect_stdout=True, redirect_stderr=True,
96 print_cmd=False)
97 successful = False
98 for header in result.output.splitlines():
99 # We must walk the output to find the string '200 OK' for use cases where
100 # a proxy is involved and may have pushed down the actual header.
101 if header.find('200 OK') != -1:
102 successful = True
103 elif header.lower().startswith("content-length:"):
104 content_length = int(header.split(":", 1)[-1].strip())
105 if successful:
106 break
107 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200108 break
109 else:
110 raise Exception('No valid URLs found!')
111
Brian Harringae0a5322012-09-15 01:46:51 -0700112 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -0700113 current_size = 0
114 if os.path.exists(tarball_dest):
115 current_size = os.path.getsize(tarball_dest)
116 if current_size > content_length:
David James56e6c2c2012-10-24 23:54:41 -0700117 osutils.SafeUnlink(tarball_dest)
Brian Harring1790ac42012-09-23 08:53:33 -0700118 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100119
Brian Harring1790ac42012-09-23 08:53:33 -0700120 if current_size < content_length:
121 cros_build_lib.RunCurl(
122 ['-f', '-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
123 print_cmd=False)
Brian Harringb938c782012-02-29 15:14:38 -0800124
Brian Harring1790ac42012-09-23 08:53:33 -0700125 # Cleanup old tarballs now since we've successfull fetched; only cleanup
126 # the tarballs for our prefix, or unknown ones.
127 ignored_prefix = ('stage3-' if tarball_name.startswith('cros-sdk-')
128 else 'cros-sdk-')
129 for filename in os.listdir(storage_dir):
130 if filename == tarball_name or filename.startswith(ignored_prefix):
131 continue
Brian Harringb938c782012-02-29 15:14:38 -0800132
Brian Harring1790ac42012-09-23 08:53:33 -0700133 print 'Cleaning up old tarball: %s' % (filename,)
David James56e6c2c2012-10-24 23:54:41 -0700134 osutils.SafeUnlink(os.path.join(storage_dir, filename))
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200135
Brian Harringb938c782012-02-29 15:14:38 -0800136 return tarball_dest
137
138
Brian Harring1790ac42012-09-23 08:53:33 -0700139def CreateChroot(chroot_path, sdk_tarball, cache_dir, nousepkg=False):
Brian Harringb938c782012-02-29 15:14:38 -0800140 """Creates a new chroot from a given SDK"""
Brian Harringb938c782012-02-29 15:14:38 -0800141
Brian Harring1790ac42012-09-23 08:53:33 -0700142 cmd = MAKE_CHROOT + ['--stage3_path', sdk_tarball,
Brian Harringae0a5322012-09-15 01:46:51 -0700143 '--chroot', chroot_path,
144 '--cache_dir', cache_dir]
Mike Frysinger2de7f042012-07-10 04:45:03 -0400145 if nousepkg:
146 cmd.append('--nousepkg')
Brian Harringb938c782012-02-29 15:14:38 -0800147
148 try:
149 cros_build_lib.RunCommand(cmd, print_cmd=False)
150 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700151 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800152
153
154def DeleteChroot(chroot_path):
155 """Deletes an existing chroot"""
156 cmd = MAKE_CHROOT + ['--chroot', chroot_path,
157 '--delete']
158 try:
159 cros_build_lib.RunCommand(cmd, print_cmd=False)
160 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700161 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800162
163
Brian Harringae0a5322012-09-15 01:46:51 -0700164def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
165 additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800166 """Enters an existing SDK chroot"""
Brian Harringae0a5322012-09-15 01:46:51 -0700167 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800168 if chrome_root:
169 cmd.extend(['--chrome_root', chrome_root])
170 if chrome_root_mount:
171 cmd.extend(['--chrome_root_mount', chrome_root_mount])
172 if len(additional_args) > 0:
173 cmd.append('--')
174 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700175
176 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True)
177 # If we were in interactive mode, ignore the exit code; it'll be whatever
178 # they last ran w/in the chroot and won't matter to us one way or another.
179 # Note this does allow chroot entrance to fail and be ignored during
180 # interactive; this is however a rare case and the user will immediately
181 # see it (nor will they be checking the exit code manually).
182 if ret.returncode != 0 and additional_args:
183 raise SystemExit('Running %r failed with exit code %i'
184 % (cmd, ret.returncode))
Brian Harringb938c782012-02-29 15:14:38 -0800185
186
David James56e6c2c2012-10-24 23:54:41 -0700187def _SudoCommand():
188 """Get the 'sudo' command, along with all needed environment variables."""
189
David James5a73b4d2013-03-07 10:23:40 -0800190 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
191 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700192 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800193 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700194 value = os.environ.get(key)
195 if value is not None:
196 cmd += ['%s=%s' % (key, value)]
197
198 # Pass in the path to the depot_tools so that users can access them from
199 # within the chroot.
Gaurav Shah9f55ef72013-11-05 11:19:09 -0800200 cmd += ['DEPOT_TOOLS=%s' % osutils.FindDepotTools()]
David James56e6c2c2012-10-24 23:54:41 -0700201 return cmd
202
203
Josh Triplett472a4182013-03-08 11:48:57 -0800204def _ReportMissing(missing):
205 """Report missing utilities, then exit.
206
207 Args:
208 missing: List of missing utilities, as returned by
209 osutils.FindMissingBinaries. If non-empty, will not return.
210 """
211
212 if missing:
213 raise SystemExit(
214 'The tool(s) %s were not found.\n'
215 'Please install the appropriate package in your host.\n'
216 'Example(ubuntu):\n'
217 ' sudo apt-get install <packagename>'
218 % ', '.join(missing))
219
220
221def _ProxySimSetup(options):
222 """Set up proxy simulator, and return only in the child environment.
223
224 TODO: Ideally, this should support multiple concurrent invocations of
225 cros_sdk --proxy-sim; currently, such invocations will conflict with each
226 other due to the veth device names and IP addresses. Either this code would
227 need to generate fresh, unused names for all of these before forking, or it
228 would need to support multiple concurrent cros_sdk invocations sharing one
229 proxy and allowing it to exit when unused (without counting on any local
230 service-management infrastructure on the host).
231 """
232
233 may_need_mpm = False
234 apache_bin = osutils.Which('apache2')
235 if apache_bin is None:
236 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
237 if apache_bin is None:
238 _ReportMissing(('apache2',))
239 else:
240 may_need_mpm = True
241
242 # Module names and .so names included for ease of grepping.
243 apache_modules = [('proxy_module', 'mod_proxy.so'),
244 ('proxy_connect_module', 'mod_proxy_connect.so'),
245 ('proxy_http_module', 'mod_proxy_http.so'),
246 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
247
248 # Find the apache module directory, and make sure it has the modules we need.
249 module_dirs = {}
250 for g in PROXY_APACHE_MODULE_GLOBS:
251 for mod, so in apache_modules:
252 for f in glob.glob(os.path.join(g, so)):
253 module_dirs.setdefault(os.path.dirname(f), []).append(so)
254 for apache_module_path, modules_found in module_dirs.iteritems():
255 if len(modules_found) == len(apache_modules):
256 break
257 else:
258 # Appease cros lint, which doesn't understand that this else block will not
259 # fall through to the subsequent code which relies on apache_module_path.
260 apache_module_path = None
261 raise SystemExit(
262 'Could not find apache module path containing all required modules: %s'
263 % ', '.join(so for mod, so in apache_modules))
264
265 def check_add_module(name):
266 so = 'mod_%s.so' % name
267 if os.access(os.path.join(apache_module_path, so), os.F_OK):
268 mod = '%s_module' % name
269 apache_modules.append((mod, so))
270 return True
271 return False
272
273 check_add_module('authz_core')
274 if may_need_mpm:
275 for mpm in PROXY_APACHE_MPMS:
276 if check_add_module('mpm_%s' % mpm):
277 break
278
279 veth_host = '%s-host' % PROXY_VETH_PREFIX
280 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
281
282 # Set up pipes from parent to child and vice versa.
283 # The child writes a byte to the parent after calling unshare, so that the
284 # parent can then assign the guest end of the veth interface to the child's
285 # new network namespace. The parent then writes a byte to the child after
286 # assigning the guest interface, so that the child can then configure that
287 # interface. In both cases, if we get back an EOF when reading from the
288 # pipe, we assume the other end exited with an error message, so just exit.
289 parent_readfd, child_writefd = os.pipe()
290 child_readfd, parent_writefd = os.pipe()
291 SUCCESS_FLAG = '+'
292
293 pid = os.fork()
294 if not pid:
295 os.close(parent_readfd)
296 os.close(parent_writefd)
297
298 namespaces.Unshare(namespaces.CLONE_NEWNET)
299 os.write(child_writefd, SUCCESS_FLAG)
300 os.close(child_writefd)
301 if os.read(child_readfd, 1) != SUCCESS_FLAG:
302 # Parent failed; it will have already have outputted an error message.
303 sys.exit(1)
304 os.close(child_readfd)
305
306 # Set up child side of the network.
307 commands = (
308 ('ip', 'address', 'add',
309 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
310 'dev', veth_guest),
311 ('ip', 'link', 'set', veth_guest, 'up'),
312 )
313 try:
314 for cmd in commands:
315 cros_build_lib.RunCommand(cmd, print_cmd=False)
316 except cros_build_lib.RunCommandError:
317 raise SystemExit('Running %r failed!' % (cmd,))
318
319 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
320 for proto in ('http', 'https', 'ftp'):
321 os.environ[proto + '_proxy'] = proxy_url
322 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
323 os.environ.pop(v, None)
324 return
325
326 os.close(child_readfd)
327 os.close(child_writefd)
328
329 if os.read(parent_readfd, 1) != SUCCESS_FLAG:
330 # Child failed; it will have already have outputted an error message.
331 sys.exit(1)
332 os.close(parent_readfd)
333
334 # Set up parent side of the network.
335 uid = int(os.environ.get('SUDO_UID', '0'))
336 gid = int(os.environ.get('SUDO_GID', '0'))
337 if uid == 0 or gid == 0:
338 for username in PROXY_APACHE_FALLBACK_USERS:
339 try:
340 pwnam = pwd.getpwnam(username)
341 uid, gid = pwnam.pw_uid, pwnam.pw_gid
342 break
343 except KeyError:
344 continue
345 if uid == 0 or gid == 0:
346 raise SystemExit('Could not find a non-root user to run Apache as')
347
348 chroot_parent, chroot_base = os.path.split(options.chroot)
349 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
350 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
351
352 apache_directives = [
353 'User #%u' % uid,
354 'Group #%u' % gid,
355 'PidFile %s' % pid_file,
356 'ErrorLog %s' % log_file,
357 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
358 'ServerName %s' % PROXY_HOST_IP,
359 'ProxyRequests On',
360 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
361 ] + [
362 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
363 for (mod, so) in apache_modules
364 ]
365 commands = (
366 ('ip', 'link', 'add', 'name', veth_host,
367 'type', 'veth', 'peer', 'name', veth_guest),
368 ('ip', 'address', 'add',
369 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
370 'dev', veth_host),
371 ('ip', 'link', 'set', veth_host, 'up'),
372 [apache_bin, '-f', '/dev/null']
373 + [arg for d in apache_directives for arg in ('-C', d)],
374 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
375 )
376 cmd = None # Make cros lint happy.
377 try:
378 for cmd in commands:
379 cros_build_lib.RunCommand(cmd, print_cmd=False)
380 except cros_build_lib.RunCommandError:
381 # Clean up existing interfaces, if any.
382 cmd_cleanup = ('ip', 'link', 'del', veth_host)
383 try:
384 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
385 except cros_build_lib.RunCommandError:
386 cros_build_lib.Error('running %r failed', cmd_cleanup)
387 raise SystemExit('Running %r failed!' % (cmd,))
388 os.write(parent_writefd, SUCCESS_FLAG)
389 os.close(parent_writefd)
390
391 sys.exit(os.waitpid(pid, 0)[1])
392
393
Mike Frysingera78a56e2012-11-20 06:02:30 -0500394def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700395 """Re-execute cros_sdk as root.
396
397 Also unshare the mount namespace so as to ensure that processes outside
398 the chroot can't mess with our mounts.
399 """
400 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500401 cmd = _SudoCommand() + ['--'] + argv
402 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500403 else:
Josh Triplette759b232013-03-08 13:03:43 -0800404 cgroups.Cgroup.InitSystem()
405 namespaces.Unshare(namespaces.CLONE_NEWNS)
David James56e6c2c2012-10-24 23:54:41 -0700406
407
Mike Frysinger34db8692013-11-11 14:54:08 -0500408def _CreateParser(sdk_latest_version, bootstrap_latest_version):
409 """Generate and return the parser with all the options."""
Brian Harring218e13c2012-10-10 16:21:26 -0700410 usage = """usage: %prog [options] [VAR1=val1 .. VARn=valn -- args]
Brian Harringb938c782012-02-29 15:14:38 -0800411
Brian Harring218e13c2012-10-10 16:21:26 -0700412This script is used for manipulating local chroot environments; creating,
413deleting, downloading, etc. If given --enter (or no args), it defaults
414to an interactive bash shell within the chroot.
Brian Harringb938c782012-02-29 15:14:38 -0800415
Brian Harring218e13c2012-10-10 16:21:26 -0700416If given args those are passed to the chroot environment, and executed."""
Brian Harring1790ac42012-09-23 08:53:33 -0700417
Brian Harring218e13c2012-10-10 16:21:26 -0700418 parser = commandline.OptionParser(usage=usage, caching=True)
419
Mike Frysinger34db8692013-11-11 14:54:08 -0500420 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500421 default_chroot = os.path.join(constants.SOURCE_ROOT,
422 constants.DEFAULT_CHROOT_DIR)
Brian Harring218e13c2012-10-10 16:21:26 -0700423 parser.add_option(
424 '--chroot', dest='chroot', default=default_chroot, type='path',
425 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Brian Harringb938c782012-02-29 15:14:38 -0800426
Brian Harring218e13c2012-10-10 16:21:26 -0700427 parser.add_option('--chrome_root', default=None, type='path',
428 help='Mount this chrome root into the SDK chroot')
429 parser.add_option('--chrome_root_mount', default=None, type='path',
430 help='Mount chrome into this path inside SDK chroot')
431 parser.add_option('--nousepkg', action='store_true', default=False,
432 help='Do not use binary packages when creating a chroot.')
Josh Triplett472a4182013-03-08 11:48:57 -0800433 parser.add_option('--proxy-sim', action='store_true', default=False,
434 help='Simulate a restrictive network requiring an outbound'
435 ' proxy.')
Brian Harringb938c782012-02-29 15:14:38 -0800436 parser.add_option('-u', '--url',
Brian Harringb6cf9142012-09-01 20:43:17 -0700437 dest='sdk_url', default=None,
Brian Harringb938c782012-02-29 15:14:38 -0800438 help=('''Use sdk tarball located at this url.
439 Use file:// for local files.'''))
Brian Harring1790ac42012-09-23 08:53:33 -0700440 parser.add_option('--sdk-version', default=None,
441 help='Use this sdk version. For prebuilt, current is %r'
Mike Frysinger34db8692013-11-11 14:54:08 -0500442 ', for bootstrapping it is %r.'
Brian Harring1790ac42012-09-23 08:53:33 -0700443 % (sdk_latest_version, bootstrap_latest_version))
Mike Frysinger34db8692013-11-11 14:54:08 -0500444
445 # Commands.
446 group = parser.add_option_group('Commands')
447 group.add_option(
448 '--enter', action='store_true', default=False,
449 help='Enter the SDK chroot. Implies --create.')
450 group.add_option(
451 '--create', action='store_true',default=False,
452 help='Create the chroot only if it does not already exist. '
453 'Implies --download.')
454 group.add_option(
455 '--bootstrap', action='store_true', default=False,
456 help='Build everything from scratch, including the sdk. '
457 'Use this only if you need to validate a change '
458 'that affects SDK creation itself (toolchain and '
459 'build are typically the only folk who need this). '
460 'Note this will quite heavily slow down the build. '
461 'This option implies --create --nousepkg.')
462 group.add_option(
463 '-r', '--replace', action='store_true', default=False,
464 help='Replace an existing SDK chroot. Basically an alias '
465 'for --delete --create.')
466 group.add_option(
467 '--delete', action='store_true', default=False,
468 help='Delete the current SDK chroot if it exists.')
469 group.add_option(
470 '--download', action='store_true', default=False,
471 help='Download the sdk.')
472 commands = group
473
474 # Internal options.
475 group = parser.add_option_group(
476 'Internal Chromium OS Build Team Options',
477 'Caution: these are for meant for the Chromium OS build team only')
478 group.add_option('--buildbot-log-version', default=False, action='store_true',
479 help='Log SDK version for buildbot consumption')
480
481 return (parser, commands)
482
483
484def main(argv):
485 conf = cros_build_lib.LoadKeyValueFile(
486 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
487 ignore_missing=True)
488 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
489 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
490 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Brian Harring218e13c2012-10-10 16:21:26 -0700491 options, chroot_command = parser.parse_args(argv)
Brian Harringb938c782012-02-29 15:14:38 -0800492
493 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500494 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800495
Brian Harring1790ac42012-09-23 08:53:33 -0700496 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700497 if host != 'x86_64':
498 parser.error(
499 "cros_sdk is currently only supported on x86_64; you're running"
500 " %s. Please find a x86_64 machine." % (host,))
501
Josh Triplett472a4182013-03-08 11:48:57 -0800502 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
503 if options.proxy_sim:
504 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Brian Harringb938c782012-02-29 15:14:38 -0800505
David James471532c2013-01-21 10:23:31 -0800506 _ReExecuteIfNeeded([sys.argv[0]] + argv)
507
Brian Harring218e13c2012-10-10 16:21:26 -0700508 # Expand out the aliases...
509 if options.replace:
510 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800511
Brian Harring218e13c2012-10-10 16:21:26 -0700512 if options.bootstrap:
513 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800514
Brian Harring218e13c2012-10-10 16:21:26 -0700515 # If a command is not given, default to enter.
516 options.enter |= not any(getattr(options, x.dest)
517 for x in commands.option_list)
518 options.enter |= bool(chroot_command)
519
520 if options.enter and options.delete and not options.create:
521 parser.error("Trying to enter the chroot when --delete "
522 "was specified makes no sense.")
523
524 # Finally, discern if we need to create the chroot.
525 chroot_exists = os.path.exists(options.chroot)
526 if options.create or options.enter:
527 # Only create if it's being wiped, or if it doesn't exist.
528 if not options.delete and chroot_exists:
529 options.create = False
530 else:
531 options.download = True
532
533 # Finally, flip create if necessary.
534 if options.enter:
535 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800536
Brian Harringb938c782012-02-29 15:14:38 -0800537 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -0700538 sdk_version = (bootstrap_latest_version if options.bootstrap
539 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -0800540 else:
541 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -0500542 if options.buildbot_log_version:
543 cros_build_lib.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -0800544
Brian Harring1790ac42012-09-23 08:53:33 -0700545 # Based on selections, fetch the tarball.
546 if options.sdk_url:
547 urls = [options.sdk_url]
548 elif options.bootstrap:
549 urls = GetStage3Urls(sdk_version)
550 else:
551 urls = GetArchStageTarballs(sdk_version)
552
Brian Harringb6cf9142012-09-01 20:43:17 -0700553 lock_path = os.path.dirname(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800554 lock_path = os.path.join(lock_path,
Brian Harringb6cf9142012-09-01 20:43:17 -0700555 '.%s_lock' % os.path.basename(options.chroot))
David James56e6c2c2012-10-24 23:54:41 -0700556 with cgroups.SimpleContainChildren('cros_sdk'):
557 with locking.FileLock(lock_path, 'chroot lock') as lock:
Brian Harring1790ac42012-09-23 08:53:33 -0700558
Josh Triplett472a4182013-03-08 11:48:57 -0800559 if options.proxy_sim:
560 _ProxySimSetup(options)
561
David James56e6c2c2012-10-24 23:54:41 -0700562 if options.delete and os.path.exists(options.chroot):
563 lock.write_lock()
564 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800565
David James56e6c2c2012-10-24 23:54:41 -0700566 sdk_cache = os.path.join(options.cache_dir, 'sdks')
567 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -0700568 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -0700569
David James56e6c2c2012-10-24 23:54:41 -0700570 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500571 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -0700572 if not os.path.exists(src):
573 osutils.SafeMakedirs(target)
574 continue
575 lock.write_lock(
576 "Upgrade to %r needed but chroot is locked; please exit "
577 "all instances so this upgrade can finish." % src)
578 if not os.path.exists(src):
579 # Note that while waiting for the write lock, src may've vanished;
580 # it's a rare race during the upgrade process that's a byproduct
581 # of us avoiding taking a write lock to do the src check. If we
582 # took a write lock for that check, it would effectively limit
583 # all cros_sdk for a chroot to a single instance.
584 osutils.SafeMakedirs(target)
585 elif not os.path.exists(target):
586 # Upgrade occurred, but a reversion, or something whacky
587 # occurred writing to the old location. Wipe and continue.
588 os.rename(src, target)
589 else:
590 # Upgrade occurred once already, but either a reversion or
591 # some before/after separate cros_sdk usage is at play.
592 # Wipe and continue.
593 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -0700594
David James56e6c2c2012-10-24 23:54:41 -0700595 if options.download:
596 lock.write_lock()
597 sdk_tarball = FetchRemoteTarballs(sdk_cache, urls)
Brian Harring218e13c2012-10-10 16:21:26 -0700598
David James56e6c2c2012-10-24 23:54:41 -0700599 if options.create:
600 lock.write_lock()
601 CreateChroot(options.chroot, sdk_tarball, options.cache_dir,
602 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -0700603
David James56e6c2c2012-10-24 23:54:41 -0700604 if options.enter:
605 lock.read_lock()
606 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
607 options.chrome_root_mount, chroot_command)