blob: 04fde1bd8ec743cd522422c60a1828582dac74d1 [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' : {
Zdenek Behan508dcce2011-12-05 15:39:32 +010049 'gdb' : PACKAGE_NONE,
50 },
51}
52# Overrides for {gcc,binutils}-config, pick a package with particular suffix.
53CONFIG_TARGET_SUFFIXES = {
54 'binutils' : {
55 'i686-pc-linux-gnu' : '-gold',
56 'x86_64-cros-linux-gnu' : '-gold',
57 },
58}
Zdenek Behan508dcce2011-12-05 15:39:32 +010059# Global per-run cache that will be filled ondemand in by GetPackageMap()
60# function as needed.
61target_version_map = {
62}
63
64
David James66a09c42012-11-05 13:31:38 -080065class Crossdev(object):
66 """Class for interacting with crossdev and caching its output."""
67
68 _CACHE_FILE = os.path.join(CROSSDEV_OVERLAY, '.configured.json')
69 _CACHE = {}
70
71 @classmethod
72 def Load(cls, reconfig):
73 """Load crossdev cache from disk."""
David James90239b92012-11-05 15:31:34 -080074 crossdev_version = GetStablePackageVersion('sys-devel/crossdev', True)
75 cls._CACHE = {'crossdev_version': crossdev_version}
David James66a09c42012-11-05 13:31:38 -080076 if os.path.exists(cls._CACHE_FILE) and not reconfig:
77 with open(cls._CACHE_FILE) as f:
78 data = json.load(f)
David James90239b92012-11-05 15:31:34 -080079 if crossdev_version == data.get('crossdev_version'):
David James66a09c42012-11-05 13:31:38 -080080 cls._CACHE = data
81
82 @classmethod
83 def Save(cls):
84 """Store crossdev cache on disk."""
85 # Save the cache from the successful run.
86 with open(cls._CACHE_FILE, 'w') as f:
87 json.dump(cls._CACHE, f)
88
89 @classmethod
90 def GetConfig(cls, target):
91 """Returns a map of crossdev provided variables about a tuple."""
92 CACHE_ATTR = '_target_tuple_map'
93
94 val = cls._CACHE.setdefault(CACHE_ATTR, {})
95 if not target in val:
96 # Find out the crossdev tuple.
97 target_tuple = target
98 if target == 'host':
David James27ac4ae2012-12-03 23:16:15 -080099 target_tuple = toolchain.GetHostTuple()
David James66a09c42012-11-05 13:31:38 -0800100 # Catch output of crossdev.
101 out = cros_build_lib.RunCommand(['crossdev', '--show-target-cfg',
102 '--ex-gdb', target_tuple],
103 print_cmd=False, redirect_stdout=True).output.splitlines()
104 # List of tuples split at the first '=', converted into dict.
105 val[target] = dict([x.split('=', 1) for x in out])
106 return val[target]
107
108 @classmethod
109 def UpdateTargets(cls, targets, usepkg, config_only=False):
110 """Calls crossdev to initialize a cross target.
111
112 Args:
113 targets - the list of targets to initialize using crossdev
114 usepkg - copies the commandline opts
115 config_only - Just update
116 """
117 configured_targets = cls._CACHE.setdefault('configured_targets', [])
118
119 cmdbase = ['crossdev', '--show-fail-log']
120 cmdbase.extend(['--env', 'FEATURES=splitdebug'])
121 # Pick stable by default, and override as necessary.
122 cmdbase.extend(['-P', '--oneshot'])
123 if usepkg:
124 cmdbase.extend(['-P', '--getbinpkg',
125 '-P', '--usepkgonly',
126 '--without-headers'])
127
128 overlays = '%s %s' % (CHROMIUMOS_OVERLAY, STABLE_OVERLAY)
129 cmdbase.extend(['--overlays', overlays])
130 cmdbase.extend(['--ov-output', CROSSDEV_OVERLAY])
131
132 for target in targets:
133 if config_only and target in configured_targets:
134 continue
135
136 cmd = cmdbase + ['-t', target]
137
138 for pkg in GetTargetPackages(target):
139 if pkg == 'gdb':
140 # Gdb does not have selectable versions.
141 cmd.append('--ex-gdb')
142 continue
143 # The first of the desired versions is the "primary" one.
144 version = GetDesiredPackageVersions(target, pkg)[0]
145 cmd.extend(['--%s' % pkg, version])
146
147 cmd.extend(targets[target]['crossdev'].split())
148 if config_only:
149 # In this case we want to just quietly reinit
150 cmd.append('--init-target')
151 cros_build_lib.RunCommand(cmd, print_cmd=False, redirect_stdout=True)
152 else:
153 cros_build_lib.RunCommand(cmd)
154
155 configured_targets.append(target)
156
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100157
Zdenek Behan508dcce2011-12-05 15:39:32 +0100158def GetPackageMap(target):
159 """Compiles a package map for the given target from the constants.
160
161 Uses a cache in target_version_map, that is dynamically filled in as needed,
162 since here everything is static data and the structuring is for ease of
163 configurability only.
164
165 args:
166 target - the target for which to return a version map
167
168 returns a map between packages and desired versions in internal format
169 (using the PACKAGE_* constants)
170 """
171 if target in target_version_map:
172 return target_version_map[target]
173
174 # Start from copy of the global defaults.
175 result = copy.copy(DEFAULT_TARGET_VERSION_MAP)
176
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100177 for pkg in GetTargetPackages(target):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100178 # prefer any specific overrides
179 if pkg in TARGET_VERSION_MAP.get(target, {}):
180 result[pkg] = TARGET_VERSION_MAP[target][pkg]
181 else:
182 # finally, if not already set, set a sane default
183 result.setdefault(pkg, DEFAULT_VERSION)
184 target_version_map[target] = result
185 return result
186
187
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100188def GetTargetPackages(target):
189 """Returns a list of packages for a given target."""
David James66a09c42012-11-05 13:31:38 -0800190 conf = Crossdev.GetConfig(target)
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100191 # Undesired packages are denoted by empty ${pkg}_pn variable.
192 return [x for x in conf['crosspkgs'].strip("'").split() if conf[x+'_pn']]
193
194
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100195# Portage helper functions:
196def GetPortagePackage(target, package):
197 """Returns a package name for the given target."""
David James66a09c42012-11-05 13:31:38 -0800198 conf = Crossdev.GetConfig(target)
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100199 # Portage category:
Zdenek Behan508dcce2011-12-05 15:39:32 +0100200 if target == 'host':
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100201 category = conf[package + '_category']
Zdenek Behan508dcce2011-12-05 15:39:32 +0100202 else:
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100203 category = conf['category']
204 # Portage package:
205 pn = conf[package + '_pn']
206 # Final package name:
207 assert(category)
208 assert(pn)
209 return '%s/%s' % (category, pn)
210
211
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100212def IsPackageDisabled(target, package):
213 """Returns if the given package is not used for the target."""
214 return GetDesiredPackageVersions(target, package) == [PACKAGE_NONE]
215
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100216
David James66a09c42012-11-05 13:31:38 -0800217def GetInstalledPackageVersions(atom):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100218 """Extracts the list of current versions of a target, package pair.
219
220 args:
David James66a09c42012-11-05 13:31:38 -0800221 atom - the atom to operate on (e.g. sys-devel/gcc)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100222
223 returns the list of versions of the package currently installed.
224 """
Zdenek Behan508dcce2011-12-05 15:39:32 +0100225 versions = []
Mike Frysinger506e75f2012-12-17 14:21:13 -0500226 # pylint: disable=E1101
David James90239b92012-11-05 15:31:34 -0800227 for pkg in portage.db['/']['vartree'].dbapi.match(atom, use_cache=0):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100228 version = portage.versions.cpv_getversion(pkg)
229 versions.append(version)
230 return versions
231
232
David James90239b92012-11-05 15:31:34 -0800233def GetStablePackageVersion(atom, installed):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100234 """Extracts the current stable version for a given package.
235
236 args:
237 target, package - the target/package to operate on eg. i686-pc-linux-gnu,gcc
Zdenek Behan699ddd32012-04-13 07:14:08 +0200238 installed - Whether we want installed packages or ebuilds
Zdenek Behan508dcce2011-12-05 15:39:32 +0100239
240 returns a string containing the latest version.
241 """
David James90239b92012-11-05 15:31:34 -0800242 pkgtype = 'vartree' if installed else 'porttree'
Mike Frysinger506e75f2012-12-17 14:21:13 -0500243 # pylint: disable=E1101
David James90239b92012-11-05 15:31:34 -0800244 cpv = portage.best(portage.db['/'][pkgtype].dbapi.match(atom, use_cache=0))
245 return portage.versions.cpv_getversion(cpv) if cpv else None
Zdenek Behan508dcce2011-12-05 15:39:32 +0100246
247
Zdenek Behan699ddd32012-04-13 07:14:08 +0200248def VersionListToNumeric(target, package, versions, installed):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100249 """Resolves keywords in a given version list for a particular package.
250
251 Resolving means replacing PACKAGE_STABLE with the actual number.
252
253 args:
254 target, package - the target/package to operate on eg. i686-pc-linux-gnu,gcc
255 versions - list of versions to resolve
256
257 returns list of purely numeric versions equivalent to argument
258 """
259 resolved = []
David James90239b92012-11-05 15:31:34 -0800260 atom = GetPortagePackage(target, package)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100261 for version in versions:
262 if version == PACKAGE_STABLE:
David James90239b92012-11-05 15:31:34 -0800263 resolved.append(GetStablePackageVersion(atom, installed))
Zdenek Behan508dcce2011-12-05 15:39:32 +0100264 elif version != PACKAGE_NONE:
265 resolved.append(version)
266 return resolved
267
268
269def GetDesiredPackageVersions(target, package):
270 """Produces the list of desired versions for each target, package pair.
271
272 The first version in the list is implicitly treated as primary, ie.
273 the version that will be initialized by crossdev and selected.
274
275 If the version is PACKAGE_STABLE, it really means the current version which
276 is emerged by using the package atom with no particular version key.
277 Since crossdev unmasks all packages by default, this will actually
278 mean 'unstable' in most cases.
279
280 args:
281 target, package - the target/package to operate on eg. i686-pc-linux-gnu,gcc
282
283 returns a list composed of either a version string, PACKAGE_STABLE
284 """
285 packagemap = GetPackageMap(target)
286
287 versions = []
288 if package in packagemap:
289 versions.append(packagemap[package])
290
291 return versions
292
293
294def TargetIsInitialized(target):
295 """Verifies if the given list of targets has been correctly initialized.
296
297 This determines whether we have to call crossdev while emerging
298 toolchain packages or can do it using emerge. Emerge is naturally
299 preferred, because all packages can be updated in a single pass.
300
301 args:
302 targets - list of individual cross targets which are checked
303
304 returns True if target is completely initialized
305 returns False otherwise
306 """
307 # Check if packages for the given target all have a proper version.
308 try:
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100309 for package in GetTargetPackages(target):
David James66a09c42012-11-05 13:31:38 -0800310 atom = GetPortagePackage(target, package)
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100311 # Do we even want this package && is it initialized?
David James90239b92012-11-05 15:31:34 -0800312 if not IsPackageDisabled(target, package) and not (
313 GetStablePackageVersion(atom, True) and
314 GetStablePackageVersion(atom, False)):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100315 return False
316 return True
317 except cros_build_lib.RunCommandError:
318 # Fails - The target has likely never been initialized before.
319 return False
320
321
322def RemovePackageMask(target):
323 """Removes a package.mask file for the given platform.
324
325 The pre-existing package.mask files can mess with the keywords.
326
327 args:
328 target - the target for which to remove the file
329 """
330 maskfile = os.path.join('/etc/portage/package.mask', 'cross-' + target)
Brian Harringaf019fb2012-05-10 15:06:13 -0700331 osutils.SafeUnlink(maskfile)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100332
333
Zdenek Behan508dcce2011-12-05 15:39:32 +0100334# Main functions performing the actual update steps.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100335def UpdateTargets(targets, usepkg):
336 """Determines which packages need update/unmerge and defers to portage.
337
338 args:
339 targets - the list of targets to update
340 usepkg - copies the commandline option
341 """
David James90239b92012-11-05 15:31:34 -0800342 # Remove keyword files created by old versions of cros_setup_toolchains.
343 osutils.SafeUnlink('/etc/portage/package.keywords/cross-host')
Zdenek Behan508dcce2011-12-05 15:39:32 +0100344
345 # For each target, we do two things. Figure out the list of updates,
346 # and figure out the appropriate keywords/masks. Crossdev will initialize
347 # these, but they need to be regenerated on every update.
348 print 'Determining required toolchain updates...'
David James90239b92012-11-05 15:31:34 -0800349 mergemap = {}
Zdenek Behan508dcce2011-12-05 15:39:32 +0100350 for target in targets:
351 # Record the highest needed version for each target, for masking purposes.
352 RemovePackageMask(target)
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100353 for package in GetTargetPackages(target):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100354 # Portage name for the package
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100355 if IsPackageDisabled(target, package):
356 continue
357 pkg = GetPortagePackage(target, package)
David James66a09c42012-11-05 13:31:38 -0800358 current = GetInstalledPackageVersions(pkg)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100359 desired = GetDesiredPackageVersions(target, package)
Zdenek Behan699ddd32012-04-13 07:14:08 +0200360 desired_num = VersionListToNumeric(target, package, desired, False)
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100361 mergemap[pkg] = set(desired_num).difference(current)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100362
Zdenek Behan508dcce2011-12-05 15:39:32 +0100363 packages = []
364 for pkg in mergemap:
365 for ver in mergemap[pkg]:
Zdenek Behan677b6d82012-04-11 05:31:47 +0200366 if ver != PACKAGE_NONE:
David James90239b92012-11-05 15:31:34 -0800367 packages.append(pkg)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100368
369 if not packages:
370 print 'Nothing to update!'
David Jamesf8c672f2012-11-06 13:38:11 -0800371 return False
Zdenek Behan508dcce2011-12-05 15:39:32 +0100372
373 print 'Updating packages:'
374 print packages
375
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100376 cmd = [EMERGE_CMD, '--oneshot', '--update']
Zdenek Behan508dcce2011-12-05 15:39:32 +0100377 if usepkg:
378 cmd.extend(['--getbinpkg', '--usepkgonly'])
379
380 cmd.extend(packages)
381 cros_build_lib.RunCommand(cmd)
David Jamesf8c672f2012-11-06 13:38:11 -0800382 return True
Zdenek Behan508dcce2011-12-05 15:39:32 +0100383
384
385def CleanTargets(targets):
386 """Unmerges old packages that are assumed unnecessary."""
387 unmergemap = {}
388 for target in targets:
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100389 for package in GetTargetPackages(target):
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100390 if IsPackageDisabled(target, package):
391 continue
392 pkg = GetPortagePackage(target, package)
David James66a09c42012-11-05 13:31:38 -0800393 current = GetInstalledPackageVersions(pkg)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100394 desired = GetDesiredPackageVersions(target, package)
Zdenek Behan699ddd32012-04-13 07:14:08 +0200395 desired_num = VersionListToNumeric(target, package, desired, True)
396 if not set(desired_num).issubset(current):
397 print 'Some packages have been held back, skipping clean!'
398 return
Zdenek Behan508dcce2011-12-05 15:39:32 +0100399 unmergemap[pkg] = set(current).difference(desired_num)
400
401 # Cleaning doesn't care about consistency and rebuilding package.* files.
402 packages = []
403 for pkg, vers in unmergemap.iteritems():
404 packages.extend('=%s-%s' % (pkg, ver) for ver in vers if ver != '9999')
405
406 if packages:
407 print 'Cleaning packages:'
408 print packages
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100409 cmd = [EMERGE_CMD, '--unmerge']
Zdenek Behan508dcce2011-12-05 15:39:32 +0100410 cmd.extend(packages)
411 cros_build_lib.RunCommand(cmd)
412 else:
413 print 'Nothing to clean!'
414
415
416def SelectActiveToolchains(targets, suffixes):
417 """Runs gcc-config and binutils-config to select the desired.
418
419 args:
420 targets - the targets to select
421 """
422 for package in ['gcc', 'binutils']:
423 for target in targets:
424 # Pick the first version in the numbered list as the selected one.
425 desired = GetDesiredPackageVersions(target, package)
Zdenek Behan699ddd32012-04-13 07:14:08 +0200426 desired_num = VersionListToNumeric(target, package, desired, True)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100427 desired = desired_num[0]
428 # *-config does not play revisions, strip them, keep just PV.
429 desired = portage.versions.pkgsplit('%s-%s' % (package, desired))[1]
430
431 if target == 'host':
432 # *-config is the only tool treating host identically (by tuple).
David James27ac4ae2012-12-03 23:16:15 -0800433 target = toolchain.GetHostTuple()
Zdenek Behan508dcce2011-12-05 15:39:32 +0100434
435 # And finally, attach target to it.
436 desired = '%s-%s' % (target, desired)
437
438 # Target specific hacks
439 if package in suffixes:
440 if target in suffixes[package]:
441 desired += suffixes[package][target]
442
David James7ec5efc2012-11-06 09:39:49 -0800443 extra_env = {'CHOST': target}
444 cmd = ['%s-config' % package, '-c', target]
Zdenek Behan508dcce2011-12-05 15:39:32 +0100445 current = cros_build_lib.RunCommand(cmd, print_cmd=False,
David James7ec5efc2012-11-06 09:39:49 -0800446 redirect_stdout=True, extra_env=extra_env).output.splitlines()[0]
Zdenek Behan508dcce2011-12-05 15:39:32 +0100447 # Do not gcc-config when the current is live or nothing needs to be done.
448 if current != desired and current != '9999':
449 cmd = [ package + '-config', desired ]
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100450 cros_build_lib.RunCommand(cmd, print_cmd=False)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100451
452
Mike Frysinger35247af2012-11-16 18:58:06 -0500453def ExpandTargets(targets_wanted):
454 """Expand any possible toolchain aliases into full targets
455
456 This will expand 'all' and 'sdk' into the respective toolchain tuples.
457
458 Args:
459 targets_wanted: The targets specified by the user.
460 Returns:
461 Full list of tuples with pseudo targets removed.
462 """
David James27ac4ae2012-12-03 23:16:15 -0800463 alltargets = toolchain.GetAllTargets()
Mike Frysinger35247af2012-11-16 18:58:06 -0500464 targets_wanted = set(targets_wanted)
465 if targets_wanted == set(['all']):
466 targets = alltargets
467 elif targets_wanted == set(['sdk']):
468 # Filter out all the non-sdk toolchains as we don't want to mess
469 # with those in all of our builds.
David James27ac4ae2012-12-03 23:16:15 -0800470 targets = toolchain.FilterToolchains(alltargets, 'sdk', True)
Mike Frysinger35247af2012-11-16 18:58:06 -0500471 else:
472 # Verify user input.
473 nonexistent = targets_wanted.difference(alltargets)
474 if nonexistent:
475 raise ValueError('Invalid targets: %s', ','.join(nonexistent))
476 targets = dict((t, alltargets[t]) for t in targets_wanted)
477 return targets
478
479
David Jamesf8c672f2012-11-06 13:38:11 -0800480def UpdateToolchains(usepkg, deleteold, hostonly, reconfig,
481 targets_wanted, boards_wanted):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100482 """Performs all steps to create a synchronized toolchain enviroment.
483
484 args:
485 arguments correspond to the given commandline flags
486 """
David Jamesf8c672f2012-11-06 13:38:11 -0800487 targets, crossdev_targets, reconfig_targets = {}, {}, {}
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100488 if not hostonly:
489 # For hostonly, we can skip most of the below logic, much of which won't
490 # work on bare systems where this is useful.
Mike Frysinger35247af2012-11-16 18:58:06 -0500491 targets = ExpandTargets(targets_wanted)
Mike Frysinger7ccee992012-06-01 21:27:59 -0400492
Mike Frysinger7ccee992012-06-01 21:27:59 -0400493 # Now re-add any targets that might be from this board. This is
494 # to allow unofficial boards to declare their own toolchains.
495 for board in boards_wanted:
David James27ac4ae2012-12-03 23:16:15 -0800496 targets.update(toolchain.GetToolchainsForBoard(board))
Zdenek Behan508dcce2011-12-05 15:39:32 +0100497
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100498 # First check and initialize all cross targets that need to be.
Mike Frysinger7ccee992012-06-01 21:27:59 -0400499 for target in targets:
500 if TargetIsInitialized(target):
501 reconfig_targets[target] = targets[target]
502 else:
503 crossdev_targets[target] = targets[target]
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100504 if crossdev_targets:
Zdenek Behan8be29ba2012-05-29 23:10:34 +0200505 print 'The following targets need to be re-initialized:'
506 print crossdev_targets
David James66a09c42012-11-05 13:31:38 -0800507 Crossdev.UpdateTargets(crossdev_targets, usepkg)
Zdenek Behan8be29ba2012-05-29 23:10:34 +0200508 # Those that were not initialized may need a config update.
David James66a09c42012-11-05 13:31:38 -0800509 Crossdev.UpdateTargets(reconfig_targets, usepkg, config_only=True)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100510
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100511 # We want host updated.
Mike Frysinger7ccee992012-06-01 21:27:59 -0400512 targets['host'] = {}
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100513
514 # Now update all packages.
David Jamesf8c672f2012-11-06 13:38:11 -0800515 if UpdateTargets(targets, usepkg) or crossdev_targets or reconfig:
516 SelectActiveToolchains(targets, CONFIG_TARGET_SUFFIXES)
David James7ec5efc2012-11-06 09:39:49 -0800517
518 if deleteold:
519 CleanTargets(targets)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100520
521
Mike Frysinger35247af2012-11-16 18:58:06 -0500522def ShowBoardConfig(board):
523 """Show the toolchain tuples used by |board|
524
525 Args:
526 board: The board to query.
527 """
David James27ac4ae2012-12-03 23:16:15 -0800528 toolchains = toolchain.GetToolchainsForBoard(board)
Mike Frysinger35247af2012-11-16 18:58:06 -0500529 # Make sure we display the default toolchain first.
David James27ac4ae2012-12-03 23:16:15 -0800530 print ','.join(
531 toolchain.FilterToolchains(toolchains, 'default', True).keys() +
532 toolchain.FilterToolchains(toolchains, 'default', False).keys())
Mike Frysinger35247af2012-11-16 18:58:06 -0500533
534
535def GenerateLdsoWrapper(root, path, interp, libpaths=()):
536 """Generate a shell script wrapper which uses local ldso to run the ELF
537
538 Since we cannot rely on the host glibc (or other libraries), we need to
539 execute the local packaged ldso directly and tell it where to find our
540 copies of libraries.
541
542 Args:
543 root: The root tree to generate scripts inside of
544 path: The full path (inside |root|) to the program to wrap
545 interp: The ldso interpreter that we need to execute
546 libpaths: Extra lib paths to search for libraries
547 """
548 basedir = os.path.dirname(path)
549 libpaths = ['/lib'] + list(libpaths)
550 replacements = {
551 'interp': os.path.join(os.path.relpath('/lib', basedir),
552 os.path.basename(interp)),
553 'libpaths': ':'.join(['${basedir}/' + os.path.relpath(p, basedir)
554 for p in libpaths]),
555 }
556 wrapper = """#!/bin/sh
557base=$(realpath "$0")
558basedir=${base%%/*}
559exec \
560 "${basedir}/%(interp)s" \
561 --library-path "%(libpaths)s" \
562 --inhibit-rpath '' \
563 "${base}.elf" \
564 "$@"
565""" % replacements
566 wrappath = root + path
567 os.rename(wrappath, wrappath + '.elf')
568 osutils.WriteFile(wrappath, wrapper)
569 os.chmod(wrappath, 0755)
570
571
572def GeneratePathWrapper(root, wrappath, path):
573 """Generate a shell script to execute another shell script
574
575 Since we can't symlink a wrapped ELF (see GenerateLdsoWrapper) because the
576 argv[0] won't be pointing to the correct path, generate a shell script that
577 just executes another program with its full path.
578
579 Args:
580 root: The root tree to generate scripts inside of
581 wrappath: The full path (inside |root|) to create the wrapper
582 path: The target program which this wrapper will execute
583 """
584 replacements = {
585 'path': path,
586 'relroot': os.path.relpath('/', os.path.dirname(wrappath)),
587 }
588 wrapper = """#!/bin/sh
589base=$(realpath "$0")
590basedir=${base%%/*}
591exec "${basedir}/%(relroot)s%(path)s" "$@"
592""" % replacements
593 root_wrapper = root + wrappath
594 if os.path.islink(root_wrapper):
595 os.unlink(root_wrapper)
596 else:
597 osutils.SafeMakedirs(os.path.dirname(root_wrapper))
598 osutils.WriteFile(root_wrapper, wrapper)
599 os.chmod(root_wrapper, 0755)
600
601
602def FileIsCrosSdkElf(elf):
603 """Determine if |elf| is an ELF that we execute in the cros_sdk
604
605 We don't need this to be perfect, just quick. It makes sure the ELF
606 is a 64bit LSB x86_64 ELF. That is the native type of cros_sdk.
607
608 Args:
609 elf: The file to check
610 Returns:
611 True if we think |elf| is a native ELF
612 """
613 with open(elf) as f:
614 data = f.read(20)
615 # Check the magic number, EI_CLASS, EI_DATA, and e_machine.
616 return (data[0:4] == '\x7fELF' and
617 data[4] == '\x02' and
618 data[5] == '\x01' and
619 data[18] == '\x3e')
620
621
622def IsPathPackagable(ptype, path):
623 """Should the specified file be included in a toolchain package?
624
625 We only need to handle files as we'll create dirs as we need them.
626
627 Further, trim files that won't be useful:
628 - non-english translations (.mo) since it'd require env vars
629 - debug files since these are for the host compiler itself
630 - info/man pages as they're big, and docs are online, and the
631 native docs should work fine for the most part (`man gcc`)
632
633 Args:
634 ptype: A string describing the path type (i.e. 'file' or 'dir' or 'sym')
635 path: The full path to inspect
636 Returns:
637 True if we want to include this path in the package
638 """
639 return not (ptype in ('dir',) or
640 path.startswith('/usr/lib/debug/') or
641 os.path.splitext(path)[1] == '.mo' or
642 ('/man/' in path or '/info/' in path))
643
644
645def ReadlinkRoot(path, root):
646 """Like os.readlink(), but relative to a |root|
647
648 Args:
649 path: The symlink to read
650 root: The path to use for resolving absolute symlinks
651 Returns:
652 A fully resolved symlink path
653 """
654 while os.path.islink(root + path):
655 path = os.path.join(os.path.dirname(path), os.readlink(root + path))
656 return path
657
658
659def _GetFilesForTarget(target, root='/'):
660 """Locate all the files to package for |target|
661
662 This does not cover ELF dependencies.
663
664 Args:
665 target: The toolchain target name
666 root: The root path to pull all packages from
667 Returns:
668 A tuple of a set of all packable paths, and a set of all paths which
669 are also native ELFs
670 """
671 paths = set()
672 elfs = set()
673
674 # Find all the files owned by the packages for this target.
675 for pkg in GetTargetPackages(target):
676 # Ignore packages that are part of the target sysroot.
677 if pkg in ('kernel', 'libc'):
678 continue
679
680 atom = GetPortagePackage(target, pkg)
681 cat, pn = atom.split('/')
682 ver = GetInstalledPackageVersions(atom)[0]
683 cros_build_lib.Info('packaging %s-%s', atom, ver)
684
685 # pylint: disable=E1101
686 dblink = portage.dblink(cat, pn + '-' + ver, myroot=root,
687 settings=portage.settings)
688 contents = dblink.getcontents()
689 for obj in contents:
690 ptype = contents[obj][0]
691 if not IsPathPackagable(ptype, obj):
692 continue
693
694 if ptype == 'obj':
695 # For native ELFs, we need to pull in their dependencies too.
696 if FileIsCrosSdkElf(obj):
697 elfs.add(obj)
698 paths.add(obj)
699
700 return paths, elfs
701
702
703def _BuildInitialPackageRoot(output_dir, paths, elfs, ldpaths,
704 path_rewrite_func=lambda x:x, root='/'):
705 """Link in all packable files and their runtime dependencies
706
707 This also wraps up executable ELFs with helper scripts.
708
709 Args:
710 output_dir: The output directory to store files
711 paths: All the files to include
712 elfs: All the files which are ELFs (a subset of |paths|)
713 ldpaths: A dict of static ldpath information
714 path_rewrite_func: User callback to rewrite paths in output_dir
715 root: The root path to pull all packages/files from
716 """
717 # Link in all the files.
718 sym_paths = []
719 for path in paths:
720 new_path = path_rewrite_func(path)
721 dst = output_dir + new_path
722 osutils.SafeMakedirs(os.path.dirname(dst))
723
724 # Is this a symlink which we have to rewrite or wrap?
725 # Delay wrap check until after we have created all paths.
726 src = root + path
727 if os.path.islink(src):
728 tgt = os.readlink(src)
729 if os.path.sep in tgt:
730 sym_paths.append((new_path, lddtree.normpath(ReadlinkRoot(src, root))))
731
732 # Rewrite absolute links to relative and then generate the symlink
733 # ourselves. All other symlinks can be hardlinked below.
734 if tgt[0] == '/':
735 tgt = os.path.relpath(tgt, os.path.dirname(new_path))
736 os.symlink(tgt, dst)
737 continue
738
739 os.link(src, dst)
740
741 # Now see if any of the symlinks need to be wrapped.
742 for sym, tgt in sym_paths:
743 if tgt in elfs:
744 GeneratePathWrapper(output_dir, sym, tgt)
745
746 # Locate all the dependencies for all the ELFs. Stick them all in the
747 # top level "lib" dir to make the wrapper simpler. This exact path does
748 # not matter since we execute ldso directly, and we tell the ldso the
749 # exact path to search for its libraries.
750 libdir = os.path.join(output_dir, 'lib')
751 osutils.SafeMakedirs(libdir)
752 donelibs = set()
753 for elf in elfs:
754 e = lddtree.ParseELF(elf, root, ldpaths)
755 interp = e['interp']
756 if interp:
757 # Generate a wrapper if it is executable.
758 GenerateLdsoWrapper(output_dir, path_rewrite_func(elf), interp,
759 libpaths=e['rpath'] + e['runpath'])
760
761 for lib, lib_data in e['libs'].iteritems():
762 if lib in donelibs:
763 continue
764
765 src = path = lib_data['path']
766 if path is None:
767 cros_build_lib.Warning('%s: could not locate %s', elf, lib)
768 continue
769 donelibs.add(lib)
770
771 # Needed libs are the SONAME, but that is usually a symlink, not a
772 # real file. So link in the target rather than the symlink itself.
773 # We have to walk all the possible symlinks (SONAME could point to a
774 # symlink which points to a symlink), and we have to handle absolute
775 # ourselves (since we have a "root" argument).
776 dst = os.path.join(libdir, os.path.basename(path))
777 src = ReadlinkRoot(src, root)
778
779 os.link(root + src, dst)
780
781
782def _EnvdGetVar(envd, var):
783 """Given a Gentoo env.d file, extract a var from it
784
785 Args:
786 envd: The env.d file to load (may be a glob path)
787 var: The var to extract
788 Returns:
789 The value of |var|
790 """
791 envds = glob.glob(envd)
792 assert len(envds) == 1, '%s: should have exactly 1 env.d file' % envd
793 envd = envds[0]
794 return cros_build_lib.LoadKeyValueFile(envd)[var]
795
796
797def _ProcessBinutilsConfig(target, output_dir):
798 """Do what binutils-config would have done"""
799 binpath = os.path.join('/bin', target + '-')
David James27ac4ae2012-12-03 23:16:15 -0800800 globpath = os.path.join(output_dir, 'usr', toolchain.GetHostTuple(), target,
Mike Frysinger35247af2012-11-16 18:58:06 -0500801 'binutils-bin', '*-gold')
802 srcpath = glob.glob(globpath)
803 assert len(srcpath) == 1, '%s: did not match 1 path' % globpath
804 srcpath = srcpath[0][len(output_dir):]
805 gccpath = os.path.join('/usr', 'libexec', 'gcc')
806 for prog in os.listdir(output_dir + srcpath):
807 # Skip binaries already wrapped.
808 if not prog.endswith('.real'):
809 GeneratePathWrapper(output_dir, binpath + prog,
810 os.path.join(srcpath, prog))
811 GeneratePathWrapper(output_dir, os.path.join(gccpath, prog),
812 os.path.join(srcpath, prog))
813
David James27ac4ae2012-12-03 23:16:15 -0800814 libpath = os.path.join('/usr', toolchain.GetHostTuple(), target, 'lib')
Mike Frysinger35247af2012-11-16 18:58:06 -0500815 envd = os.path.join(output_dir, 'etc', 'env.d', 'binutils', '*-gold')
816 srcpath = _EnvdGetVar(envd, 'LIBPATH')
817 os.symlink(os.path.relpath(srcpath, os.path.dirname(libpath)),
818 output_dir + libpath)
819
820
821def _ProcessGccConfig(target, output_dir):
822 """Do what gcc-config would have done"""
823 binpath = '/bin'
824 envd = os.path.join(output_dir, 'etc', 'env.d', 'gcc', '*')
825 srcpath = _EnvdGetVar(envd, 'GCC_PATH')
826 for prog in os.listdir(output_dir + srcpath):
827 # Skip binaries already wrapped.
828 if (not prog.endswith('.real') and
829 not prog.endswith('.elf') and
830 prog.startswith(target)):
831 GeneratePathWrapper(output_dir, os.path.join(binpath, prog),
832 os.path.join(srcpath, prog))
833 return srcpath
834
835
836def _ProcessSysrootWrapper(_target, output_dir, srcpath):
837 """Remove chroot-specific things from our sysroot wrapper"""
838 # Disable ccache since we know it won't work outside of chroot.
839 sysroot_wrapper = glob.glob(os.path.join(
840 output_dir + srcpath, 'sysroot_wrapper*'))[0]
841 contents = osutils.ReadFile(sysroot_wrapper).splitlines()
842 for num in xrange(len(contents)):
843 if '@CCACHE_DEFAULT@' in contents[num]:
844 contents[num] = 'use_ccache = False'
845 break
846 # Can't update the wrapper in place since it's a hardlink to a file in /.
847 os.unlink(sysroot_wrapper)
848 osutils.WriteFile(sysroot_wrapper, '\n'.join(contents))
849 os.chmod(sysroot_wrapper, 0755)
850
851
852def _ProcessDistroCleanups(target, output_dir):
853 """Clean up the tree and remove all distro-specific requirements
854
855 Args:
856 target: The toolchain target name
857 output_dir: The output directory to clean up
858 """
859 _ProcessBinutilsConfig(target, output_dir)
860 gcc_path = _ProcessGccConfig(target, output_dir)
861 _ProcessSysrootWrapper(target, output_dir, gcc_path)
862
863 osutils.RmDir(os.path.join(output_dir, 'etc'))
864
865
866def CreatePackagableRoot(target, output_dir, ldpaths, root='/'):
867 """Setup a tree from the packages for the specified target
868
869 This populates a path with all the files from toolchain packages so that
870 a tarball can easily be generated from the result.
871
872 Args:
873 target: The target to create a packagable root from
874 output_dir: The output directory to place all the files
875 ldpaths: A dict of static ldpath information
876 root: The root path to pull all packages/files from
877 """
878 # Find all the files owned by the packages for this target.
879 paths, elfs = _GetFilesForTarget(target, root=root)
880
881 # Link in all the package's files, any ELF dependencies, and wrap any
882 # executable ELFs with helper scripts.
883 def MoveUsrBinToBin(path):
884 """Move /usr/bin to /bin so people can just use that toplevel dir"""
885 return path[4:] if path.startswith('/usr/bin/') else path
886 _BuildInitialPackageRoot(output_dir, paths, elfs, ldpaths,
887 path_rewrite_func=MoveUsrBinToBin, root=root)
888
889 # The packages, when part of the normal distro, have helper scripts
890 # that setup paths and such. Since we are making this standalone, we
891 # need to preprocess all that ourselves.
892 _ProcessDistroCleanups(target, output_dir)
893
894
895def CreatePackages(targets_wanted, output_dir, root='/'):
896 """Create redistributable cross-compiler packages for the specified targets
897
898 This creates toolchain packages that should be usable in conjunction with
899 a downloaded sysroot (created elsewhere).
900
901 Tarballs (one per target) will be created in $PWD.
902
903 Args:
904 targets_wanted: The targets to package up
905 root: The root path to pull all packages/files from
906 """
907 osutils.SafeMakedirs(output_dir)
908 ldpaths = lddtree.LoadLdpaths(root)
909 targets = ExpandTargets(targets_wanted)
910
911 with osutils.TempDirContextManager() as tempdir:
912 # We have to split the root generation from the compression stages. This is
913 # because we hardlink in all the files (to avoid overhead of reading/writing
914 # the copies multiple times). But tar gets angry if a file's hardlink count
915 # changes from when it starts reading a file to when it finishes.
916 with parallel.BackgroundTaskRunner(CreatePackagableRoot) as queue:
917 for target in targets:
918 output_target_dir = os.path.join(tempdir, target)
919 queue.put([target, output_target_dir, ldpaths, root])
920
921 # Build the tarball.
922 with parallel.BackgroundTaskRunner(cros_build_lib.CreateTarball) as queue:
923 for target in targets:
924 tar_file = os.path.join(output_dir, target + '.tar.xz')
925 queue.put([tar_file, os.path.join(tempdir, target)])
926
927
Brian Harring30675052012-02-29 12:18:22 -0800928def main(argv):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100929 usage = """usage: %prog [options]
930
Mike Frysinger506e75f2012-12-17 14:21:13 -0500931 The script installs and updates the toolchains in your chroot."""
932 parser = commandline.OptionParser(usage)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100933 parser.add_option('-u', '--nousepkg',
934 action='store_false', dest='usepkg', default=True,
Mike Frysinger506e75f2012-12-17 14:21:13 -0500935 help='Use prebuilt packages if possible')
Zdenek Behan508dcce2011-12-05 15:39:32 +0100936 parser.add_option('-d', '--deleteold',
937 action='store_true', dest='deleteold', default=False,
Mike Frysinger506e75f2012-12-17 14:21:13 -0500938 help='Unmerge deprecated packages')
Zdenek Behan508dcce2011-12-05 15:39:32 +0100939 parser.add_option('-t', '--targets',
Mike Frysingereaebb582012-06-19 13:04:53 -0400940 dest='targets', default='sdk',
Mike Frysinger506e75f2012-12-17 14:21:13 -0500941 help='Comma separated list of tuples. '
942 'Special keyword \'host\' is allowed. Default: sdk')
Mike Frysinger7ccee992012-06-01 21:27:59 -0400943 parser.add_option('--include-boards',
944 dest='include_boards', default='',
Mike Frysinger506e75f2012-12-17 14:21:13 -0500945 help='Comma separated list of boards whose toolchains we'
946 ' will always include. Default: none')
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100947 parser.add_option('--hostonly',
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100948 dest='hostonly', default=False, action='store_true',
Mike Frysinger506e75f2012-12-17 14:21:13 -0500949 help='Only setup the host toolchain. '
950 'Useful for bootstrapping chroot')
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100951 parser.add_option('--show-board-cfg',
952 dest='board_cfg', default=None,
Mike Frysinger506e75f2012-12-17 14:21:13 -0500953 help='Board to list toolchain tuples for')
Mike Frysinger35247af2012-11-16 18:58:06 -0500954 parser.add_option('--create-packages',
955 action='store_true', default=False,
956 help='Build redistributable packages')
957 parser.add_option('--output-dir', default=os.getcwd(), type='path',
958 help='Output directory')
David James66a09c42012-11-05 13:31:38 -0800959 parser.add_option('--reconfig', default=False, action='store_true',
Mike Frysinger506e75f2012-12-17 14:21:13 -0500960 help='Reload crossdev config and reselect toolchains')
Zdenek Behan508dcce2011-12-05 15:39:32 +0100961
Mike Frysinger35247af2012-11-16 18:58:06 -0500962 (options, remaining_arguments) = parser.parse_args(argv)
963 if len(remaining_arguments):
964 parser.error('script does not take arguments: %s' % remaining_arguments)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100965
Mike Frysinger35247af2012-11-16 18:58:06 -0500966 # Figure out what we're supposed to do and reject conflicting options.
967 if options.board_cfg and options.create_packages:
968 parser.error('conflicting options: create-packages & show-board-cfg')
Mike Frysinger984d0622012-06-01 16:08:44 -0400969
Zdenek Behan508dcce2011-12-05 15:39:32 +0100970 targets = set(options.targets.split(','))
Mike Frysinger7ccee992012-06-01 21:27:59 -0400971 boards = set(options.include_boards.split(',')) if options.include_boards \
972 else set()
Mike Frysinger35247af2012-11-16 18:58:06 -0500973
974 if options.board_cfg:
975 ShowBoardConfig(options.board_cfg)
976 elif options.create_packages:
977 cros_build_lib.AssertInsideChroot()
978 Crossdev.Load(False)
979 CreatePackages(targets, options.output_dir)
980 else:
981 cros_build_lib.AssertInsideChroot()
982 # This has to be always run as root.
983 if os.geteuid() != 0:
984 cros_build_lib.Die('this script must be run as root')
985
986 Crossdev.Load(options.reconfig)
987 UpdateToolchains(options.usepkg, options.deleteold, options.hostonly,
988 options.reconfig, targets, boards)
989 Crossdev.Save()
990
991 return 0