blob: ac23d4408b43534b78830ebac3e417536d7c8b4f [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
David Jamesc93e6a4d2014-01-13 11:37:36 -080022from chromite.lib import retry_util
Mike Frysinger8e727a32013-01-16 16:57:53 -050023from chromite.lib import toolchain
Brian Harringb938c782012-02-29 15:14:38 -080024
25cros_build_lib.STRICT_SUDO = True
26
27
Zdenek Behanaa52cea2012-05-30 01:31:11 +020028COMPRESSION_PREFERENCE = ('xz', 'bz2')
Zdenek Behanfd0efe42012-04-13 04:36:40 +020029
Brian Harringb938c782012-02-29 15:14:38 -080030# TODO(zbehan): Remove the dependency on these, reimplement them in python
Mike Frysinger648ba2d2013-01-08 14:19:34 -050031MAKE_CHROOT = [os.path.join(constants.SOURCE_ROOT,
32 'src/scripts/sdk_lib/make_chroot.sh')]
33ENTER_CHROOT = [os.path.join(constants.SOURCE_ROOT,
34 'src/scripts/sdk_lib/enter_chroot.sh')]
Brian Harringb938c782012-02-29 15:14:38 -080035
Josh Triplett472a4182013-03-08 11:48:57 -080036# Proxy simulator configuration.
37PROXY_HOST_IP = '192.168.240.1'
38PROXY_PORT = 8080
39PROXY_GUEST_IP = '192.168.240.2'
40PROXY_NETMASK = 30
41PROXY_VETH_PREFIX = 'veth'
42PROXY_CONNECT_PORTS = (80, 443, 9418)
43PROXY_APACHE_FALLBACK_USERS = ('www-data', 'apache', 'nobody')
44PROXY_APACHE_MPMS = ('event', 'worker', 'prefork')
45PROXY_APACHE_FALLBACK_PATH = ':'.join(
46 '/usr/lib/apache2/mpm-%s' % mpm for mpm in PROXY_APACHE_MPMS
47)
48PROXY_APACHE_MODULE_GLOBS = ('/usr/lib*/apache2/modules', '/usr/lib*/apache2')
49
Josh Triplett9a495f62013-03-15 18:06:55 -070050# We need these tools to run. Very common tools (tar,..) are omitted.
Josh Triplette759b232013-03-08 13:03:43 -080051NEEDED_TOOLS = ('curl', 'xz')
Brian Harringb938c782012-02-29 15:14:38 -080052
Josh Triplett472a4182013-03-08 11:48:57 -080053# Tools needed for --proxy-sim only.
54PROXY_NEEDED_TOOLS = ('ip',)
Brian Harringb938c782012-02-29 15:14:38 -080055
Brian Harring1790ac42012-09-23 08:53:33 -070056def GetArchStageTarballs(version):
Brian Harringb938c782012-02-29 15:14:38 -080057 """Returns the URL for a given arch/version"""
Brian Harring1790ac42012-09-23 08:53:33 -070058 extension = {'bz2':'tbz2', 'xz':'tar.xz'}
Mike Frysinger8e727a32013-01-16 16:57:53 -050059 return [toolchain.GetSdkURL(suburl='cros-sdk-%s.%s'
60 % (version, extension[compressor]))
Brian Harring1790ac42012-09-23 08:53:33 -070061 for compressor in COMPRESSION_PREFERENCE]
62
63
64def GetStage3Urls(version):
Mike Frysinger8e727a32013-01-16 16:57:53 -050065 return [toolchain.GetSdkURL(suburl='stage3-amd64-%s.tar.%s' % (version, ext))
Brian Harring1790ac42012-09-23 08:53:33 -070066 for ext in COMPRESSION_PREFERENCE]
Brian Harringb938c782012-02-29 15:14:38 -080067
68
Brian Harringae0a5322012-09-15 01:46:51 -070069def FetchRemoteTarballs(storage_dir, urls):
Mike Frysinger34db8692013-11-11 14:54:08 -050070 """Fetches a tarball given by url, and place it in |storage_dir|.
Zdenek Behanfd0efe42012-04-13 04:36:40 +020071
72 Args:
Mike Frysinger34db8692013-11-11 14:54:08 -050073 storage_dir: Path where to save the tarball.
Zdenek Behanfd0efe42012-04-13 04:36:40 +020074 urls: List of URLs to try to download. Download will stop on first success.
75
76 Returns:
77 Full path to the downloaded file
78 """
Zdenek Behanfd0efe42012-04-13 04:36:40 +020079
Brian Harring1790ac42012-09-23 08:53:33 -070080 # Note we track content length ourselves since certain versions of curl
81 # fail if asked to resume a complete file.
82 # pylint: disable=C0301,W0631
83 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
Zdenek Behanfd0efe42012-04-13 04:36:40 +020084 for url in urls:
Brian Harring1790ac42012-09-23 08:53:33 -070085 # http://www.logilab.org/ticket/8766
86 # pylint: disable=E1101
87 parsed = urlparse.urlparse(url)
88 tarball_name = os.path.basename(parsed.path)
89 if parsed.scheme in ('', 'file'):
90 if os.path.exists(parsed.path):
91 return parsed.path
92 continue
93 content_length = 0
Zdenek Behanfd0efe42012-04-13 04:36:40 +020094 print 'Attempting download: %s' % url
David Jamesc93e6a4d2014-01-13 11:37:36 -080095 result = retry_util.RunCurl(
Brian Harring1790ac42012-09-23 08:53:33 -070096 ['-I', url], redirect_stdout=True, redirect_stderr=True,
97 print_cmd=False)
98 successful = False
99 for header in result.output.splitlines():
100 # We must walk the output to find the string '200 OK' for use cases where
101 # a proxy is involved and may have pushed down the actual header.
102 if header.find('200 OK') != -1:
103 successful = True
104 elif header.lower().startswith("content-length:"):
105 content_length = int(header.split(":", 1)[-1].strip())
106 if successful:
107 break
108 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200109 break
110 else:
111 raise Exception('No valid URLs found!')
112
Brian Harringae0a5322012-09-15 01:46:51 -0700113 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -0700114 current_size = 0
115 if os.path.exists(tarball_dest):
116 current_size = os.path.getsize(tarball_dest)
117 if current_size > content_length:
David James56e6c2c2012-10-24 23:54:41 -0700118 osutils.SafeUnlink(tarball_dest)
Brian Harring1790ac42012-09-23 08:53:33 -0700119 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100120
Brian Harring1790ac42012-09-23 08:53:33 -0700121 if current_size < content_length:
David Jamesc93e6a4d2014-01-13 11:37:36 -0800122 retry_util.RunCurl(
Brian Harring1790ac42012-09-23 08:53:33 -0700123 ['-f', '-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
124 print_cmd=False)
Brian Harringb938c782012-02-29 15:14:38 -0800125
Brian Harring1790ac42012-09-23 08:53:33 -0700126 # Cleanup old tarballs now since we've successfull fetched; only cleanup
127 # the tarballs for our prefix, or unknown ones.
128 ignored_prefix = ('stage3-' if tarball_name.startswith('cros-sdk-')
129 else 'cros-sdk-')
130 for filename in os.listdir(storage_dir):
131 if filename == tarball_name or filename.startswith(ignored_prefix):
132 continue
Brian Harringb938c782012-02-29 15:14:38 -0800133
Brian Harring1790ac42012-09-23 08:53:33 -0700134 print 'Cleaning up old tarball: %s' % (filename,)
David James56e6c2c2012-10-24 23:54:41 -0700135 osutils.SafeUnlink(os.path.join(storage_dir, filename))
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200136
Brian Harringb938c782012-02-29 15:14:38 -0800137 return tarball_dest
138
139
Brian Harring1790ac42012-09-23 08:53:33 -0700140def CreateChroot(chroot_path, sdk_tarball, cache_dir, nousepkg=False):
Brian Harringb938c782012-02-29 15:14:38 -0800141 """Creates a new chroot from a given SDK"""
Brian Harringb938c782012-02-29 15:14:38 -0800142
Brian Harring1790ac42012-09-23 08:53:33 -0700143 cmd = MAKE_CHROOT + ['--stage3_path', sdk_tarball,
Brian Harringae0a5322012-09-15 01:46:51 -0700144 '--chroot', chroot_path,
145 '--cache_dir', cache_dir]
Mike Frysinger2de7f042012-07-10 04:45:03 -0400146 if nousepkg:
147 cmd.append('--nousepkg')
Brian Harringb938c782012-02-29 15:14:38 -0800148
149 try:
150 cros_build_lib.RunCommand(cmd, print_cmd=False)
151 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700152 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800153
154
155def DeleteChroot(chroot_path):
156 """Deletes an existing chroot"""
157 cmd = MAKE_CHROOT + ['--chroot', chroot_path,
158 '--delete']
159 try:
160 cros_build_lib.RunCommand(cmd, print_cmd=False)
161 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700162 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800163
164
Brian Harringae0a5322012-09-15 01:46:51 -0700165def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
166 additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800167 """Enters an existing SDK chroot"""
Brian Harringae0a5322012-09-15 01:46:51 -0700168 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800169 if chrome_root:
170 cmd.extend(['--chrome_root', chrome_root])
171 if chrome_root_mount:
172 cmd.extend(['--chrome_root_mount', chrome_root_mount])
173 if len(additional_args) > 0:
174 cmd.append('--')
175 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700176
177 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True)
178 # If we were in interactive mode, ignore the exit code; it'll be whatever
179 # they last ran w/in the chroot and won't matter to us one way or another.
180 # Note this does allow chroot entrance to fail and be ignored during
181 # interactive; this is however a rare case and the user will immediately
182 # see it (nor will they be checking the exit code manually).
183 if ret.returncode != 0 and additional_args:
184 raise SystemExit('Running %r failed with exit code %i'
185 % (cmd, ret.returncode))
Brian Harringb938c782012-02-29 15:14:38 -0800186
187
David James56e6c2c2012-10-24 23:54:41 -0700188def _SudoCommand():
189 """Get the 'sudo' command, along with all needed environment variables."""
190
David James5a73b4d2013-03-07 10:23:40 -0800191 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
192 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700193 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800194 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700195 value = os.environ.get(key)
196 if value is not None:
197 cmd += ['%s=%s' % (key, value)]
198
199 # Pass in the path to the depot_tools so that users can access them from
200 # within the chroot.
Gaurav Shah9f55ef72013-11-05 11:19:09 -0800201 cmd += ['DEPOT_TOOLS=%s' % osutils.FindDepotTools()]
David James56e6c2c2012-10-24 23:54:41 -0700202 return cmd
203
204
Josh Triplett472a4182013-03-08 11:48:57 -0800205def _ReportMissing(missing):
206 """Report missing utilities, then exit.
207
208 Args:
209 missing: List of missing utilities, as returned by
210 osutils.FindMissingBinaries. If non-empty, will not return.
211 """
212
213 if missing:
214 raise SystemExit(
215 'The tool(s) %s were not found.\n'
216 'Please install the appropriate package in your host.\n'
217 'Example(ubuntu):\n'
218 ' sudo apt-get install <packagename>'
219 % ', '.join(missing))
220
221
222def _ProxySimSetup(options):
223 """Set up proxy simulator, and return only in the child environment.
224
225 TODO: Ideally, this should support multiple concurrent invocations of
226 cros_sdk --proxy-sim; currently, such invocations will conflict with each
227 other due to the veth device names and IP addresses. Either this code would
228 need to generate fresh, unused names for all of these before forking, or it
229 would need to support multiple concurrent cros_sdk invocations sharing one
230 proxy and allowing it to exit when unused (without counting on any local
231 service-management infrastructure on the host).
232 """
233
234 may_need_mpm = False
235 apache_bin = osutils.Which('apache2')
236 if apache_bin is None:
237 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
238 if apache_bin is None:
239 _ReportMissing(('apache2',))
240 else:
241 may_need_mpm = True
242
243 # Module names and .so names included for ease of grepping.
244 apache_modules = [('proxy_module', 'mod_proxy.so'),
245 ('proxy_connect_module', 'mod_proxy_connect.so'),
246 ('proxy_http_module', 'mod_proxy_http.so'),
247 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
248
249 # Find the apache module directory, and make sure it has the modules we need.
250 module_dirs = {}
251 for g in PROXY_APACHE_MODULE_GLOBS:
252 for mod, so in apache_modules:
253 for f in glob.glob(os.path.join(g, so)):
254 module_dirs.setdefault(os.path.dirname(f), []).append(so)
255 for apache_module_path, modules_found in module_dirs.iteritems():
256 if len(modules_found) == len(apache_modules):
257 break
258 else:
259 # Appease cros lint, which doesn't understand that this else block will not
260 # fall through to the subsequent code which relies on apache_module_path.
261 apache_module_path = None
262 raise SystemExit(
263 'Could not find apache module path containing all required modules: %s'
264 % ', '.join(so for mod, so in apache_modules))
265
266 def check_add_module(name):
267 so = 'mod_%s.so' % name
268 if os.access(os.path.join(apache_module_path, so), os.F_OK):
269 mod = '%s_module' % name
270 apache_modules.append((mod, so))
271 return True
272 return False
273
274 check_add_module('authz_core')
275 if may_need_mpm:
276 for mpm in PROXY_APACHE_MPMS:
277 if check_add_module('mpm_%s' % mpm):
278 break
279
280 veth_host = '%s-host' % PROXY_VETH_PREFIX
281 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
282
283 # Set up pipes from parent to child and vice versa.
284 # The child writes a byte to the parent after calling unshare, so that the
285 # parent can then assign the guest end of the veth interface to the child's
286 # new network namespace. The parent then writes a byte to the child after
287 # assigning the guest interface, so that the child can then configure that
288 # interface. In both cases, if we get back an EOF when reading from the
289 # pipe, we assume the other end exited with an error message, so just exit.
290 parent_readfd, child_writefd = os.pipe()
291 child_readfd, parent_writefd = os.pipe()
292 SUCCESS_FLAG = '+'
293
294 pid = os.fork()
295 if not pid:
296 os.close(parent_readfd)
297 os.close(parent_writefd)
298
299 namespaces.Unshare(namespaces.CLONE_NEWNET)
300 os.write(child_writefd, SUCCESS_FLAG)
301 os.close(child_writefd)
302 if os.read(child_readfd, 1) != SUCCESS_FLAG:
303 # Parent failed; it will have already have outputted an error message.
304 sys.exit(1)
305 os.close(child_readfd)
306
307 # Set up child side of the network.
308 commands = (
309 ('ip', 'address', 'add',
310 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
311 'dev', veth_guest),
312 ('ip', 'link', 'set', veth_guest, 'up'),
313 )
314 try:
315 for cmd in commands:
316 cros_build_lib.RunCommand(cmd, print_cmd=False)
317 except cros_build_lib.RunCommandError:
318 raise SystemExit('Running %r failed!' % (cmd,))
319
320 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
321 for proto in ('http', 'https', 'ftp'):
322 os.environ[proto + '_proxy'] = proxy_url
323 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
324 os.environ.pop(v, None)
325 return
326
327 os.close(child_readfd)
328 os.close(child_writefd)
329
330 if os.read(parent_readfd, 1) != SUCCESS_FLAG:
331 # Child failed; it will have already have outputted an error message.
332 sys.exit(1)
333 os.close(parent_readfd)
334
335 # Set up parent side of the network.
336 uid = int(os.environ.get('SUDO_UID', '0'))
337 gid = int(os.environ.get('SUDO_GID', '0'))
338 if uid == 0 or gid == 0:
339 for username in PROXY_APACHE_FALLBACK_USERS:
340 try:
341 pwnam = pwd.getpwnam(username)
342 uid, gid = pwnam.pw_uid, pwnam.pw_gid
343 break
344 except KeyError:
345 continue
346 if uid == 0 or gid == 0:
347 raise SystemExit('Could not find a non-root user to run Apache as')
348
349 chroot_parent, chroot_base = os.path.split(options.chroot)
350 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
351 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
352
353 apache_directives = [
354 'User #%u' % uid,
355 'Group #%u' % gid,
356 'PidFile %s' % pid_file,
357 'ErrorLog %s' % log_file,
358 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
359 'ServerName %s' % PROXY_HOST_IP,
360 'ProxyRequests On',
361 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
362 ] + [
363 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
364 for (mod, so) in apache_modules
365 ]
366 commands = (
367 ('ip', 'link', 'add', 'name', veth_host,
368 'type', 'veth', 'peer', 'name', veth_guest),
369 ('ip', 'address', 'add',
370 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
371 'dev', veth_host),
372 ('ip', 'link', 'set', veth_host, 'up'),
373 [apache_bin, '-f', '/dev/null']
374 + [arg for d in apache_directives for arg in ('-C', d)],
375 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
376 )
377 cmd = None # Make cros lint happy.
378 try:
379 for cmd in commands:
380 cros_build_lib.RunCommand(cmd, print_cmd=False)
381 except cros_build_lib.RunCommandError:
382 # Clean up existing interfaces, if any.
383 cmd_cleanup = ('ip', 'link', 'del', veth_host)
384 try:
385 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
386 except cros_build_lib.RunCommandError:
387 cros_build_lib.Error('running %r failed', cmd_cleanup)
388 raise SystemExit('Running %r failed!' % (cmd,))
389 os.write(parent_writefd, SUCCESS_FLAG)
390 os.close(parent_writefd)
391
392 sys.exit(os.waitpid(pid, 0)[1])
393
394
Mike Frysingera78a56e2012-11-20 06:02:30 -0500395def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700396 """Re-execute cros_sdk as root.
397
398 Also unshare the mount namespace so as to ensure that processes outside
399 the chroot can't mess with our mounts.
400 """
401 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500402 cmd = _SudoCommand() + ['--'] + argv
403 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500404 else:
Josh Triplette759b232013-03-08 13:03:43 -0800405 cgroups.Cgroup.InitSystem()
406 namespaces.Unshare(namespaces.CLONE_NEWNS)
David James56e6c2c2012-10-24 23:54:41 -0700407
408
Mike Frysinger34db8692013-11-11 14:54:08 -0500409def _CreateParser(sdk_latest_version, bootstrap_latest_version):
410 """Generate and return the parser with all the options."""
Brian Harring218e13c2012-10-10 16:21:26 -0700411 usage = """usage: %prog [options] [VAR1=val1 .. VARn=valn -- args]
Brian Harringb938c782012-02-29 15:14:38 -0800412
Brian Harring218e13c2012-10-10 16:21:26 -0700413This script is used for manipulating local chroot environments; creating,
414deleting, downloading, etc. If given --enter (or no args), it defaults
415to an interactive bash shell within the chroot.
Brian Harringb938c782012-02-29 15:14:38 -0800416
Brian Harring218e13c2012-10-10 16:21:26 -0700417If given args those are passed to the chroot environment, and executed."""
Brian Harring1790ac42012-09-23 08:53:33 -0700418
Brian Harring218e13c2012-10-10 16:21:26 -0700419 parser = commandline.OptionParser(usage=usage, caching=True)
420
Mike Frysinger34db8692013-11-11 14:54:08 -0500421 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500422 default_chroot = os.path.join(constants.SOURCE_ROOT,
423 constants.DEFAULT_CHROOT_DIR)
Brian Harring218e13c2012-10-10 16:21:26 -0700424 parser.add_option(
425 '--chroot', dest='chroot', default=default_chroot, type='path',
426 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Brian Harringb938c782012-02-29 15:14:38 -0800427
Brian Harring218e13c2012-10-10 16:21:26 -0700428 parser.add_option('--chrome_root', default=None, type='path',
429 help='Mount this chrome root into the SDK chroot')
430 parser.add_option('--chrome_root_mount', default=None, type='path',
431 help='Mount chrome into this path inside SDK chroot')
432 parser.add_option('--nousepkg', action='store_true', default=False,
433 help='Do not use binary packages when creating a chroot.')
Josh Triplett472a4182013-03-08 11:48:57 -0800434 parser.add_option('--proxy-sim', action='store_true', default=False,
435 help='Simulate a restrictive network requiring an outbound'
436 ' proxy.')
Brian Harringb938c782012-02-29 15:14:38 -0800437 parser.add_option('-u', '--url',
Brian Harringb6cf9142012-09-01 20:43:17 -0700438 dest='sdk_url', default=None,
Brian Harringb938c782012-02-29 15:14:38 -0800439 help=('''Use sdk tarball located at this url.
440 Use file:// for local files.'''))
Brian Harring1790ac42012-09-23 08:53:33 -0700441 parser.add_option('--sdk-version', default=None,
442 help='Use this sdk version. For prebuilt, current is %r'
Mike Frysinger34db8692013-11-11 14:54:08 -0500443 ', for bootstrapping it is %r.'
Brian Harring1790ac42012-09-23 08:53:33 -0700444 % (sdk_latest_version, bootstrap_latest_version))
Mike Frysinger34db8692013-11-11 14:54:08 -0500445
446 # Commands.
447 group = parser.add_option_group('Commands')
448 group.add_option(
449 '--enter', action='store_true', default=False,
450 help='Enter the SDK chroot. Implies --create.')
451 group.add_option(
452 '--create', action='store_true',default=False,
453 help='Create the chroot only if it does not already exist. '
454 'Implies --download.')
455 group.add_option(
456 '--bootstrap', action='store_true', default=False,
457 help='Build everything from scratch, including the sdk. '
458 'Use this only if you need to validate a change '
459 'that affects SDK creation itself (toolchain and '
460 'build are typically the only folk who need this). '
461 'Note this will quite heavily slow down the build. '
462 'This option implies --create --nousepkg.')
463 group.add_option(
464 '-r', '--replace', action='store_true', default=False,
465 help='Replace an existing SDK chroot. Basically an alias '
466 'for --delete --create.')
467 group.add_option(
468 '--delete', action='store_true', default=False,
469 help='Delete the current SDK chroot if it exists.')
470 group.add_option(
471 '--download', action='store_true', default=False,
472 help='Download the sdk.')
473 commands = group
474
475 # Internal options.
476 group = parser.add_option_group(
477 'Internal Chromium OS Build Team Options',
478 'Caution: these are for meant for the Chromium OS build team only')
479 group.add_option('--buildbot-log-version', default=False, action='store_true',
480 help='Log SDK version for buildbot consumption')
481
482 return (parser, commands)
483
484
485def main(argv):
486 conf = cros_build_lib.LoadKeyValueFile(
487 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
488 ignore_missing=True)
489 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
490 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
491 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Brian Harring218e13c2012-10-10 16:21:26 -0700492 options, chroot_command = parser.parse_args(argv)
Brian Harringb938c782012-02-29 15:14:38 -0800493
494 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500495 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800496
Brian Harring1790ac42012-09-23 08:53:33 -0700497 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700498 if host != 'x86_64':
499 parser.error(
500 "cros_sdk is currently only supported on x86_64; you're running"
501 " %s. Please find a x86_64 machine." % (host,))
502
Josh Triplett472a4182013-03-08 11:48:57 -0800503 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
504 if options.proxy_sim:
505 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Brian Harringb938c782012-02-29 15:14:38 -0800506
David James471532c2013-01-21 10:23:31 -0800507 _ReExecuteIfNeeded([sys.argv[0]] + argv)
508
Brian Harring218e13c2012-10-10 16:21:26 -0700509 # Expand out the aliases...
510 if options.replace:
511 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800512
Brian Harring218e13c2012-10-10 16:21:26 -0700513 if options.bootstrap:
514 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800515
Brian Harring218e13c2012-10-10 16:21:26 -0700516 # If a command is not given, default to enter.
517 options.enter |= not any(getattr(options, x.dest)
518 for x in commands.option_list)
519 options.enter |= bool(chroot_command)
520
521 if options.enter and options.delete and not options.create:
522 parser.error("Trying to enter the chroot when --delete "
523 "was specified makes no sense.")
524
525 # Finally, discern if we need to create the chroot.
526 chroot_exists = os.path.exists(options.chroot)
527 if options.create or options.enter:
528 # Only create if it's being wiped, or if it doesn't exist.
529 if not options.delete and chroot_exists:
530 options.create = False
531 else:
532 options.download = True
533
534 # Finally, flip create if necessary.
535 if options.enter:
536 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800537
Brian Harringb938c782012-02-29 15:14:38 -0800538 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -0700539 sdk_version = (bootstrap_latest_version if options.bootstrap
540 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -0800541 else:
542 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -0500543 if options.buildbot_log_version:
544 cros_build_lib.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -0800545
Brian Harring1790ac42012-09-23 08:53:33 -0700546 # Based on selections, fetch the tarball.
547 if options.sdk_url:
548 urls = [options.sdk_url]
549 elif options.bootstrap:
550 urls = GetStage3Urls(sdk_version)
551 else:
552 urls = GetArchStageTarballs(sdk_version)
553
Brian Harringb6cf9142012-09-01 20:43:17 -0700554 lock_path = os.path.dirname(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800555 lock_path = os.path.join(lock_path,
Brian Harringb6cf9142012-09-01 20:43:17 -0700556 '.%s_lock' % os.path.basename(options.chroot))
David James56e6c2c2012-10-24 23:54:41 -0700557 with cgroups.SimpleContainChildren('cros_sdk'):
558 with locking.FileLock(lock_path, 'chroot lock') as lock:
Brian Harring1790ac42012-09-23 08:53:33 -0700559
Josh Triplett472a4182013-03-08 11:48:57 -0800560 if options.proxy_sim:
561 _ProxySimSetup(options)
562
David James56e6c2c2012-10-24 23:54:41 -0700563 if options.delete and os.path.exists(options.chroot):
564 lock.write_lock()
565 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800566
David James56e6c2c2012-10-24 23:54:41 -0700567 sdk_cache = os.path.join(options.cache_dir, 'sdks')
568 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -0700569 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -0700570
David James56e6c2c2012-10-24 23:54:41 -0700571 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500572 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -0700573 if not os.path.exists(src):
574 osutils.SafeMakedirs(target)
575 continue
576 lock.write_lock(
577 "Upgrade to %r needed but chroot is locked; please exit "
578 "all instances so this upgrade can finish." % src)
579 if not os.path.exists(src):
580 # Note that while waiting for the write lock, src may've vanished;
581 # it's a rare race during the upgrade process that's a byproduct
582 # of us avoiding taking a write lock to do the src check. If we
583 # took a write lock for that check, it would effectively limit
584 # all cros_sdk for a chroot to a single instance.
585 osutils.SafeMakedirs(target)
586 elif not os.path.exists(target):
587 # Upgrade occurred, but a reversion, or something whacky
588 # occurred writing to the old location. Wipe and continue.
589 os.rename(src, target)
590 else:
591 # Upgrade occurred once already, but either a reversion or
592 # some before/after separate cros_sdk usage is at play.
593 # Wipe and continue.
594 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -0700595
David James56e6c2c2012-10-24 23:54:41 -0700596 if options.download:
597 lock.write_lock()
598 sdk_tarball = FetchRemoteTarballs(sdk_cache, urls)
Brian Harring218e13c2012-10-10 16:21:26 -0700599
David James56e6c2c2012-10-24 23:54:41 -0700600 if options.create:
601 lock.write_lock()
602 CreateChroot(options.chroot, sdk_tarball, options.cache_dir,
603 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -0700604
David James56e6c2c2012-10-24 23:54:41 -0700605 if options.enter:
606 lock.read_lock()
607 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
608 options.chrome_root_mount, chroot_command)