blob: 7c91ecd15d4908e3d7faeb4d2ebd1a85dd98db20 [file] [log] [blame]
Brian Harringb938c782012-02-29 15:14:38 -08001#!/usr/bin/env python
2# Copyright (c) 2011-2012 The Chromium OS Authors. All rights reserved.
3# 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
9import optparse
10import os
11import sys
12import urlparse
13
14from chromite.buildbot import constants
Brian Harringcfe762a2012-02-29 13:03:53 -080015from chromite.lib import cgroups
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 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
23DEFAULT_URL = 'https://commondatastorage.googleapis.com/chromiumos-sdk/'
24SRC_ROOT = os.path.realpath(constants.SOURCE_ROOT)
25SDK_DIR = os.path.join(SRC_ROOT, 'sdks')
26OVERLAY_DIR = os.path.join(SRC_ROOT, 'src/third_party/chromiumos-overlay')
27SDK_VERSION_FILE = os.path.join(OVERLAY_DIR,
28 'chromeos/binhost/host/sdk_version.conf')
29
30# TODO(zbehan): Remove the dependency on these, reimplement them in python
31MAKE_CHROOT = [os.path.join(SRC_ROOT, 'src/scripts/sdk_lib/make_chroot.sh')]
32ENTER_CHROOT = [os.path.join(SRC_ROOT, 'src/scripts/sdk_lib/enter_chroot.sh')]
33
34# We need these tools to run. Very common tools (tar,..) are ommited.
35NEEDED_TOOLS = ['curl']
36
37def GetHostArch():
38 """Returns a string for the host architecture"""
39 out = cros_build_lib.RunCommand(['uname', '-m'],
40 redirect_stdout=True, print_cmd=False).output
41 return out.rstrip('\n')
42
43def CheckPrerequisites(needed_tools):
44 """Verifies that the required tools are present on the system.
45
46 This is especially important as this script is intended to run
47 outside the chroot.
48
49 Arguments:
50 needed_tools: an array of string specified binaries to look for.
51
52 Returns:
53 True if all needed tools were found.
54 """
55 for tool in needed_tools:
56 cmd = ['which', tool]
57 try:
58 cros_build_lib.RunCommand(cmd, print_cmd=False, redirect_stdout=True,
59 combine_stdout_stderr=True)
60 except cros_build_lib.RunCommandError:
61 print 'The tool \'' + tool + '\' not found.'
62 print 'Please install the appropriate package in your host.'
63 print 'Example(ubuntu):'
64 print ' sudo apt-get install <packagename>'
65 return False
66 return True
67
68def GetLatestVersion():
69 """Extracts latest version from chromiumos-overlay."""
70 sdk_file = open(SDK_VERSION_FILE)
71 buf = sdk_file.readline().rstrip('\n').split('=')
72 if buf[0] != 'SDK_LATEST_VERSION':
73 raise Exception('Malformed version file')
74 return buf[1].strip('"')
75
76
77def GetArchStageTarball(tarballArch, version):
78 """Returns the URL for a given arch/version"""
79 D = { 'x86_64': 'cros-sdk-' }
80 try:
81 return DEFAULT_URL + D[tarballArch] + version + '.tbz2'
82 except KeyError:
83 sys.exit('Unsupported arch: %s' % (tarballArch,))
84
85
86def FetchRemoteTarball(url):
87 """Fetches a tarball given by url, and place it in sdk/."""
88 tarball_dest = os.path.join(SDK_DIR,
89 os.path.basename(urlparse.urlparse(url).path))
90
91 print 'Downloading sdk: "%s"' % url
92 cmd = ['curl', '-f', '--retry', '5', '-L', '-y', '30',
93 '--output', tarball_dest]
94
95 if not url.startswith('file://') and os.path.exists(tarball_dest):
96 # Only resume for remote URLs. If the file is local, there's no
97 # real speedup, and using the same filename for different files
98 # locally will cause issues.
99 cmd.extend(['-C', '-'])
100
101 # Additionally, certain versions of curl incorrectly fail if
102 # told to resume a file that is fully downloaded, thus do a
103 # check on our own.
104 # see:
105 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
106 result = cros_build_lib.RunCommand(['curl', '-I', url],
107 redirect_stdout=True, print_cmd=False)
108 for x in result.output.splitlines():
109 if x.lower().startswith("content-length:"):
110 length = int(x.split(":", 1)[-1].strip())
111 if length == os.path.getsize(tarball_dest):
112 # Fully fetched; bypass invoking curl, since it can screw up handling
113 # of this (>=7.21.4 and up).
114 return tarball_dest
115 break
116
117 cmd.append(url)
118
119 cros_build_lib.RunCommand(cmd)
120 return tarball_dest
121
122
123def BootstrapChroot(chroot_path, stage_url, replace):
124 """Builds a new chroot from source"""
125 cmd = MAKE_CHROOT + ['--chroot', chroot_path,
126 '--nousepkg']
127
128 stage = None
129 if stage_url:
130 stage = FetchRemoteTarball(stage_url)
131
132 if stage:
133 cmd.extend(['--stage3_path', stage])
134
135 if replace:
136 cmd.append('--replace')
137
138 try:
139 cros_build_lib.RunCommand(cmd, print_cmd=False)
140 except cros_build_lib.RunCommandError:
141 print 'Running %r failed!' % cmd
142 sys.exit(1)
143
144
145def CreateChroot(sdk_url, sdk_version, chroot_path, replace):
146 """Creates a new chroot from a given SDK"""
147 if not os.path.exists(SDK_DIR):
148 cros_build_lib.RunCommand(['mkdir', '-p', SDK_DIR], print_cmd=False)
149
150 # Based on selections, fetch the tarball
151 if sdk_url:
152 url = sdk_url
153 else:
154 arch = GetHostArch()
155 if sdk_version:
156 url = GetArchStageTarball(arch, sdk_version)
157 else:
158 url = GetArchStageTarball(arch)
159
160 sdk = FetchRemoteTarball(url)
161
162 # TODO(zbehan): Unpack and install
163 # For now, we simply call make_chroot on the prebuilt chromeos-sdk.
164 # make_chroot provides a variety of hacks to make the chroot useable.
165 # These should all be eliminated/minimised, after which, we can change
166 # this to just unpacking the sdk.
167 cmd = MAKE_CHROOT + ['--stage3_path', sdk,
168 '--chroot', chroot_path]
169
170 if replace:
171 cmd.append('--replace')
172
173 try:
174 cros_build_lib.RunCommand(cmd, print_cmd=False)
175 except cros_build_lib.RunCommandError:
176 print 'Running %r failed!' % cmd
177 sys.exit(1)
178
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:
187 print 'Running %r failed!' % cmd
188 sys.exit(1)
189
190
191def _CreateLockFile(path):
192 """Create a lockfile via sudo that is writable by current user."""
193 cros_build_lib.SudoRunCommand(['touch', path], print_cmd=False)
194 cros_build_lib.SudoRunCommand(['chown', str(os.getuid()), path],
195 print_cmd=False)
196 cros_build_lib.SudoRunCommand(['chmod', '644', path], print_cmd=False)
197
198
199def EnterChroot(chroot_path, chrome_root, chrome_root_mount, additional_args):
200 """Enters an existing SDK chroot"""
201 cmd = ENTER_CHROOT + ['--chroot', chroot_path]
202 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)
209 try:
210 cros_build_lib.RunCommand(cmd, print_cmd=False)
211 except cros_build_lib.RunCommandError:
212 print 'Running %r failed!' % cmd
213 sys.exit(1)
214
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.
Brian Harringb938c782012-02-29 15:14:38 -0800218 usage="""usage: %prog [options] [VAR1=val1 .. VARn=valn -- <args>]
219
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)
226--bootstrap .. Builds a chroot from source (enter if --enter)
227--delete .. Removes a chroot
228"""
229 sdk_latest_version = GetLatestVersion()
230 parser = optparse.OptionParser(usage)
231 # Actions:
232 parser.add_option('', '--bootstrap',
233 action='store_true', dest='bootstrap', default=False,
234 help=('Build a new SDK chroot from source'))
235 parser.add_option('', '--delete',
236 action='store_true', dest='delete', default=False,
237 help=('Delete the current SDK chroot'))
238 parser.add_option('', '--download',
239 action='store_true', dest='download', default=False,
240 help=('Download and install a prebuilt SDK'))
241 parser.add_option('', '--enter',
242 action='store_true', dest='enter', default=False,
243 help=('Enter the SDK chroot, possibly (re)create first'))
244
245 # Global options:
246 parser.add_option('', '--chroot',
247 dest='chroot', default=constants.DEFAULT_CHROOT_DIR,
248 help=('SDK chroot dir name [%s]' %
249 constants.DEFAULT_CHROOT_DIR))
250
251 # Additional options:
252 parser.add_option('', '--chrome_root',
253 dest='chrome_root', default='',
254 help=('Mount this chrome root into the SDK chroot'))
255 parser.add_option('', '--chrome_root_mount',
256 dest='chrome_root_mount', default='',
257 help=('Mount chrome into this path inside SDK chroot'))
258 parser.add_option('-r', '--replace',
259 action='store_true', dest='replace', default=False,
260 help=('Replace an existing SDK chroot'))
261 parser.add_option('-u', '--url',
262 dest='sdk_url', default='',
263 help=('''Use sdk tarball located at this url.
264 Use file:// for local files.'''))
265 parser.add_option('-v', '--version',
266 dest='sdk_version', default='',
267 help=('Use this sdk version [%s]' % sdk_latest_version))
268 (options, remaining_arguments) = parser.parse_args(argv)
269
270 # Some sanity checks first, before we ask for sudo credentials.
271 if cros_build_lib.IsInsideChroot():
272 print "This needs to be ran outside the chroot"
273 sys.exit(1)
274
275 if not CheckPrerequisites(NEEDED_TOOLS):
276 sys.exit(1)
277
278 # Default action is --enter, if no other is selected.
279 if not (options.bootstrap or options.download or options.delete):
280 options.enter = True
281
282 # Only --enter can process additional args as passthrough commands.
283 # Warn and exit for least surprise.
284 if len(remaining_arguments) > 0 and not options.enter:
285 print "Additional arguments not permitted, unless running with --enter"
286 parser.print_help()
287 sys.exit(1)
288
289 # Some actions can be combined, as they merely modify how is the chroot
290 # going to be made. The only option that hates all others is --delete.
291 if options.delete and \
292 (options.enter or options.download or options.bootstrap):
293 print "--delete cannot be combined with --enter, --download or --bootstrap"
294 parser.print_help()
295 sys.exit(1)
296 # NOTE: --delete is a true hater, it doesn't like other options either, but
297 # those will hardly lead to confusion. Nobody can expect to pass --version to
298 # delete and actually change something.
299
300 if options.bootstrap and options.download:
301 print "Either --bootstrap or --download, not both"
302 sys.exit(1)
303
304 # Bootstrap will start off from a non-selectable stage3 tarball. Attempts to
305 # select sdk by version are confusing. Warn and exit. We can still specify a
306 # tarball by path or URL though.
307 if options.bootstrap and options.sdk_version:
308 print "Cannot use --version when bootstrapping"
309 parser.print_help()
310 sys.exit(1)
311
312 chroot_path = os.path.join(SRC_ROOT, options.chroot)
313 chroot_path = os.path.abspath(chroot_path)
314 chroot_path = os.path.normpath(chroot_path)
315
316 if not options.sdk_version:
317 sdk_version = sdk_latest_version
318 else:
319 sdk_version = options.sdk_version
320
321 if options.delete and not os.path.exists(chroot_path):
322 print "Not doing anything. The chroot you want to remove doesn't exist."
323 sys.exit(0)
324
325 lock_path = os.path.dirname(chroot_path)
326 lock_path = os.path.join(lock_path,
327 '.%s_lock' % os.path.basename(chroot_path))
328 with sudo.SudoKeepAlive():
Brian Harring4e6412d2012-03-09 20:54:02 -0800329 with cgroups.SimpleContainChildren('cros_sdk'):
Brian Harringcfe762a2012-02-29 13:03:53 -0800330 _CreateLockFile(lock_path)
331 with locking.FileLock(lock_path, 'chroot lock') as lock:
332 if options.delete:
333 lock.write_lock()
334 DeleteChroot(chroot_path)
335 sys.exit(0)
Brian Harringb938c782012-02-29 15:14:38 -0800336
Brian Harringcfe762a2012-02-29 13:03:53 -0800337 # Print a suggestion for replacement, but not if running just --enter.
338 if os.path.exists(chroot_path) and not options.replace and \
339 (options.bootstrap or options.download):
340 print "Chroot already exists. Run with --replace to re-create."
Brian Harringb938c782012-02-29 15:14:38 -0800341
Brian Harringcfe762a2012-02-29 13:03:53 -0800342 # Chroot doesn't exist or asked to replace.
343 if not os.path.exists(chroot_path) or options.replace:
344 lock.write_lock()
345 if options.bootstrap:
346 BootstrapChroot(chroot_path, options.sdk_url,
347 options.replace)
348 else:
349 CreateChroot(options.sdk_url, sdk_version,
350 chroot_path, options.replace)
351 if options.enter:
352 lock.read_lock()
353 EnterChroot(chroot_path, options.chrome_root,
354 options.chrome_root_mount, remaining_arguments)