blob: 93101d138725cf7048b0006ab30930f43864e0b6 [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
Brian Harringb938c782012-02-29 15:14:38 -08009import os
David James56e6c2c2012-10-24 23:54:41 -070010import sys
Brian Harringb938c782012-02-29 15:14:38 -080011import urlparse
12
13from chromite.buildbot import constants
Brian Harringcfe762a2012-02-29 13:03:53 -080014from chromite.lib import cgroups
Brian Harringb6cf9142012-09-01 20:43:17 -070015from chromite.lib import commandline
Brian Harringb938c782012-02-29 15:14:38 -080016from chromite.lib import cros_build_lib
Brian Harringb938c782012-02-29 15:14:38 -080017from chromite.lib import locking
Brian Harringae0a5322012-09-15 01:46:51 -070018from chromite.lib import osutils
Mike Frysinger8e727a32013-01-16 16:57:53 -050019from chromite.lib import toolchain
Brian Harringb938c782012-02-29 15:14:38 -080020
21cros_build_lib.STRICT_SUDO = True
22
23
Zdenek Behanaa52cea2012-05-30 01:31:11 +020024COMPRESSION_PREFERENCE = ('xz', 'bz2')
Zdenek Behanfd0efe42012-04-13 04:36:40 +020025
Brian Harringb938c782012-02-29 15:14:38 -080026# TODO(zbehan): Remove the dependency on these, reimplement them in python
Mike Frysinger648ba2d2013-01-08 14:19:34 -050027MAKE_CHROOT = [os.path.join(constants.SOURCE_ROOT,
28 'src/scripts/sdk_lib/make_chroot.sh')]
29ENTER_CHROOT = [os.path.join(constants.SOURCE_ROOT,
30 'src/scripts/sdk_lib/enter_chroot.sh')]
Brian Harringb938c782012-02-29 15:14:38 -080031
32# We need these tools to run. Very common tools (tar,..) are ommited.
David James56e6c2c2012-10-24 23:54:41 -070033NEEDED_TOOLS = ('curl', 'xz', 'unshare')
Brian Harringb938c782012-02-29 15:14:38 -080034
Brian Harringb938c782012-02-29 15:14:38 -080035
Brian Harring1790ac42012-09-23 08:53:33 -070036def GetArchStageTarballs(version):
Brian Harringb938c782012-02-29 15:14:38 -080037 """Returns the URL for a given arch/version"""
Brian Harring1790ac42012-09-23 08:53:33 -070038 extension = {'bz2':'tbz2', 'xz':'tar.xz'}
Mike Frysinger8e727a32013-01-16 16:57:53 -050039 return [toolchain.GetSdkURL(suburl='cros-sdk-%s.%s'
40 % (version, extension[compressor]))
Brian Harring1790ac42012-09-23 08:53:33 -070041 for compressor in COMPRESSION_PREFERENCE]
42
43
44def GetStage3Urls(version):
Mike Frysinger8e727a32013-01-16 16:57:53 -050045 return [toolchain.GetSdkURL(suburl='stage3-amd64-%s.tar.%s' % (version, ext))
Brian Harring1790ac42012-09-23 08:53:33 -070046 for ext in COMPRESSION_PREFERENCE]
Brian Harringb938c782012-02-29 15:14:38 -080047
48
Brian Harringae0a5322012-09-15 01:46:51 -070049def FetchRemoteTarballs(storage_dir, urls):
Zdenek Behanfd0efe42012-04-13 04:36:40 +020050 """Fetches a tarball given by url, and place it in sdk/.
51
52 Args:
53 urls: List of URLs to try to download. Download will stop on first success.
54
55 Returns:
56 Full path to the downloaded file
57 """
Zdenek Behanfd0efe42012-04-13 04:36:40 +020058
Brian Harring1790ac42012-09-23 08:53:33 -070059 # Note we track content length ourselves since certain versions of curl
60 # fail if asked to resume a complete file.
61 # pylint: disable=C0301,W0631
62 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
Zdenek Behanfd0efe42012-04-13 04:36:40 +020063 for url in urls:
Brian Harring1790ac42012-09-23 08:53:33 -070064 # http://www.logilab.org/ticket/8766
65 # pylint: disable=E1101
66 parsed = urlparse.urlparse(url)
67 tarball_name = os.path.basename(parsed.path)
68 if parsed.scheme in ('', 'file'):
69 if os.path.exists(parsed.path):
70 return parsed.path
71 continue
72 content_length = 0
Zdenek Behanfd0efe42012-04-13 04:36:40 +020073 print 'Attempting download: %s' % url
Brian Harring1790ac42012-09-23 08:53:33 -070074 result = cros_build_lib.RunCurl(
75 ['-I', url], redirect_stdout=True, redirect_stderr=True,
76 print_cmd=False)
77 successful = False
78 for header in result.output.splitlines():
79 # We must walk the output to find the string '200 OK' for use cases where
80 # a proxy is involved and may have pushed down the actual header.
81 if header.find('200 OK') != -1:
82 successful = True
83 elif header.lower().startswith("content-length:"):
84 content_length = int(header.split(":", 1)[-1].strip())
85 if successful:
86 break
87 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +020088 break
89 else:
90 raise Exception('No valid URLs found!')
91
Brian Harringae0a5322012-09-15 01:46:51 -070092 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -070093 current_size = 0
94 if os.path.exists(tarball_dest):
95 current_size = os.path.getsize(tarball_dest)
96 if current_size > content_length:
David James56e6c2c2012-10-24 23:54:41 -070097 osutils.SafeUnlink(tarball_dest)
Brian Harring1790ac42012-09-23 08:53:33 -070098 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +010099
Brian Harring1790ac42012-09-23 08:53:33 -0700100 if current_size < content_length:
101 cros_build_lib.RunCurl(
102 ['-f', '-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
103 print_cmd=False)
Brian Harringb938c782012-02-29 15:14:38 -0800104
Brian Harring1790ac42012-09-23 08:53:33 -0700105 # Cleanup old tarballs now since we've successfull fetched; only cleanup
106 # the tarballs for our prefix, or unknown ones.
107 ignored_prefix = ('stage3-' if tarball_name.startswith('cros-sdk-')
108 else 'cros-sdk-')
109 for filename in os.listdir(storage_dir):
110 if filename == tarball_name or filename.startswith(ignored_prefix):
111 continue
Brian Harringb938c782012-02-29 15:14:38 -0800112
Brian Harring1790ac42012-09-23 08:53:33 -0700113 print 'Cleaning up old tarball: %s' % (filename,)
David James56e6c2c2012-10-24 23:54:41 -0700114 osutils.SafeUnlink(os.path.join(storage_dir, filename))
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200115
Brian Harringb938c782012-02-29 15:14:38 -0800116 return tarball_dest
117
118
Brian Harring1790ac42012-09-23 08:53:33 -0700119def CreateChroot(chroot_path, sdk_tarball, cache_dir, nousepkg=False):
Brian Harringb938c782012-02-29 15:14:38 -0800120 """Creates a new chroot from a given SDK"""
Brian Harringb938c782012-02-29 15:14:38 -0800121
Brian Harring1790ac42012-09-23 08:53:33 -0700122 cmd = MAKE_CHROOT + ['--stage3_path', sdk_tarball,
Brian Harringae0a5322012-09-15 01:46:51 -0700123 '--chroot', chroot_path,
124 '--cache_dir', cache_dir]
Mike Frysinger2de7f042012-07-10 04:45:03 -0400125 if nousepkg:
126 cmd.append('--nousepkg')
Brian Harringb938c782012-02-29 15:14:38 -0800127
128 try:
129 cros_build_lib.RunCommand(cmd, print_cmd=False)
130 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700131 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800132
133
134def DeleteChroot(chroot_path):
135 """Deletes an existing chroot"""
136 cmd = MAKE_CHROOT + ['--chroot', chroot_path,
137 '--delete']
138 try:
139 cros_build_lib.RunCommand(cmd, print_cmd=False)
140 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700141 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800142
143
Brian Harringae0a5322012-09-15 01:46:51 -0700144def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
145 additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800146 """Enters an existing SDK chroot"""
Brian Harringae0a5322012-09-15 01:46:51 -0700147 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800148 if chrome_root:
149 cmd.extend(['--chrome_root', chrome_root])
150 if chrome_root_mount:
151 cmd.extend(['--chrome_root_mount', chrome_root_mount])
152 if len(additional_args) > 0:
153 cmd.append('--')
154 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700155
156 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True)
157 # If we were in interactive mode, ignore the exit code; it'll be whatever
158 # they last ran w/in the chroot and won't matter to us one way or another.
159 # Note this does allow chroot entrance to fail and be ignored during
160 # interactive; this is however a rare case and the user will immediately
161 # see it (nor will they be checking the exit code manually).
162 if ret.returncode != 0 and additional_args:
163 raise SystemExit('Running %r failed with exit code %i'
164 % (cmd, ret.returncode))
Brian Harringb938c782012-02-29 15:14:38 -0800165
166
David James56e6c2c2012-10-24 23:54:41 -0700167def _SudoCommand():
168 """Get the 'sudo' command, along with all needed environment variables."""
169
David James5a73b4d2013-03-07 10:23:40 -0800170 # Pass in the ENVIRONMENT_WHITELIST and ENV_PASSTHRU variables so that
171 # scripts in the chroot know what variables to pass through.
David James56e6c2c2012-10-24 23:54:41 -0700172 cmd = ['sudo']
David James5a73b4d2013-03-07 10:23:40 -0800173 for key in constants.CHROOT_ENVIRONMENT_WHITELIST + constants.ENV_PASSTHRU:
David James56e6c2c2012-10-24 23:54:41 -0700174 value = os.environ.get(key)
175 if value is not None:
176 cmd += ['%s=%s' % (key, value)]
177
178 # Pass in the path to the depot_tools so that users can access them from
179 # within the chroot.
180 gclient = osutils.Which('gclient')
181 if gclient is not None:
182 cmd += ['DEPOT_TOOLS=%s' % os.path.realpath(os.path.dirname(gclient))]
183
184 return cmd
185
186
Mike Frysingera78a56e2012-11-20 06:02:30 -0500187def _ReExecuteIfNeeded(argv):
David James56e6c2c2012-10-24 23:54:41 -0700188 """Re-execute cros_sdk as root.
189
190 Also unshare the mount namespace so as to ensure that processes outside
191 the chroot can't mess with our mounts.
192 """
Mike Frysingera78a56e2012-11-20 06:02:30 -0500193 MAGIC_VAR = '%CROS_SDK_MOUNT_NS'
David James56e6c2c2012-10-24 23:54:41 -0700194 if os.geteuid() != 0:
Mike Frysingera78a56e2012-11-20 06:02:30 -0500195 cmd = _SudoCommand() + ['--'] + argv
196 os.execvp(cmd[0], cmd)
197 elif os.environ.get(MAGIC_VAR, '0') == '0':
198 cgroups.Cgroup.InitSystem()
199 os.environ[MAGIC_VAR] = '1'
200 os.execvp('unshare', ['unshare', '-m', '--'] + argv)
201 else:
202 os.environ.pop(MAGIC_VAR)
David James56e6c2c2012-10-24 23:54:41 -0700203
204
Brian Harring6be2efc2012-03-01 05:04:00 -0800205def main(argv):
Brian Harring218e13c2012-10-10 16:21:26 -0700206 usage = """usage: %prog [options] [VAR1=val1 .. VARn=valn -- args]
Brian Harringb938c782012-02-29 15:14:38 -0800207
Brian Harring218e13c2012-10-10 16:21:26 -0700208This script is used for manipulating local chroot environments; creating,
209deleting, downloading, etc. If given --enter (or no args), it defaults
210to an interactive bash shell within the chroot.
Brian Harringb938c782012-02-29 15:14:38 -0800211
Brian Harring218e13c2012-10-10 16:21:26 -0700212If given args those are passed to the chroot environment, and executed."""
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500213 conf = cros_build_lib.LoadKeyValueFile(
214 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
215 ignore_missing=True)
Brian Harring1790ac42012-09-23 08:53:33 -0700216 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
217 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
218
Brian Harring218e13c2012-10-10 16:21:26 -0700219 parser = commandline.OptionParser(usage=usage, caching=True)
220
221 commands = parser.add_option_group("Commands")
222 commands.add_option(
223 '--enter', action='store_true', default=False,
224 help='Enter the SDK chroot. Implies --create.')
225 commands.add_option(
226 '--create', action='store_true',default=False,
227 help='Create the chroot only if it does not already exist. '
228 'Implies --download.')
229 commands.add_option(
230 '--bootstrap', action='store_true', default=False,
231 help='Build everything from scratch, including the sdk. '
232 'Use this only if you need to validate a change '
233 'that affects SDK creation itself (toolchain and '
234 'build are typically the only folk who need this). '
235 'Note this will quite heavily slow down the build. '
236 'This option implies --create --nousepkg.')
237 commands.add_option(
238 '-r', '--replace', action='store_true', default=False,
239 help='Replace an existing SDK chroot. Basically an alias '
240 'for --delete --create.')
241 commands.add_option(
242 '--delete', action='store_true', default=False,
243 help='Delete the current SDK chroot if it exists.')
244 commands.add_option(
245 '--download', action='store_true', default=False,
246 help='Download the sdk.')
Brian Harringb938c782012-02-29 15:14:38 -0800247
248 # Global options:
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500249 default_chroot = os.path.join(constants.SOURCE_ROOT,
250 constants.DEFAULT_CHROOT_DIR)
Brian Harring218e13c2012-10-10 16:21:26 -0700251 parser.add_option(
252 '--chroot', dest='chroot', default=default_chroot, type='path',
253 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Brian Harringb938c782012-02-29 15:14:38 -0800254
Brian Harring218e13c2012-10-10 16:21:26 -0700255 parser.add_option('--chrome_root', default=None, type='path',
256 help='Mount this chrome root into the SDK chroot')
257 parser.add_option('--chrome_root_mount', default=None, type='path',
258 help='Mount chrome into this path inside SDK chroot')
259 parser.add_option('--nousepkg', action='store_true', default=False,
260 help='Do not use binary packages when creating a chroot.')
Brian Harringb938c782012-02-29 15:14:38 -0800261 parser.add_option('-u', '--url',
Brian Harringb6cf9142012-09-01 20:43:17 -0700262 dest='sdk_url', default=None,
Brian Harringb938c782012-02-29 15:14:38 -0800263 help=('''Use sdk tarball located at this url.
264 Use file:// for local files.'''))
Brian Harring1790ac42012-09-23 08:53:33 -0700265 parser.add_option('--sdk-version', default=None,
266 help='Use this sdk version. For prebuilt, current is %r'
267 ', for bootstrapping its %r.'
268 % (sdk_latest_version, bootstrap_latest_version))
Brian Harring218e13c2012-10-10 16:21:26 -0700269 options, chroot_command = parser.parse_args(argv)
Brian Harringb938c782012-02-29 15:14:38 -0800270
271 # Some sanity checks first, before we ask for sudo credentials.
Mike Frysinger8fd67dc2012-12-03 23:51:18 -0500272 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800273
Brian Harring1790ac42012-09-23 08:53:33 -0700274 host = os.uname()[4]
Brian Harring1790ac42012-09-23 08:53:33 -0700275 if host != 'x86_64':
276 parser.error(
277 "cros_sdk is currently only supported on x86_64; you're running"
278 " %s. Please find a x86_64 machine." % (host,))
279
David Jamesaad5cc72012-10-26 15:03:13 -0700280 missing = osutils.FindMissingBinaries(NEEDED_TOOLS)
Brian Harring98b54902012-03-23 04:05:42 -0700281 if missing:
282 parser.error((
David James471532c2013-01-21 10:23:31 -0800283 'The tool(s) %s were not found.\n'
284 'Please install the appropriate package in your host.\n'
285 'Example(ubuntu):\n'
Brian Harring98b54902012-03-23 04:05:42 -0700286 ' sudo apt-get install <packagename>'
287 % (', '.join(missing))))
Brian Harringb938c782012-02-29 15:14:38 -0800288
David James471532c2013-01-21 10:23:31 -0800289 _ReExecuteIfNeeded([sys.argv[0]] + argv)
290
Brian Harring218e13c2012-10-10 16:21:26 -0700291 # Expand out the aliases...
292 if options.replace:
293 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800294
Brian Harring218e13c2012-10-10 16:21:26 -0700295 if options.bootstrap:
296 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800297
Brian Harring218e13c2012-10-10 16:21:26 -0700298 # If a command is not given, default to enter.
299 options.enter |= not any(getattr(options, x.dest)
300 for x in commands.option_list)
301 options.enter |= bool(chroot_command)
302
303 if options.enter and options.delete and not options.create:
304 parser.error("Trying to enter the chroot when --delete "
305 "was specified makes no sense.")
306
307 # Finally, discern if we need to create the chroot.
308 chroot_exists = os.path.exists(options.chroot)
309 if options.create or options.enter:
310 # Only create if it's being wiped, or if it doesn't exist.
311 if not options.delete and chroot_exists:
312 options.create = False
313 else:
314 options.download = True
315
316 # Finally, flip create if necessary.
317 if options.enter:
318 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800319
Brian Harringb938c782012-02-29 15:14:38 -0800320 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -0700321 sdk_version = (bootstrap_latest_version if options.bootstrap
322 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -0800323 else:
324 sdk_version = options.sdk_version
325
Brian Harring1790ac42012-09-23 08:53:33 -0700326 # Based on selections, fetch the tarball.
327 if options.sdk_url:
328 urls = [options.sdk_url]
329 elif options.bootstrap:
330 urls = GetStage3Urls(sdk_version)
331 else:
332 urls = GetArchStageTarballs(sdk_version)
333
Brian Harringb6cf9142012-09-01 20:43:17 -0700334 lock_path = os.path.dirname(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800335 lock_path = os.path.join(lock_path,
Brian Harringb6cf9142012-09-01 20:43:17 -0700336 '.%s_lock' % os.path.basename(options.chroot))
David James56e6c2c2012-10-24 23:54:41 -0700337 with cgroups.SimpleContainChildren('cros_sdk'):
338 with locking.FileLock(lock_path, 'chroot lock') as lock:
Brian Harring1790ac42012-09-23 08:53:33 -0700339
David James56e6c2c2012-10-24 23:54:41 -0700340 if options.delete and os.path.exists(options.chroot):
341 lock.write_lock()
342 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800343
David James56e6c2c2012-10-24 23:54:41 -0700344 sdk_cache = os.path.join(options.cache_dir, 'sdks')
345 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
346 osutils.SafeMakedirs(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -0700347
David James56e6c2c2012-10-24 23:54:41 -0700348 for target in (sdk_cache, distfiles_cache):
Mike Frysinger648ba2d2013-01-08 14:19:34 -0500349 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
David James56e6c2c2012-10-24 23:54:41 -0700350 if not os.path.exists(src):
351 osutils.SafeMakedirs(target)
352 continue
353 lock.write_lock(
354 "Upgrade to %r needed but chroot is locked; please exit "
355 "all instances so this upgrade can finish." % src)
356 if not os.path.exists(src):
357 # Note that while waiting for the write lock, src may've vanished;
358 # it's a rare race during the upgrade process that's a byproduct
359 # of us avoiding taking a write lock to do the src check. If we
360 # took a write lock for that check, it would effectively limit
361 # all cros_sdk for a chroot to a single instance.
362 osutils.SafeMakedirs(target)
363 elif not os.path.exists(target):
364 # Upgrade occurred, but a reversion, or something whacky
365 # occurred writing to the old location. Wipe and continue.
366 os.rename(src, target)
367 else:
368 # Upgrade occurred once already, but either a reversion or
369 # some before/after separate cros_sdk usage is at play.
370 # Wipe and continue.
371 osutils.RmDir(src)
Brian Harringae0a5322012-09-15 01:46:51 -0700372
David James56e6c2c2012-10-24 23:54:41 -0700373 if options.download:
374 lock.write_lock()
375 sdk_tarball = FetchRemoteTarballs(sdk_cache, urls)
Brian Harring218e13c2012-10-10 16:21:26 -0700376
David James56e6c2c2012-10-24 23:54:41 -0700377 if options.create:
378 lock.write_lock()
379 CreateChroot(options.chroot, sdk_tarball, options.cache_dir,
380 nousepkg=(options.bootstrap or options.nousepkg))
Brian Harring1790ac42012-09-23 08:53:33 -0700381
David James56e6c2c2012-10-24 23:54:41 -0700382 if options.enter:
383 lock.read_lock()
384 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
385 options.chrome_root_mount, chroot_command)