blob: 89760144c190ebf67131c0fbb790f4f62028f3d4 [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
Brian Harringb938c782012-02-29 15:14:38 -080010import urlparse
11
12from chromite.buildbot import constants
Brian Harringcfe762a2012-02-29 13:03:53 -080013from chromite.lib import cgroups
Brian Harringb6cf9142012-09-01 20:43:17 -070014from chromite.lib import commandline
Brian Harringb938c782012-02-29 15:14:38 -080015from chromite.lib import cros_build_lib
Brian Harringb938c782012-02-29 15:14:38 -080016from chromite.lib import locking
Brian Harringae0a5322012-09-15 01:46:51 -070017from chromite.lib import osutils
Brian Harringcfe762a2012-02-29 13:03:53 -080018from chromite.lib import sudo
Brian Harringb938c782012-02-29 15:14:38 -080019
20cros_build_lib.STRICT_SUDO = True
21
22
Brian Harring1790ac42012-09-23 08:53:33 -070023DEFAULT_URL = 'https://commondatastorage.googleapis.com/chromiumos-sdk'
David James7b070ee2012-10-05 15:06:32 -070024COMPRESSION_PREFERENCE = ('bz2', 'xz')
Zdenek Behanfd0efe42012-04-13 04:36:40 +020025
Brian Harringb938c782012-02-29 15:14:38 -080026SRC_ROOT = os.path.realpath(constants.SOURCE_ROOT)
Brian Harringb938c782012-02-29 15:14:38 -080027OVERLAY_DIR = os.path.join(SRC_ROOT, 'src/third_party/chromiumos-overlay')
28SDK_VERSION_FILE = os.path.join(OVERLAY_DIR,
29 'chromeos/binhost/host/sdk_version.conf')
30
31# TODO(zbehan): Remove the dependency on these, reimplement them in python
32MAKE_CHROOT = [os.path.join(SRC_ROOT, 'src/scripts/sdk_lib/make_chroot.sh')]
33ENTER_CHROOT = [os.path.join(SRC_ROOT, 'src/scripts/sdk_lib/enter_chroot.sh')]
34
35# We need these tools to run. Very common tools (tar,..) are ommited.
David James7b070ee2012-10-05 15:06:32 -070036NEEDED_TOOLS = ('curl',)
Brian Harringb938c782012-02-29 15:14:38 -080037
Brian Harringb938c782012-02-29 15:14:38 -080038
39def CheckPrerequisites(needed_tools):
40 """Verifies that the required tools are present on the system.
41
42 This is especially important as this script is intended to run
43 outside the chroot.
44
45 Arguments:
46 needed_tools: an array of string specified binaries to look for.
47
48 Returns:
49 True if all needed tools were found.
50 """
Brian Harring98b54902012-03-23 04:05:42 -070051 missing = []
Brian Harringb938c782012-02-29 15:14:38 -080052 for tool in needed_tools:
53 cmd = ['which', tool]
54 try:
55 cros_build_lib.RunCommand(cmd, print_cmd=False, redirect_stdout=True,
56 combine_stdout_stderr=True)
57 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -070058 missing.append(tool)
59 return missing
60
Brian Harringb938c782012-02-29 15:14:38 -080061
Brian Harring1790ac42012-09-23 08:53:33 -070062def GetSdkConfig():
Brian Harringb938c782012-02-29 15:14:38 -080063 """Extracts latest version from chromiumos-overlay."""
Brian Harring1790ac42012-09-23 08:53:33 -070064 d = {}
65 with open(SDK_VERSION_FILE) as f:
66 for raw_line in f:
67 line = raw_line.split('#')[0].strip()
68 if not line:
69 continue
70 chunks = line.split('=', 1)
71 if len(chunks) != 2:
72 raise Exception('Malformed version file; line %r' % raw_line)
73 d[chunks[0]] = chunks[1].strip().strip('"')
74 return d
Brian Harringb938c782012-02-29 15:14:38 -080075
76
Brian Harring1790ac42012-09-23 08:53:33 -070077def GetArchStageTarballs(version):
Brian Harringb938c782012-02-29 15:14:38 -080078 """Returns the URL for a given arch/version"""
Brian Harring1790ac42012-09-23 08:53:33 -070079 extension = {'bz2':'tbz2', 'xz':'tar.xz'}
80 return ['%s/cros-sdk-%s.%s'
81 % (DEFAULT_URL, version, extension[compressor])
82 for compressor in COMPRESSION_PREFERENCE]
83
84
85def GetStage3Urls(version):
86 return ['%s/stage3-amd64-%s.tar.%s' % (DEFAULT_URL, version, ext)
87 for ext in COMPRESSION_PREFERENCE]
Brian Harringb938c782012-02-29 15:14:38 -080088
89
Brian Harringae0a5322012-09-15 01:46:51 -070090def FetchRemoteTarballs(storage_dir, urls):
Zdenek Behanfd0efe42012-04-13 04:36:40 +020091 """Fetches a tarball given by url, and place it in sdk/.
92
93 Args:
94 urls: List of URLs to try to download. Download will stop on first success.
95
96 Returns:
97 Full path to the downloaded file
98 """
Zdenek Behanfd0efe42012-04-13 04:36:40 +020099
Brian Harring1790ac42012-09-23 08:53:33 -0700100 # Note we track content length ourselves since certain versions of curl
101 # fail if asked to resume a complete file.
102 # pylint: disable=C0301,W0631
103 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200104 for url in urls:
Brian Harring1790ac42012-09-23 08:53:33 -0700105 # http://www.logilab.org/ticket/8766
106 # pylint: disable=E1101
107 parsed = urlparse.urlparse(url)
108 tarball_name = os.path.basename(parsed.path)
109 if parsed.scheme in ('', 'file'):
110 if os.path.exists(parsed.path):
111 return parsed.path
112 continue
113 content_length = 0
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200114 print 'Attempting download: %s' % url
Brian Harring1790ac42012-09-23 08:53:33 -0700115 result = cros_build_lib.RunCurl(
116 ['-I', url], redirect_stdout=True, redirect_stderr=True,
117 print_cmd=False)
118 successful = False
119 for header in result.output.splitlines():
120 # We must walk the output to find the string '200 OK' for use cases where
121 # a proxy is involved and may have pushed down the actual header.
122 if header.find('200 OK') != -1:
123 successful = True
124 elif header.lower().startswith("content-length:"):
125 content_length = int(header.split(":", 1)[-1].strip())
126 if successful:
127 break
128 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200129 break
130 else:
131 raise Exception('No valid URLs found!')
132
Brian Harringae0a5322012-09-15 01:46:51 -0700133 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -0700134 current_size = 0
135 if os.path.exists(tarball_dest):
136 current_size = os.path.getsize(tarball_dest)
137 if current_size > content_length:
138 osutils.SafeUnlink(tarball_dest, sudo=True)
139 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100140
Brian Harring1790ac42012-09-23 08:53:33 -0700141 if current_size < content_length:
142 cros_build_lib.RunCurl(
143 ['-f', '-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
144 print_cmd=False)
Brian Harringb938c782012-02-29 15:14:38 -0800145
Brian Harring1790ac42012-09-23 08:53:33 -0700146 # Cleanup old tarballs now since we've successfull fetched; only cleanup
147 # the tarballs for our prefix, or unknown ones.
148 ignored_prefix = ('stage3-' if tarball_name.startswith('cros-sdk-')
149 else 'cros-sdk-')
150 for filename in os.listdir(storage_dir):
151 if filename == tarball_name or filename.startswith(ignored_prefix):
152 continue
Brian Harringb938c782012-02-29 15:14:38 -0800153
Brian Harring1790ac42012-09-23 08:53:33 -0700154 print 'Cleaning up old tarball: %s' % (filename,)
155 osutils.SafeUnlink(os.path.join(storage_dir, filename), sudo=True)
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200156
Brian Harringb938c782012-02-29 15:14:38 -0800157 return tarball_dest
158
159
Brian Harring1790ac42012-09-23 08:53:33 -0700160def CreateChroot(chroot_path, sdk_tarball, cache_dir, nousepkg=False):
Brian Harringb938c782012-02-29 15:14:38 -0800161 """Creates a new chroot from a given SDK"""
Brian Harringb938c782012-02-29 15:14:38 -0800162
Brian Harring1790ac42012-09-23 08:53:33 -0700163 cmd = MAKE_CHROOT + ['--stage3_path', sdk_tarball,
Brian Harringae0a5322012-09-15 01:46:51 -0700164 '--chroot', chroot_path,
165 '--cache_dir', cache_dir]
Mike Frysinger2de7f042012-07-10 04:45:03 -0400166 if nousepkg:
167 cmd.append('--nousepkg')
Brian Harringb938c782012-02-29 15:14:38 -0800168
169 try:
170 cros_build_lib.RunCommand(cmd, print_cmd=False)
171 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700172 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800173
174
175def DeleteChroot(chroot_path):
176 """Deletes an existing chroot"""
177 cmd = MAKE_CHROOT + ['--chroot', chroot_path,
178 '--delete']
179 try:
180 cros_build_lib.RunCommand(cmd, print_cmd=False)
181 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700182 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800183
184
185def _CreateLockFile(path):
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200186 """Create a lockfile via sudo that is writable by current user."""
187 cros_build_lib.SudoRunCommand(['touch', path], print_cmd=False)
188 cros_build_lib.SudoRunCommand(['chown', str(os.getuid()), path],
189 print_cmd=False)
190 cros_build_lib.SudoRunCommand(['chmod', '644', path], print_cmd=False)
Brian Harringb938c782012-02-29 15:14:38 -0800191
192
Brian Harringae0a5322012-09-15 01:46:51 -0700193def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
194 additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800195 """Enters an existing SDK chroot"""
Brian Harringae0a5322012-09-15 01:46:51 -0700196 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800197 if chrome_root:
198 cmd.extend(['--chrome_root', chrome_root])
199 if chrome_root_mount:
200 cmd.extend(['--chrome_root_mount', chrome_root_mount])
201 if len(additional_args) > 0:
202 cmd.append('--')
203 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700204
205 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True)
206 # If we were in interactive mode, ignore the exit code; it'll be whatever
207 # they last ran w/in the chroot and won't matter to us one way or another.
208 # Note this does allow chroot entrance to fail and be ignored during
209 # interactive; this is however a rare case and the user will immediately
210 # see it (nor will they be checking the exit code manually).
211 if ret.returncode != 0 and additional_args:
212 raise SystemExit('Running %r failed with exit code %i'
213 % (cmd, ret.returncode))
Brian Harringb938c782012-02-29 15:14:38 -0800214
215
Brian Harring6be2efc2012-03-01 05:04:00 -0800216def main(argv):
Brian Harringb938c782012-02-29 15:14:38 -0800217 # TODO(ferringb): make argv required once depot_tools is fixed.
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200218 usage = """usage: %prog [options] [VAR1=val1 .. VARn=valn -- <args>]
Brian Harringb938c782012-02-29 15:14:38 -0800219
220This script manages a local CrOS SDK chroot. Depending on the flags,
221it can download, build or enter a chroot.
222
223Action taken is the following:
224--enter (default) .. Installs and enters a chroot
225--download .. Just download a chroot (enter if combined with --enter)
Brian Harringb938c782012-02-29 15:14:38 -0800226--delete .. Removes a chroot
227"""
Brian Harring1790ac42012-09-23 08:53:33 -0700228 conf = GetSdkConfig()
229 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
230 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
231
Brian Harringae0a5322012-09-15 01:46:51 -0700232 parser = commandline.OptionParser(usage, caching=True)
Brian Harringb938c782012-02-29 15:14:38 -0800233 # Actions:
Brian Harring0ebc33c2012-05-25 13:36:19 -0700234 parser.add_option('--bootstrap',
Brian Harringb938c782012-02-29 15:14:38 -0800235 action='store_true', dest='bootstrap', default=False,
Brian Harring6ed482a2012-08-21 18:01:44 -0700236 help=('Build everything from scratch, including the sdk. '
237 'Use this only if you need to validate a change '
238 'that affects SDK creation itself (toolchain and '
239 'build are typically the only folk who need this). '
240 'Note this will quite heavily slow down the build. '
241 'Finally, this option implies --enter.'))
Brian Harring0ebc33c2012-05-25 13:36:19 -0700242 parser.add_option('--delete',
Brian Harringb938c782012-02-29 15:14:38 -0800243 action='store_true', dest='delete', default=False,
244 help=('Delete the current SDK chroot'))
Brian Harring0ebc33c2012-05-25 13:36:19 -0700245 parser.add_option('--download',
Brian Harringb938c782012-02-29 15:14:38 -0800246 action='store_true', dest='download', default=False,
247 help=('Download and install a prebuilt SDK'))
Brian Harring0ebc33c2012-05-25 13:36:19 -0700248 parser.add_option('--enter',
Brian Harringb938c782012-02-29 15:14:38 -0800249 action='store_true', dest='enter', default=False,
250 help=('Enter the SDK chroot, possibly (re)create first'))
251
252 # Global options:
Brian Harringb6cf9142012-09-01 20:43:17 -0700253 default_chroot = os.path.join(SRC_ROOT, constants.DEFAULT_CHROOT_DIR)
254 parser.add_option('--chroot', dest='chroot', default=default_chroot,
255 type='path',
Brian Harringb938c782012-02-29 15:14:38 -0800256 help=('SDK chroot dir name [%s]' %
257 constants.DEFAULT_CHROOT_DIR))
258
259 # Additional options:
Brian Harring0ebc33c2012-05-25 13:36:19 -0700260 parser.add_option('--chrome_root',
Brian Harringb6cf9142012-09-01 20:43:17 -0700261 dest='chrome_root', default=None, type='path',
Brian Harringb938c782012-02-29 15:14:38 -0800262 help=('Mount this chrome root into the SDK chroot'))
Brian Harring0ebc33c2012-05-25 13:36:19 -0700263 parser.add_option('--chrome_root_mount',
Brian Harringb6cf9142012-09-01 20:43:17 -0700264 dest='chrome_root_mount', default=None, type='path',
Brian Harringb938c782012-02-29 15:14:38 -0800265 help=('Mount chrome into this path inside SDK chroot'))
266 parser.add_option('-r', '--replace',
267 action='store_true', dest='replace', default=False,
268 help=('Replace an existing SDK chroot'))
Mike Frysinger2de7f042012-07-10 04:45:03 -0400269 parser.add_option('--nousepkg',
270 action='store_true', dest='nousepkg', default=False,
271 help=('Do not use binary packages when creating a chroot'))
Brian Harringb938c782012-02-29 15:14:38 -0800272 parser.add_option('-u', '--url',
Brian Harringb6cf9142012-09-01 20:43:17 -0700273 dest='sdk_url', default=None,
Brian Harringb938c782012-02-29 15:14:38 -0800274 help=('''Use sdk tarball located at this url.
275 Use file:// for local files.'''))
Brian Harring1790ac42012-09-23 08:53:33 -0700276 parser.add_option('--sdk-version', default=None,
277 help='Use this sdk version. For prebuilt, current is %r'
278 ', for bootstrapping its %r.'
279 % (sdk_latest_version, bootstrap_latest_version))
Brian Harringb938c782012-02-29 15:14:38 -0800280 (options, remaining_arguments) = parser.parse_args(argv)
281
282 # Some sanity checks first, before we ask for sudo credentials.
283 if cros_build_lib.IsInsideChroot():
Brian Harring98b54902012-03-23 04:05:42 -0700284 parser.error("This needs to be ran outside the chroot")
Brian Harringb938c782012-02-29 15:14:38 -0800285
Brian Harring1790ac42012-09-23 08:53:33 -0700286 host = os.uname()[4]
287
288 if host != 'x86_64':
289 parser.error(
290 "cros_sdk is currently only supported on x86_64; you're running"
291 " %s. Please find a x86_64 machine." % (host,))
292
Brian Harring98b54902012-03-23 04:05:42 -0700293 missing = CheckPrerequisites(NEEDED_TOOLS)
294 if missing:
295 parser.error((
296 'The tool(s) %s were not found.'
297 'Please install the appropriate package in your host.'
298 'Example(ubuntu):'
299 ' sudo apt-get install <packagename>'
300 % (', '.join(missing))))
Brian Harringb938c782012-02-29 15:14:38 -0800301
302 # Default action is --enter, if no other is selected.
303 if not (options.bootstrap or options.download or options.delete):
304 options.enter = True
305
306 # Only --enter can process additional args as passthrough commands.
307 # Warn and exit for least surprise.
308 if len(remaining_arguments) > 0 and not options.enter:
Brian Harring98b54902012-03-23 04:05:42 -0700309 parser.error("Additional arguments are not permitted, unless running "
310 "with --enter")
Brian Harringb938c782012-02-29 15:14:38 -0800311
312 # Some actions can be combined, as they merely modify how is the chroot
313 # going to be made. The only option that hates all others is --delete.
314 if options.delete and \
315 (options.enter or options.download or options.bootstrap):
Brian Harring98b54902012-03-23 04:05:42 -0700316 parser.error("--delete cannot be combined with --enter, "
317 "--download or --bootstrap")
Brian Harringb938c782012-02-29 15:14:38 -0800318
Brian Harringb938c782012-02-29 15:14:38 -0800319 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -0700320 sdk_version = (bootstrap_latest_version if options.bootstrap
321 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -0800322 else:
323 sdk_version = options.sdk_version
324
Brian Harring1790ac42012-09-23 08:53:33 -0700325 # Based on selections, fetch the tarball.
326 if options.sdk_url:
327 urls = [options.sdk_url]
328 elif options.bootstrap:
329 urls = GetStage3Urls(sdk_version)
330 else:
331 urls = GetArchStageTarballs(sdk_version)
332
Brian Harringb6cf9142012-09-01 20:43:17 -0700333 if options.delete and not os.path.exists(options.chroot):
Brian Harringb938c782012-02-29 15:14:38 -0800334 print "Not doing anything. The chroot you want to remove doesn't exist."
Brian Harring98b54902012-03-23 04:05:42 -0700335 return 0
Brian Harringb938c782012-02-29 15:14:38 -0800336
Brian Harringb6cf9142012-09-01 20:43:17 -0700337 lock_path = os.path.dirname(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800338 lock_path = os.path.join(lock_path,
Brian Harringb6cf9142012-09-01 20:43:17 -0700339 '.%s_lock' % os.path.basename(options.chroot))
David James891dccf2012-08-20 14:19:54 -0700340 with sudo.SudoKeepAlive(ttyless_sudo=False):
Brian Harring4e6412d2012-03-09 20:54:02 -0800341 with cgroups.SimpleContainChildren('cros_sdk'):
Brian Harringcfe762a2012-02-29 13:03:53 -0800342 _CreateLockFile(lock_path)
343 with locking.FileLock(lock_path, 'chroot lock') as lock:
Brian Harring1790ac42012-09-23 08:53:33 -0700344
345 if os.path.exists(options.chroot):
346 if options.delete or options.replace:
347 lock.write_lock()
348 DeleteChroot(options.chroot)
349 if options.delete:
350 return 0
351 elif not options.enter and not options.download:
352 print "Chroot already exists. Run with --replace to re-create."
353 elif options.delete:
Brian Harring98b54902012-03-23 04:05:42 -0700354 return 0
Brian Harringb938c782012-02-29 15:14:38 -0800355
Brian Harringae0a5322012-09-15 01:46:51 -0700356 sdk_cache = os.path.join(options.cache_dir, 'sdks')
357 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Brian Harringfa327912012-09-28 20:57:01 -0700358 osutils.SafeMakedirs(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -0700359
360 for target in (sdk_cache, distfiles_cache):
361 src = os.path.join(SRC_ROOT, os.path.basename(target))
362 if not os.path.exists(src):
363 osutils.SafeMakedirs(target)
364 continue
Brian Harringae0a5322012-09-15 01:46:51 -0700365 lock.write_lock(
366 "Upgrade to %r needed but chroot is locked; please exit "
367 "all instances so this upgrade can finish." % src)
368 if not os.path.exists(src):
369 # Note that while waiting for the write lock, src may've vanished;
370 # it's a rare race during the upgrade process that's a byproduct
371 # of us avoiding taking a write lock to do the src check. If we
372 # took a write lock for that check, it would effectively limit
373 # all cros_sdk for a chroot to a single instance.
374 osutils.SafeMakedirs(target)
375 elif not os.path.exists(target):
376 # Upgrade occurred, but a reversion, or something whacky
377 # occurred writing to the old location. Wipe and continue.
378 cros_build_lib.SudoRunCommand(
379 ['mv', '--', src, target], print_cmd=False)
380 else:
381 # Upgrade occurred once already, but either a reversion or
382 # some before/after separate cros_sdk usage is at play.
383 # Wipe and continue.
384 osutils.RmDir(src, sudo=True)
385
Brian Harring1790ac42012-09-23 08:53:33 -0700386 if not os.path.exists(options.chroot) or options.download:
Brian Harringcfe762a2012-02-29 13:03:53 -0800387 lock.write_lock()
Brian Harring1790ac42012-09-23 08:53:33 -0700388 sdk_tarball = FetchRemoteTarballs(sdk_cache, urls)
389 if options.download:
390 # Nothing further to do.
391 return 0
392 CreateChroot(options.chroot, sdk_tarball, options.cache_dir,
393 nousepkg=(options.bootstrap or options.nousepkg))
394
Brian Harringcfe762a2012-02-29 13:03:53 -0800395 if options.enter:
396 lock.read_lock()
Brian Harringae0a5322012-09-15 01:46:51 -0700397 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
Brian Harringcfe762a2012-02-29 13:03:53 -0800398 options.chrome_root_mount, remaining_arguments)