blob: 6d6d00e8c4f477e0a2c2ed30d7f72a1ddfb0c7c9 [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 Harring218e13c2012-10-10 16:21:26 -07009import errno
Brian Harringb938c782012-02-29 15:14:38 -080010import os
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
Brian Harringcfe762a2012-02-29 13:03:53 -080019from chromite.lib import sudo
Brian Harringb938c782012-02-29 15:14:38 -080020
21cros_build_lib.STRICT_SUDO = True
22
23
Brian Harring1790ac42012-09-23 08:53:33 -070024DEFAULT_URL = 'https://commondatastorage.googleapis.com/chromiumos-sdk'
David James7b070ee2012-10-05 15:06:32 -070025COMPRESSION_PREFERENCE = ('bz2', 'xz')
Zdenek Behanfd0efe42012-04-13 04:36:40 +020026
Brian Harringb938c782012-02-29 15:14:38 -080027SRC_ROOT = os.path.realpath(constants.SOURCE_ROOT)
Brian Harringb938c782012-02-29 15:14:38 -080028OVERLAY_DIR = os.path.join(SRC_ROOT, 'src/third_party/chromiumos-overlay')
29SDK_VERSION_FILE = os.path.join(OVERLAY_DIR,
30 'chromeos/binhost/host/sdk_version.conf')
31
32# TODO(zbehan): Remove the dependency on these, reimplement them in python
33MAKE_CHROOT = [os.path.join(SRC_ROOT, 'src/scripts/sdk_lib/make_chroot.sh')]
34ENTER_CHROOT = [os.path.join(SRC_ROOT, 'src/scripts/sdk_lib/enter_chroot.sh')]
35
36# We need these tools to run. Very common tools (tar,..) are ommited.
David James7b070ee2012-10-05 15:06:32 -070037NEEDED_TOOLS = ('curl',)
Brian Harringb938c782012-02-29 15:14:38 -080038
Brian Harringb938c782012-02-29 15:14:38 -080039
40def CheckPrerequisites(needed_tools):
41 """Verifies that the required tools are present on the system.
42
43 This is especially important as this script is intended to run
44 outside the chroot.
45
46 Arguments:
47 needed_tools: an array of string specified binaries to look for.
48
49 Returns:
50 True if all needed tools were found.
51 """
Brian Harring98b54902012-03-23 04:05:42 -070052 missing = []
Brian Harringb938c782012-02-29 15:14:38 -080053 for tool in needed_tools:
54 cmd = ['which', tool]
55 try:
56 cros_build_lib.RunCommand(cmd, print_cmd=False, redirect_stdout=True,
57 combine_stdout_stderr=True)
58 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -070059 missing.append(tool)
60 return missing
61
Brian Harringb938c782012-02-29 15:14:38 -080062
Brian Harring1790ac42012-09-23 08:53:33 -070063def GetSdkConfig():
Brian Harringb938c782012-02-29 15:14:38 -080064 """Extracts latest version from chromiumos-overlay."""
Brian Harring1790ac42012-09-23 08:53:33 -070065 d = {}
Brian Harring218e13c2012-10-10 16:21:26 -070066 try:
67 with open(SDK_VERSION_FILE) as f:
68 for raw_line in f:
69 line = raw_line.split('#')[0].strip()
70 if not line:
71 continue
72 chunks = line.split('=', 1)
73 if len(chunks) != 2:
74 raise Exception('Malformed version file; line %r' % raw_line)
75 d[chunks[0]] = chunks[1].strip().strip('"')
76 except EnvironmentError, e:
77 if e.errnor != errno.ENOENT:
78 raise
Brian Harring1790ac42012-09-23 08:53:33 -070079 return d
Brian Harringb938c782012-02-29 15:14:38 -080080
81
Brian Harring1790ac42012-09-23 08:53:33 -070082def GetArchStageTarballs(version):
Brian Harringb938c782012-02-29 15:14:38 -080083 """Returns the URL for a given arch/version"""
Brian Harring1790ac42012-09-23 08:53:33 -070084 extension = {'bz2':'tbz2', 'xz':'tar.xz'}
85 return ['%s/cros-sdk-%s.%s'
86 % (DEFAULT_URL, version, extension[compressor])
87 for compressor in COMPRESSION_PREFERENCE]
88
89
90def GetStage3Urls(version):
91 return ['%s/stage3-amd64-%s.tar.%s' % (DEFAULT_URL, version, ext)
92 for ext in COMPRESSION_PREFERENCE]
Brian Harringb938c782012-02-29 15:14:38 -080093
94
Brian Harringae0a5322012-09-15 01:46:51 -070095def FetchRemoteTarballs(storage_dir, urls):
Zdenek Behanfd0efe42012-04-13 04:36:40 +020096 """Fetches a tarball given by url, and place it in sdk/.
97
98 Args:
99 urls: List of URLs to try to download. Download will stop on first success.
100
101 Returns:
102 Full path to the downloaded file
103 """
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200104
Brian Harring1790ac42012-09-23 08:53:33 -0700105 # Note we track content length ourselves since certain versions of curl
106 # fail if asked to resume a complete file.
107 # pylint: disable=C0301,W0631
108 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200109 for url in urls:
Brian Harring1790ac42012-09-23 08:53:33 -0700110 # http://www.logilab.org/ticket/8766
111 # pylint: disable=E1101
112 parsed = urlparse.urlparse(url)
113 tarball_name = os.path.basename(parsed.path)
114 if parsed.scheme in ('', 'file'):
115 if os.path.exists(parsed.path):
116 return parsed.path
117 continue
118 content_length = 0
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200119 print 'Attempting download: %s' % url
Brian Harring1790ac42012-09-23 08:53:33 -0700120 result = cros_build_lib.RunCurl(
121 ['-I', url], redirect_stdout=True, redirect_stderr=True,
122 print_cmd=False)
123 successful = False
124 for header in result.output.splitlines():
125 # We must walk the output to find the string '200 OK' for use cases where
126 # a proxy is involved and may have pushed down the actual header.
127 if header.find('200 OK') != -1:
128 successful = True
129 elif header.lower().startswith("content-length:"):
130 content_length = int(header.split(":", 1)[-1].strip())
131 if successful:
132 break
133 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200134 break
135 else:
136 raise Exception('No valid URLs found!')
137
Brian Harringae0a5322012-09-15 01:46:51 -0700138 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -0700139 current_size = 0
140 if os.path.exists(tarball_dest):
141 current_size = os.path.getsize(tarball_dest)
142 if current_size > content_length:
143 osutils.SafeUnlink(tarball_dest, sudo=True)
144 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100145
Brian Harring1790ac42012-09-23 08:53:33 -0700146 if current_size < content_length:
147 cros_build_lib.RunCurl(
148 ['-f', '-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
149 print_cmd=False)
Brian Harringb938c782012-02-29 15:14:38 -0800150
Brian Harring1790ac42012-09-23 08:53:33 -0700151 # Cleanup old tarballs now since we've successfull fetched; only cleanup
152 # the tarballs for our prefix, or unknown ones.
153 ignored_prefix = ('stage3-' if tarball_name.startswith('cros-sdk-')
154 else 'cros-sdk-')
155 for filename in os.listdir(storage_dir):
156 if filename == tarball_name or filename.startswith(ignored_prefix):
157 continue
Brian Harringb938c782012-02-29 15:14:38 -0800158
Brian Harring1790ac42012-09-23 08:53:33 -0700159 print 'Cleaning up old tarball: %s' % (filename,)
160 osutils.SafeUnlink(os.path.join(storage_dir, filename), sudo=True)
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200161
Brian Harringb938c782012-02-29 15:14:38 -0800162 return tarball_dest
163
164
Brian Harring1790ac42012-09-23 08:53:33 -0700165def CreateChroot(chroot_path, sdk_tarball, cache_dir, nousepkg=False):
Brian Harringb938c782012-02-29 15:14:38 -0800166 """Creates a new chroot from a given SDK"""
Brian Harringb938c782012-02-29 15:14:38 -0800167
Brian Harring1790ac42012-09-23 08:53:33 -0700168 cmd = MAKE_CHROOT + ['--stage3_path', sdk_tarball,
Brian Harringae0a5322012-09-15 01:46:51 -0700169 '--chroot', chroot_path,
170 '--cache_dir', cache_dir]
Mike Frysinger2de7f042012-07-10 04:45:03 -0400171 if nousepkg:
172 cmd.append('--nousepkg')
Brian Harringb938c782012-02-29 15:14:38 -0800173
174 try:
175 cros_build_lib.RunCommand(cmd, print_cmd=False)
176 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700177 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800178
179
180def DeleteChroot(chroot_path):
181 """Deletes an existing chroot"""
182 cmd = MAKE_CHROOT + ['--chroot', chroot_path,
183 '--delete']
184 try:
185 cros_build_lib.RunCommand(cmd, print_cmd=False)
186 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700187 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800188
189
190def _CreateLockFile(path):
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200191 """Create a lockfile via sudo that is writable by current user."""
192 cros_build_lib.SudoRunCommand(['touch', path], print_cmd=False)
193 cros_build_lib.SudoRunCommand(['chown', str(os.getuid()), path],
194 print_cmd=False)
195 cros_build_lib.SudoRunCommand(['chmod', '644', path], print_cmd=False)
Brian Harringb938c782012-02-29 15:14:38 -0800196
197
Brian Harringae0a5322012-09-15 01:46:51 -0700198def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
199 additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800200 """Enters an existing SDK chroot"""
Brian Harringae0a5322012-09-15 01:46:51 -0700201 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800202 if chrome_root:
203 cmd.extend(['--chrome_root', chrome_root])
204 if chrome_root_mount:
205 cmd.extend(['--chrome_root_mount', chrome_root_mount])
206 if len(additional_args) > 0:
207 cmd.append('--')
208 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700209
210 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True)
211 # If we were in interactive mode, ignore the exit code; it'll be whatever
212 # they last ran w/in the chroot and won't matter to us one way or another.
213 # Note this does allow chroot entrance to fail and be ignored during
214 # interactive; this is however a rare case and the user will immediately
215 # see it (nor will they be checking the exit code manually).
216 if ret.returncode != 0 and additional_args:
217 raise SystemExit('Running %r failed with exit code %i'
218 % (cmd, ret.returncode))
Brian Harringb938c782012-02-29 15:14:38 -0800219
220
Brian Harring6be2efc2012-03-01 05:04:00 -0800221def main(argv):
Brian Harring218e13c2012-10-10 16:21:26 -0700222 usage = """usage: %prog [options] [VAR1=val1 .. VARn=valn -- args]
Brian Harringb938c782012-02-29 15:14:38 -0800223
Brian Harring218e13c2012-10-10 16:21:26 -0700224This script is used for manipulating local chroot environments; creating,
225deleting, downloading, etc. If given --enter (or no args), it defaults
226to an interactive bash shell within the chroot.
Brian Harringb938c782012-02-29 15:14:38 -0800227
Brian Harring218e13c2012-10-10 16:21:26 -0700228If given args those are passed to the chroot environment, and executed."""
Brian Harring1790ac42012-09-23 08:53:33 -0700229 conf = GetSdkConfig()
230 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
231 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
232
Brian Harring218e13c2012-10-10 16:21:26 -0700233 parser = commandline.OptionParser(usage=usage, caching=True)
234
235 commands = parser.add_option_group("Commands")
236 commands.add_option(
237 '--enter', action='store_true', default=False,
238 help='Enter the SDK chroot. Implies --create.')
239 commands.add_option(
240 '--create', action='store_true',default=False,
241 help='Create the chroot only if it does not already exist. '
242 'Implies --download.')
243 commands.add_option(
244 '--bootstrap', action='store_true', default=False,
245 help='Build everything from scratch, including the sdk. '
246 'Use this only if you need to validate a change '
247 'that affects SDK creation itself (toolchain and '
248 'build are typically the only folk who need this). '
249 'Note this will quite heavily slow down the build. '
250 'This option implies --create --nousepkg.')
251 commands.add_option(
252 '-r', '--replace', action='store_true', default=False,
253 help='Replace an existing SDK chroot. Basically an alias '
254 'for --delete --create.')
255 commands.add_option(
256 '--delete', action='store_true', default=False,
257 help='Delete the current SDK chroot if it exists.')
258 commands.add_option(
259 '--download', action='store_true', default=False,
260 help='Download the sdk.')
Brian Harringb938c782012-02-29 15:14:38 -0800261
262 # Global options:
Brian Harringb6cf9142012-09-01 20:43:17 -0700263 default_chroot = os.path.join(SRC_ROOT, constants.DEFAULT_CHROOT_DIR)
Brian Harring218e13c2012-10-10 16:21:26 -0700264 parser.add_option(
265 '--chroot', dest='chroot', default=default_chroot, type='path',
266 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Brian Harringb938c782012-02-29 15:14:38 -0800267
Brian Harring218e13c2012-10-10 16:21:26 -0700268 parser.add_option('--chrome_root', default=None, type='path',
269 help='Mount this chrome root into the SDK chroot')
270 parser.add_option('--chrome_root_mount', default=None, type='path',
271 help='Mount chrome into this path inside SDK chroot')
272 parser.add_option('--nousepkg', action='store_true', default=False,
273 help='Do not use binary packages when creating a chroot.')
Brian Harringb938c782012-02-29 15:14:38 -0800274 parser.add_option('-u', '--url',
Brian Harringb6cf9142012-09-01 20:43:17 -0700275 dest='sdk_url', default=None,
Brian Harringb938c782012-02-29 15:14:38 -0800276 help=('''Use sdk tarball located at this url.
277 Use file:// for local files.'''))
Brian Harring1790ac42012-09-23 08:53:33 -0700278 parser.add_option('--sdk-version', default=None,
279 help='Use this sdk version. For prebuilt, current is %r'
280 ', for bootstrapping its %r.'
281 % (sdk_latest_version, bootstrap_latest_version))
Brian Harring218e13c2012-10-10 16:21:26 -0700282 options, chroot_command = parser.parse_args(argv)
Brian Harringb938c782012-02-29 15:14:38 -0800283
284 # Some sanity checks first, before we ask for sudo credentials.
285 if cros_build_lib.IsInsideChroot():
Brian Harring98b54902012-03-23 04:05:42 -0700286 parser.error("This needs to be ran outside the chroot")
Brian Harringb938c782012-02-29 15:14:38 -0800287
Brian Harring1790ac42012-09-23 08:53:33 -0700288 host = os.uname()[4]
289
290 if host != 'x86_64':
291 parser.error(
292 "cros_sdk is currently only supported on x86_64; you're running"
293 " %s. Please find a x86_64 machine." % (host,))
294
Brian Harring98b54902012-03-23 04:05:42 -0700295 missing = CheckPrerequisites(NEEDED_TOOLS)
296 if missing:
297 parser.error((
298 'The tool(s) %s were not found.'
299 'Please install the appropriate package in your host.'
300 'Example(ubuntu):'
301 ' sudo apt-get install <packagename>'
302 % (', '.join(missing))))
Brian Harringb938c782012-02-29 15:14:38 -0800303
Brian Harring218e13c2012-10-10 16:21:26 -0700304 # Expand out the aliases...
305 if options.replace:
306 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800307
Brian Harring218e13c2012-10-10 16:21:26 -0700308 if options.bootstrap:
309 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800310
Brian Harring218e13c2012-10-10 16:21:26 -0700311 # If a command is not given, default to enter.
312 options.enter |= not any(getattr(options, x.dest)
313 for x in commands.option_list)
314 options.enter |= bool(chroot_command)
315
316 if options.enter and options.delete and not options.create:
317 parser.error("Trying to enter the chroot when --delete "
318 "was specified makes no sense.")
319
320 # Finally, discern if we need to create the chroot.
321 chroot_exists = os.path.exists(options.chroot)
322 if options.create or options.enter:
323 # Only create if it's being wiped, or if it doesn't exist.
324 if not options.delete and chroot_exists:
325 options.create = False
326 else:
327 options.download = True
328
329 # Finally, flip create if necessary.
330 if options.enter:
331 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800332
Brian Harringb938c782012-02-29 15:14:38 -0800333 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -0700334 sdk_version = (bootstrap_latest_version if options.bootstrap
335 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -0800336 else:
337 sdk_version = options.sdk_version
338
Brian Harring1790ac42012-09-23 08:53:33 -0700339 # Based on selections, fetch the tarball.
340 if options.sdk_url:
341 urls = [options.sdk_url]
342 elif options.bootstrap:
343 urls = GetStage3Urls(sdk_version)
344 else:
345 urls = GetArchStageTarballs(sdk_version)
346
Brian Harringb6cf9142012-09-01 20:43:17 -0700347 lock_path = os.path.dirname(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800348 lock_path = os.path.join(lock_path,
Brian Harringb6cf9142012-09-01 20:43:17 -0700349 '.%s_lock' % os.path.basename(options.chroot))
David James891dccf2012-08-20 14:19:54 -0700350 with sudo.SudoKeepAlive(ttyless_sudo=False):
Brian Harring4e6412d2012-03-09 20:54:02 -0800351 with cgroups.SimpleContainChildren('cros_sdk'):
Brian Harringcfe762a2012-02-29 13:03:53 -0800352 _CreateLockFile(lock_path)
353 with locking.FileLock(lock_path, 'chroot lock') as lock:
Brian Harring1790ac42012-09-23 08:53:33 -0700354
Brian Harring218e13c2012-10-10 16:21:26 -0700355 if options.delete and os.path.exists(options.chroot):
356 lock.write_lock()
357 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800358
Brian Harringae0a5322012-09-15 01:46:51 -0700359 sdk_cache = os.path.join(options.cache_dir, 'sdks')
360 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Brian Harringfa327912012-09-28 20:57:01 -0700361 osutils.SafeMakedirs(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -0700362
363 for target in (sdk_cache, distfiles_cache):
364 src = os.path.join(SRC_ROOT, os.path.basename(target))
365 if not os.path.exists(src):
366 osutils.SafeMakedirs(target)
367 continue
Brian Harringae0a5322012-09-15 01:46:51 -0700368 lock.write_lock(
369 "Upgrade to %r needed but chroot is locked; please exit "
370 "all instances so this upgrade can finish." % src)
371 if not os.path.exists(src):
372 # Note that while waiting for the write lock, src may've vanished;
373 # it's a rare race during the upgrade process that's a byproduct
374 # of us avoiding taking a write lock to do the src check. If we
375 # took a write lock for that check, it would effectively limit
376 # all cros_sdk for a chroot to a single instance.
377 osutils.SafeMakedirs(target)
378 elif not os.path.exists(target):
379 # Upgrade occurred, but a reversion, or something whacky
380 # occurred writing to the old location. Wipe and continue.
381 cros_build_lib.SudoRunCommand(
382 ['mv', '--', src, target], print_cmd=False)
383 else:
384 # Upgrade occurred once already, but either a reversion or
385 # some before/after separate cros_sdk usage is at play.
386 # Wipe and continue.
387 osutils.RmDir(src, sudo=True)
388
Brian Harring218e13c2012-10-10 16:21:26 -0700389 if options.download:
Brian Harringcfe762a2012-02-29 13:03:53 -0800390 lock.write_lock()
Brian Harring1790ac42012-09-23 08:53:33 -0700391 sdk_tarball = FetchRemoteTarballs(sdk_cache, urls)
Brian Harring218e13c2012-10-10 16:21:26 -0700392
393 if options.create:
394 lock.write_lock()
Brian Harring1790ac42012-09-23 08:53:33 -0700395 CreateChroot(options.chroot, sdk_tarball, options.cache_dir,
396 nousepkg=(options.bootstrap or options.nousepkg))
397
Brian Harringcfe762a2012-02-29 13:03:53 -0800398 if options.enter:
399 lock.read_lock()
Brian Harringae0a5322012-09-15 01:46:51 -0700400 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
Brian Harring218e13c2012-10-10 16:21:26 -0700401 options.chrome_root_mount, chroot_command)