blob: f7d2f11a83e7e09923a9b78a7a3bfc3c6831309a [file] [log] [blame]
Zdenek Behan508dcce2011-12-05 15:39:32 +01001#!/usr/bin/env python
2# Copyright (c) 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 manages the installed toolchains in the chroot.
7"""
8
9import copy
Mike Frysinger35247af2012-11-16 18:58:06 -050010import glob
Mike Frysinger7ccee992012-06-01 21:27:59 -040011import json
Zdenek Behan508dcce2011-12-05 15:39:32 +010012import os
Zdenek Behan508dcce2011-12-05 15:39:32 +010013
Brian Harring503f3ab2012-03-09 21:39:41 -080014from chromite.buildbot import constants
Mike Frysinger506e75f2012-12-17 14:21:13 -050015from chromite.lib import commandline
Brian Harring503f3ab2012-03-09 21:39:41 -080016from chromite.lib import cros_build_lib
Brian Harringaf019fb2012-05-10 15:06:13 -070017from chromite.lib import osutils
Mike Frysinger35247af2012-11-16 18:58:06 -050018from chromite.lib import parallel
David James27ac4ae2012-12-03 23:16:15 -080019from chromite.lib import toolchain
Mike Frysinger35247af2012-11-16 18:58:06 -050020
21# Needs to be after chromite imports.
22import lddtree
Zdenek Behan508dcce2011-12-05 15:39:32 +010023
Mike Frysinger31596002012-12-03 23:54:24 -050024if cros_build_lib.IsInsideChroot():
25 # Only import portage after we've checked that we're inside the chroot.
26 # Outside may not have portage, in which case the above may not happen.
27 # We'll check in main() if the operation needs portage.
28 import portage
Zdenek Behan508dcce2011-12-05 15:39:32 +010029
30
Matt Tennantf1e30972012-03-02 16:30:07 -080031EMERGE_CMD = os.path.join(constants.CHROMITE_BIN_DIR, 'parallel_emerge')
Zdenek Behan508dcce2011-12-05 15:39:32 +010032PACKAGE_STABLE = '[stable]'
33PACKAGE_NONE = '[none]'
34SRC_ROOT = os.path.realpath(constants.SOURCE_ROOT)
Zdenek Behan4eb6fd22012-03-12 17:00:56 +010035
36CHROMIUMOS_OVERLAY = '/usr/local/portage/chromiumos'
37STABLE_OVERLAY = '/usr/local/portage/stable'
38CROSSDEV_OVERLAY = '/usr/local/portage/crossdev'
Zdenek Behan508dcce2011-12-05 15:39:32 +010039
40
41# TODO: The versions are stored here very much like in setup_board.
42# The goal for future is to differentiate these using a config file.
43# This is done essentially by messing with GetDesiredPackageVersions()
44DEFAULT_VERSION = PACKAGE_STABLE
45DEFAULT_TARGET_VERSION_MAP = {
Zdenek Behan508dcce2011-12-05 15:39:32 +010046}
47TARGET_VERSION_MAP = {
Zdenek Behan508dcce2011-12-05 15:39:32 +010048 'host' : {
49 'binutils' : '2.21.1',
50 'gdb' : PACKAGE_NONE,
51 },
52}
53# Overrides for {gcc,binutils}-config, pick a package with particular suffix.
54CONFIG_TARGET_SUFFIXES = {
55 'binutils' : {
56 'i686-pc-linux-gnu' : '-gold',
57 'x86_64-cros-linux-gnu' : '-gold',
58 },
59}
Zdenek Behan508dcce2011-12-05 15:39:32 +010060# Global per-run cache that will be filled ondemand in by GetPackageMap()
61# function as needed.
62target_version_map = {
63}
64
65
David James66a09c42012-11-05 13:31:38 -080066class Crossdev(object):
67 """Class for interacting with crossdev and caching its output."""
68
69 _CACHE_FILE = os.path.join(CROSSDEV_OVERLAY, '.configured.json')
70 _CACHE = {}
71
72 @classmethod
73 def Load(cls, reconfig):
74 """Load crossdev cache from disk."""
David James90239b92012-11-05 15:31:34 -080075 crossdev_version = GetStablePackageVersion('sys-devel/crossdev', True)
76 cls._CACHE = {'crossdev_version': crossdev_version}
David James66a09c42012-11-05 13:31:38 -080077 if os.path.exists(cls._CACHE_FILE) and not reconfig:
78 with open(cls._CACHE_FILE) as f:
79 data = json.load(f)
David James90239b92012-11-05 15:31:34 -080080 if crossdev_version == data.get('crossdev_version'):
David James66a09c42012-11-05 13:31:38 -080081 cls._CACHE = data
82
83 @classmethod
84 def Save(cls):
85 """Store crossdev cache on disk."""
86 # Save the cache from the successful run.
87 with open(cls._CACHE_FILE, 'w') as f:
88 json.dump(cls._CACHE, f)
89
90 @classmethod
91 def GetConfig(cls, target):
92 """Returns a map of crossdev provided variables about a tuple."""
93 CACHE_ATTR = '_target_tuple_map'
94
95 val = cls._CACHE.setdefault(CACHE_ATTR, {})
96 if not target in val:
97 # Find out the crossdev tuple.
98 target_tuple = target
99 if target == 'host':
David James27ac4ae2012-12-03 23:16:15 -0800100 target_tuple = toolchain.GetHostTuple()
David James66a09c42012-11-05 13:31:38 -0800101 # Catch output of crossdev.
102 out = cros_build_lib.RunCommand(['crossdev', '--show-target-cfg',
103 '--ex-gdb', target_tuple],
104 print_cmd=False, redirect_stdout=True).output.splitlines()
105 # List of tuples split at the first '=', converted into dict.
106 val[target] = dict([x.split('=', 1) for x in out])
107 return val[target]
108
109 @classmethod
110 def UpdateTargets(cls, targets, usepkg, config_only=False):
111 """Calls crossdev to initialize a cross target.
112
113 Args:
114 targets - the list of targets to initialize using crossdev
115 usepkg - copies the commandline opts
116 config_only - Just update
117 """
118 configured_targets = cls._CACHE.setdefault('configured_targets', [])
119
120 cmdbase = ['crossdev', '--show-fail-log']
121 cmdbase.extend(['--env', 'FEATURES=splitdebug'])
122 # Pick stable by default, and override as necessary.
123 cmdbase.extend(['-P', '--oneshot'])
124 if usepkg:
125 cmdbase.extend(['-P', '--getbinpkg',
126 '-P', '--usepkgonly',
127 '--without-headers'])
128
129 overlays = '%s %s' % (CHROMIUMOS_OVERLAY, STABLE_OVERLAY)
130 cmdbase.extend(['--overlays', overlays])
131 cmdbase.extend(['--ov-output', CROSSDEV_OVERLAY])
132
133 for target in targets:
134 if config_only and target in configured_targets:
135 continue
136
137 cmd = cmdbase + ['-t', target]
138
139 for pkg in GetTargetPackages(target):
140 if pkg == 'gdb':
141 # Gdb does not have selectable versions.
142 cmd.append('--ex-gdb')
143 continue
144 # The first of the desired versions is the "primary" one.
145 version = GetDesiredPackageVersions(target, pkg)[0]
146 cmd.extend(['--%s' % pkg, version])
147
148 cmd.extend(targets[target]['crossdev'].split())
149 if config_only:
150 # In this case we want to just quietly reinit
151 cmd.append('--init-target')
152 cros_build_lib.RunCommand(cmd, print_cmd=False, redirect_stdout=True)
153 else:
154 cros_build_lib.RunCommand(cmd)
155
156 configured_targets.append(target)
157
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100158
Zdenek Behan508dcce2011-12-05 15:39:32 +0100159def GetPackageMap(target):
160 """Compiles a package map for the given target from the constants.
161
162 Uses a cache in target_version_map, that is dynamically filled in as needed,
163 since here everything is static data and the structuring is for ease of
164 configurability only.
165
166 args:
167 target - the target for which to return a version map
168
169 returns a map between packages and desired versions in internal format
170 (using the PACKAGE_* constants)
171 """
172 if target in target_version_map:
173 return target_version_map[target]
174
175 # Start from copy of the global defaults.
176 result = copy.copy(DEFAULT_TARGET_VERSION_MAP)
177
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100178 for pkg in GetTargetPackages(target):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100179 # prefer any specific overrides
180 if pkg in TARGET_VERSION_MAP.get(target, {}):
181 result[pkg] = TARGET_VERSION_MAP[target][pkg]
182 else:
183 # finally, if not already set, set a sane default
184 result.setdefault(pkg, DEFAULT_VERSION)
185 target_version_map[target] = result
186 return result
187
188
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100189def GetTargetPackages(target):
190 """Returns a list of packages for a given target."""
David James66a09c42012-11-05 13:31:38 -0800191 conf = Crossdev.GetConfig(target)
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100192 # Undesired packages are denoted by empty ${pkg}_pn variable.
193 return [x for x in conf['crosspkgs'].strip("'").split() if conf[x+'_pn']]
194
195
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100196# Portage helper functions:
197def GetPortagePackage(target, package):
198 """Returns a package name for the given target."""
David James66a09c42012-11-05 13:31:38 -0800199 conf = Crossdev.GetConfig(target)
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100200 # Portage category:
Zdenek Behan508dcce2011-12-05 15:39:32 +0100201 if target == 'host':
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100202 category = conf[package + '_category']
Zdenek Behan508dcce2011-12-05 15:39:32 +0100203 else:
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100204 category = conf['category']
205 # Portage package:
206 pn = conf[package + '_pn']
207 # Final package name:
208 assert(category)
209 assert(pn)
210 return '%s/%s' % (category, pn)
211
212
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100213def IsPackageDisabled(target, package):
214 """Returns if the given package is not used for the target."""
215 return GetDesiredPackageVersions(target, package) == [PACKAGE_NONE]
216
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100217
David James66a09c42012-11-05 13:31:38 -0800218def GetInstalledPackageVersions(atom):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100219 """Extracts the list of current versions of a target, package pair.
220
221 args:
David James66a09c42012-11-05 13:31:38 -0800222 atom - the atom to operate on (e.g. sys-devel/gcc)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100223
224 returns the list of versions of the package currently installed.
225 """
Zdenek Behan508dcce2011-12-05 15:39:32 +0100226 versions = []
Mike Frysinger506e75f2012-12-17 14:21:13 -0500227 # pylint: disable=E1101
David James90239b92012-11-05 15:31:34 -0800228 for pkg in portage.db['/']['vartree'].dbapi.match(atom, use_cache=0):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100229 version = portage.versions.cpv_getversion(pkg)
230 versions.append(version)
231 return versions
232
233
David James90239b92012-11-05 15:31:34 -0800234def GetStablePackageVersion(atom, installed):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100235 """Extracts the current stable version for a given package.
236
237 args:
238 target, package - the target/package to operate on eg. i686-pc-linux-gnu,gcc
Zdenek Behan699ddd32012-04-13 07:14:08 +0200239 installed - Whether we want installed packages or ebuilds
Zdenek Behan508dcce2011-12-05 15:39:32 +0100240
241 returns a string containing the latest version.
242 """
David James90239b92012-11-05 15:31:34 -0800243 pkgtype = 'vartree' if installed else 'porttree'
Mike Frysinger506e75f2012-12-17 14:21:13 -0500244 # pylint: disable=E1101
David James90239b92012-11-05 15:31:34 -0800245 cpv = portage.best(portage.db['/'][pkgtype].dbapi.match(atom, use_cache=0))
246 return portage.versions.cpv_getversion(cpv) if cpv else None
Zdenek Behan508dcce2011-12-05 15:39:32 +0100247
248
Zdenek Behan699ddd32012-04-13 07:14:08 +0200249def VersionListToNumeric(target, package, versions, installed):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100250 """Resolves keywords in a given version list for a particular package.
251
252 Resolving means replacing PACKAGE_STABLE with the actual number.
253
254 args:
255 target, package - the target/package to operate on eg. i686-pc-linux-gnu,gcc
256 versions - list of versions to resolve
257
258 returns list of purely numeric versions equivalent to argument
259 """
260 resolved = []
David James90239b92012-11-05 15:31:34 -0800261 atom = GetPortagePackage(target, package)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100262 for version in versions:
263 if version == PACKAGE_STABLE:
David James90239b92012-11-05 15:31:34 -0800264 resolved.append(GetStablePackageVersion(atom, installed))
Zdenek Behan508dcce2011-12-05 15:39:32 +0100265 elif version != PACKAGE_NONE:
266 resolved.append(version)
267 return resolved
268
269
270def GetDesiredPackageVersions(target, package):
271 """Produces the list of desired versions for each target, package pair.
272
273 The first version in the list is implicitly treated as primary, ie.
274 the version that will be initialized by crossdev and selected.
275
276 If the version is PACKAGE_STABLE, it really means the current version which
277 is emerged by using the package atom with no particular version key.
278 Since crossdev unmasks all packages by default, this will actually
279 mean 'unstable' in most cases.
280
281 args:
282 target, package - the target/package to operate on eg. i686-pc-linux-gnu,gcc
283
284 returns a list composed of either a version string, PACKAGE_STABLE
285 """
286 packagemap = GetPackageMap(target)
287
288 versions = []
289 if package in packagemap:
290 versions.append(packagemap[package])
291
292 return versions
293
294
295def TargetIsInitialized(target):
296 """Verifies if the given list of targets has been correctly initialized.
297
298 This determines whether we have to call crossdev while emerging
299 toolchain packages or can do it using emerge. Emerge is naturally
300 preferred, because all packages can be updated in a single pass.
301
302 args:
303 targets - list of individual cross targets which are checked
304
305 returns True if target is completely initialized
306 returns False otherwise
307 """
308 # Check if packages for the given target all have a proper version.
309 try:
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100310 for package in GetTargetPackages(target):
David James66a09c42012-11-05 13:31:38 -0800311 atom = GetPortagePackage(target, package)
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100312 # Do we even want this package && is it initialized?
David James90239b92012-11-05 15:31:34 -0800313 if not IsPackageDisabled(target, package) and not (
314 GetStablePackageVersion(atom, True) and
315 GetStablePackageVersion(atom, False)):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100316 return False
317 return True
318 except cros_build_lib.RunCommandError:
319 # Fails - The target has likely never been initialized before.
320 return False
321
322
323def RemovePackageMask(target):
324 """Removes a package.mask file for the given platform.
325
326 The pre-existing package.mask files can mess with the keywords.
327
328 args:
329 target - the target for which to remove the file
330 """
331 maskfile = os.path.join('/etc/portage/package.mask', 'cross-' + target)
Brian Harringaf019fb2012-05-10 15:06:13 -0700332 osutils.SafeUnlink(maskfile)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100333
334
Zdenek Behan508dcce2011-12-05 15:39:32 +0100335# Main functions performing the actual update steps.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100336def UpdateTargets(targets, usepkg):
337 """Determines which packages need update/unmerge and defers to portage.
338
339 args:
340 targets - the list of targets to update
341 usepkg - copies the commandline option
342 """
David James90239b92012-11-05 15:31:34 -0800343 # Remove keyword files created by old versions of cros_setup_toolchains.
344 osutils.SafeUnlink('/etc/portage/package.keywords/cross-host')
Zdenek Behan508dcce2011-12-05 15:39:32 +0100345
346 # For each target, we do two things. Figure out the list of updates,
347 # and figure out the appropriate keywords/masks. Crossdev will initialize
348 # these, but they need to be regenerated on every update.
349 print 'Determining required toolchain updates...'
David James90239b92012-11-05 15:31:34 -0800350 mergemap = {}
Zdenek Behan508dcce2011-12-05 15:39:32 +0100351 for target in targets:
352 # Record the highest needed version for each target, for masking purposes.
353 RemovePackageMask(target)
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100354 for package in GetTargetPackages(target):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100355 # Portage name for the package
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100356 if IsPackageDisabled(target, package):
357 continue
358 pkg = GetPortagePackage(target, package)
David James66a09c42012-11-05 13:31:38 -0800359 current = GetInstalledPackageVersions(pkg)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100360 desired = GetDesiredPackageVersions(target, package)
Zdenek Behan699ddd32012-04-13 07:14:08 +0200361 desired_num = VersionListToNumeric(target, package, desired, False)
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100362 mergemap[pkg] = set(desired_num).difference(current)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100363
Zdenek Behan508dcce2011-12-05 15:39:32 +0100364 packages = []
365 for pkg in mergemap:
366 for ver in mergemap[pkg]:
Zdenek Behan677b6d82012-04-11 05:31:47 +0200367 if ver != PACKAGE_NONE:
David James90239b92012-11-05 15:31:34 -0800368 packages.append(pkg)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100369
370 if not packages:
371 print 'Nothing to update!'
David Jamesf8c672f2012-11-06 13:38:11 -0800372 return False
Zdenek Behan508dcce2011-12-05 15:39:32 +0100373
374 print 'Updating packages:'
375 print packages
376
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100377 cmd = [EMERGE_CMD, '--oneshot', '--update']
Zdenek Behan508dcce2011-12-05 15:39:32 +0100378 if usepkg:
379 cmd.extend(['--getbinpkg', '--usepkgonly'])
380
381 cmd.extend(packages)
382 cros_build_lib.RunCommand(cmd)
David Jamesf8c672f2012-11-06 13:38:11 -0800383 return True
Zdenek Behan508dcce2011-12-05 15:39:32 +0100384
385
386def CleanTargets(targets):
387 """Unmerges old packages that are assumed unnecessary."""
388 unmergemap = {}
389 for target in targets:
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100390 for package in GetTargetPackages(target):
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100391 if IsPackageDisabled(target, package):
392 continue
393 pkg = GetPortagePackage(target, package)
David James66a09c42012-11-05 13:31:38 -0800394 current = GetInstalledPackageVersions(pkg)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100395 desired = GetDesiredPackageVersions(target, package)
Zdenek Behan699ddd32012-04-13 07:14:08 +0200396 desired_num = VersionListToNumeric(target, package, desired, True)
397 if not set(desired_num).issubset(current):
398 print 'Some packages have been held back, skipping clean!'
399 return
Zdenek Behan508dcce2011-12-05 15:39:32 +0100400 unmergemap[pkg] = set(current).difference(desired_num)
401
402 # Cleaning doesn't care about consistency and rebuilding package.* files.
403 packages = []
404 for pkg, vers in unmergemap.iteritems():
405 packages.extend('=%s-%s' % (pkg, ver) for ver in vers if ver != '9999')
406
407 if packages:
408 print 'Cleaning packages:'
409 print packages
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100410 cmd = [EMERGE_CMD, '--unmerge']
Zdenek Behan508dcce2011-12-05 15:39:32 +0100411 cmd.extend(packages)
412 cros_build_lib.RunCommand(cmd)
413 else:
414 print 'Nothing to clean!'
415
416
417def SelectActiveToolchains(targets, suffixes):
418 """Runs gcc-config and binutils-config to select the desired.
419
420 args:
421 targets - the targets to select
422 """
423 for package in ['gcc', 'binutils']:
424 for target in targets:
425 # Pick the first version in the numbered list as the selected one.
426 desired = GetDesiredPackageVersions(target, package)
Zdenek Behan699ddd32012-04-13 07:14:08 +0200427 desired_num = VersionListToNumeric(target, package, desired, True)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100428 desired = desired_num[0]
429 # *-config does not play revisions, strip them, keep just PV.
430 desired = portage.versions.pkgsplit('%s-%s' % (package, desired))[1]
431
432 if target == 'host':
433 # *-config is the only tool treating host identically (by tuple).
David James27ac4ae2012-12-03 23:16:15 -0800434 target = toolchain.GetHostTuple()
Zdenek Behan508dcce2011-12-05 15:39:32 +0100435
436 # And finally, attach target to it.
437 desired = '%s-%s' % (target, desired)
438
439 # Target specific hacks
440 if package in suffixes:
441 if target in suffixes[package]:
442 desired += suffixes[package][target]
443
David James7ec5efc2012-11-06 09:39:49 -0800444 extra_env = {'CHOST': target}
445 cmd = ['%s-config' % package, '-c', target]
Zdenek Behan508dcce2011-12-05 15:39:32 +0100446 current = cros_build_lib.RunCommand(cmd, print_cmd=False,
David James7ec5efc2012-11-06 09:39:49 -0800447 redirect_stdout=True, extra_env=extra_env).output.splitlines()[0]
Zdenek Behan508dcce2011-12-05 15:39:32 +0100448 # Do not gcc-config when the current is live or nothing needs to be done.
449 if current != desired and current != '9999':
450 cmd = [ package + '-config', desired ]
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100451 cros_build_lib.RunCommand(cmd, print_cmd=False)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100452
453
Mike Frysinger35247af2012-11-16 18:58:06 -0500454def ExpandTargets(targets_wanted):
455 """Expand any possible toolchain aliases into full targets
456
457 This will expand 'all' and 'sdk' into the respective toolchain tuples.
458
459 Args:
460 targets_wanted: The targets specified by the user.
461 Returns:
462 Full list of tuples with pseudo targets removed.
463 """
David James27ac4ae2012-12-03 23:16:15 -0800464 alltargets = toolchain.GetAllTargets()
Mike Frysinger35247af2012-11-16 18:58:06 -0500465 targets_wanted = set(targets_wanted)
466 if targets_wanted == set(['all']):
467 targets = alltargets
468 elif targets_wanted == set(['sdk']):
469 # Filter out all the non-sdk toolchains as we don't want to mess
470 # with those in all of our builds.
David James27ac4ae2012-12-03 23:16:15 -0800471 targets = toolchain.FilterToolchains(alltargets, 'sdk', True)
Mike Frysinger35247af2012-11-16 18:58:06 -0500472 else:
473 # Verify user input.
474 nonexistent = targets_wanted.difference(alltargets)
475 if nonexistent:
476 raise ValueError('Invalid targets: %s', ','.join(nonexistent))
477 targets = dict((t, alltargets[t]) for t in targets_wanted)
478 return targets
479
480
David Jamesf8c672f2012-11-06 13:38:11 -0800481def UpdateToolchains(usepkg, deleteold, hostonly, reconfig,
482 targets_wanted, boards_wanted):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100483 """Performs all steps to create a synchronized toolchain enviroment.
484
485 args:
486 arguments correspond to the given commandline flags
487 """
David Jamesf8c672f2012-11-06 13:38:11 -0800488 targets, crossdev_targets, reconfig_targets = {}, {}, {}
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100489 if not hostonly:
490 # For hostonly, we can skip most of the below logic, much of which won't
491 # work on bare systems where this is useful.
Mike Frysinger35247af2012-11-16 18:58:06 -0500492 targets = ExpandTargets(targets_wanted)
Mike Frysinger7ccee992012-06-01 21:27:59 -0400493
Mike Frysinger7ccee992012-06-01 21:27:59 -0400494 # Now re-add any targets that might be from this board. This is
495 # to allow unofficial boards to declare their own toolchains.
496 for board in boards_wanted:
David James27ac4ae2012-12-03 23:16:15 -0800497 targets.update(toolchain.GetToolchainsForBoard(board))
Zdenek Behan508dcce2011-12-05 15:39:32 +0100498
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100499 # First check and initialize all cross targets that need to be.
Mike Frysinger7ccee992012-06-01 21:27:59 -0400500 for target in targets:
501 if TargetIsInitialized(target):
502 reconfig_targets[target] = targets[target]
503 else:
504 crossdev_targets[target] = targets[target]
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100505 if crossdev_targets:
Zdenek Behan8be29ba2012-05-29 23:10:34 +0200506 print 'The following targets need to be re-initialized:'
507 print crossdev_targets
David James66a09c42012-11-05 13:31:38 -0800508 Crossdev.UpdateTargets(crossdev_targets, usepkg)
Zdenek Behan8be29ba2012-05-29 23:10:34 +0200509 # Those that were not initialized may need a config update.
David James66a09c42012-11-05 13:31:38 -0800510 Crossdev.UpdateTargets(reconfig_targets, usepkg, config_only=True)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100511
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100512 # We want host updated.
Mike Frysinger7ccee992012-06-01 21:27:59 -0400513 targets['host'] = {}
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100514
515 # Now update all packages.
David Jamesf8c672f2012-11-06 13:38:11 -0800516 if UpdateTargets(targets, usepkg) or crossdev_targets or reconfig:
517 SelectActiveToolchains(targets, CONFIG_TARGET_SUFFIXES)
David James7ec5efc2012-11-06 09:39:49 -0800518
519 if deleteold:
520 CleanTargets(targets)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100521
522
Mike Frysinger35247af2012-11-16 18:58:06 -0500523def ShowBoardConfig(board):
524 """Show the toolchain tuples used by |board|
525
526 Args:
527 board: The board to query.
528 """
David James27ac4ae2012-12-03 23:16:15 -0800529 toolchains = toolchain.GetToolchainsForBoard(board)
Mike Frysinger35247af2012-11-16 18:58:06 -0500530 # Make sure we display the default toolchain first.
David James27ac4ae2012-12-03 23:16:15 -0800531 print ','.join(
532 toolchain.FilterToolchains(toolchains, 'default', True).keys() +
533 toolchain.FilterToolchains(toolchains, 'default', False).keys())
Mike Frysinger35247af2012-11-16 18:58:06 -0500534
535
536def GenerateLdsoWrapper(root, path, interp, libpaths=()):
537 """Generate a shell script wrapper which uses local ldso to run the ELF
538
539 Since we cannot rely on the host glibc (or other libraries), we need to
540 execute the local packaged ldso directly and tell it where to find our
541 copies of libraries.
542
543 Args:
544 root: The root tree to generate scripts inside of
545 path: The full path (inside |root|) to the program to wrap
546 interp: The ldso interpreter that we need to execute
547 libpaths: Extra lib paths to search for libraries
548 """
549 basedir = os.path.dirname(path)
550 libpaths = ['/lib'] + list(libpaths)
551 replacements = {
552 'interp': os.path.join(os.path.relpath('/lib', basedir),
553 os.path.basename(interp)),
554 'libpaths': ':'.join(['${basedir}/' + os.path.relpath(p, basedir)
555 for p in libpaths]),
556 }
557 wrapper = """#!/bin/sh
558base=$(realpath "$0")
559basedir=${base%%/*}
560exec \
561 "${basedir}/%(interp)s" \
562 --library-path "%(libpaths)s" \
563 --inhibit-rpath '' \
564 "${base}.elf" \
565 "$@"
566""" % replacements
567 wrappath = root + path
568 os.rename(wrappath, wrappath + '.elf')
569 osutils.WriteFile(wrappath, wrapper)
570 os.chmod(wrappath, 0755)
571
572
573def GeneratePathWrapper(root, wrappath, path):
574 """Generate a shell script to execute another shell script
575
576 Since we can't symlink a wrapped ELF (see GenerateLdsoWrapper) because the
577 argv[0] won't be pointing to the correct path, generate a shell script that
578 just executes another program with its full path.
579
580 Args:
581 root: The root tree to generate scripts inside of
582 wrappath: The full path (inside |root|) to create the wrapper
583 path: The target program which this wrapper will execute
584 """
585 replacements = {
586 'path': path,
587 'relroot': os.path.relpath('/', os.path.dirname(wrappath)),
588 }
589 wrapper = """#!/bin/sh
590base=$(realpath "$0")
591basedir=${base%%/*}
592exec "${basedir}/%(relroot)s%(path)s" "$@"
593""" % replacements
594 root_wrapper = root + wrappath
595 if os.path.islink(root_wrapper):
596 os.unlink(root_wrapper)
597 else:
598 osutils.SafeMakedirs(os.path.dirname(root_wrapper))
599 osutils.WriteFile(root_wrapper, wrapper)
600 os.chmod(root_wrapper, 0755)
601
602
603def FileIsCrosSdkElf(elf):
604 """Determine if |elf| is an ELF that we execute in the cros_sdk
605
606 We don't need this to be perfect, just quick. It makes sure the ELF
607 is a 64bit LSB x86_64 ELF. That is the native type of cros_sdk.
608
609 Args:
610 elf: The file to check
611 Returns:
612 True if we think |elf| is a native ELF
613 """
614 with open(elf) as f:
615 data = f.read(20)
616 # Check the magic number, EI_CLASS, EI_DATA, and e_machine.
617 return (data[0:4] == '\x7fELF' and
618 data[4] == '\x02' and
619 data[5] == '\x01' and
620 data[18] == '\x3e')
621
622
623def IsPathPackagable(ptype, path):
624 """Should the specified file be included in a toolchain package?
625
626 We only need to handle files as we'll create dirs as we need them.
627
628 Further, trim files that won't be useful:
629 - non-english translations (.mo) since it'd require env vars
630 - debug files since these are for the host compiler itself
631 - info/man pages as they're big, and docs are online, and the
632 native docs should work fine for the most part (`man gcc`)
633
634 Args:
635 ptype: A string describing the path type (i.e. 'file' or 'dir' or 'sym')
636 path: The full path to inspect
637 Returns:
638 True if we want to include this path in the package
639 """
640 return not (ptype in ('dir',) or
641 path.startswith('/usr/lib/debug/') or
642 os.path.splitext(path)[1] == '.mo' or
643 ('/man/' in path or '/info/' in path))
644
645
646def ReadlinkRoot(path, root):
647 """Like os.readlink(), but relative to a |root|
648
649 Args:
650 path: The symlink to read
651 root: The path to use for resolving absolute symlinks
652 Returns:
653 A fully resolved symlink path
654 """
655 while os.path.islink(root + path):
656 path = os.path.join(os.path.dirname(path), os.readlink(root + path))
657 return path
658
659
660def _GetFilesForTarget(target, root='/'):
661 """Locate all the files to package for |target|
662
663 This does not cover ELF dependencies.
664
665 Args:
666 target: The toolchain target name
667 root: The root path to pull all packages from
668 Returns:
669 A tuple of a set of all packable paths, and a set of all paths which
670 are also native ELFs
671 """
672 paths = set()
673 elfs = set()
674
675 # Find all the files owned by the packages for this target.
676 for pkg in GetTargetPackages(target):
677 # Ignore packages that are part of the target sysroot.
678 if pkg in ('kernel', 'libc'):
679 continue
680
681 atom = GetPortagePackage(target, pkg)
682 cat, pn = atom.split('/')
683 ver = GetInstalledPackageVersions(atom)[0]
684 cros_build_lib.Info('packaging %s-%s', atom, ver)
685
686 # pylint: disable=E1101
687 dblink = portage.dblink(cat, pn + '-' + ver, myroot=root,
688 settings=portage.settings)
689 contents = dblink.getcontents()
690 for obj in contents:
691 ptype = contents[obj][0]
692 if not IsPathPackagable(ptype, obj):
693 continue
694
695 if ptype == 'obj':
696 # For native ELFs, we need to pull in their dependencies too.
697 if FileIsCrosSdkElf(obj):
698 elfs.add(obj)
699 paths.add(obj)
700
701 return paths, elfs
702
703
704def _BuildInitialPackageRoot(output_dir, paths, elfs, ldpaths,
705 path_rewrite_func=lambda x:x, root='/'):
706 """Link in all packable files and their runtime dependencies
707
708 This also wraps up executable ELFs with helper scripts.
709
710 Args:
711 output_dir: The output directory to store files
712 paths: All the files to include
713 elfs: All the files which are ELFs (a subset of |paths|)
714 ldpaths: A dict of static ldpath information
715 path_rewrite_func: User callback to rewrite paths in output_dir
716 root: The root path to pull all packages/files from
717 """
718 # Link in all the files.
719 sym_paths = []
720 for path in paths:
721 new_path = path_rewrite_func(path)
722 dst = output_dir + new_path
723 osutils.SafeMakedirs(os.path.dirname(dst))
724
725 # Is this a symlink which we have to rewrite or wrap?
726 # Delay wrap check until after we have created all paths.
727 src = root + path
728 if os.path.islink(src):
729 tgt = os.readlink(src)
730 if os.path.sep in tgt:
731 sym_paths.append((new_path, lddtree.normpath(ReadlinkRoot(src, root))))
732
733 # Rewrite absolute links to relative and then generate the symlink
734 # ourselves. All other symlinks can be hardlinked below.
735 if tgt[0] == '/':
736 tgt = os.path.relpath(tgt, os.path.dirname(new_path))
737 os.symlink(tgt, dst)
738 continue
739
740 os.link(src, dst)
741
742 # Now see if any of the symlinks need to be wrapped.
743 for sym, tgt in sym_paths:
744 if tgt in elfs:
745 GeneratePathWrapper(output_dir, sym, tgt)
746
747 # Locate all the dependencies for all the ELFs. Stick them all in the
748 # top level "lib" dir to make the wrapper simpler. This exact path does
749 # not matter since we execute ldso directly, and we tell the ldso the
750 # exact path to search for its libraries.
751 libdir = os.path.join(output_dir, 'lib')
752 osutils.SafeMakedirs(libdir)
753 donelibs = set()
754 for elf in elfs:
755 e = lddtree.ParseELF(elf, root, ldpaths)
756 interp = e['interp']
757 if interp:
758 # Generate a wrapper if it is executable.
759 GenerateLdsoWrapper(output_dir, path_rewrite_func(elf), interp,
760 libpaths=e['rpath'] + e['runpath'])
761
762 for lib, lib_data in e['libs'].iteritems():
763 if lib in donelibs:
764 continue
765
766 src = path = lib_data['path']
767 if path is None:
768 cros_build_lib.Warning('%s: could not locate %s', elf, lib)
769 continue
770 donelibs.add(lib)
771
772 # Needed libs are the SONAME, but that is usually a symlink, not a
773 # real file. So link in the target rather than the symlink itself.
774 # We have to walk all the possible symlinks (SONAME could point to a
775 # symlink which points to a symlink), and we have to handle absolute
776 # ourselves (since we have a "root" argument).
777 dst = os.path.join(libdir, os.path.basename(path))
778 src = ReadlinkRoot(src, root)
779
780 os.link(root + src, dst)
781
782
783def _EnvdGetVar(envd, var):
784 """Given a Gentoo env.d file, extract a var from it
785
786 Args:
787 envd: The env.d file to load (may be a glob path)
788 var: The var to extract
789 Returns:
790 The value of |var|
791 """
792 envds = glob.glob(envd)
793 assert len(envds) == 1, '%s: should have exactly 1 env.d file' % envd
794 envd = envds[0]
795 return cros_build_lib.LoadKeyValueFile(envd)[var]
796
797
798def _ProcessBinutilsConfig(target, output_dir):
799 """Do what binutils-config would have done"""
800 binpath = os.path.join('/bin', target + '-')
David James27ac4ae2012-12-03 23:16:15 -0800801 globpath = os.path.join(output_dir, 'usr', toolchain.GetHostTuple(), target,
Mike Frysinger35247af2012-11-16 18:58:06 -0500802 'binutils-bin', '*-gold')
803 srcpath = glob.glob(globpath)
804 assert len(srcpath) == 1, '%s: did not match 1 path' % globpath
805 srcpath = srcpath[0][len(output_dir):]
806 gccpath = os.path.join('/usr', 'libexec', 'gcc')
807 for prog in os.listdir(output_dir + srcpath):
808 # Skip binaries already wrapped.
809 if not prog.endswith('.real'):
810 GeneratePathWrapper(output_dir, binpath + prog,
811 os.path.join(srcpath, prog))
812 GeneratePathWrapper(output_dir, os.path.join(gccpath, prog),
813 os.path.join(srcpath, prog))
814
David James27ac4ae2012-12-03 23:16:15 -0800815 libpath = os.path.join('/usr', toolchain.GetHostTuple(), target, 'lib')
Mike Frysinger35247af2012-11-16 18:58:06 -0500816 envd = os.path.join(output_dir, 'etc', 'env.d', 'binutils', '*-gold')
817 srcpath = _EnvdGetVar(envd, 'LIBPATH')
818 os.symlink(os.path.relpath(srcpath, os.path.dirname(libpath)),
819 output_dir + libpath)
820
821
822def _ProcessGccConfig(target, output_dir):
823 """Do what gcc-config would have done"""
824 binpath = '/bin'
825 envd = os.path.join(output_dir, 'etc', 'env.d', 'gcc', '*')
826 srcpath = _EnvdGetVar(envd, 'GCC_PATH')
827 for prog in os.listdir(output_dir + srcpath):
828 # Skip binaries already wrapped.
829 if (not prog.endswith('.real') and
830 not prog.endswith('.elf') and
831 prog.startswith(target)):
832 GeneratePathWrapper(output_dir, os.path.join(binpath, prog),
833 os.path.join(srcpath, prog))
834 return srcpath
835
836
837def _ProcessSysrootWrapper(_target, output_dir, srcpath):
838 """Remove chroot-specific things from our sysroot wrapper"""
839 # Disable ccache since we know it won't work outside of chroot.
840 sysroot_wrapper = glob.glob(os.path.join(
841 output_dir + srcpath, 'sysroot_wrapper*'))[0]
842 contents = osutils.ReadFile(sysroot_wrapper).splitlines()
843 for num in xrange(len(contents)):
844 if '@CCACHE_DEFAULT@' in contents[num]:
845 contents[num] = 'use_ccache = False'
846 break
847 # Can't update the wrapper in place since it's a hardlink to a file in /.
848 os.unlink(sysroot_wrapper)
849 osutils.WriteFile(sysroot_wrapper, '\n'.join(contents))
850 os.chmod(sysroot_wrapper, 0755)
851
852
853def _ProcessDistroCleanups(target, output_dir):
854 """Clean up the tree and remove all distro-specific requirements
855
856 Args:
857 target: The toolchain target name
858 output_dir: The output directory to clean up
859 """
860 _ProcessBinutilsConfig(target, output_dir)
861 gcc_path = _ProcessGccConfig(target, output_dir)
862 _ProcessSysrootWrapper(target, output_dir, gcc_path)
863
864 osutils.RmDir(os.path.join(output_dir, 'etc'))
865
866
867def CreatePackagableRoot(target, output_dir, ldpaths, root='/'):
868 """Setup a tree from the packages for the specified target
869
870 This populates a path with all the files from toolchain packages so that
871 a tarball can easily be generated from the result.
872
873 Args:
874 target: The target to create a packagable root from
875 output_dir: The output directory to place all the files
876 ldpaths: A dict of static ldpath information
877 root: The root path to pull all packages/files from
878 """
879 # Find all the files owned by the packages for this target.
880 paths, elfs = _GetFilesForTarget(target, root=root)
881
882 # Link in all the package's files, any ELF dependencies, and wrap any
883 # executable ELFs with helper scripts.
884 def MoveUsrBinToBin(path):
885 """Move /usr/bin to /bin so people can just use that toplevel dir"""
886 return path[4:] if path.startswith('/usr/bin/') else path
887 _BuildInitialPackageRoot(output_dir, paths, elfs, ldpaths,
888 path_rewrite_func=MoveUsrBinToBin, root=root)
889
890 # The packages, when part of the normal distro, have helper scripts
891 # that setup paths and such. Since we are making this standalone, we
892 # need to preprocess all that ourselves.
893 _ProcessDistroCleanups(target, output_dir)
894
895
896def CreatePackages(targets_wanted, output_dir, root='/'):
897 """Create redistributable cross-compiler packages for the specified targets
898
899 This creates toolchain packages that should be usable in conjunction with
900 a downloaded sysroot (created elsewhere).
901
902 Tarballs (one per target) will be created in $PWD.
903
904 Args:
905 targets_wanted: The targets to package up
906 root: The root path to pull all packages/files from
907 """
908 osutils.SafeMakedirs(output_dir)
909 ldpaths = lddtree.LoadLdpaths(root)
910 targets = ExpandTargets(targets_wanted)
911
912 with osutils.TempDirContextManager() as tempdir:
913 # We have to split the root generation from the compression stages. This is
914 # because we hardlink in all the files (to avoid overhead of reading/writing
915 # the copies multiple times). But tar gets angry if a file's hardlink count
916 # changes from when it starts reading a file to when it finishes.
917 with parallel.BackgroundTaskRunner(CreatePackagableRoot) as queue:
918 for target in targets:
919 output_target_dir = os.path.join(tempdir, target)
920 queue.put([target, output_target_dir, ldpaths, root])
921
922 # Build the tarball.
923 with parallel.BackgroundTaskRunner(cros_build_lib.CreateTarball) as queue:
924 for target in targets:
925 tar_file = os.path.join(output_dir, target + '.tar.xz')
926 queue.put([tar_file, os.path.join(tempdir, target)])
927
928
Brian Harring30675052012-02-29 12:18:22 -0800929def main(argv):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100930 usage = """usage: %prog [options]
931
Mike Frysinger506e75f2012-12-17 14:21:13 -0500932 The script installs and updates the toolchains in your chroot."""
933 parser = commandline.OptionParser(usage)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100934 parser.add_option('-u', '--nousepkg',
935 action='store_false', dest='usepkg', default=True,
Mike Frysinger506e75f2012-12-17 14:21:13 -0500936 help='Use prebuilt packages if possible')
Zdenek Behan508dcce2011-12-05 15:39:32 +0100937 parser.add_option('-d', '--deleteold',
938 action='store_true', dest='deleteold', default=False,
Mike Frysinger506e75f2012-12-17 14:21:13 -0500939 help='Unmerge deprecated packages')
Zdenek Behan508dcce2011-12-05 15:39:32 +0100940 parser.add_option('-t', '--targets',
Mike Frysingereaebb582012-06-19 13:04:53 -0400941 dest='targets', default='sdk',
Mike Frysinger506e75f2012-12-17 14:21:13 -0500942 help='Comma separated list of tuples. '
943 'Special keyword \'host\' is allowed. Default: sdk')
Mike Frysinger7ccee992012-06-01 21:27:59 -0400944 parser.add_option('--include-boards',
945 dest='include_boards', default='',
Mike Frysinger506e75f2012-12-17 14:21:13 -0500946 help='Comma separated list of boards whose toolchains we'
947 ' will always include. Default: none')
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100948 parser.add_option('--hostonly',
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100949 dest='hostonly', default=False, action='store_true',
Mike Frysinger506e75f2012-12-17 14:21:13 -0500950 help='Only setup the host toolchain. '
951 'Useful for bootstrapping chroot')
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100952 parser.add_option('--show-board-cfg',
953 dest='board_cfg', default=None,
Mike Frysinger506e75f2012-12-17 14:21:13 -0500954 help='Board to list toolchain tuples for')
Mike Frysinger35247af2012-11-16 18:58:06 -0500955 parser.add_option('--create-packages',
956 action='store_true', default=False,
957 help='Build redistributable packages')
958 parser.add_option('--output-dir', default=os.getcwd(), type='path',
959 help='Output directory')
David James66a09c42012-11-05 13:31:38 -0800960 parser.add_option('--reconfig', default=False, action='store_true',
Mike Frysinger506e75f2012-12-17 14:21:13 -0500961 help='Reload crossdev config and reselect toolchains')
Zdenek Behan508dcce2011-12-05 15:39:32 +0100962
Mike Frysinger35247af2012-11-16 18:58:06 -0500963 (options, remaining_arguments) = parser.parse_args(argv)
964 if len(remaining_arguments):
965 parser.error('script does not take arguments: %s' % remaining_arguments)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100966
Mike Frysinger35247af2012-11-16 18:58:06 -0500967 # Figure out what we're supposed to do and reject conflicting options.
968 if options.board_cfg and options.create_packages:
969 parser.error('conflicting options: create-packages & show-board-cfg')
Mike Frysinger984d0622012-06-01 16:08:44 -0400970
Zdenek Behan508dcce2011-12-05 15:39:32 +0100971 targets = set(options.targets.split(','))
Mike Frysinger7ccee992012-06-01 21:27:59 -0400972 boards = set(options.include_boards.split(',')) if options.include_boards \
973 else set()
Mike Frysinger35247af2012-11-16 18:58:06 -0500974
975 if options.board_cfg:
976 ShowBoardConfig(options.board_cfg)
977 elif options.create_packages:
978 cros_build_lib.AssertInsideChroot()
979 Crossdev.Load(False)
980 CreatePackages(targets, options.output_dir)
981 else:
982 cros_build_lib.AssertInsideChroot()
983 # This has to be always run as root.
984 if os.geteuid() != 0:
985 cros_build_lib.Die('this script must be run as root')
986
987 Crossdev.Load(options.reconfig)
988 UpdateToolchains(options.usepkg, options.deleteold, options.hostonly,
989 options.reconfig, targets, boards)
990 Crossdev.Save()
991
992 return 0