blob: a6ddb0a61f0337892255a34f860c09575cc2e5c2 [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.
Mike Frysingerc880a962013-11-08 13:59:06 -0500335def RebuildLibtool():
336 """Rebuild libtool as needed
337
338 Libtool hardcodes full paths to internal gcc files, so whenever we upgrade
339 gcc, libtool will break. We can't use binary packages either as those will
340 most likely be compiled against the previous version of gcc.
341 """
342 needs_update = False
343 with open('/usr/bin/libtool') as f:
344 for line in f:
345 # Look for a line like:
346 # sys_lib_search_path_spec="..."
347 # It'll be a list of paths and gcc will be one of them.
348 if line.startswith('sys_lib_search_path_spec='):
349 line = line.rstrip()
350 for path in line.split('=', 1)[1].strip('"').split():
351 if not os.path.exists(path):
352 print 'Rebuilding libtool after gcc upgrade'
353 print ' %s' % line
354 print ' missing path: %s' % path
355 needs_update = True
356 break
357
358 if needs_update:
359 break
360
361 if needs_update:
362 cmd = [EMERGE_CMD, '--oneshot', 'sys-devel/libtool']
363 cros_build_lib.RunCommand(cmd)
364
365
Zdenek Behan508dcce2011-12-05 15:39:32 +0100366def UpdateTargets(targets, usepkg):
367 """Determines which packages need update/unmerge and defers to portage.
368
369 args:
370 targets - the list of targets to update
371 usepkg - copies the commandline option
372 """
David James90239b92012-11-05 15:31:34 -0800373 # Remove keyword files created by old versions of cros_setup_toolchains.
374 osutils.SafeUnlink('/etc/portage/package.keywords/cross-host')
Zdenek Behan508dcce2011-12-05 15:39:32 +0100375
376 # For each target, we do two things. Figure out the list of updates,
377 # and figure out the appropriate keywords/masks. Crossdev will initialize
378 # these, but they need to be regenerated on every update.
379 print 'Determining required toolchain updates...'
David James90239b92012-11-05 15:31:34 -0800380 mergemap = {}
Zdenek Behan508dcce2011-12-05 15:39:32 +0100381 for target in targets:
382 # Record the highest needed version for each target, for masking purposes.
383 RemovePackageMask(target)
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100384 for package in GetTargetPackages(target):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100385 # Portage name for the package
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100386 if IsPackageDisabled(target, package):
387 continue
388 pkg = GetPortagePackage(target, package)
David James66a09c42012-11-05 13:31:38 -0800389 current = GetInstalledPackageVersions(pkg)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100390 desired = GetDesiredPackageVersions(target, package)
Zdenek Behan699ddd32012-04-13 07:14:08 +0200391 desired_num = VersionListToNumeric(target, package, desired, False)
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100392 mergemap[pkg] = set(desired_num).difference(current)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100393
Zdenek Behan508dcce2011-12-05 15:39:32 +0100394 packages = []
395 for pkg in mergemap:
396 for ver in mergemap[pkg]:
Zdenek Behan677b6d82012-04-11 05:31:47 +0200397 if ver != PACKAGE_NONE:
David James90239b92012-11-05 15:31:34 -0800398 packages.append(pkg)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100399
400 if not packages:
401 print 'Nothing to update!'
David Jamesf8c672f2012-11-06 13:38:11 -0800402 return False
Zdenek Behan508dcce2011-12-05 15:39:32 +0100403
404 print 'Updating packages:'
405 print packages
406
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100407 cmd = [EMERGE_CMD, '--oneshot', '--update']
Zdenek Behan508dcce2011-12-05 15:39:32 +0100408 if usepkg:
409 cmd.extend(['--getbinpkg', '--usepkgonly'])
410
411 cmd.extend(packages)
412 cros_build_lib.RunCommand(cmd)
David Jamesf8c672f2012-11-06 13:38:11 -0800413 return True
Zdenek Behan508dcce2011-12-05 15:39:32 +0100414
415
416def CleanTargets(targets):
417 """Unmerges old packages that are assumed unnecessary."""
418 unmergemap = {}
419 for target in targets:
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100420 for package in GetTargetPackages(target):
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100421 if IsPackageDisabled(target, package):
422 continue
423 pkg = GetPortagePackage(target, package)
David James66a09c42012-11-05 13:31:38 -0800424 current = GetInstalledPackageVersions(pkg)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100425 desired = GetDesiredPackageVersions(target, package)
Zdenek Behan699ddd32012-04-13 07:14:08 +0200426 desired_num = VersionListToNumeric(target, package, desired, True)
427 if not set(desired_num).issubset(current):
428 print 'Some packages have been held back, skipping clean!'
429 return
Zdenek Behan508dcce2011-12-05 15:39:32 +0100430 unmergemap[pkg] = set(current).difference(desired_num)
431
432 # Cleaning doesn't care about consistency and rebuilding package.* files.
433 packages = []
434 for pkg, vers in unmergemap.iteritems():
435 packages.extend('=%s-%s' % (pkg, ver) for ver in vers if ver != '9999')
436
437 if packages:
438 print 'Cleaning packages:'
439 print packages
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100440 cmd = [EMERGE_CMD, '--unmerge']
Zdenek Behan508dcce2011-12-05 15:39:32 +0100441 cmd.extend(packages)
442 cros_build_lib.RunCommand(cmd)
443 else:
444 print 'Nothing to clean!'
445
446
447def SelectActiveToolchains(targets, suffixes):
448 """Runs gcc-config and binutils-config to select the desired.
449
450 args:
451 targets - the targets to select
452 """
453 for package in ['gcc', 'binutils']:
454 for target in targets:
455 # Pick the first version in the numbered list as the selected one.
456 desired = GetDesiredPackageVersions(target, package)
Zdenek Behan699ddd32012-04-13 07:14:08 +0200457 desired_num = VersionListToNumeric(target, package, desired, True)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100458 desired = desired_num[0]
459 # *-config does not play revisions, strip them, keep just PV.
460 desired = portage.versions.pkgsplit('%s-%s' % (package, desired))[1]
461
462 if target == 'host':
463 # *-config is the only tool treating host identically (by tuple).
David James27ac4ae2012-12-03 23:16:15 -0800464 target = toolchain.GetHostTuple()
Zdenek Behan508dcce2011-12-05 15:39:32 +0100465
466 # And finally, attach target to it.
467 desired = '%s-%s' % (target, desired)
468
469 # Target specific hacks
470 if package in suffixes:
471 if target in suffixes[package]:
472 desired += suffixes[package][target]
473
David James7ec5efc2012-11-06 09:39:49 -0800474 extra_env = {'CHOST': target}
475 cmd = ['%s-config' % package, '-c', target]
Zdenek Behan508dcce2011-12-05 15:39:32 +0100476 current = cros_build_lib.RunCommand(cmd, print_cmd=False,
David James7ec5efc2012-11-06 09:39:49 -0800477 redirect_stdout=True, extra_env=extra_env).output.splitlines()[0]
Zdenek Behan508dcce2011-12-05 15:39:32 +0100478 # Do not gcc-config when the current is live or nothing needs to be done.
479 if current != desired and current != '9999':
480 cmd = [ package + '-config', desired ]
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100481 cros_build_lib.RunCommand(cmd, print_cmd=False)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100482
483
Mike Frysinger35247af2012-11-16 18:58:06 -0500484def ExpandTargets(targets_wanted):
485 """Expand any possible toolchain aliases into full targets
486
487 This will expand 'all' and 'sdk' into the respective toolchain tuples.
488
489 Args:
490 targets_wanted: The targets specified by the user.
491 Returns:
492 Full list of tuples with pseudo targets removed.
493 """
David James27ac4ae2012-12-03 23:16:15 -0800494 alltargets = toolchain.GetAllTargets()
Mike Frysinger35247af2012-11-16 18:58:06 -0500495 targets_wanted = set(targets_wanted)
496 if targets_wanted == set(['all']):
497 targets = alltargets
498 elif targets_wanted == set(['sdk']):
499 # Filter out all the non-sdk toolchains as we don't want to mess
500 # with those in all of our builds.
David James27ac4ae2012-12-03 23:16:15 -0800501 targets = toolchain.FilterToolchains(alltargets, 'sdk', True)
Mike Frysinger35247af2012-11-16 18:58:06 -0500502 else:
503 # Verify user input.
504 nonexistent = targets_wanted.difference(alltargets)
505 if nonexistent:
506 raise ValueError('Invalid targets: %s', ','.join(nonexistent))
507 targets = dict((t, alltargets[t]) for t in targets_wanted)
508 return targets
509
510
David Jamesf8c672f2012-11-06 13:38:11 -0800511def UpdateToolchains(usepkg, deleteold, hostonly, reconfig,
512 targets_wanted, boards_wanted):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100513 """Performs all steps to create a synchronized toolchain enviroment.
514
515 args:
516 arguments correspond to the given commandline flags
517 """
David Jamesf8c672f2012-11-06 13:38:11 -0800518 targets, crossdev_targets, reconfig_targets = {}, {}, {}
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100519 if not hostonly:
520 # For hostonly, we can skip most of the below logic, much of which won't
521 # work on bare systems where this is useful.
Mike Frysinger35247af2012-11-16 18:58:06 -0500522 targets = ExpandTargets(targets_wanted)
Mike Frysinger7ccee992012-06-01 21:27:59 -0400523
Mike Frysinger7ccee992012-06-01 21:27:59 -0400524 # Now re-add any targets that might be from this board. This is
525 # to allow unofficial boards to declare their own toolchains.
526 for board in boards_wanted:
David James27ac4ae2012-12-03 23:16:15 -0800527 targets.update(toolchain.GetToolchainsForBoard(board))
Zdenek Behan508dcce2011-12-05 15:39:32 +0100528
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100529 # First check and initialize all cross targets that need to be.
Mike Frysinger7ccee992012-06-01 21:27:59 -0400530 for target in targets:
531 if TargetIsInitialized(target):
532 reconfig_targets[target] = targets[target]
533 else:
534 crossdev_targets[target] = targets[target]
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100535 if crossdev_targets:
Zdenek Behan8be29ba2012-05-29 23:10:34 +0200536 print 'The following targets need to be re-initialized:'
537 print crossdev_targets
David James66a09c42012-11-05 13:31:38 -0800538 Crossdev.UpdateTargets(crossdev_targets, usepkg)
Zdenek Behan8be29ba2012-05-29 23:10:34 +0200539 # Those that were not initialized may need a config update.
David James66a09c42012-11-05 13:31:38 -0800540 Crossdev.UpdateTargets(reconfig_targets, usepkg, config_only=True)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100541
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100542 # We want host updated.
Mike Frysinger7ccee992012-06-01 21:27:59 -0400543 targets['host'] = {}
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100544
545 # Now update all packages.
David Jamesf8c672f2012-11-06 13:38:11 -0800546 if UpdateTargets(targets, usepkg) or crossdev_targets or reconfig:
547 SelectActiveToolchains(targets, CONFIG_TARGET_SUFFIXES)
David James7ec5efc2012-11-06 09:39:49 -0800548
549 if deleteold:
550 CleanTargets(targets)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100551
Mike Frysingerc880a962013-11-08 13:59:06 -0500552 # Now that we've cleared out old versions, see if we need to rebuild
553 # anything. Can't do this earlier as it might not be broken.
554 RebuildLibtool()
555
Zdenek Behan508dcce2011-12-05 15:39:32 +0100556
Mike Frysinger35247af2012-11-16 18:58:06 -0500557def ShowBoardConfig(board):
558 """Show the toolchain tuples used by |board|
559
560 Args:
561 board: The board to query.
562 """
David James27ac4ae2012-12-03 23:16:15 -0800563 toolchains = toolchain.GetToolchainsForBoard(board)
Mike Frysinger35247af2012-11-16 18:58:06 -0500564 # Make sure we display the default toolchain first.
David James27ac4ae2012-12-03 23:16:15 -0800565 print ','.join(
566 toolchain.FilterToolchains(toolchains, 'default', True).keys() +
567 toolchain.FilterToolchains(toolchains, 'default', False).keys())
Mike Frysinger35247af2012-11-16 18:58:06 -0500568
569
Mike Frysinger35247af2012-11-16 18:58:06 -0500570def GeneratePathWrapper(root, wrappath, path):
571 """Generate a shell script to execute another shell script
572
573 Since we can't symlink a wrapped ELF (see GenerateLdsoWrapper) because the
574 argv[0] won't be pointing to the correct path, generate a shell script that
575 just executes another program with its full path.
576
577 Args:
578 root: The root tree to generate scripts inside of
579 wrappath: The full path (inside |root|) to create the wrapper
580 path: The target program which this wrapper will execute
581 """
582 replacements = {
583 'path': path,
584 'relroot': os.path.relpath('/', os.path.dirname(wrappath)),
585 }
586 wrapper = """#!/bin/sh
587base=$(realpath "$0")
588basedir=${base%%/*}
589exec "${basedir}/%(relroot)s%(path)s" "$@"
590""" % replacements
591 root_wrapper = root + wrappath
592 if os.path.islink(root_wrapper):
593 os.unlink(root_wrapper)
594 else:
595 osutils.SafeMakedirs(os.path.dirname(root_wrapper))
596 osutils.WriteFile(root_wrapper, wrapper)
Mike Frysinger60ec1012013-10-21 00:11:10 -0400597 os.chmod(root_wrapper, 0o755)
Mike Frysinger35247af2012-11-16 18:58:06 -0500598
599
600def FileIsCrosSdkElf(elf):
601 """Determine if |elf| is an ELF that we execute in the cros_sdk
602
603 We don't need this to be perfect, just quick. It makes sure the ELF
604 is a 64bit LSB x86_64 ELF. That is the native type of cros_sdk.
605
606 Args:
607 elf: The file to check
608 Returns:
609 True if we think |elf| is a native ELF
610 """
611 with open(elf) as f:
612 data = f.read(20)
613 # Check the magic number, EI_CLASS, EI_DATA, and e_machine.
614 return (data[0:4] == '\x7fELF' and
615 data[4] == '\x02' and
616 data[5] == '\x01' and
617 data[18] == '\x3e')
618
619
620def IsPathPackagable(ptype, path):
621 """Should the specified file be included in a toolchain package?
622
623 We only need to handle files as we'll create dirs as we need them.
624
625 Further, trim files that won't be useful:
626 - non-english translations (.mo) since it'd require env vars
627 - debug files since these are for the host compiler itself
628 - info/man pages as they're big, and docs are online, and the
629 native docs should work fine for the most part (`man gcc`)
630
631 Args:
632 ptype: A string describing the path type (i.e. 'file' or 'dir' or 'sym')
633 path: The full path to inspect
634 Returns:
635 True if we want to include this path in the package
636 """
637 return not (ptype in ('dir',) or
638 path.startswith('/usr/lib/debug/') or
639 os.path.splitext(path)[1] == '.mo' or
640 ('/man/' in path or '/info/' in path))
641
642
643def ReadlinkRoot(path, root):
644 """Like os.readlink(), but relative to a |root|
645
646 Args:
647 path: The symlink to read
648 root: The path to use for resolving absolute symlinks
649 Returns:
650 A fully resolved symlink path
651 """
652 while os.path.islink(root + path):
653 path = os.path.join(os.path.dirname(path), os.readlink(root + path))
654 return path
655
656
657def _GetFilesForTarget(target, root='/'):
658 """Locate all the files to package for |target|
659
660 This does not cover ELF dependencies.
661
662 Args:
663 target: The toolchain target name
664 root: The root path to pull all packages from
665 Returns:
666 A tuple of a set of all packable paths, and a set of all paths which
667 are also native ELFs
668 """
669 paths = set()
670 elfs = set()
671
672 # Find all the files owned by the packages for this target.
673 for pkg in GetTargetPackages(target):
674 # Ignore packages that are part of the target sysroot.
675 if pkg in ('kernel', 'libc'):
676 continue
677
678 atom = GetPortagePackage(target, pkg)
679 cat, pn = atom.split('/')
680 ver = GetInstalledPackageVersions(atom)[0]
681 cros_build_lib.Info('packaging %s-%s', atom, ver)
682
683 # pylint: disable=E1101
684 dblink = portage.dblink(cat, pn + '-' + ver, myroot=root,
685 settings=portage.settings)
686 contents = dblink.getcontents()
687 for obj in contents:
688 ptype = contents[obj][0]
689 if not IsPathPackagable(ptype, obj):
690 continue
691
692 if ptype == 'obj':
693 # For native ELFs, we need to pull in their dependencies too.
694 if FileIsCrosSdkElf(obj):
695 elfs.add(obj)
696 paths.add(obj)
697
698 return paths, elfs
699
700
701def _BuildInitialPackageRoot(output_dir, paths, elfs, ldpaths,
702 path_rewrite_func=lambda x:x, root='/'):
703 """Link in all packable files and their runtime dependencies
704
705 This also wraps up executable ELFs with helper scripts.
706
707 Args:
708 output_dir: The output directory to store files
709 paths: All the files to include
710 elfs: All the files which are ELFs (a subset of |paths|)
711 ldpaths: A dict of static ldpath information
712 path_rewrite_func: User callback to rewrite paths in output_dir
713 root: The root path to pull all packages/files from
714 """
715 # Link in all the files.
716 sym_paths = []
717 for path in paths:
718 new_path = path_rewrite_func(path)
719 dst = output_dir + new_path
720 osutils.SafeMakedirs(os.path.dirname(dst))
721
722 # Is this a symlink which we have to rewrite or wrap?
723 # Delay wrap check until after we have created all paths.
724 src = root + path
725 if os.path.islink(src):
726 tgt = os.readlink(src)
727 if os.path.sep in tgt:
728 sym_paths.append((new_path, lddtree.normpath(ReadlinkRoot(src, root))))
729
730 # Rewrite absolute links to relative and then generate the symlink
731 # ourselves. All other symlinks can be hardlinked below.
732 if tgt[0] == '/':
733 tgt = os.path.relpath(tgt, os.path.dirname(new_path))
734 os.symlink(tgt, dst)
735 continue
736
737 os.link(src, dst)
738
739 # Now see if any of the symlinks need to be wrapped.
740 for sym, tgt in sym_paths:
741 if tgt in elfs:
742 GeneratePathWrapper(output_dir, sym, tgt)
743
744 # Locate all the dependencies for all the ELFs. Stick them all in the
745 # top level "lib" dir to make the wrapper simpler. This exact path does
746 # not matter since we execute ldso directly, and we tell the ldso the
747 # exact path to search for its libraries.
748 libdir = os.path.join(output_dir, 'lib')
749 osutils.SafeMakedirs(libdir)
750 donelibs = set()
751 for elf in elfs:
752 e = lddtree.ParseELF(elf, root, ldpaths)
753 interp = e['interp']
754 if interp:
755 # Generate a wrapper if it is executable.
Mike Frysingerc2ec0902013-03-26 01:28:45 -0400756 interp = os.path.join('/lib', os.path.basename(interp))
757 lddtree.GenerateLdsoWrapper(output_dir, path_rewrite_func(elf), interp,
758 libpaths=e['rpath'] + e['runpath'])
Mike Frysinger35247af2012-11-16 18:58:06 -0500759
760 for lib, lib_data in e['libs'].iteritems():
761 if lib in donelibs:
762 continue
763
764 src = path = lib_data['path']
765 if path is None:
766 cros_build_lib.Warning('%s: could not locate %s', elf, lib)
767 continue
768 donelibs.add(lib)
769
770 # Needed libs are the SONAME, but that is usually a symlink, not a
771 # real file. So link in the target rather than the symlink itself.
772 # We have to walk all the possible symlinks (SONAME could point to a
773 # symlink which points to a symlink), and we have to handle absolute
774 # ourselves (since we have a "root" argument).
775 dst = os.path.join(libdir, os.path.basename(path))
776 src = ReadlinkRoot(src, root)
777
778 os.link(root + src, dst)
779
780
781def _EnvdGetVar(envd, var):
782 """Given a Gentoo env.d file, extract a var from it
783
784 Args:
785 envd: The env.d file to load (may be a glob path)
786 var: The var to extract
787 Returns:
788 The value of |var|
789 """
790 envds = glob.glob(envd)
791 assert len(envds) == 1, '%s: should have exactly 1 env.d file' % envd
792 envd = envds[0]
793 return cros_build_lib.LoadKeyValueFile(envd)[var]
794
795
796def _ProcessBinutilsConfig(target, output_dir):
797 """Do what binutils-config would have done"""
798 binpath = os.path.join('/bin', target + '-')
David James27ac4ae2012-12-03 23:16:15 -0800799 globpath = os.path.join(output_dir, 'usr', toolchain.GetHostTuple(), target,
Mike Frysinger35247af2012-11-16 18:58:06 -0500800 'binutils-bin', '*-gold')
801 srcpath = glob.glob(globpath)
802 assert len(srcpath) == 1, '%s: did not match 1 path' % globpath
803 srcpath = srcpath[0][len(output_dir):]
804 gccpath = os.path.join('/usr', 'libexec', 'gcc')
805 for prog in os.listdir(output_dir + srcpath):
806 # Skip binaries already wrapped.
807 if not prog.endswith('.real'):
808 GeneratePathWrapper(output_dir, binpath + prog,
809 os.path.join(srcpath, prog))
810 GeneratePathWrapper(output_dir, os.path.join(gccpath, prog),
811 os.path.join(srcpath, prog))
812
David James27ac4ae2012-12-03 23:16:15 -0800813 libpath = os.path.join('/usr', toolchain.GetHostTuple(), target, 'lib')
Mike Frysinger35247af2012-11-16 18:58:06 -0500814 envd = os.path.join(output_dir, 'etc', 'env.d', 'binutils', '*-gold')
815 srcpath = _EnvdGetVar(envd, 'LIBPATH')
816 os.symlink(os.path.relpath(srcpath, os.path.dirname(libpath)),
817 output_dir + libpath)
818
819
820def _ProcessGccConfig(target, output_dir):
821 """Do what gcc-config would have done"""
822 binpath = '/bin'
823 envd = os.path.join(output_dir, 'etc', 'env.d', 'gcc', '*')
824 srcpath = _EnvdGetVar(envd, 'GCC_PATH')
825 for prog in os.listdir(output_dir + srcpath):
826 # Skip binaries already wrapped.
827 if (not prog.endswith('.real') and
828 not prog.endswith('.elf') and
829 prog.startswith(target)):
830 GeneratePathWrapper(output_dir, os.path.join(binpath, prog),
831 os.path.join(srcpath, prog))
832 return srcpath
833
834
835def _ProcessSysrootWrapper(_target, output_dir, srcpath):
836 """Remove chroot-specific things from our sysroot wrapper"""
837 # Disable ccache since we know it won't work outside of chroot.
838 sysroot_wrapper = glob.glob(os.path.join(
839 output_dir + srcpath, 'sysroot_wrapper*'))[0]
840 contents = osutils.ReadFile(sysroot_wrapper).splitlines()
841 for num in xrange(len(contents)):
842 if '@CCACHE_DEFAULT@' in contents[num]:
843 contents[num] = 'use_ccache = False'
844 break
845 # Can't update the wrapper in place since it's a hardlink to a file in /.
846 os.unlink(sysroot_wrapper)
847 osutils.WriteFile(sysroot_wrapper, '\n'.join(contents))
Mike Frysinger60ec1012013-10-21 00:11:10 -0400848 os.chmod(sysroot_wrapper, 0o755)
Mike Frysinger35247af2012-11-16 18:58:06 -0500849
850
851def _ProcessDistroCleanups(target, output_dir):
852 """Clean up the tree and remove all distro-specific requirements
853
854 Args:
855 target: The toolchain target name
856 output_dir: The output directory to clean up
857 """
858 _ProcessBinutilsConfig(target, output_dir)
859 gcc_path = _ProcessGccConfig(target, output_dir)
860 _ProcessSysrootWrapper(target, output_dir, gcc_path)
861
862 osutils.RmDir(os.path.join(output_dir, 'etc'))
863
864
865def CreatePackagableRoot(target, output_dir, ldpaths, root='/'):
866 """Setup a tree from the packages for the specified target
867
868 This populates a path with all the files from toolchain packages so that
869 a tarball can easily be generated from the result.
870
871 Args:
872 target: The target to create a packagable root from
873 output_dir: The output directory to place all the files
874 ldpaths: A dict of static ldpath information
875 root: The root path to pull all packages/files from
876 """
877 # Find all the files owned by the packages for this target.
878 paths, elfs = _GetFilesForTarget(target, root=root)
879
880 # Link in all the package's files, any ELF dependencies, and wrap any
881 # executable ELFs with helper scripts.
882 def MoveUsrBinToBin(path):
883 """Move /usr/bin to /bin so people can just use that toplevel dir"""
884 return path[4:] if path.startswith('/usr/bin/') else path
885 _BuildInitialPackageRoot(output_dir, paths, elfs, ldpaths,
886 path_rewrite_func=MoveUsrBinToBin, root=root)
887
888 # The packages, when part of the normal distro, have helper scripts
889 # that setup paths and such. Since we are making this standalone, we
890 # need to preprocess all that ourselves.
891 _ProcessDistroCleanups(target, output_dir)
892
893
894def CreatePackages(targets_wanted, output_dir, root='/'):
895 """Create redistributable cross-compiler packages for the specified targets
896
897 This creates toolchain packages that should be usable in conjunction with
898 a downloaded sysroot (created elsewhere).
899
900 Tarballs (one per target) will be created in $PWD.
901
902 Args:
903 targets_wanted: The targets to package up
904 root: The root path to pull all packages/files from
905 """
906 osutils.SafeMakedirs(output_dir)
907 ldpaths = lddtree.LoadLdpaths(root)
908 targets = ExpandTargets(targets_wanted)
909
David James4bc13702013-03-26 08:08:04 -0700910 with osutils.TempDir() as tempdir:
Mike Frysinger35247af2012-11-16 18:58:06 -0500911 # We have to split the root generation from the compression stages. This is
912 # because we hardlink in all the files (to avoid overhead of reading/writing
913 # the copies multiple times). But tar gets angry if a file's hardlink count
914 # changes from when it starts reading a file to when it finishes.
915 with parallel.BackgroundTaskRunner(CreatePackagableRoot) as queue:
916 for target in targets:
917 output_target_dir = os.path.join(tempdir, target)
918 queue.put([target, output_target_dir, ldpaths, root])
919
920 # Build the tarball.
921 with parallel.BackgroundTaskRunner(cros_build_lib.CreateTarball) as queue:
922 for target in targets:
923 tar_file = os.path.join(output_dir, target + '.tar.xz')
924 queue.put([tar_file, os.path.join(tempdir, target)])
925
926
Brian Harring30675052012-02-29 12:18:22 -0800927def main(argv):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100928 usage = """usage: %prog [options]
929
Mike Frysinger506e75f2012-12-17 14:21:13 -0500930 The script installs and updates the toolchains in your chroot."""
931 parser = commandline.OptionParser(usage)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100932 parser.add_option('-u', '--nousepkg',
933 action='store_false', dest='usepkg', default=True,
Mike Frysinger506e75f2012-12-17 14:21:13 -0500934 help='Use prebuilt packages if possible')
Zdenek Behan508dcce2011-12-05 15:39:32 +0100935 parser.add_option('-d', '--deleteold',
936 action='store_true', dest='deleteold', default=False,
Mike Frysinger506e75f2012-12-17 14:21:13 -0500937 help='Unmerge deprecated packages')
Zdenek Behan508dcce2011-12-05 15:39:32 +0100938 parser.add_option('-t', '--targets',
Mike Frysingereaebb582012-06-19 13:04:53 -0400939 dest='targets', default='sdk',
Mike Frysinger506e75f2012-12-17 14:21:13 -0500940 help='Comma separated list of tuples. '
941 'Special keyword \'host\' is allowed. Default: sdk')
Mike Frysinger7ccee992012-06-01 21:27:59 -0400942 parser.add_option('--include-boards',
943 dest='include_boards', default='',
Mike Frysinger506e75f2012-12-17 14:21:13 -0500944 help='Comma separated list of boards whose toolchains we'
945 ' will always include. Default: none')
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100946 parser.add_option('--hostonly',
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100947 dest='hostonly', default=False, action='store_true',
Mike Frysinger506e75f2012-12-17 14:21:13 -0500948 help='Only setup the host toolchain. '
949 'Useful for bootstrapping chroot')
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100950 parser.add_option('--show-board-cfg',
951 dest='board_cfg', default=None,
Mike Frysinger506e75f2012-12-17 14:21:13 -0500952 help='Board to list toolchain tuples for')
Mike Frysinger35247af2012-11-16 18:58:06 -0500953 parser.add_option('--create-packages',
954 action='store_true', default=False,
955 help='Build redistributable packages')
956 parser.add_option('--output-dir', default=os.getcwd(), type='path',
957 help='Output directory')
David James66a09c42012-11-05 13:31:38 -0800958 parser.add_option('--reconfig', default=False, action='store_true',
Mike Frysinger506e75f2012-12-17 14:21:13 -0500959 help='Reload crossdev config and reselect toolchains')
Zdenek Behan508dcce2011-12-05 15:39:32 +0100960
Mike Frysinger35247af2012-11-16 18:58:06 -0500961 (options, remaining_arguments) = parser.parse_args(argv)
962 if len(remaining_arguments):
963 parser.error('script does not take arguments: %s' % remaining_arguments)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100964
Mike Frysinger35247af2012-11-16 18:58:06 -0500965 # Figure out what we're supposed to do and reject conflicting options.
966 if options.board_cfg and options.create_packages:
967 parser.error('conflicting options: create-packages & show-board-cfg')
Mike Frysinger984d0622012-06-01 16:08:44 -0400968
Zdenek Behan508dcce2011-12-05 15:39:32 +0100969 targets = set(options.targets.split(','))
Mike Frysinger7ccee992012-06-01 21:27:59 -0400970 boards = set(options.include_boards.split(',')) if options.include_boards \
971 else set()
Mike Frysinger35247af2012-11-16 18:58:06 -0500972
973 if options.board_cfg:
974 ShowBoardConfig(options.board_cfg)
975 elif options.create_packages:
976 cros_build_lib.AssertInsideChroot()
977 Crossdev.Load(False)
978 CreatePackages(targets, options.output_dir)
979 else:
980 cros_build_lib.AssertInsideChroot()
981 # This has to be always run as root.
982 if os.geteuid() != 0:
983 cros_build_lib.Die('this script must be run as root')
984
985 Crossdev.Load(options.reconfig)
986 UpdateToolchains(options.usepkg, options.deleteold, options.hostonly,
987 options.reconfig, targets, boards)
988 Crossdev.Save()
989
990 return 0