blob: 56aae043b03f2a969d364cb338dd47a9ea1b80d3 [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
Don Garrett88b8d782014-05-13 17:30:55 -070015from chromite.cbuildbot 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
Mike Frysingercc838832014-05-24 13:10:30 -040056
Brian Harring1790ac42012-09-23 08:53:33 -070057def GetArchStageTarballs(version):
Brian Harringb938c782012-02-29 15:14:38 -080058 """Returns the URL for a given arch/version"""
Brian Harring1790ac42012-09-23 08:53:33 -070059 extension = {'bz2':'tbz2', 'xz':'tar.xz'}
Mike Frysinger8e727a32013-01-16 16:57:53 -050060 return [toolchain.GetSdkURL(suburl='cros-sdk-%s.%s'
61 % (version, extension[compressor]))
Brian Harring1790ac42012-09-23 08:53:33 -070062 for compressor in COMPRESSION_PREFERENCE]
63
64
65def GetStage3Urls(version):
Mike Frysinger8e727a32013-01-16 16:57:53 -050066 return [toolchain.GetSdkURL(suburl='stage3-amd64-%s.tar.%s' % (version, ext))
Brian Harring1790ac42012-09-23 08:53:33 -070067 for ext in COMPRESSION_PREFERENCE]
Brian Harringb938c782012-02-29 15:14:38 -080068
69
Brian Harringae0a5322012-09-15 01:46:51 -070070def FetchRemoteTarballs(storage_dir, urls):
Mike Frysinger34db8692013-11-11 14:54:08 -050071 """Fetches a tarball given by url, and place it in |storage_dir|.
Zdenek Behanfd0efe42012-04-13 04:36:40 +020072
73 Args:
Mike Frysinger34db8692013-11-11 14:54:08 -050074 storage_dir: Path where to save the tarball.
Zdenek Behanfd0efe42012-04-13 04:36:40 +020075 urls: List of URLs to try to download. Download will stop on first success.
76
77 Returns:
78 Full path to the downloaded file
79 """
Zdenek Behanfd0efe42012-04-13 04:36:40 +020080
Brian Harring1790ac42012-09-23 08:53:33 -070081 # Note we track content length ourselves since certain versions of curl
82 # fail if asked to resume a complete file.
83 # pylint: disable=C0301,W0631
84 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
Zdenek Behanfd0efe42012-04-13 04:36:40 +020085 for url in urls:
Brian Harring1790ac42012-09-23 08:53:33 -070086 # http://www.logilab.org/ticket/8766
87 # pylint: disable=E1101
88 parsed = urlparse.urlparse(url)
89 tarball_name = os.path.basename(parsed.path)
90 if parsed.scheme in ('', 'file'):
91 if os.path.exists(parsed.path):
92 return parsed.path
93 continue
94 content_length = 0
Zdenek Behanfd0efe42012-04-13 04:36:40 +020095 print 'Attempting download: %s' % url
David Jamesc93e6a4d2014-01-13 11:37:36 -080096 result = retry_util.RunCurl(
Brian Harring1790ac42012-09-23 08:53:33 -070097 ['-I', url], redirect_stdout=True, redirect_stderr=True,
98 print_cmd=False)
99 successful = False
100 for header in result.output.splitlines():
101 # We must walk the output to find the string '200 OK' for use cases where
102 # a proxy is involved and may have pushed down the actual header.
103 if header.find('200 OK') != -1:
104 successful = True
105 elif header.lower().startswith("content-length:"):
106 content_length = int(header.split(":", 1)[-1].strip())
107 if successful:
108 break
109 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200110 break
111 else:
112 raise Exception('No valid URLs found!')
113
Brian Harringae0a5322012-09-15 01:46:51 -0700114 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -0700115 current_size = 0
116 if os.path.exists(tarball_dest):
117 current_size = os.path.getsize(tarball_dest)
118 if current_size > content_length:
David James56e6c2c2012-10-24 23:54:41 -0700119 osutils.SafeUnlink(tarball_dest)
Brian Harring1790ac42012-09-23 08:53:33 -0700120 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100121
Brian Harring1790ac42012-09-23 08:53:33 -0700122 if current_size < content_length:
David Jamesc93e6a4d2014-01-13 11:37:36 -0800123 retry_util.RunCurl(
Brian Harring1790ac42012-09-23 08:53:33 -0700124 ['-f', '-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
125 print_cmd=False)
Brian Harringb938c782012-02-29 15:14:38 -0800126
Brian Harring1790ac42012-09-23 08:53:33 -0700127 # Cleanup old tarballs now since we've successfull fetched; only cleanup
128 # the tarballs for our prefix, or unknown ones.
129 ignored_prefix = ('stage3-' if tarball_name.startswith('cros-sdk-')
130 else 'cros-sdk-')
131 for filename in os.listdir(storage_dir):
132 if filename == tarball_name or filename.startswith(ignored_prefix):
133 continue
Brian Harringb938c782012-02-29 15:14:38 -0800134
Brian Harring1790ac42012-09-23 08:53:33 -0700135 print 'Cleaning up old tarball: %s' % (filename,)
David James56e6c2c2012-10-24 23:54:41 -0700136 osutils.SafeUnlink(os.path.join(storage_dir, filename))
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200137
Brian Harringb938c782012-02-29 15:14:38 -0800138 return tarball_dest
139
140
Brian Harring1790ac42012-09-23 08:53:33 -0700141def CreateChroot(chroot_path, sdk_tarball, cache_dir, nousepkg=False):
Brian Harringb938c782012-02-29 15:14:38 -0800142 """Creates a new chroot from a given SDK"""
Brian Harringb938c782012-02-29 15:14:38 -0800143
Brian Harring1790ac42012-09-23 08:53:33 -0700144 cmd = MAKE_CHROOT + ['--stage3_path', sdk_tarball,
Brian Harringae0a5322012-09-15 01:46:51 -0700145 '--chroot', chroot_path,
146 '--cache_dir', cache_dir]
Mike Frysinger2de7f042012-07-10 04:45:03 -0400147 if nousepkg:
148 cmd.append('--nousepkg')
Brian Harringb938c782012-02-29 15:14:38 -0800149
150 try:
151 cros_build_lib.RunCommand(cmd, print_cmd=False)
152 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700153 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800154
155
156def DeleteChroot(chroot_path):
157 """Deletes an existing chroot"""
158 cmd = MAKE_CHROOT + ['--chroot', chroot_path,
159 '--delete']
160 try:
161 cros_build_lib.RunCommand(cmd, print_cmd=False)
162 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700163 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800164
165
Brian Harringae0a5322012-09-15 01:46:51 -0700166def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
167 additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800168 """Enters an existing SDK chroot"""
Mike Frysingere5456972013-06-13 00:07:23 -0400169 st = os.statvfs(os.path.join(chroot_path, 'usr', 'bin', 'sudo'))
170 # The os.ST_NOSUID constant wasn't added until python-3.2.
171 if st.f_flag & 0x2:
172 cros_build_lib.Die('chroot cannot be in a nosuid mount')
173
Brian Harringae0a5322012-09-15 01:46:51 -0700174 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800175 if chrome_root:
176 cmd.extend(['--chrome_root', chrome_root])
177 if chrome_root_mount:
178 cmd.extend(['--chrome_root_mount', chrome_root_mount])
179 if len(additional_args) > 0:
180 cmd.append('--')
181 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700182
183 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True)
184 # If we were in interactive mode, ignore the exit code; it'll be whatever
185 # they last ran w/in the chroot and won't matter to us one way or another.
186 # Note this does allow chroot entrance to fail and be ignored during
187 # interactive; this is however a rare case and the user will immediately
188 # see it (nor will they be checking the exit code manually).
189 if ret.returncode != 0 and additional_args:
190 raise SystemExit('Running %r failed with exit code %i'
191 % (cmd, ret.returncode))
Brian Harringb938c782012-02-29 15:14:38 -0800192
193
David James56e6c2c2012-10-24 23:54:41 -0700194def _SudoCommand():
195 """Get the 'sudo' command, along with all needed environment variables."""
196
David James5a73b4d2013-03-07 10:23:40 -0800197 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
198 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700199 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800200 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700201 value = os.environ.get(key)
202 if value is not None:
203 cmd += ['%s=%s' % (key, value)]
204
205 # Pass in the path to the depot_tools so that users can access them from
206 # within the chroot.
Mike Frysinger08e75f12014-08-13 01:30:09 -0400207 cmd += ['DEPOT_TOOLS=%s' % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500208
David James56e6c2c2012-10-24 23:54:41 -0700209 return cmd
210
211
Josh Triplett472a4182013-03-08 11:48:57 -0800212def _ReportMissing(missing):
213 """Report missing utilities, then exit.
214
215 Args:
216 missing: List of missing utilities, as returned by
217 osutils.FindMissingBinaries. If non-empty, will not return.
218 """
219
220 if missing:
221 raise SystemExit(
222 'The tool(s) %s were not found.\n'
223 'Please install the appropriate package in your host.\n'
224 'Example(ubuntu):\n'
225 ' sudo apt-get install <packagename>'
226 % ', '.join(missing))
227
228
229def _ProxySimSetup(options):
230 """Set up proxy simulator, and return only in the child environment.
231
232 TODO: Ideally, this should support multiple concurrent invocations of
233 cros_sdk --proxy-sim; currently, such invocations will conflict with each
234 other due to the veth device names and IP addresses. Either this code would
235 need to generate fresh, unused names for all of these before forking, or it
236 would need to support multiple concurrent cros_sdk invocations sharing one
237 proxy and allowing it to exit when unused (without counting on any local
238 service-management infrastructure on the host).
239 """
240
241 may_need_mpm = False
242 apache_bin = osutils.Which('apache2')
243 if apache_bin is None:
244 apache_bin = osutils.Which('apache2', PROXY_APACHE_FALLBACK_PATH)
245 if apache_bin is None:
246 _ReportMissing(('apache2',))
247 else:
248 may_need_mpm = True
249
250 # Module names and .so names included for ease of grepping.
251 apache_modules = [('proxy_module', 'mod_proxy.so'),
252 ('proxy_connect_module', 'mod_proxy_connect.so'),
253 ('proxy_http_module', 'mod_proxy_http.so'),
254 ('proxy_ftp_module', 'mod_proxy_ftp.so')]
255
256 # Find the apache module directory, and make sure it has the modules we need.
257 module_dirs = {}
258 for g in PROXY_APACHE_MODULE_GLOBS:
259 for mod, so in apache_modules:
260 for f in glob.glob(os.path.join(g, so)):
261 module_dirs.setdefault(os.path.dirname(f), []).append(so)
262 for apache_module_path, modules_found in module_dirs.iteritems():
263 if len(modules_found) == len(apache_modules):
264 break
265 else:
266 # Appease cros lint, which doesn't understand that this else block will not
267 # fall through to the subsequent code which relies on apache_module_path.
268 apache_module_path = None
269 raise SystemExit(
270 'Could not find apache module path containing all required modules: %s'
271 % ', '.join(so for mod, so in apache_modules))
272
273 def check_add_module(name):
274 so = 'mod_%s.so' % name
275 if os.access(os.path.join(apache_module_path, so), os.F_OK):
276 mod = '%s_module' % name
277 apache_modules.append((mod, so))
278 return True
279 return False
280
281 check_add_module('authz_core')
282 if may_need_mpm:
283 for mpm in PROXY_APACHE_MPMS:
284 if check_add_module('mpm_%s' % mpm):
285 break
286
287 veth_host = '%s-host' % PROXY_VETH_PREFIX
288 veth_guest = '%s-guest' % PROXY_VETH_PREFIX
289
290 # Set up pipes from parent to child and vice versa.
291 # The child writes a byte to the parent after calling unshare, so that the
292 # parent can then assign the guest end of the veth interface to the child's
293 # new network namespace. The parent then writes a byte to the child after
294 # assigning the guest interface, so that the child can then configure that
295 # interface. In both cases, if we get back an EOF when reading from the
296 # pipe, we assume the other end exited with an error message, so just exit.
297 parent_readfd, child_writefd = os.pipe()
298 child_readfd, parent_writefd = os.pipe()
299 SUCCESS_FLAG = '+'
300
301 pid = os.fork()
302 if not pid:
303 os.close(parent_readfd)
304 os.close(parent_writefd)
305
306 namespaces.Unshare(namespaces.CLONE_NEWNET)
307 os.write(child_writefd, SUCCESS_FLAG)
308 os.close(child_writefd)
309 if os.read(child_readfd, 1) != SUCCESS_FLAG:
310 # Parent failed; it will have already have outputted an error message.
311 sys.exit(1)
312 os.close(child_readfd)
313
314 # Set up child side of the network.
315 commands = (
316 ('ip', 'address', 'add',
317 '%s/%u' % (PROXY_GUEST_IP, PROXY_NETMASK),
318 'dev', veth_guest),
319 ('ip', 'link', 'set', veth_guest, 'up'),
320 )
321 try:
322 for cmd in commands:
323 cros_build_lib.RunCommand(cmd, print_cmd=False)
324 except cros_build_lib.RunCommandError:
325 raise SystemExit('Running %r failed!' % (cmd,))
326
327 proxy_url = 'http://%s:%u' % (PROXY_HOST_IP, PROXY_PORT)
328 for proto in ('http', 'https', 'ftp'):
329 os.environ[proto + '_proxy'] = proxy_url
330 for v in ('all_proxy', 'RSYNC_PROXY', 'no_proxy'):
331 os.environ.pop(v, None)
332 return
333
334 os.close(child_readfd)
335 os.close(child_writefd)
336
337 if os.read(parent_readfd, 1) != SUCCESS_FLAG:
338 # Child failed; it will have already have outputted an error message.
339 sys.exit(1)
340 os.close(parent_readfd)
341
342 # Set up parent side of the network.
343 uid = int(os.environ.get('SUDO_UID', '0'))
344 gid = int(os.environ.get('SUDO_GID', '0'))
345 if uid == 0 or gid == 0:
346 for username in PROXY_APACHE_FALLBACK_USERS:
347 try:
348 pwnam = pwd.getpwnam(username)
349 uid, gid = pwnam.pw_uid, pwnam.pw_gid
350 break
351 except KeyError:
352 continue
353 if uid == 0 or gid == 0:
354 raise SystemExit('Could not find a non-root user to run Apache as')
355
356 chroot_parent, chroot_base = os.path.split(options.chroot)
357 pid_file = os.path.join(chroot_parent, '.%s-apache-proxy.pid' % chroot_base)
358 log_file = os.path.join(chroot_parent, '.%s-apache-proxy.log' % chroot_base)
359
360 apache_directives = [
361 'User #%u' % uid,
362 'Group #%u' % gid,
363 'PidFile %s' % pid_file,
364 'ErrorLog %s' % log_file,
365 'Listen %s:%u' % (PROXY_HOST_IP, PROXY_PORT),
366 'ServerName %s' % PROXY_HOST_IP,
367 'ProxyRequests On',
368 'AllowCONNECT %s' % ' '.join(map(str, PROXY_CONNECT_PORTS)),
369 ] + [
370 'LoadModule %s %s' % (mod, os.path.join(apache_module_path, so))
371 for (mod, so) in apache_modules
372 ]
373 commands = (
374 ('ip', 'link', 'add', 'name', veth_host,
375 'type', 'veth', 'peer', 'name', veth_guest),
376 ('ip', 'address', 'add',
377 '%s/%u' % (PROXY_HOST_IP, PROXY_NETMASK),
378 'dev', veth_host),
379 ('ip', 'link', 'set', veth_host, 'up'),
380 [apache_bin, '-f', '/dev/null']
381 + [arg for d in apache_directives for arg in ('-C', d)],
382 ('ip', 'link', 'set', veth_guest, 'netns', str(pid)),
383 )
384 cmd = None # Make cros lint happy.
385 try:
386 for cmd in commands:
387 cros_build_lib.RunCommand(cmd, print_cmd=False)
388 except cros_build_lib.RunCommandError:
389 # Clean up existing interfaces, if any.
390 cmd_cleanup = ('ip', 'link', 'del', veth_host)
391 try:
392 cros_build_lib.RunCommand(cmd_cleanup, print_cmd=False)
393 except cros_build_lib.RunCommandError:
394 cros_build_lib.Error('running %r failed', cmd_cleanup)
395 raise SystemExit('Running %r failed!' % (cmd,))
396 os.write(parent_writefd, SUCCESS_FLAG)
397 os.close(parent_writefd)
398
399 sys.exit(os.waitpid(pid, 0)[1])
400
401
Mike Frysingera78a56e2012-11-20 06:02:30 -0500402def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700403 """Re-execute cros_sdk as root.
404
405 Also unshare the mount namespace so as to ensure that processes outside
406 the chroot can't mess with our mounts.
407 """
408 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500409 cmd = _SudoCommand() + ['--'] + argv
410 os.execvp(cmd[0], cmd)
Mike Frysingera78a56e2012-11-20 06:02:30 -0500411 else:
Josh Triplette759b232013-03-08 13:03:43 -0800412 cgroups.Cgroup.InitSystem()
Mike Frysinger6da18852014-04-20 21:16:25 -0400413 namespaces.Unshare(namespaces.CLONE_NEWNS | namespaces.CLONE_NEWUTS)
David James56e6c2c2012-10-24 23:54:41 -0700414
415
Mike Frysinger34db8692013-11-11 14:54:08 -0500416def _CreateParser(sdk_latest_version, bootstrap_latest_version):
417 """Generate and return the parser with all the options."""
Brian Harring218e13c2012-10-10 16:21:26 -0700418 usage = """usage: %prog [options] [VAR1=val1 .. VARn=valn -- args]
Brian Harringb938c782012-02-29 15:14:38 -0800419
Brian Harring218e13c2012-10-10 16:21:26 -0700420This script is used for manipulating local chroot environments; creating,
421deleting, downloading, etc. If given --enter (or no args), it defaults
422to an interactive bash shell within the chroot.
Brian Harringb938c782012-02-29 15:14:38 -0800423
Brian Harring218e13c2012-10-10 16:21:26 -0700424If given args those are passed to the chroot environment, and executed."""
Brian Harring1790ac42012-09-23 08:53:33 -0700425
Brian Harring218e13c2012-10-10 16:21:26 -0700426 parser = commandline.OptionParser(usage=usage, caching=True)
427
Mike Frysinger34db8692013-11-11 14:54:08 -0500428 # Global options.
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500429 default_chroot = os.path.join(constants.SOURCE_ROOT,
430 constants.DEFAULT_CHROOT_DIR)
Brian Harring218e13c2012-10-10 16:21:26 -0700431 parser.add_option(
432 '--chroot', dest='chroot', default=default_chroot, type='path',
433 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Brian Harringb938c782012-02-29 15:14:38 -0800434
Brian Harring218e13c2012-10-10 16:21:26 -0700435 parser.add_option('--chrome_root', default=None, type='path',
436 help='Mount this chrome root into the SDK chroot')
437 parser.add_option('--chrome_root_mount', default=None, type='path',
438 help='Mount chrome into this path inside SDK chroot')
439 parser.add_option('--nousepkg', action='store_true', default=False,
440 help='Do not use binary packages when creating a chroot.')
Josh Triplett472a4182013-03-08 11:48:57 -0800441 parser.add_option('--proxy-sim', action='store_true', default=False,
442 help='Simulate a restrictive network requiring an outbound'
443 ' proxy.')
Brian Harringb938c782012-02-29 15:14:38 -0800444 parser.add_option('-u', '--url',
Brian Harringb6cf9142012-09-01 20:43:17 -0700445 dest='sdk_url', default=None,
Brian Harringb938c782012-02-29 15:14:38 -0800446 help=('''Use sdk tarball located at this url.
447 Use file:// for local files.'''))
Brian Harring1790ac42012-09-23 08:53:33 -0700448 parser.add_option('--sdk-version', default=None,
449 help='Use this sdk version. For prebuilt, current is %r'
Mike Frysinger34db8692013-11-11 14:54:08 -0500450 ', for bootstrapping it is %r.'
Brian Harring1790ac42012-09-23 08:53:33 -0700451 % (sdk_latest_version, bootstrap_latest_version))
Mike Frysinger34db8692013-11-11 14:54:08 -0500452
453 # Commands.
454 group = parser.add_option_group('Commands')
455 group.add_option(
456 '--enter', action='store_true', default=False,
457 help='Enter the SDK chroot. Implies --create.')
458 group.add_option(
459 '--create', action='store_true',default=False,
460 help='Create the chroot only if it does not already exist. '
461 'Implies --download.')
462 group.add_option(
463 '--bootstrap', action='store_true', default=False,
464 help='Build everything from scratch, including the sdk. '
465 'Use this only if you need to validate a change '
466 'that affects SDK creation itself (toolchain and '
467 'build are typically the only folk who need this). '
468 'Note this will quite heavily slow down the build. '
469 'This option implies --create --nousepkg.')
470 group.add_option(
471 '-r', '--replace', action='store_true', default=False,
472 help='Replace an existing SDK chroot. Basically an alias '
473 'for --delete --create.')
474 group.add_option(
475 '--delete', action='store_true', default=False,
476 help='Delete the current SDK chroot if it exists.')
477 group.add_option(
478 '--download', action='store_true', default=False,
479 help='Download the sdk.')
480 commands = group
481
482 # Internal options.
483 group = parser.add_option_group(
484 'Internal Chromium OS Build Team Options',
485 'Caution: these are for meant for the Chromium OS build team only')
486 group.add_option('--buildbot-log-version', default=False, action='store_true',
487 help='Log SDK version for buildbot consumption')
488
489 return (parser, commands)
490
491
492def main(argv):
493 conf = cros_build_lib.LoadKeyValueFile(
494 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
495 ignore_missing=True)
496 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
497 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
498 parser, commands = _CreateParser(sdk_latest_version, bootstrap_latest_version)
Brian Harring218e13c2012-10-10 16:21:26 -0700499 options, chroot_command = parser.parse_args(argv)
Brian Harringb938c782012-02-29 15:14:38 -0800500
501 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500502 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800503
Brian Harring1790ac42012-09-23 08:53:33 -0700504 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700505 if host != 'x86_64':
506 parser.error(
507 "cros_sdk is currently only supported on x86_64; you're running"
508 " %s. Please find a x86_64 machine." % (host,))
509
Josh Triplett472a4182013-03-08 11:48:57 -0800510 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
511 if options.proxy_sim:
512 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Brian Harringb938c782012-02-29 15:14:38 -0800513
David James471532c2013-01-21 10:23:31 -0800514 _ReExecuteIfNeeded([sys.argv[0]] + argv)
515
Brian Harring218e13c2012-10-10 16:21:26 -0700516 # Expand out the aliases...
517 if options.replace:
518 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800519
Brian Harring218e13c2012-10-10 16:21:26 -0700520 if options.bootstrap:
521 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800522
Brian Harring218e13c2012-10-10 16:21:26 -0700523 # If a command is not given, default to enter.
524 options.enter |= not any(getattr(options, x.dest)
525 for x in commands.option_list)
526 options.enter |= bool(chroot_command)
527
528 if options.enter and options.delete and not options.create:
529 parser.error("Trying to enter the chroot when --delete "
530 "was specified makes no sense.")
531
532 # Finally, discern if we need to create the chroot.
533 chroot_exists = os.path.exists(options.chroot)
534 if options.create or options.enter:
535 # Only create if it's being wiped, or if it doesn't exist.
536 if not options.delete and chroot_exists:
537 options.create = False
538 else:
539 options.download = True
540
541 # Finally, flip create if necessary.
542 if options.enter:
543 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800544
Brian Harringb938c782012-02-29 15:14:38 -0800545 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -0700546 sdk_version = (bootstrap_latest_version if options.bootstrap
547 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -0800548 else:
549 sdk_version = options.sdk_version
Mike Frysinger34db8692013-11-11 14:54:08 -0500550 if options.buildbot_log_version:
551 cros_build_lib.PrintBuildbotStepText(sdk_version)
Brian Harringb938c782012-02-29 15:14:38 -0800552
Brian Harring1790ac42012-09-23 08:53:33 -0700553 # Based on selections, fetch the tarball.
554 if options.sdk_url:
555 urls = [options.sdk_url]
556 elif options.bootstrap:
557 urls = GetStage3Urls(sdk_version)
558 else:
559 urls = GetArchStageTarballs(sdk_version)
560
Brian Harringb6cf9142012-09-01 20:43:17 -0700561 lock_path = os.path.dirname(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800562 lock_path = os.path.join(lock_path,
Brian Harringb6cf9142012-09-01 20:43:17 -0700563 '.%s_lock' % os.path.basename(options.chroot))
David James56e6c2c2012-10-24 23:54:41 -0700564 with cgroups.SimpleContainChildren('cros_sdk'):
565 with locking.FileLock(lock_path, 'chroot lock') as lock:
Brian Harring1790ac42012-09-23 08:53:33 -0700566
Josh Triplett472a4182013-03-08 11:48:57 -0800567 if options.proxy_sim:
568 _ProxySimSetup(options)
569
David James56e6c2c2012-10-24 23:54:41 -0700570 if options.delete and os.path.exists(options.chroot):
571 lock.write_lock()
572 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800573
David James56e6c2c2012-10-24 23:54:41 -0700574 sdk_cache = os.path.join(options.cache_dir, 'sdks')
575 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Yu-Ju Hong2c066762013-10-28 14:05:08 -0700576 osutils.SafeMakedirsNonRoot(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -0700577
David James56e6c2c2012-10-24 23:54:41 -0700578 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500579 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -0700580 if not os.path.exists(src):
581 osutils.SafeMakedirs(target)
582 continue
583 lock.write_lock(
584 "Upgrade to %r needed but chroot is locked; please exit "
585 "all instances so this upgrade can finish." % src)
586 if not os.path.exists(src):
587 # Note that while waiting for the write lock, src may've vanished;
588 # it's a rare race during the upgrade process that's a byproduct
589 # of us avoiding taking a write lock to do the src check. If we
590 # took a write lock for that check, it would effectively limit
591 # all cros_sdk for a chroot to a single instance.
592 osutils.SafeMakedirs(target)
593 elif not os.path.exists(target):
594 # Upgrade occurred, but a reversion, or something whacky
595 # occurred writing to the old location. Wipe and continue.
596 os.rename(src, target)
597 else:
598 # Upgrade occurred once already, but either a reversion or
599 # some before/after separate cros_sdk usage is at play.
600 # Wipe and continue.
601 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -0700602
David James56e6c2c2012-10-24 23:54:41 -0700603 if options.download:
604 lock.write_lock()
605 sdk_tarball = FetchRemoteTarballs(sdk_cache, urls)
Brian Harring218e13c2012-10-10 16:21:26 -0700606
David James56e6c2c2012-10-24 23:54:41 -0700607 if options.create:
608 lock.write_lock()
609 CreateChroot(options.chroot, sdk_tarball, options.cache_dir,
610 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -0700611
David James56e6c2c2012-10-24 23:54:41 -0700612 if options.enter:
613 lock.read_lock()
614 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
615 options.chrome_root_mount, chroot_command)