blob: 9c9e0dd76d4790bac97966e0b22149d58c937409 [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'
Zdenek Behanaa52cea2012-05-30 01:31:11 +020025COMPRESSION_PREFERENCE = ('xz', 'bz2')
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.
Zdenek Behanaa52cea2012-05-30 01:31:11 +020037NEEDED_TOOLS = ('curl', 'xz')
Brian Harringb938c782012-02-29 15:14:38 -080038
Brian Harringb938c782012-02-29 15:14:38 -080039
Brian Harring1790ac42012-09-23 08:53:33 -070040def GetSdkConfig():
Brian Harringb938c782012-02-29 15:14:38 -080041 """Extracts latest version from chromiumos-overlay."""
Brian Harring1790ac42012-09-23 08:53:33 -070042 d = {}
Brian Harring218e13c2012-10-10 16:21:26 -070043 try:
44 with open(SDK_VERSION_FILE) as f:
45 for raw_line in f:
46 line = raw_line.split('#')[0].strip()
47 if not line:
48 continue
49 chunks = line.split('=', 1)
50 if len(chunks) != 2:
51 raise Exception('Malformed version file; line %r' % raw_line)
52 d[chunks[0]] = chunks[1].strip().strip('"')
53 except EnvironmentError, e:
54 if e.errnor != errno.ENOENT:
55 raise
Brian Harring1790ac42012-09-23 08:53:33 -070056 return d
Brian Harringb938c782012-02-29 15:14:38 -080057
58
Brian Harring1790ac42012-09-23 08:53:33 -070059def GetArchStageTarballs(version):
Brian Harringb938c782012-02-29 15:14:38 -080060 """Returns the URL for a given arch/version"""
Brian Harring1790ac42012-09-23 08:53:33 -070061 extension = {'bz2':'tbz2', 'xz':'tar.xz'}
62 return ['%s/cros-sdk-%s.%s'
63 % (DEFAULT_URL, version, extension[compressor])
64 for compressor in COMPRESSION_PREFERENCE]
65
66
67def GetStage3Urls(version):
68 return ['%s/stage3-amd64-%s.tar.%s' % (DEFAULT_URL, version, ext)
69 for ext in COMPRESSION_PREFERENCE]
Brian Harringb938c782012-02-29 15:14:38 -080070
71
Brian Harringae0a5322012-09-15 01:46:51 -070072def FetchRemoteTarballs(storage_dir, urls):
Zdenek Behanfd0efe42012-04-13 04:36:40 +020073 """Fetches a tarball given by url, and place it in sdk/.
74
75 Args:
76 urls: List of URLs to try to download. Download will stop on first success.
77
78 Returns:
79 Full path to the downloaded file
80 """
Zdenek Behanfd0efe42012-04-13 04:36:40 +020081
Brian Harring1790ac42012-09-23 08:53:33 -070082 # Note we track content length ourselves since certain versions of curl
83 # fail if asked to resume a complete file.
84 # pylint: disable=C0301,W0631
85 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
Zdenek Behanfd0efe42012-04-13 04:36:40 +020086 for url in urls:
Brian Harring1790ac42012-09-23 08:53:33 -070087 # http://www.logilab.org/ticket/8766
88 # pylint: disable=E1101
89 parsed = urlparse.urlparse(url)
90 tarball_name = os.path.basename(parsed.path)
91 if parsed.scheme in ('', 'file'):
92 if os.path.exists(parsed.path):
93 return parsed.path
94 continue
95 content_length = 0
Zdenek Behanfd0efe42012-04-13 04:36:40 +020096 print 'Attempting download: %s' % url
Brian Harring1790ac42012-09-23 08:53:33 -070097 result = cros_build_lib.RunCurl(
98 ['-I', url], redirect_stdout=True, redirect_stderr=True,
99 print_cmd=False)
100 successful = False
101 for header in result.output.splitlines():
102 # We must walk the output to find the string '200 OK' for use cases where
103 # a proxy is involved and may have pushed down the actual header.
104 if header.find('200 OK') != -1:
105 successful = True
106 elif header.lower().startswith("content-length:"):
107 content_length = int(header.split(":", 1)[-1].strip())
108 if successful:
109 break
110 if successful:
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200111 break
112 else:
113 raise Exception('No valid URLs found!')
114
Brian Harringae0a5322012-09-15 01:46:51 -0700115 tarball_dest = os.path.join(storage_dir, tarball_name)
Brian Harring1790ac42012-09-23 08:53:33 -0700116 current_size = 0
117 if os.path.exists(tarball_dest):
118 current_size = os.path.getsize(tarball_dest)
119 if current_size > content_length:
120 osutils.SafeUnlink(tarball_dest, sudo=True)
121 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100122
Brian Harring1790ac42012-09-23 08:53:33 -0700123 if current_size < content_length:
124 cros_build_lib.RunCurl(
125 ['-f', '-L', '-y', '30', '-C', '-', '--output', tarball_dest, url],
126 print_cmd=False)
Brian Harringb938c782012-02-29 15:14:38 -0800127
Brian Harring1790ac42012-09-23 08:53:33 -0700128 # Cleanup old tarballs now since we've successfull fetched; only cleanup
129 # the tarballs for our prefix, or unknown ones.
130 ignored_prefix = ('stage3-' if tarball_name.startswith('cros-sdk-')
131 else 'cros-sdk-')
132 for filename in os.listdir(storage_dir):
133 if filename == tarball_name or filename.startswith(ignored_prefix):
134 continue
Brian Harringb938c782012-02-29 15:14:38 -0800135
Brian Harring1790ac42012-09-23 08:53:33 -0700136 print 'Cleaning up old tarball: %s' % (filename,)
137 osutils.SafeUnlink(os.path.join(storage_dir, filename), sudo=True)
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200138
Brian Harringb938c782012-02-29 15:14:38 -0800139 return tarball_dest
140
141
Brian Harring1790ac42012-09-23 08:53:33 -0700142def CreateChroot(chroot_path, sdk_tarball, cache_dir, nousepkg=False):
Brian Harringb938c782012-02-29 15:14:38 -0800143 """Creates a new chroot from a given SDK"""
Brian Harringb938c782012-02-29 15:14:38 -0800144
Brian Harring1790ac42012-09-23 08:53:33 -0700145 cmd = MAKE_CHROOT + ['--stage3_path', sdk_tarball,
Brian Harringae0a5322012-09-15 01:46:51 -0700146 '--chroot', chroot_path,
147 '--cache_dir', cache_dir]
Mike Frysinger2de7f042012-07-10 04:45:03 -0400148 if nousepkg:
149 cmd.append('--nousepkg')
Brian Harringb938c782012-02-29 15:14:38 -0800150
151 try:
152 cros_build_lib.RunCommand(cmd, print_cmd=False)
153 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700154 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800155
156
157def DeleteChroot(chroot_path):
158 """Deletes an existing chroot"""
159 cmd = MAKE_CHROOT + ['--chroot', chroot_path,
160 '--delete']
161 try:
162 cros_build_lib.RunCommand(cmd, print_cmd=False)
163 except cros_build_lib.RunCommandError:
Brian Harring98b54902012-03-23 04:05:42 -0700164 raise SystemExit('Running %r failed!' % cmd)
Brian Harringb938c782012-02-29 15:14:38 -0800165
166
167def _CreateLockFile(path):
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200168 """Create a lockfile via sudo that is writable by current user."""
169 cros_build_lib.SudoRunCommand(['touch', path], print_cmd=False)
170 cros_build_lib.SudoRunCommand(['chown', str(os.getuid()), path],
171 print_cmd=False)
172 cros_build_lib.SudoRunCommand(['chmod', '644', path], print_cmd=False)
Brian Harringb938c782012-02-29 15:14:38 -0800173
174
Brian Harringae0a5322012-09-15 01:46:51 -0700175def EnterChroot(chroot_path, cache_dir, chrome_root, chrome_root_mount,
176 additional_args):
Brian Harringb938c782012-02-29 15:14:38 -0800177 """Enters an existing SDK chroot"""
Brian Harringae0a5322012-09-15 01:46:51 -0700178 cmd = ENTER_CHROOT + ['--chroot', chroot_path, '--cache_dir', cache_dir]
Brian Harringb938c782012-02-29 15:14:38 -0800179 if chrome_root:
180 cmd.extend(['--chrome_root', chrome_root])
181 if chrome_root_mount:
182 cmd.extend(['--chrome_root_mount', chrome_root_mount])
183 if len(additional_args) > 0:
184 cmd.append('--')
185 cmd.extend(additional_args)
Brian Harring7199e7d2012-03-23 04:10:08 -0700186
187 ret = cros_build_lib.RunCommand(cmd, print_cmd=False, error_code_ok=True)
188 # If we were in interactive mode, ignore the exit code; it'll be whatever
189 # they last ran w/in the chroot and won't matter to us one way or another.
190 # Note this does allow chroot entrance to fail and be ignored during
191 # interactive; this is however a rare case and the user will immediately
192 # see it (nor will they be checking the exit code manually).
193 if ret.returncode != 0 and additional_args:
194 raise SystemExit('Running %r failed with exit code %i'
195 % (cmd, ret.returncode))
Brian Harringb938c782012-02-29 15:14:38 -0800196
197
Brian Harring6be2efc2012-03-01 05:04:00 -0800198def main(argv):
Brian Harring218e13c2012-10-10 16:21:26 -0700199 usage = """usage: %prog [options] [VAR1=val1 .. VARn=valn -- args]
Brian Harringb938c782012-02-29 15:14:38 -0800200
Brian Harring218e13c2012-10-10 16:21:26 -0700201This script is used for manipulating local chroot environments; creating,
202deleting, downloading, etc. If given --enter (or no args), it defaults
203to an interactive bash shell within the chroot.
Brian Harringb938c782012-02-29 15:14:38 -0800204
Brian Harring218e13c2012-10-10 16:21:26 -0700205If given args those are passed to the chroot environment, and executed."""
Brian Harring1790ac42012-09-23 08:53:33 -0700206 conf = GetSdkConfig()
207 sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
208 bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
209
Brian Harring218e13c2012-10-10 16:21:26 -0700210 parser = commandline.OptionParser(usage=usage, caching=True)
211
212 commands = parser.add_option_group("Commands")
213 commands.add_option(
214 '--enter', action='store_true', default=False,
215 help='Enter the SDK chroot. Implies --create.')
216 commands.add_option(
217 '--create', action='store_true',default=False,
218 help='Create the chroot only if it does not already exist. '
219 'Implies --download.')
220 commands.add_option(
221 '--bootstrap', action='store_true', default=False,
222 help='Build everything from scratch, including the sdk. '
223 'Use this only if you need to validate a change '
224 'that affects SDK creation itself (toolchain and '
225 'build are typically the only folk who need this). '
226 'Note this will quite heavily slow down the build. '
227 'This option implies --create --nousepkg.')
228 commands.add_option(
229 '-r', '--replace', action='store_true', default=False,
230 help='Replace an existing SDK chroot. Basically an alias '
231 'for --delete --create.')
232 commands.add_option(
233 '--delete', action='store_true', default=False,
234 help='Delete the current SDK chroot if it exists.')
235 commands.add_option(
236 '--download', action='store_true', default=False,
237 help='Download the sdk.')
Brian Harringb938c782012-02-29 15:14:38 -0800238
239 # Global options:
Brian Harringb6cf9142012-09-01 20:43:17 -0700240 default_chroot = os.path.join(SRC_ROOT, constants.DEFAULT_CHROOT_DIR)
Brian Harring218e13c2012-10-10 16:21:26 -0700241 parser.add_option(
242 '--chroot', dest='chroot', default=default_chroot, type='path',
243 help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
Brian Harringb938c782012-02-29 15:14:38 -0800244
Brian Harring218e13c2012-10-10 16:21:26 -0700245 parser.add_option('--chrome_root', default=None, type='path',
246 help='Mount this chrome root into the SDK chroot')
247 parser.add_option('--chrome_root_mount', default=None, type='path',
248 help='Mount chrome into this path inside SDK chroot')
249 parser.add_option('--nousepkg', action='store_true', default=False,
250 help='Do not use binary packages when creating a chroot.')
Brian Harringb938c782012-02-29 15:14:38 -0800251 parser.add_option('-u', '--url',
Brian Harringb6cf9142012-09-01 20:43:17 -0700252 dest='sdk_url', default=None,
Brian Harringb938c782012-02-29 15:14:38 -0800253 help=('''Use sdk tarball located at this url.
254 Use file:// for local files.'''))
Brian Harring1790ac42012-09-23 08:53:33 -0700255 parser.add_option('--sdk-version', default=None,
256 help='Use this sdk version. For prebuilt, current is %r'
257 ', for bootstrapping its %r.'
258 % (sdk_latest_version, bootstrap_latest_version))
Brian Harring218e13c2012-10-10 16:21:26 -0700259 options, chroot_command = parser.parse_args(argv)
Brian Harringb938c782012-02-29 15:14:38 -0800260
261 # Some sanity checks first, before we ask for sudo credentials.
262 if cros_build_lib.IsInsideChroot():
Brian Harring98b54902012-03-23 04:05:42 -0700263 parser.error("This needs to be ran outside the chroot")
Brian Harringb938c782012-02-29 15:14:38 -0800264
Brian Harring1790ac42012-09-23 08:53:33 -0700265 host = os.uname()[4]
266
267 if host != 'x86_64':
268 parser.error(
269 "cros_sdk is currently only supported on x86_64; you're running"
270 " %s. Please find a x86_64 machine." % (host,))
271
David Jamesaad5cc72012-10-26 15:03:13 -0700272 missing = osutils.FindMissingBinaries(NEEDED_TOOLS)
Brian Harring98b54902012-03-23 04:05:42 -0700273 if missing:
274 parser.error((
275 'The tool(s) %s were not found.'
276 'Please install the appropriate package in your host.'
277 'Example(ubuntu):'
278 ' sudo apt-get install <packagename>'
279 % (', '.join(missing))))
Brian Harringb938c782012-02-29 15:14:38 -0800280
Brian Harring218e13c2012-10-10 16:21:26 -0700281 # Expand out the aliases...
282 if options.replace:
283 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800284
Brian Harring218e13c2012-10-10 16:21:26 -0700285 if options.bootstrap:
286 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800287
Brian Harring218e13c2012-10-10 16:21:26 -0700288 # If a command is not given, default to enter.
289 options.enter |= not any(getattr(options, x.dest)
290 for x in commands.option_list)
291 options.enter |= bool(chroot_command)
292
293 if options.enter and options.delete and not options.create:
294 parser.error("Trying to enter the chroot when --delete "
295 "was specified makes no sense.")
296
297 # Finally, discern if we need to create the chroot.
298 chroot_exists = os.path.exists(options.chroot)
299 if options.create or options.enter:
300 # Only create if it's being wiped, or if it doesn't exist.
301 if not options.delete and chroot_exists:
302 options.create = False
303 else:
304 options.download = True
305
306 # Finally, flip create if necessary.
307 if options.enter:
308 options.create |= not chroot_exists
Brian Harringb938c782012-02-29 15:14:38 -0800309
Brian Harringb938c782012-02-29 15:14:38 -0800310 if not options.sdk_version:
Brian Harring1790ac42012-09-23 08:53:33 -0700311 sdk_version = (bootstrap_latest_version if options.bootstrap
312 else sdk_latest_version)
Brian Harringb938c782012-02-29 15:14:38 -0800313 else:
314 sdk_version = options.sdk_version
315
Brian Harring1790ac42012-09-23 08:53:33 -0700316 # Based on selections, fetch the tarball.
317 if options.sdk_url:
318 urls = [options.sdk_url]
319 elif options.bootstrap:
320 urls = GetStage3Urls(sdk_version)
321 else:
322 urls = GetArchStageTarballs(sdk_version)
323
Brian Harringb6cf9142012-09-01 20:43:17 -0700324 lock_path = os.path.dirname(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800325 lock_path = os.path.join(lock_path,
Brian Harringb6cf9142012-09-01 20:43:17 -0700326 '.%s_lock' % os.path.basename(options.chroot))
David James891dccf2012-08-20 14:19:54 -0700327 with sudo.SudoKeepAlive(ttyless_sudo=False):
Brian Harring4e6412d2012-03-09 20:54:02 -0800328 with cgroups.SimpleContainChildren('cros_sdk'):
Brian Harringcfe762a2012-02-29 13:03:53 -0800329 _CreateLockFile(lock_path)
330 with locking.FileLock(lock_path, 'chroot lock') as lock:
Brian Harring1790ac42012-09-23 08:53:33 -0700331
Brian Harring218e13c2012-10-10 16:21:26 -0700332 if options.delete and os.path.exists(options.chroot):
333 lock.write_lock()
334 DeleteChroot(options.chroot)
Brian Harringb938c782012-02-29 15:14:38 -0800335
Brian Harringae0a5322012-09-15 01:46:51 -0700336 sdk_cache = os.path.join(options.cache_dir, 'sdks')
337 distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
Brian Harringfa327912012-09-28 20:57:01 -0700338 osutils.SafeMakedirs(options.cache_dir)
Brian Harringae0a5322012-09-15 01:46:51 -0700339
340 for target in (sdk_cache, distfiles_cache):
341 src = os.path.join(SRC_ROOT, os.path.basename(target))
342 if not os.path.exists(src):
343 osutils.SafeMakedirs(target)
344 continue
Brian Harringae0a5322012-09-15 01:46:51 -0700345 lock.write_lock(
346 "Upgrade to %r needed but chroot is locked; please exit "
347 "all instances so this upgrade can finish." % src)
348 if not os.path.exists(src):
349 # Note that while waiting for the write lock, src may've vanished;
350 # it's a rare race during the upgrade process that's a byproduct
351 # of us avoiding taking a write lock to do the src check. If we
352 # took a write lock for that check, it would effectively limit
353 # all cros_sdk for a chroot to a single instance.
354 osutils.SafeMakedirs(target)
355 elif not os.path.exists(target):
356 # Upgrade occurred, but a reversion, or something whacky
357 # occurred writing to the old location. Wipe and continue.
358 cros_build_lib.SudoRunCommand(
359 ['mv', '--', src, target], print_cmd=False)
360 else:
361 # Upgrade occurred once already, but either a reversion or
362 # some before/after separate cros_sdk usage is at play.
363 # Wipe and continue.
364 osutils.RmDir(src, sudo=True)
365
Brian Harring218e13c2012-10-10 16:21:26 -0700366 if options.download:
Brian Harringcfe762a2012-02-29 13:03:53 -0800367 lock.write_lock()
Brian Harring1790ac42012-09-23 08:53:33 -0700368 sdk_tarball = FetchRemoteTarballs(sdk_cache, urls)
Brian Harring218e13c2012-10-10 16:21:26 -0700369
370 if options.create:
371 lock.write_lock()
Brian Harring1790ac42012-09-23 08:53:33 -0700372 CreateChroot(options.chroot, sdk_tarball, options.cache_dir,
373 nousepkg=(options.bootstrap or options.nousepkg))
374
Brian Harringcfe762a2012-02-29 13:03:53 -0800375 if options.enter:
376 lock.read_lock()
Brian Harringae0a5322012-09-15 01:46:51 -0700377 EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
Brian Harring218e13c2012-10-10 16:21:26 -0700378 options.chrome_root_mount, chroot_command)