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