blob: 459cff4c1230b046c85e27254f7519e9c18f74d5 [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 Behane6fd5f82012-08-16 18:50:31 +0200457 cmdbase = ['crossdev', '--show-fail-log']
458 cmdbase.extend(['--env', 'FEATURES=splitdebug'])
459 # Pick stable by default, and override as necessary.
460 cmdbase.extend(['-P', '--oneshot'])
461 if usepkg:
462 cmdbase.extend(['-P', '--getbinpkg',
463 '-P', '--usepkgonly',
464 '--without-headers'])
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100465
Zdenek Behane6fd5f82012-08-16 18:50:31 +0200466 cmdbase.extend(['--overlays', '%s %s' % (CHROMIUMOS_OVERLAY, STABLE_OVERLAY)])
467 cmdbase.extend(['--ov-output', CROSSDEV_OVERLAY])
468
469 for target in targets:
470 cmd = cmdbase + ['-t', target]
Zdenek Behan508dcce2011-12-05 15:39:32 +0100471
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100472 for pkg in GetTargetPackages(target):
473 if pkg == 'gdb':
474 # Gdb does not have selectable versions.
475 cmd.append('--ex-gdb')
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100476 continue
Zdenek Behan508dcce2011-12-05 15:39:32 +0100477 # The first of the desired versions is the "primary" one.
478 version = GetDesiredPackageVersions(target, pkg)[0]
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100479 cmd.extend(['--%s' % pkg, version])
Zdenek Behan8be29ba2012-05-29 23:10:34 +0200480
Mike Frysinger7ccee992012-06-01 21:27:59 -0400481 cmd.extend(targets[target]['crossdev'].split())
Zdenek Behan8be29ba2012-05-29 23:10:34 +0200482 if config_only:
483 # In this case we want to just quietly reinit
484 cmd.append('--init-target')
485 cros_build_lib.RunCommand(cmd, print_cmd=False, redirect_stdout=True)
486 else:
487 cros_build_lib.RunCommand(cmd)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100488
489
490def UpdateTargets(targets, usepkg):
491 """Determines which packages need update/unmerge and defers to portage.
492
493 args:
494 targets - the list of targets to update
495 usepkg - copies the commandline option
496 """
497 # TODO(zbehan): This process is rather complex due to package.* handling.
498 # With some semantic changes over the original setup_board functionality,
499 # it can be considerably cleaned up.
500 mergemap = {}
501
502 # For each target, we do two things. Figure out the list of updates,
503 # and figure out the appropriate keywords/masks. Crossdev will initialize
504 # these, but they need to be regenerated on every update.
505 print 'Determining required toolchain updates...'
506 for target in targets:
507 # Record the highest needed version for each target, for masking purposes.
508 RemovePackageMask(target)
509 CreatePackageKeywords(target)
510 packagemasks = {}
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100511 for package in GetTargetPackages(target):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100512 # Portage name for the package
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100513 if IsPackageDisabled(target, package):
514 continue
515 pkg = GetPortagePackage(target, package)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100516 current = GetInstalledPackageVersions(target, package)
517 desired = GetDesiredPackageVersions(target, package)
Zdenek Behan699ddd32012-04-13 07:14:08 +0200518 desired_num = VersionListToNumeric(target, package, desired, False)
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100519 mergemap[pkg] = set(desired_num).difference(current)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100520
521 # Pick the highest version for mask.
522 packagemasks[pkg] = portage.versions.best(desired_num)
523
524 CreatePackageMask(target, packagemasks)
525
526 packages = []
527 for pkg in mergemap:
528 for ver in mergemap[pkg]:
Zdenek Behan677b6d82012-04-11 05:31:47 +0200529 if ver != PACKAGE_NONE:
530 # Be a little more permissive for usepkg, the binaries may not exist.
531 packages.append('%s%s-%s' % ('<=' if usepkg else '=', pkg, ver))
Zdenek Behan508dcce2011-12-05 15:39:32 +0100532
533 if not packages:
534 print 'Nothing to update!'
535 return
536
537 print 'Updating packages:'
538 print packages
539
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100540 cmd = [EMERGE_CMD, '--oneshot', '--update']
Zdenek Behan508dcce2011-12-05 15:39:32 +0100541 if usepkg:
542 cmd.extend(['--getbinpkg', '--usepkgonly'])
543
544 cmd.extend(packages)
545 cros_build_lib.RunCommand(cmd)
546
547
548def CleanTargets(targets):
549 """Unmerges old packages that are assumed unnecessary."""
550 unmergemap = {}
551 for target in targets:
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100552 for package in GetTargetPackages(target):
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100553 if IsPackageDisabled(target, package):
554 continue
555 pkg = GetPortagePackage(target, package)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100556 current = GetInstalledPackageVersions(target, package)
557 desired = GetDesiredPackageVersions(target, package)
Zdenek Behan699ddd32012-04-13 07:14:08 +0200558 desired_num = VersionListToNumeric(target, package, desired, True)
559 if not set(desired_num).issubset(current):
560 print 'Some packages have been held back, skipping clean!'
561 return
Zdenek Behan508dcce2011-12-05 15:39:32 +0100562 unmergemap[pkg] = set(current).difference(desired_num)
563
564 # Cleaning doesn't care about consistency and rebuilding package.* files.
565 packages = []
566 for pkg, vers in unmergemap.iteritems():
567 packages.extend('=%s-%s' % (pkg, ver) for ver in vers if ver != '9999')
568
569 if packages:
570 print 'Cleaning packages:'
571 print packages
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100572 cmd = [EMERGE_CMD, '--unmerge']
Zdenek Behan508dcce2011-12-05 15:39:32 +0100573 cmd.extend(packages)
574 cros_build_lib.RunCommand(cmd)
575 else:
576 print 'Nothing to clean!'
577
578
579def SelectActiveToolchains(targets, suffixes):
580 """Runs gcc-config and binutils-config to select the desired.
581
582 args:
583 targets - the targets to select
584 """
585 for package in ['gcc', 'binutils']:
586 for target in targets:
587 # Pick the first version in the numbered list as the selected one.
588 desired = GetDesiredPackageVersions(target, package)
Zdenek Behan699ddd32012-04-13 07:14:08 +0200589 desired_num = VersionListToNumeric(target, package, desired, True)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100590 desired = desired_num[0]
591 # *-config does not play revisions, strip them, keep just PV.
592 desired = portage.versions.pkgsplit('%s-%s' % (package, desired))[1]
593
594 if target == 'host':
595 # *-config is the only tool treating host identically (by tuple).
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100596 target = GetHostTuple()
Zdenek Behan508dcce2011-12-05 15:39:32 +0100597
598 # And finally, attach target to it.
599 desired = '%s-%s' % (target, desired)
600
601 # Target specific hacks
602 if package in suffixes:
603 if target in suffixes[package]:
604 desired += suffixes[package][target]
605
606 cmd = [ package + '-config', '-c', target ]
607 current = cros_build_lib.RunCommand(cmd, print_cmd=False,
Zdenek Behan699ddd32012-04-13 07:14:08 +0200608 redirect_stdout=True).output.splitlines()[0]
Zdenek Behan508dcce2011-12-05 15:39:32 +0100609 # Do not gcc-config when the current is live or nothing needs to be done.
610 if current != desired and current != '9999':
611 cmd = [ package + '-config', desired ]
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100612 cros_build_lib.RunCommand(cmd, print_cmd=False)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100613
614
Mike Frysinger7ccee992012-06-01 21:27:59 -0400615def UpdateToolchains(usepkg, deleteold, hostonly, targets_wanted,
616 boards_wanted):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100617 """Performs all steps to create a synchronized toolchain enviroment.
618
619 args:
620 arguments correspond to the given commandline flags
621 """
Mike Frysinger7ccee992012-06-01 21:27:59 -0400622 targets = {}
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100623 if not hostonly:
624 # For hostonly, we can skip most of the below logic, much of which won't
625 # work on bare systems where this is useful.
626 alltargets = GetAllTargets()
Mike Frysinger7ccee992012-06-01 21:27:59 -0400627 targets_wanted = set(targets_wanted)
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100628 if targets_wanted == set(['all']):
Mike Frysinger7ccee992012-06-01 21:27:59 -0400629 targets = alltargets
Mike Frysingereaebb582012-06-19 13:04:53 -0400630 elif targets_wanted == set(['sdk']):
631 # Filter out all the non-sdk toolchains as we don't want to mess
632 # with those in all of our builds.
633 targets = FilterToolchains(alltargets, 'sdk', True)
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100634 else:
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100635 # Verify user input.
Mike Frysinger7ccee992012-06-01 21:27:59 -0400636 nonexistant = []
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100637 for target in targets_wanted:
638 if target not in alltargets:
639 nonexistant.append(target)
Mike Frysinger7ccee992012-06-01 21:27:59 -0400640 else:
641 targets[target] = alltargets[target]
642 if nonexistant:
643 cros_build_lib.Die('Invalid targets: ' + ','.join(nonexistant))
644
Mike Frysinger7ccee992012-06-01 21:27:59 -0400645 # Now re-add any targets that might be from this board. This is
646 # to allow unofficial boards to declare their own toolchains.
647 for board in boards_wanted:
648 targets.update(GetToolchainsForBoard(board))
Zdenek Behan508dcce2011-12-05 15:39:32 +0100649
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100650 # First check and initialize all cross targets that need to be.
Mike Frysinger7ccee992012-06-01 21:27:59 -0400651 crossdev_targets = {}
652 reconfig_targets = {}
653 for target in targets:
654 if TargetIsInitialized(target):
655 reconfig_targets[target] = targets[target]
656 else:
657 crossdev_targets[target] = targets[target]
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100658 if crossdev_targets:
Zdenek Behan8be29ba2012-05-29 23:10:34 +0200659 print 'The following targets need to be re-initialized:'
660 print crossdev_targets
661 UpdateCrossdevTargets(crossdev_targets, usepkg)
662 # Those that were not initialized may need a config update.
Zdenek Behan8be29ba2012-05-29 23:10:34 +0200663 UpdateCrossdevTargets(reconfig_targets, usepkg, config_only=True)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100664
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100665 # We want host updated.
Mike Frysinger7ccee992012-06-01 21:27:59 -0400666 targets['host'] = {}
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100667
668 # Now update all packages.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100669 UpdateTargets(targets, usepkg)
670 SelectActiveToolchains(targets, CONFIG_TARGET_SUFFIXES)
671
672 if deleteold:
673 CleanTargets(targets)
674
675
Brian Harring30675052012-02-29 12:18:22 -0800676def main(argv):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100677 usage = """usage: %prog [options]
678
679 The script installs and updates the toolchains in your chroot.
680 """
681 parser = optparse.OptionParser(usage)
682 parser.add_option('-u', '--nousepkg',
683 action='store_false', dest='usepkg', default=True,
684 help=('Use prebuilt packages if possible.'))
685 parser.add_option('-d', '--deleteold',
686 action='store_true', dest='deleteold', default=False,
687 help=('Unmerge deprecated packages.'))
688 parser.add_option('-t', '--targets',
Mike Frysingereaebb582012-06-19 13:04:53 -0400689 dest='targets', default='sdk',
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100690 help=('Comma separated list of tuples. '
Mike Frysingereaebb582012-06-19 13:04:53 -0400691 'Special keyword \'host\' is allowed. Default: sdk.'))
Mike Frysinger7ccee992012-06-01 21:27:59 -0400692 parser.add_option('--include-boards',
693 dest='include_boards', default='',
694 help=('Comma separated list of boards whose toolchains we'
695 ' will always include. Default: none.'))
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100696 parser.add_option('--hostonly',
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100697 dest='hostonly', default=False, action='store_true',
698 help=('Only setup the host toolchain. '
699 'Useful for bootstrapping chroot.'))
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100700 parser.add_option('--show-board-cfg',
701 dest='board_cfg', default=None,
702 help=('Board to list toolchain tuples for'))
Zdenek Behan508dcce2011-12-05 15:39:32 +0100703
Brian Harring30675052012-02-29 12:18:22 -0800704 (options, _remaining_arguments) = parser.parse_args(argv)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100705
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100706 if options.board_cfg:
Mike Frysinger47ad3ec2012-06-04 17:41:13 -0400707 toolchains = GetToolchainsForBoard(options.board_cfg)
708 # Make sure we display the default toolchain first.
709 tuples = toolchains.keys()
710 for tuple in tuples:
711 if toolchains[tuple]['default']:
712 tuples.remove(tuple)
713 tuples.insert(0, tuple)
714 break
715 print ','.join(tuples)
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100716 return 0
717
Mike Frysinger984d0622012-06-01 16:08:44 -0400718 # This has to be always run as root.
719 if not os.getuid() == 0:
720 print "%s: This script must be run as root!" % sys.argv[0]
721 sys.exit(1)
722
Zdenek Behan508dcce2011-12-05 15:39:32 +0100723 targets = set(options.targets.split(','))
Mike Frysinger7ccee992012-06-01 21:27:59 -0400724 boards = set(options.include_boards.split(',')) if options.include_boards \
725 else set()
726 UpdateToolchains(options.usepkg, options.deleteold, options.hostonly, targets,
727 boards)