blob: c8e9280f371495c0864959ad817de1f9aa530bc1 [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.
Mike Frysinger749251e2014-01-29 05:04:27 -0500201 depot_tools = osutils.FindDepotTools()
202 if depot_tools:
203 cmd += ['DEPOT_TOOLS=%s' % depot_tools]
204
David James56e6c2c2012-10-24 23:54:41 -0700205 return cmd
206
207
Josh Triplett472a4182013-03-08 11:48:57 -0800208def _ReportMissing(missing):
209 """Report missing utilities, then exit.
210
211 Args:
212 missing: List of missing utilities, as returned by
213 osutils.FindMissingBinaries. If non-empty, will not return.
214 """
215
216 if missing:
217 raise SystemExit(
218 'The tool(s) %s were not found.\n'
219 'Please install the appropriate package in your host.\n'
220 'Example(ubuntu):\n'
221 ' sudo apt-get install <packagename>'
222 % ', '.join(missing))
223
224
225def _ProxySimSetup(options):
226 """Set up proxy simulator, and return only in the child environment.
227
228 TODO: Ideally, this should support multiple concurrent invocations of
229 cros_sdk --proxy-sim; currently, such invocations will conflict with each
230 other due to the veth device names and IP addresses. Either this code would
231 need to generate fresh, unused names for all of these before forking, or it
232 would need to support multiple concurrent cros_sdk invocations sharing one
233 proxy and allowing it to exit when unused (without counting on any local
234 service-management infrastructure on the host).
235 """
236
237 may_need_mpm = False
238 apache_bin = osutils.Which('apache2')
239 if apache_bin is None:
240 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
241 if apache_bin is None:
242 _ReportMissing(('apache2',))
243 else:
244 may_need_mpm = True
245
246 # Module names and .so names included for ease of grepping.
247 apache_modules = [('proxy_module', 'mod_proxy.so'),
248 ('proxy_connect_module', 'mod_proxy_connect.so'),
249 ('proxy_http_module', 'mod_proxy_http.so'),
250 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
251
252 # Find the apache module directory, and make sure it has the modules we need.
253 module_dirs = {}
254 for g in PROXY_APACHE_MODULE_GLOBS:
255 for mod, so in apache_modules:
256 for f in glob.glob(os.path.join(g, so)):
257 module_dirs.setdefault(os.path.dirname(f), []).append(so)
258 for apache_module_path, modules_found in module_dirs.iteritems():
259 if len(modules_found) == len(apache_modules):
260 break
261 else:
262 # Appease cros lint, which doesn't understand that this else block will not
263 # fall through to the subsequent code which relies on apache_module_path.
264 apache_module_path = None
265 raise SystemExit(
266 'Could not find apache module path containing all required modules: %s'
267 % ', '.join(so for mod, so in apache_modules))
268
269 def check_add_module(name):
270 so = 'mod_%s.so' % name
271 if os.access(os.path.join(apache_module_path, so), os.F_OK):
272 mod = '%s_module' % name
273 apache_modules.append((mod, so))
274 return True
275 return False
276
277 check_add_module('authz_core')
278 if may_need_mpm:
279 for mpm in PROXY_APACHE_MPMS:
280 if check_add_module('mpm_%s' % mpm):
281 break
282
283 veth_host = '%s-host' % PROXY_VETH_PREFIX
284 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
285
286 # Set up pipes from parent to child and vice versa.
287 # The child writes a byte to the parent after calling unshare, so that the
288 # parent can then assign the guest end of the veth interface to the child's
289 # new network namespace. The parent then writes a byte to the child after
290 # assigning the guest interface, so that the child can then configure that
291 # interface. In both cases, if we get back an EOF when reading from the
292 # pipe, we assume the other end exited with an error message, so just exit.
293 parent_readfd, child_writefd = os.pipe()
294 child_readfd, parent_writefd = os.pipe()
295 SUCCESS_FLAG = '+'
296
297 pid = os.fork()
298 if not pid:
299 os.close(parent_readfd)
300 os.close(parent_writefd)
301
302 namespaces.Unshare(namespaces.CLONE_NEWNET)
303 os.write(child_writefd, SUCCESS_FLAG)
304 os.close(child_writefd)
305 if os.read(child_readfd, 1) != SUCCESS_FLAG:
306 # Parent failed; it will have already have outputted an error message.
307 sys.exit(1)
308 os.close(child_readfd)
309
310 # Set up child side of the network.
311 commands = (
312 ('ip', 'address', 'add',
313 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
314 'dev', veth_guest),
315 ('ip', 'link', 'set', veth_guest, 'up'),
316 )
317 try:
318 for cmd in commands:
319 cros_build_lib.RunCommand(cmd, print_cmd=False)
320 except cros_build_lib.RunCommandError:
321 raise SystemExit('Running %r failed!' % (cmd,))
322
323 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
324 for proto in ('http', 'https', 'ftp'):
325 os.environ[proto + '_proxy'] = proxy_url
326 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
327 os.environ.pop(v, None)
328 return
329
330 os.close(child_readfd)
331 os.close(child_writefd)
332
333 if os.read(parent_readfd, 1) != SUCCESS_FLAG:
334 # Child failed; it will have already have outputted an error message.
335 sys.exit(1)
336 os.close(parent_readfd)
337
338 # Set up parent side of the network.
339 uid = int(os.environ.get('SUDO_UID', '0'))
340 gid = int(os.environ.get('SUDO_GID', '0'))
341 if uid == 0 or gid == 0:
342 for username in PROXY_APACHE_FALLBACK_USERS:
343 try:
344 pwnam = pwd.getpwnam(username)
345 uid, gid = pwnam.pw_uid, pwnam.pw_gid
346 break
347 except KeyError:
348 continue
349 if uid == 0 or gid == 0:
350 raise SystemExit('Could not find a non-root user to run Apache as')
351
352 chroot_parent, chroot_base = os.path.split(options.chroot)
353 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
354 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
355
356 apache_directives = [
357 'User #%u' % uid,
358 'Group #%u' % gid,
359 'PidFile %s' % pid_file,
360 'ErrorLog %s' % log_file,
361 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
362 'ServerName %s' % PROXY_HOST_IP,
363 'ProxyRequests On',
364 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
365 ] + [
366 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
367 for (mod, so) in apache_modules
368 ]
369 commands = (
370 ('ip', 'link', 'add', 'name', veth_host,
371 'type', 'veth', 'peer', 'name', veth_guest),
372 ('ip', 'address', 'add',
373 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
374 'dev', veth_host),
375 ('ip', 'link', 'set', veth_host, 'up'),
376 [apache_bin, '-f', '/dev/null']
377 + [arg for d in apache_directives for arg in ('-C', d)],
378 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
379 )
380 cmd = None # Make cros lint happy.
381 try:
382 for cmd in commands:
383 cros_build_lib.RunCommand(cmd, print_cmd=False)
384 except cros_build_lib.RunCommandError:
385 # Clean up existing interfaces, if any.
386 cmd_cleanup = ('ip', 'link', 'del', veth_host)
387 try:
388 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
389 except cros_build_lib.RunCommandError:
390 cros_build_lib.Error('running %r failed', cmd_cleanup)
391 raise SystemExit('Running %r failed!' % (cmd,))
392 os.write(parent_writefd, SUCCESS_FLAG)
393 os.close(parent_writefd)
394
395 sys.exit(os.waitpid(pid, 0)[1])
396
397
Mike Frysingera78a56e2012-11-20 06:02:30 -0500398def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700399 """Re-execute cros_sdk as root.
400
401 Also unshare the mount namespace so as to ensure that processes outside
402 the chroot can't mess with our mounts.
403 """
404 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500405 cmd = _SudoCommand() + ['--'] + argv
406 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500407 else:
Josh Triplette759b232013-03-08 13:03:43 -0800408 cgroups.Cgroup.InitSystem()
Mike Frysinger6da18852014-04-20 21:16:25 -0400409 namespaces.Unshare(namespaces.CLONE_NEWNS | namespaces.CLONE_NEWUTS)
David James56e6c2c2012-10-24 23:54:41 -0700410
411
Mike Frysinger34db8692013-11-11 14:54:08 -0500412def _CreateParser(sdk_latest_version, bootstrap_latest_version):
413 """Generate and return the parser with all the options."""
Brian Harring218e13c2012-10-10 16:21:26 -0700414 usage = """usage: %prog [options] [VAR1=val1 .. VARn=valn -- args]
Brian Harringb938c782012-02-29 15:14:38 -0800415
Brian Harring218e13c2012-10-10 16:21:26 -0700416This script is used for manipulating local chroot environments; creating,
417deleting, downloading, etc. If given --enter (or no args), it defaults
418to an interactive bash shell within the chroot.
Brian Harringb938c782012-02-29 15:14:38 -0800419
Brian Harring218e13c2012-10-10 16:21:26 -0700420If given args those are passed to the chroot environment, and executed."""
Brian Harring1790ac42012-09-23 08:53:33 -0700421
Brian Harring218e13c2012-10-10 16:21:26 -0700422 parser = commandline.OptionParser(usage=usage, caching=True)
423
Mike Frysinger34db8692013-11-11 14:54:08 -0500424 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500425 default_chroot = os.path.join(constants.SOURCE_ROOT,
426 constants.DEFAULT_CHROOT_DIR)
Brian Harring218e13c2012-10-10 16:21:26 -0700427 parser.add_option(
428 '--chroot', dest='chroot', default=default_chroot, type='path',
429 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Brian Harringb938c782012-02-29 15:14:38 -0800430
Brian Harring218e13c2012-10-10 16:21:26 -0700431 parser.add_option('--chrome_root', default=None, type='path',
432 help='Mount this chrome root into the SDK chroot')
433 parser.add_option('--chrome_root_mount', default=None, type='path',
434 help='Mount chrome into this path inside SDK chroot')
435 parser.add_option('--nousepkg', action='store_true', default=False,
436 help='Do not use binary packages when creating a chroot.')
Josh Triplett472a4182013-03-08 11:48:57 -0800437 parser.add_option('--proxy-sim', action='store_true', default=False,
438 help='Simulate a restrictive network requiring an outbound'
439 ' proxy.')
Brian Harringb938c782012-02-29 15:14:38 -0800440 parser.add_option('-u', '--url',
Brian Harringb6cf9142012-09-01 20:43:17 -0700441 dest='sdk_url', default=None,
Brian Harringb938c782012-02-29 15:14:38 -0800442 help=('''Use sdk tarball located at this url.
443 Use file:// for local files.'''))
Brian Harring1790ac42012-09-23 08:53:33 -0700444 parser.add_option('--sdk-version', default=None,
445 help='Use this sdk version. For prebuilt, current is %r'
Mike Frysinger34db8692013-11-11 14:54:08 -0500446 ', for bootstrapping it is %r.'
Brian Harring1790ac42012-09-23 08:53:33 -0700447 % (sdk_latest_version, bootstrap_latest_version))
Mike Frysinger34db8692013-11-11 14:54:08 -0500448
449 # Commands.
450 group = parser.add_option_group('Commands')
451 group.add_option(
452 '--enter', action='store_true', default=False,
453 help='Enter the SDK chroot. Implies --create.')
454 group.add_option(
455 '--create', action='store_true',default=False,
456 help='Create the chroot only if it does not already exist. '
457 'Implies --download.')
458 group.add_option(
459 '--bootstrap', action='store_true', default=False,
460 help='Build everything from scratch, including the sdk. '
461 'Use this only if you need to validate a change '
462 'that affects SDK creation itself (toolchain and '
463 'build are typically the only folk who need this). '
464 'Note this will quite heavily slow down the build. '
465 'This option implies --create --nousepkg.')
466 group.add_option(
467 '-r', '--replace', action='store_true', default=False,
468 help='Replace an existing SDK chroot. Basically an alias '
469 'for --delete --create.')
470 group.add_option(
471 '--delete', action='store_true', default=False,
472 help='Delete the current SDK chroot if it exists.')
473 group.add_option(
474 '--download', action='store_true', default=False,
475 help='Download the sdk.')
476 commands = group
477
478 # Internal options.
479 group = parser.add_option_group(
480 'Internal Chromium OS Build Team Options',
481 'Caution: these are for meant for the Chromium OS build team only')
482 group.add_option('--buildbot-log-version', default=False, action='store_true',
483 help='Log SDK version for buildbot consumption')
484
485 return (parser, commands)
486
487
488def main(argv):
489 conf = cros_build_lib.LoadKeyValueFile(
490 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
491 ignore_missing=True)
492 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
493 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
494 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Brian Harring218e13c2012-10-10 16:21:26 -0700495 options, chroot_command = parser.parse_args(argv)
Brian Harringb938c782012-02-29 15:14:38 -0800496
497 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500498 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800499
Brian Harring1790ac42012-09-23 08:53:33 -0700500 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700501 if host != 'x86_64':
502 parser.error(
503 "cros_sdk is currently only supported on x86_64; you're running"
504 " %s. Please find a x86_64 machine." % (host,))
505
Josh Triplett472a4182013-03-08 11:48:57 -0800506 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
507 if options.proxy_sim:
508 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Brian Harringb938c782012-02-29 15:14:38 -0800509
David James471532c2013-01-21 10:23:31 -0800510 _ReExecuteIfNeeded([sys.argv[0]] + argv)
511
Brian Harring218e13c2012-10-10 16:21:26 -0700512 # Expand out the aliases...
513 if options.replace:
514 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800515
Brian Harring218e13c2012-10-10 16:21:26 -0700516 if options.bootstrap:
517 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800518
Brian Harring218e13c2012-10-10 16:21:26 -0700519 # If a command is not given, default to enter.
520 options.enter |= not any(getattr(options, x.dest)
521 for x in commands.option_list)
522 options.enter |= bool(chroot_command)
523
524 if options.enter and options.delete and not options.create:
525 parser.error("Trying to enter the chroot when --delete "
526 "was specified makes no sense.")
527
528 # Finally, discern if we need to create the chroot.
529 chroot_exists = os.path.exists(options.chroot)
530 if options.create or options.enter:
531 # Only create if it's being wiped, or if it doesn't exist.
532 if not options.delete and chroot_exists:
533 options.create = False
534 else:
535 options.download = True
536
537 # Finally, flip create if necessary.
538 if options.enter:
539 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800540
Brian Harringb938c782012-02-29 15:14:38 -0800541 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -0700542 sdk_version = (bootstrap_latest_version if options.bootstrap
543 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -0800544 else:
545 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -0500546 if options.buildbot_log_version:
547 cros_build_lib.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -0800548
Brian Harring1790ac42012-09-23 08:53:33 -0700549 # Based on selections, fetch the tarball.
550 if options.sdk_url:
551 urls = [options.sdk_url]
552 elif options.bootstrap:
553 urls = GetStage3Urls(sdk_version)
554 else:
555 urls = GetArchStageTarballs(sdk_version)
556
Brian Harringb6cf9142012-09-01 20:43:17 -0700557 lock_path = os.path.dirname(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800558 lock_path = os.path.join(lock_path,
Brian Harringb6cf9142012-09-01 20:43:17 -0700559 '.%s_lock' % os.path.basename(options.chroot))
David James56e6c2c2012-10-24 23:54:41 -0700560 with cgroups.SimpleContainChildren('cros_sdk'):
561 with locking.FileLock(lock_path, 'chroot lock') as lock:
Brian Harring1790ac42012-09-23 08:53:33 -0700562
Josh Triplett472a4182013-03-08 11:48:57 -0800563 if options.proxy_sim:
564 _ProxySimSetup(options)
565
David James56e6c2c2012-10-24 23:54:41 -0700566 if options.delete and os.path.exists(options.chroot):
567 lock.write_lock()
568 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800569
David James56e6c2c2012-10-24 23:54:41 -0700570 sdk_cache = os.path.join(options.cache_dir, 'sdks')
571 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -0700572 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -0700573
David James56e6c2c2012-10-24 23:54:41 -0700574 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500575 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -0700576 if not os.path.exists(src):
577 osutils.SafeMakedirs(target)
578 continue
579 lock.write_lock(
580 "Upgrade to %r needed but chroot is locked; please exit "
581 "all instances so this upgrade can finish." % src)
582 if not os.path.exists(src):
583 # Note that while waiting for the write lock, src may've vanished;
584 # it's a rare race during the upgrade process that's a byproduct
585 # of us avoiding taking a write lock to do the src check. If we
586 # took a write lock for that check, it would effectively limit
587 # all cros_sdk for a chroot to a single instance.
588 osutils.SafeMakedirs(target)
589 elif not os.path.exists(target):
590 # Upgrade occurred, but a reversion, or something whacky
591 # occurred writing to the old location. Wipe and continue.
592 os.rename(src, target)
593 else:
594 # Upgrade occurred once already, but either a reversion or
595 # some before/after separate cros_sdk usage is at play.
596 # Wipe and continue.
597 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -0700598
David James56e6c2c2012-10-24 23:54:41 -0700599 if options.download:
600 lock.write_lock()
601 sdk_tarball = FetchRemoteTarballs(sdk_cache, urls)
Brian Harring218e13c2012-10-10 16:21:26 -0700602
David James56e6c2c2012-10-24 23:54:41 -0700603 if options.create:
604 lock.write_lock()
605 CreateChroot(options.chroot, sdk_tarball, options.cache_dir,
606 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -0700607
David James56e6c2c2012-10-24 23:54:41 -0700608 if options.enter:
609 lock.read_lock()
610 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
611 options.chrome_root_mount, chroot_command)