blob: 814b379373c4225eb9ab2fecdec31c212c2faf3d [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
Zdenek Behan7e33b4e2012-03-12 17:00:56 +010010import errno
Mike Frysinger7ccee992012-06-01 21:27:59 -040011import json
Zdenek Behan508dcce2011-12-05 15:39:32 +010012import optparse
13import os
14import sys
15
Brian Harring503f3ab2012-03-09 21:39:41 -080016from chromite.buildbot import constants
17from chromite.lib import cros_build_lib
Brian Harringaf019fb2012-05-10 15:06:13 -070018from chromite.lib import osutils
Zdenek Behan508dcce2011-12-05 15:39:32 +010019
20# Some sanity checks first.
21if not cros_build_lib.IsInsideChroot():
22 print '%s: This needs to be run inside the chroot' % sys.argv[0]
23 sys.exit(1)
24# Only import portage after we've checked that we're inside the chroot.
25# Outside may not have portage, in which case the above may not happen.
26import portage
27
28
Matt Tennantf1e30972012-03-02 16:30:07 -080029EMERGE_CMD = os.path.join(constants.CHROMITE_BIN_DIR, 'parallel_emerge')
Zdenek Behan331f9822012-04-13 05:02:36 +020030CROS_OVERLAY_LIST_CMD = os.path.join(
31 constants.SOURCE_ROOT, 'src/platform/dev/host/cros_overlay_list')
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
Zdenek Behan4eb6fd22012-03-12 17:00:56 +010066# Global variable cache. It has to be a descendant of 'object', rather than
67# instance thereof, because attributes cannot be set on 'object' instances.
68class VariableCache(object):
69 pass
70VAR_CACHE = VariableCache()
71
72
Zdenek Behan508dcce2011-12-05 15:39:32 +010073def GetPackageMap(target):
74 """Compiles a package map for the given target from the constants.
75
76 Uses a cache in target_version_map, that is dynamically filled in as needed,
77 since here everything is static data and the structuring is for ease of
78 configurability only.
79
80 args:
81 target - the target for which to return a version map
82
83 returns a map between packages and desired versions in internal format
84 (using the PACKAGE_* constants)
85 """
86 if target in target_version_map:
87 return target_version_map[target]
88
89 # Start from copy of the global defaults.
90 result = copy.copy(DEFAULT_TARGET_VERSION_MAP)
91
Zdenek Behanf4d18a02012-03-22 15:45:05 +010092 for pkg in GetTargetPackages(target):
Zdenek Behan508dcce2011-12-05 15:39:32 +010093 # prefer any specific overrides
94 if pkg in TARGET_VERSION_MAP.get(target, {}):
95 result[pkg] = TARGET_VERSION_MAP[target][pkg]
96 else:
97 # finally, if not already set, set a sane default
98 result.setdefault(pkg, DEFAULT_VERSION)
99 target_version_map[target] = result
100 return result
101
102
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100103def GetHostTuple():
104 """Returns compiler tuple for the host system.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100105
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100106 Caches the result, because the command can be fairly expensive, and never
107 changes throughout a single run.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100108 """
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100109 CACHE_ATTR = '_host_tuple'
Zdenek Behan508dcce2011-12-05 15:39:32 +0100110
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100111 val = getattr(VAR_CACHE, CACHE_ATTR, None)
112 if val is None:
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100113 val = portage.settings['CHOST']
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100114 setattr(VAR_CACHE, CACHE_ATTR, val)
115 return val
116
117
118def GetCrossdevConf(target):
119 """Returns a map of crossdev provided variables about a tuple."""
120 CACHE_ATTR = '_target_tuple_map'
121
122 val = getattr(VAR_CACHE, CACHE_ATTR, {})
123 if not target in val:
124 # Find out the crossdev tuple.
125 target_tuple = target
126 if target == 'host':
127 target_tuple = GetHostTuple()
128 # Catch output of crossdev.
129 out = cros_build_lib.RunCommand(['crossdev', '--show-target-cfg',
130 '--ex-gdb', target_tuple],
131 print_cmd=False, redirect_stdout=True).output.splitlines()
132 # List of tuples split at the first '=', converted into dict.
133 val[target] = dict([x.split('=', 1) for x in out])
134 setattr(VAR_CACHE, CACHE_ATTR, {})
135 return val[target]
136
137
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100138def GetTargetPackages(target):
139 """Returns a list of packages for a given target."""
140 conf = GetCrossdevConf(target)
141 # Undesired packages are denoted by empty ${pkg}_pn variable.
142 return [x for x in conf['crosspkgs'].strip("'").split() if conf[x+'_pn']]
143
144
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100145# Portage helper functions:
146def GetPortagePackage(target, package):
147 """Returns a package name for the given target."""
148 conf = GetCrossdevConf(target)
149 # Portage category:
Zdenek Behan508dcce2011-12-05 15:39:32 +0100150 if target == 'host':
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100151 category = conf[package + '_category']
Zdenek Behan508dcce2011-12-05 15:39:32 +0100152 else:
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100153 category = conf['category']
154 # Portage package:
155 pn = conf[package + '_pn']
156 # Final package name:
157 assert(category)
158 assert(pn)
159 return '%s/%s' % (category, pn)
160
161
162def GetPortageKeyword(target):
163 """Returns a portage friendly keyword for a given target."""
164 return GetCrossdevConf(target)['arch']
Zdenek Behan508dcce2011-12-05 15:39:32 +0100165
166
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100167def IsPackageDisabled(target, package):
168 """Returns if the given package is not used for the target."""
169 return GetDesiredPackageVersions(target, package) == [PACKAGE_NONE]
170
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100171
172def GetTuplesForOverlays(overlays):
173 """Returns a set of tuples for a given set of overlays."""
Mike Frysinger7ccee992012-06-01 21:27:59 -0400174 tuples = {}
175 default_settings = {
176 'sdk' : True,
177 'crossdev' : '',
Mike Frysinger47ad3ec2012-06-04 17:41:13 -0400178 'default' : False,
Mike Frysinger7ccee992012-06-01 21:27:59 -0400179 }
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100180
181 for overlay in overlays:
182 config = os.path.join(overlay, 'toolchain.conf')
183 if os.path.exists(config):
Mike Frysinger47ad3ec2012-06-04 17:41:13 -0400184 first_tuple = None
185 seen_default = False
186
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100187 for line in osutils.ReadFile(config).splitlines():
Mike Frysinger7ccee992012-06-01 21:27:59 -0400188 # Split by hash sign so that comments are ignored.
189 # Then split the line to get the tuple and its options.
190 line = line.split('#', 1)[0].split()
191
192 if len(line) > 0:
193 tuple = line[0]
Mike Frysinger47ad3ec2012-06-04 17:41:13 -0400194 if not first_tuple:
195 first_tuple = tuple
Mike Frysinger7ccee992012-06-01 21:27:59 -0400196 if tuple not in tuples:
197 tuples[tuple] = copy.copy(default_settings)
198 if len(line) > 1:
199 tuples[tuple].update(json.loads(' '.join(line[1:])))
Mike Frysinger47ad3ec2012-06-04 17:41:13 -0400200 if tuples[tuple]['default']:
201 seen_default = True
202
203 # If the user has not explicitly marked a toolchain as default,
204 # automatically select the first tuple that we saw in the conf.
205 if not seen_default and first_tuple:
206 tuples[first_tuple]['default'] = True
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100207
208 return tuples
209
210
Zdenek Behan508dcce2011-12-05 15:39:32 +0100211# Tree interface functions. They help with retrieving data about the current
212# state of the tree:
213def GetAllTargets():
214 """Get the complete list of targets.
215
216 returns the list of cross targets for the current tree
217 """
Mike Frysingereeecfda2012-06-01 16:34:50 -0400218 targets = GetToolchainsForBoard('all')
Zdenek Behan508dcce2011-12-05 15:39:32 +0100219
220 # Remove the host target as that is not a cross-target. Replace with 'host'.
Mike Frysinger7ccee992012-06-01 21:27:59 -0400221 del targets[GetHostTuple()]
Zdenek Behan508dcce2011-12-05 15:39:32 +0100222 return targets
223
224
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100225def GetToolchainsForBoard(board):
226 """Get a list of toolchain tuples for a given board name
227
228 returns the list of toolchain tuples for the given board
229 """
Mike Frysingereeecfda2012-06-01 16:34:50 -0400230 cmd = [CROS_OVERLAY_LIST_CMD]
Mike Frysingereaebb582012-06-19 13:04:53 -0400231 if board == 'all' or board == 'sdk':
Mike Frysingereeecfda2012-06-01 16:34:50 -0400232 cmd.append('--all_boards')
233 else:
Mike Frysinger652a6f92012-06-19 03:37:04 -0400234 # TODO(vapier):
235 # Some tools will give us the base board name ('tegra2') while others will
236 # give us the board+variant ('tegra2_kaen'). The cros_overlay_list does
237 # not like getting variant names, so strip that off. Not entirely clear
238 # if this is what we want to do, or if the cros_overlay_list should give
239 # us back the overlays (including variants). I'm inclined to go with the
240 # latter, but that requires more testing to prevent fallout.
241 cmd.append('--board=' + board.split('_')[0])
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100242 overlays = cros_build_lib.RunCommand(
243 cmd, print_cmd=False, redirect_stdout=True).output.splitlines()
244
Mike Frysingereaebb582012-06-19 13:04:53 -0400245 toolchains = GetTuplesForOverlays(overlays)
246 if board == 'sdk':
247 toolchains = FilterToolchains(toolchains, 'sdk', True)
248 return toolchains
249
250
251def FilterToolchains(targets, key, value):
252 """Filter out targets based on their attributes.
253
254 args:
255 targets - dict of toolchains
256 key - metadata to examine
257 value - expected value for metadata
258
259 returns a dict where all targets whose metadata key does not match value
260 have been deleted.
261 """
262 for target, metadata in targets.items():
263 if metadata[key] != value:
264 del targets[target]
265 return targets
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100266
267
Zdenek Behan508dcce2011-12-05 15:39:32 +0100268def GetInstalledPackageVersions(target, package):
269 """Extracts the list of current versions of a target, package pair.
270
271 args:
272 target, package - the target/package to operate on eg. i686-pc-linux-gnu,gcc
273
274 returns the list of versions of the package currently installed.
275 """
Zdenek Behan508dcce2011-12-05 15:39:32 +0100276 versions = []
277 # This is the package name in terms of portage.
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100278 atom = GetPortagePackage(target, package)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100279 for pkg in portage.db['/']['vartree'].dbapi.match(atom):
280 version = portage.versions.cpv_getversion(pkg)
281 versions.append(version)
282 return versions
283
284
Zdenek Behan699ddd32012-04-13 07:14:08 +0200285def GetStablePackageVersion(target, package, installed):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100286 """Extracts the current stable version for a given package.
287
288 args:
289 target, package - the target/package to operate on eg. i686-pc-linux-gnu,gcc
Zdenek Behan699ddd32012-04-13 07:14:08 +0200290 installed - Whether we want installed packages or ebuilds
Zdenek Behan508dcce2011-12-05 15:39:32 +0100291
292 returns a string containing the latest version.
293 """
Zdenek Behan20d6ea22012-04-05 05:15:39 +0200294 def mass_portageq_splitline(entry):
295 """Splits the output of mass_best_visible into package:version tuple."""
296 # mass_best_visible returns lines of the format "package:cpv"
297 split_string = entry.split(':', 1)
Zdenek Behan699ddd32012-04-13 07:14:08 +0200298 if split_string[1]:
299 split_string[1] = portage.versions.cpv_getversion(split_string[1])
Zdenek Behan20d6ea22012-04-05 05:15:39 +0200300 return split_string
301
302 CACHE_ATTR = '_target_stable_map'
303
Zdenek Behan699ddd32012-04-13 07:14:08 +0200304 pkgtype = "installed" if installed else "ebuild"
305
Zdenek Behan20d6ea22012-04-05 05:15:39 +0200306 val = getattr(VAR_CACHE, CACHE_ATTR, {})
307 if not target in val:
Zdenek Behan699ddd32012-04-13 07:14:08 +0200308 val[target] = {}
309 if not pkgtype in val[target]:
Zdenek Behan20d6ea22012-04-05 05:15:39 +0200310 keyword = GetPortageKeyword(target)
311 extra_env = {'ACCEPT_KEYWORDS' : '-* ' + keyword}
312 # Evaluate all packages for a target in one swoop, because it's much faster.
313 pkgs = [GetPortagePackage(target, p) for p in GetTargetPackages(target)]
Zdenek Behan699ddd32012-04-13 07:14:08 +0200314 cmd = ['portageq', 'mass_best_visible', '/', pkgtype] + pkgs
Zdenek Behan20d6ea22012-04-05 05:15:39 +0200315 cpvs = cros_build_lib.RunCommand(cmd,
316 print_cmd=False, redirect_stdout=True,
317 extra_env=extra_env).output.splitlines()
Zdenek Behan699ddd32012-04-13 07:14:08 +0200318 val[target][pkgtype] = dict(map(mass_portageq_splitline, cpvs))
Zdenek Behan20d6ea22012-04-05 05:15:39 +0200319 setattr(VAR_CACHE, CACHE_ATTR, val)
320
Zdenek Behan699ddd32012-04-13 07:14:08 +0200321 return val[target][pkgtype][GetPortagePackage(target, package)]
Zdenek Behan508dcce2011-12-05 15:39:32 +0100322
323
Zdenek Behan699ddd32012-04-13 07:14:08 +0200324def VersionListToNumeric(target, package, versions, installed):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100325 """Resolves keywords in a given version list for a particular package.
326
327 Resolving means replacing PACKAGE_STABLE with the actual number.
328
329 args:
330 target, package - the target/package to operate on eg. i686-pc-linux-gnu,gcc
331 versions - list of versions to resolve
332
333 returns list of purely numeric versions equivalent to argument
334 """
335 resolved = []
336 for version in versions:
337 if version == PACKAGE_STABLE:
Zdenek Behan699ddd32012-04-13 07:14:08 +0200338 resolved.append(GetStablePackageVersion(target, package, installed))
Zdenek Behan508dcce2011-12-05 15:39:32 +0100339 elif version != PACKAGE_NONE:
340 resolved.append(version)
341 return resolved
342
343
344def GetDesiredPackageVersions(target, package):
345 """Produces the list of desired versions for each target, package pair.
346
347 The first version in the list is implicitly treated as primary, ie.
348 the version that will be initialized by crossdev and selected.
349
350 If the version is PACKAGE_STABLE, it really means the current version which
351 is emerged by using the package atom with no particular version key.
352 Since crossdev unmasks all packages by default, this will actually
353 mean 'unstable' in most cases.
354
355 args:
356 target, package - the target/package to operate on eg. i686-pc-linux-gnu,gcc
357
358 returns a list composed of either a version string, PACKAGE_STABLE
359 """
360 packagemap = GetPackageMap(target)
361
362 versions = []
363 if package in packagemap:
364 versions.append(packagemap[package])
365
366 return versions
367
368
369def TargetIsInitialized(target):
370 """Verifies if the given list of targets has been correctly initialized.
371
372 This determines whether we have to call crossdev while emerging
373 toolchain packages or can do it using emerge. Emerge is naturally
374 preferred, because all packages can be updated in a single pass.
375
376 args:
377 targets - list of individual cross targets which are checked
378
379 returns True if target is completely initialized
380 returns False otherwise
381 """
382 # Check if packages for the given target all have a proper version.
383 try:
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100384 for package in GetTargetPackages(target):
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100385 # Do we even want this package && is it initialized?
386 if not IsPackageDisabled(target, package) and \
387 not GetInstalledPackageVersions(target, package):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100388 return False
389 return True
390 except cros_build_lib.RunCommandError:
391 # Fails - The target has likely never been initialized before.
392 return False
393
394
395def RemovePackageMask(target):
396 """Removes a package.mask file for the given platform.
397
398 The pre-existing package.mask files can mess with the keywords.
399
400 args:
401 target - the target for which to remove the file
402 """
403 maskfile = os.path.join('/etc/portage/package.mask', 'cross-' + target)
Brian Harringaf019fb2012-05-10 15:06:13 -0700404 osutils.SafeUnlink(maskfile)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100405
406
407def CreatePackageMask(target, masks):
408 """[Re]creates a package.mask file for the given platform.
409
410 args:
411 target - the given target on which to operate
412 masks - a map of package : version,
413 where version is the highest permissible version (mask >)
414 """
415 maskfile = os.path.join('/etc/portage/package.mask', 'cross-' + target)
416 assert not os.path.exists(maskfile)
Brian Harringaf019fb2012-05-10 15:06:13 -0700417 osutils.SafeMakedirs(os.path.dirname(maskfile))
Zdenek Behan508dcce2011-12-05 15:39:32 +0100418
419 with open(maskfile, 'w') as f:
420 for pkg, m in masks.items():
421 f.write('>%s-%s\n' % (pkg, m))
422
423
424def CreatePackageKeywords(target):
425 """[Re]create a package.keywords file for the platform.
426
427 This sets all package.keywords files to unmask all stable/testing packages.
428 TODO: Note that this approach should be deprecated and is only done for
429 compatibility reasons. In the future, we'd like to stop using keywords
430 altogether, and keep just stable unmasked.
431
432 args:
433 target - target for which to recreate package.keywords
434 """
435 maskfile = os.path.join('/etc/portage/package.keywords', 'cross-' + target)
Brian Harringaf019fb2012-05-10 15:06:13 -0700436 osutils.SafeUnlink(maskfile)
437
438 osutils.SafeMakedirs(os.path.dirname(maskfile))
Zdenek Behan508dcce2011-12-05 15:39:32 +0100439
440 keyword = GetPortageKeyword(target)
441
442 with open(maskfile, 'w') as f:
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100443 for pkg in GetTargetPackages(target):
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100444 if IsPackageDisabled(target, pkg):
445 continue
446 f.write('%s %s ~%s\n' %
447 (GetPortagePackage(target, pkg), keyword, keyword))
Zdenek Behan508dcce2011-12-05 15:39:32 +0100448
449
450# Main functions performing the actual update steps.
Zdenek Behan8be29ba2012-05-29 23:10:34 +0200451def UpdateCrossdevTargets(targets, usepkg, config_only=False):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100452 """Calls crossdev to initialize a cross target.
453 args:
454 targets - the list of targets to initialize using crossdev
455 usepkg - copies the commandline opts
456 """
Zdenek Behan508dcce2011-12-05 15:39:32 +0100457 for target in targets:
Zdenek Behan8be29ba2012-05-29 23:10:34 +0200458 cmd = ['crossdev', '--show-fail-log', '-t', target]
459 cmd.extend(['--env', 'FEATURES=splitdebug'])
Zdenek Behan508dcce2011-12-05 15:39:32 +0100460 # Pick stable by default, and override as necessary.
461 cmd.extend(['-P', '--oneshot'])
462 if usepkg:
463 cmd.extend(['-P', '--getbinpkg',
464 '-P', '--usepkgonly',
465 '--without-headers'])
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100466
467 cmd.extend(['--overlays', '%s %s' % (CHROMIUMOS_OVERLAY, STABLE_OVERLAY)])
468 cmd.extend(['--ov-output', CROSSDEV_OVERLAY])
Zdenek Behan508dcce2011-12-05 15:39:32 +0100469
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100470 for pkg in GetTargetPackages(target):
471 if pkg == 'gdb':
472 # Gdb does not have selectable versions.
473 cmd.append('--ex-gdb')
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100474 continue
Zdenek Behan508dcce2011-12-05 15:39:32 +0100475 # The first of the desired versions is the "primary" one.
476 version = GetDesiredPackageVersions(target, pkg)[0]
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100477 cmd.extend(['--%s' % pkg, version])
Zdenek Behan8be29ba2012-05-29 23:10:34 +0200478
Mike Frysinger7ccee992012-06-01 21:27:59 -0400479 cmd.extend(targets[target]['crossdev'].split())
Zdenek Behan8be29ba2012-05-29 23:10:34 +0200480 if config_only:
481 # In this case we want to just quietly reinit
482 cmd.append('--init-target')
483 cros_build_lib.RunCommand(cmd, print_cmd=False, redirect_stdout=True)
484 else:
485 cros_build_lib.RunCommand(cmd)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100486
487
488def UpdateTargets(targets, usepkg):
489 """Determines which packages need update/unmerge and defers to portage.
490
491 args:
492 targets - the list of targets to update
493 usepkg - copies the commandline option
494 """
495 # TODO(zbehan): This process is rather complex due to package.* handling.
496 # With some semantic changes over the original setup_board functionality,
497 # it can be considerably cleaned up.
498 mergemap = {}
499
500 # For each target, we do two things. Figure out the list of updates,
501 # and figure out the appropriate keywords/masks. Crossdev will initialize
502 # these, but they need to be regenerated on every update.
503 print 'Determining required toolchain updates...'
504 for target in targets:
505 # Record the highest needed version for each target, for masking purposes.
506 RemovePackageMask(target)
507 CreatePackageKeywords(target)
508 packagemasks = {}
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100509 for package in GetTargetPackages(target):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100510 # Portage name for the package
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100511 if IsPackageDisabled(target, package):
512 continue
513 pkg = GetPortagePackage(target, package)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100514 current = GetInstalledPackageVersions(target, package)
515 desired = GetDesiredPackageVersions(target, package)
Zdenek Behan699ddd32012-04-13 07:14:08 +0200516 desired_num = VersionListToNumeric(target, package, desired, False)
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100517 mergemap[pkg] = set(desired_num).difference(current)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100518
519 # Pick the highest version for mask.
520 packagemasks[pkg] = portage.versions.best(desired_num)
521
522 CreatePackageMask(target, packagemasks)
523
524 packages = []
525 for pkg in mergemap:
526 for ver in mergemap[pkg]:
Zdenek Behan677b6d82012-04-11 05:31:47 +0200527 if ver != PACKAGE_NONE:
528 # Be a little more permissive for usepkg, the binaries may not exist.
529 packages.append('%s%s-%s' % ('<=' if usepkg else '=', pkg, ver))
Zdenek Behan508dcce2011-12-05 15:39:32 +0100530
531 if not packages:
532 print 'Nothing to update!'
533 return
534
535 print 'Updating packages:'
536 print packages
537
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100538 cmd = [EMERGE_CMD, '--oneshot', '--update']
Zdenek Behan508dcce2011-12-05 15:39:32 +0100539 if usepkg:
540 cmd.extend(['--getbinpkg', '--usepkgonly'])
541
542 cmd.extend(packages)
543 cros_build_lib.RunCommand(cmd)
544
545
546def CleanTargets(targets):
547 """Unmerges old packages that are assumed unnecessary."""
548 unmergemap = {}
549 for target in targets:
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100550 for package in GetTargetPackages(target):
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100551 if IsPackageDisabled(target, package):
552 continue
553 pkg = GetPortagePackage(target, package)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100554 current = GetInstalledPackageVersions(target, package)
555 desired = GetDesiredPackageVersions(target, package)
Zdenek Behan699ddd32012-04-13 07:14:08 +0200556 desired_num = VersionListToNumeric(target, package, desired, True)
557 if not set(desired_num).issubset(current):
558 print 'Some packages have been held back, skipping clean!'
559 return
Zdenek Behan508dcce2011-12-05 15:39:32 +0100560 unmergemap[pkg] = set(current).difference(desired_num)
561
562 # Cleaning doesn't care about consistency and rebuilding package.* files.
563 packages = []
564 for pkg, vers in unmergemap.iteritems():
565 packages.extend('=%s-%s' % (pkg, ver) for ver in vers if ver != '9999')
566
567 if packages:
568 print 'Cleaning packages:'
569 print packages
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100570 cmd = [EMERGE_CMD, '--unmerge']
Zdenek Behan508dcce2011-12-05 15:39:32 +0100571 cmd.extend(packages)
572 cros_build_lib.RunCommand(cmd)
573 else:
574 print 'Nothing to clean!'
575
576
577def SelectActiveToolchains(targets, suffixes):
578 """Runs gcc-config and binutils-config to select the desired.
579
580 args:
581 targets - the targets to select
582 """
583 for package in ['gcc', 'binutils']:
584 for target in targets:
585 # Pick the first version in the numbered list as the selected one.
586 desired = GetDesiredPackageVersions(target, package)
Zdenek Behan699ddd32012-04-13 07:14:08 +0200587 desired_num = VersionListToNumeric(target, package, desired, True)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100588 desired = desired_num[0]
589 # *-config does not play revisions, strip them, keep just PV.
590 desired = portage.versions.pkgsplit('%s-%s' % (package, desired))[1]
591
592 if target == 'host':
593 # *-config is the only tool treating host identically (by tuple).
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100594 target = GetHostTuple()
Zdenek Behan508dcce2011-12-05 15:39:32 +0100595
596 # And finally, attach target to it.
597 desired = '%s-%s' % (target, desired)
598
599 # Target specific hacks
600 if package in suffixes:
601 if target in suffixes[package]:
602 desired += suffixes[package][target]
603
604 cmd = [ package + '-config', '-c', target ]
605 current = cros_build_lib.RunCommand(cmd, print_cmd=False,
Zdenek Behan699ddd32012-04-13 07:14:08 +0200606 redirect_stdout=True).output.splitlines()[0]
Zdenek Behan508dcce2011-12-05 15:39:32 +0100607 # Do not gcc-config when the current is live or nothing needs to be done.
608 if current != desired and current != '9999':
609 cmd = [ package + '-config', desired ]
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100610 cros_build_lib.RunCommand(cmd, print_cmd=False)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100611
612
Mike Frysinger7ccee992012-06-01 21:27:59 -0400613def UpdateToolchains(usepkg, deleteold, hostonly, targets_wanted,
614 boards_wanted):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100615 """Performs all steps to create a synchronized toolchain enviroment.
616
617 args:
618 arguments correspond to the given commandline flags
619 """
Mike Frysinger7ccee992012-06-01 21:27:59 -0400620 targets = {}
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100621 if not hostonly:
622 # For hostonly, we can skip most of the below logic, much of which won't
623 # work on bare systems where this is useful.
624 alltargets = GetAllTargets()
Mike Frysinger7ccee992012-06-01 21:27:59 -0400625 targets_wanted = set(targets_wanted)
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100626 if targets_wanted == set(['all']):
Mike Frysinger7ccee992012-06-01 21:27:59 -0400627 targets = alltargets
Mike Frysingereaebb582012-06-19 13:04:53 -0400628 elif targets_wanted == set(['sdk']):
629 # Filter out all the non-sdk toolchains as we don't want to mess
630 # with those in all of our builds.
631 targets = FilterToolchains(alltargets, 'sdk', True)
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100632 else:
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100633 # Verify user input.
Mike Frysinger7ccee992012-06-01 21:27:59 -0400634 nonexistant = []
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100635 for target in targets_wanted:
636 if target not in alltargets:
637 nonexistant.append(target)
Mike Frysinger7ccee992012-06-01 21:27:59 -0400638 else:
639 targets[target] = alltargets[target]
640 if nonexistant:
641 cros_build_lib.Die('Invalid targets: ' + ','.join(nonexistant))
642
Mike Frysinger7ccee992012-06-01 21:27:59 -0400643 # Now re-add any targets that might be from this board. This is
644 # to allow unofficial boards to declare their own toolchains.
645 for board in boards_wanted:
646 targets.update(GetToolchainsForBoard(board))
Zdenek Behan508dcce2011-12-05 15:39:32 +0100647
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100648 # First check and initialize all cross targets that need to be.
Mike Frysinger7ccee992012-06-01 21:27:59 -0400649 crossdev_targets = {}
650 reconfig_targets = {}
651 for target in targets:
652 if TargetIsInitialized(target):
653 reconfig_targets[target] = targets[target]
654 else:
655 crossdev_targets[target] = targets[target]
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100656 if crossdev_targets:
Zdenek Behan8be29ba2012-05-29 23:10:34 +0200657 print 'The following targets need to be re-initialized:'
658 print crossdev_targets
659 UpdateCrossdevTargets(crossdev_targets, usepkg)
660 # Those that were not initialized may need a config update.
Zdenek Behan8be29ba2012-05-29 23:10:34 +0200661 UpdateCrossdevTargets(reconfig_targets, usepkg, config_only=True)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100662
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100663 # We want host updated.
Mike Frysinger7ccee992012-06-01 21:27:59 -0400664 targets['host'] = {}
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100665
666 # Now update all packages.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100667 UpdateTargets(targets, usepkg)
668 SelectActiveToolchains(targets, CONFIG_TARGET_SUFFIXES)
669
670 if deleteold:
671 CleanTargets(targets)
672
673
Brian Harring30675052012-02-29 12:18:22 -0800674def main(argv):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100675 usage = """usage: %prog [options]
676
677 The script installs and updates the toolchains in your chroot.
678 """
679 parser = optparse.OptionParser(usage)
680 parser.add_option('-u', '--nousepkg',
681 action='store_false', dest='usepkg', default=True,
682 help=('Use prebuilt packages if possible.'))
683 parser.add_option('-d', '--deleteold',
684 action='store_true', dest='deleteold', default=False,
685 help=('Unmerge deprecated packages.'))
686 parser.add_option('-t', '--targets',
Mike Frysingereaebb582012-06-19 13:04:53 -0400687 dest='targets', default='sdk',
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100688 help=('Comma separated list of tuples. '
Mike Frysingereaebb582012-06-19 13:04:53 -0400689 'Special keyword \'host\' is allowed. Default: sdk.'))
Mike Frysinger7ccee992012-06-01 21:27:59 -0400690 parser.add_option('--include-boards',
691 dest='include_boards', default='',
692 help=('Comma separated list of boards whose toolchains we'
693 ' will always include. Default: none.'))
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100694 parser.add_option('--hostonly',
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100695 dest='hostonly', default=False, action='store_true',
696 help=('Only setup the host toolchain. '
697 'Useful for bootstrapping chroot.'))
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100698 parser.add_option('--show-board-cfg',
699 dest='board_cfg', default=None,
700 help=('Board to list toolchain tuples for'))
Zdenek Behan508dcce2011-12-05 15:39:32 +0100701
Brian Harring30675052012-02-29 12:18:22 -0800702 (options, _remaining_arguments) = parser.parse_args(argv)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100703
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100704 if options.board_cfg:
Mike Frysinger47ad3ec2012-06-04 17:41:13 -0400705 toolchains = GetToolchainsForBoard(options.board_cfg)
706 # Make sure we display the default toolchain first.
707 tuples = toolchains.keys()
708 for tuple in tuples:
709 if toolchains[tuple]['default']:
710 tuples.remove(tuple)
711 tuples.insert(0, tuple)
712 break
713 print ','.join(tuples)
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100714 return 0
715
Mike Frysinger984d0622012-06-01 16:08:44 -0400716 # This has to be always run as root.
717 if not os.getuid() == 0:
718 print "%s: This script must be run as root!" % sys.argv[0]
719 sys.exit(1)
720
Zdenek Behan508dcce2011-12-05 15:39:32 +0100721 targets = set(options.targets.split(','))
Mike Frysinger7ccee992012-06-01 21:27:59 -0400722 boards = set(options.include_boards.split(',')) if options.include_boards \
723 else set()
724 UpdateToolchains(options.usepkg, options.deleteold, options.hostonly, targets,
725 boards)