blob: 66385ead1487fb93043bf6502e20524580ae6b12 [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 Frysinger7ccee992012-06-01 21:27:59 -040010import json
Zdenek Behan508dcce2011-12-05 15:39:32 +010011import optparse
12import os
13import sys
14
Brian Harring503f3ab2012-03-09 21:39:41 -080015from chromite.buildbot import constants
David Jamese5867812012-10-19 12:02:20 -070016from chromite.buildbot import portage_utilities
Brian Harring503f3ab2012-03-09 21:39:41 -080017from 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 Behan508dcce2011-12-05 15:39:32 +010030PACKAGE_STABLE = '[stable]'
31PACKAGE_NONE = '[none]'
32SRC_ROOT = os.path.realpath(constants.SOURCE_ROOT)
Zdenek Behan4eb6fd22012-03-12 17:00:56 +010033
34CHROMIUMOS_OVERLAY = '/usr/local/portage/chromiumos'
35STABLE_OVERLAY = '/usr/local/portage/stable'
36CROSSDEV_OVERLAY = '/usr/local/portage/crossdev'
Zdenek Behan508dcce2011-12-05 15:39:32 +010037
38
39# TODO: The versions are stored here very much like in setup_board.
40# The goal for future is to differentiate these using a config file.
41# This is done essentially by messing with GetDesiredPackageVersions()
42DEFAULT_VERSION = PACKAGE_STABLE
43DEFAULT_TARGET_VERSION_MAP = {
Zdenek Behan508dcce2011-12-05 15:39:32 +010044}
45TARGET_VERSION_MAP = {
Zdenek Behan508dcce2011-12-05 15:39:32 +010046 'host' : {
47 'binutils' : '2.21.1',
48 'gdb' : PACKAGE_NONE,
49 },
50}
51# Overrides for {gcc,binutils}-config, pick a package with particular suffix.
52CONFIG_TARGET_SUFFIXES = {
53 'binutils' : {
54 'i686-pc-linux-gnu' : '-gold',
55 'x86_64-cros-linux-gnu' : '-gold',
56 },
57}
Zdenek Behan508dcce2011-12-05 15:39:32 +010058# Global per-run cache that will be filled ondemand in by GetPackageMap()
59# function as needed.
60target_version_map = {
61}
62
63
Zdenek Behan4eb6fd22012-03-12 17:00:56 +010064# Global variable cache. It has to be a descendant of 'object', rather than
65# instance thereof, because attributes cannot be set on 'object' instances.
66class VariableCache(object):
67 pass
68VAR_CACHE = VariableCache()
69
David James66a09c42012-11-05 13:31:38 -080070class Crossdev(object):
71 """Class for interacting with crossdev and caching its output."""
72
73 _CACHE_FILE = os.path.join(CROSSDEV_OVERLAY, '.configured.json')
74 _CACHE = {}
75
76 @classmethod
77 def Load(cls, reconfig):
78 """Load crossdev cache from disk."""
79 crossdev_versions = GetInstalledPackageVersions('sys-devel/crossdev')
80 cls._CACHE = {'crossdev_versions': crossdev_versions}
81 if os.path.exists(cls._CACHE_FILE) and not reconfig:
82 with open(cls._CACHE_FILE) as f:
83 data = json.load(f)
84 if crossdev_versions == data['crossdev_versions']:
85 cls._CACHE = data
86
87 @classmethod
88 def Save(cls):
89 """Store crossdev cache on disk."""
90 # Save the cache from the successful run.
91 with open(cls._CACHE_FILE, 'w') as f:
92 json.dump(cls._CACHE, f)
93
94 @classmethod
95 def GetConfig(cls, target):
96 """Returns a map of crossdev provided variables about a tuple."""
97 CACHE_ATTR = '_target_tuple_map'
98
99 val = cls._CACHE.setdefault(CACHE_ATTR, {})
100 if not target in val:
101 # Find out the crossdev tuple.
102 target_tuple = target
103 if target == 'host':
104 target_tuple = GetHostTuple()
105 # Catch output of crossdev.
106 out = cros_build_lib.RunCommand(['crossdev', '--show-target-cfg',
107 '--ex-gdb', target_tuple],
108 print_cmd=False, redirect_stdout=True).output.splitlines()
109 # List of tuples split at the first '=', converted into dict.
110 val[target] = dict([x.split('=', 1) for x in out])
111 return val[target]
112
113 @classmethod
114 def UpdateTargets(cls, targets, usepkg, config_only=False):
115 """Calls crossdev to initialize a cross target.
116
117 Args:
118 targets - the list of targets to initialize using crossdev
119 usepkg - copies the commandline opts
120 config_only - Just update
121 """
122 configured_targets = cls._CACHE.setdefault('configured_targets', [])
123
124 cmdbase = ['crossdev', '--show-fail-log']
125 cmdbase.extend(['--env', 'FEATURES=splitdebug'])
126 # Pick stable by default, and override as necessary.
127 cmdbase.extend(['-P', '--oneshot'])
128 if usepkg:
129 cmdbase.extend(['-P', '--getbinpkg',
130 '-P', '--usepkgonly',
131 '--without-headers'])
132
133 overlays = '%s %s' % (CHROMIUMOS_OVERLAY, STABLE_OVERLAY)
134 cmdbase.extend(['--overlays', overlays])
135 cmdbase.extend(['--ov-output', CROSSDEV_OVERLAY])
136
137 for target in targets:
138 if config_only and target in configured_targets:
139 continue
140
141 cmd = cmdbase + ['-t', target]
142
143 for pkg in GetTargetPackages(target):
144 if pkg == 'gdb':
145 # Gdb does not have selectable versions.
146 cmd.append('--ex-gdb')
147 continue
148 # The first of the desired versions is the "primary" one.
149 version = GetDesiredPackageVersions(target, pkg)[0]
150 cmd.extend(['--%s' % pkg, version])
151
152 cmd.extend(targets[target]['crossdev'].split())
153 if config_only:
154 # In this case we want to just quietly reinit
155 cmd.append('--init-target')
156 cros_build_lib.RunCommand(cmd, print_cmd=False, redirect_stdout=True)
157 else:
158 cros_build_lib.RunCommand(cmd)
159
160 configured_targets.append(target)
161
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100162
Zdenek Behan508dcce2011-12-05 15:39:32 +0100163def GetPackageMap(target):
164 """Compiles a package map for the given target from the constants.
165
166 Uses a cache in target_version_map, that is dynamically filled in as needed,
167 since here everything is static data and the structuring is for ease of
168 configurability only.
169
170 args:
171 target - the target for which to return a version map
172
173 returns a map between packages and desired versions in internal format
174 (using the PACKAGE_* constants)
175 """
176 if target in target_version_map:
177 return target_version_map[target]
178
179 # Start from copy of the global defaults.
180 result = copy.copy(DEFAULT_TARGET_VERSION_MAP)
181
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100182 for pkg in GetTargetPackages(target):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100183 # prefer any specific overrides
184 if pkg in TARGET_VERSION_MAP.get(target, {}):
185 result[pkg] = TARGET_VERSION_MAP[target][pkg]
186 else:
187 # finally, if not already set, set a sane default
188 result.setdefault(pkg, DEFAULT_VERSION)
189 target_version_map[target] = result
190 return result
191
192
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100193def GetHostTuple():
194 """Returns compiler tuple for the host system.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100195
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100196 Caches the result, because the command can be fairly expensive, and never
197 changes throughout a single run.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100198 """
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100199 CACHE_ATTR = '_host_tuple'
Zdenek Behan508dcce2011-12-05 15:39:32 +0100200
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100201 val = getattr(VAR_CACHE, CACHE_ATTR, None)
202 if val is None:
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100203 val = portage.settings['CHOST']
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100204 setattr(VAR_CACHE, CACHE_ATTR, val)
205 return val
206
207
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100208def GetTargetPackages(target):
209 """Returns a list of packages for a given target."""
David James66a09c42012-11-05 13:31:38 -0800210 conf = Crossdev.GetConfig(target)
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100211 # Undesired packages are denoted by empty ${pkg}_pn variable.
212 return [x for x in conf['crosspkgs'].strip("'").split() if conf[x+'_pn']]
213
214
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100215# Portage helper functions:
216def GetPortagePackage(target, package):
217 """Returns a package name for the given target."""
David James66a09c42012-11-05 13:31:38 -0800218 conf = Crossdev.GetConfig(target)
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100219 # Portage category:
Zdenek Behan508dcce2011-12-05 15:39:32 +0100220 if target == 'host':
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100221 category = conf[package + '_category']
Zdenek Behan508dcce2011-12-05 15:39:32 +0100222 else:
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100223 category = conf['category']
224 # Portage package:
225 pn = conf[package + '_pn']
226 # Final package name:
227 assert(category)
228 assert(pn)
229 return '%s/%s' % (category, pn)
230
231
232def GetPortageKeyword(target):
233 """Returns a portage friendly keyword for a given target."""
David James66a09c42012-11-05 13:31:38 -0800234 return Crossdev.GetConfig(target)['arch']
Zdenek Behan508dcce2011-12-05 15:39:32 +0100235
236
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100237def IsPackageDisabled(target, package):
238 """Returns if the given package is not used for the target."""
239 return GetDesiredPackageVersions(target, package) == [PACKAGE_NONE]
240
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100241
242def GetTuplesForOverlays(overlays):
243 """Returns a set of tuples for a given set of overlays."""
Mike Frysinger7ccee992012-06-01 21:27:59 -0400244 tuples = {}
245 default_settings = {
246 'sdk' : True,
247 'crossdev' : '',
Mike Frysinger47ad3ec2012-06-04 17:41:13 -0400248 'default' : False,
Mike Frysinger7ccee992012-06-01 21:27:59 -0400249 }
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100250
251 for overlay in overlays:
252 config = os.path.join(overlay, 'toolchain.conf')
253 if os.path.exists(config):
Mike Frysinger47ad3ec2012-06-04 17:41:13 -0400254 first_tuple = None
255 seen_default = False
256
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100257 for line in osutils.ReadFile(config).splitlines():
Mike Frysinger7ccee992012-06-01 21:27:59 -0400258 # Split by hash sign so that comments are ignored.
259 # Then split the line to get the tuple and its options.
260 line = line.split('#', 1)[0].split()
261
262 if len(line) > 0:
263 tuple = line[0]
Mike Frysinger47ad3ec2012-06-04 17:41:13 -0400264 if not first_tuple:
265 first_tuple = tuple
Mike Frysinger7ccee992012-06-01 21:27:59 -0400266 if tuple not in tuples:
267 tuples[tuple] = copy.copy(default_settings)
268 if len(line) > 1:
269 tuples[tuple].update(json.loads(' '.join(line[1:])))
Mike Frysinger47ad3ec2012-06-04 17:41:13 -0400270 if tuples[tuple]['default']:
271 seen_default = True
272
273 # If the user has not explicitly marked a toolchain as default,
274 # automatically select the first tuple that we saw in the conf.
275 if not seen_default and first_tuple:
276 tuples[first_tuple]['default'] = True
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100277
278 return tuples
279
280
Zdenek Behan508dcce2011-12-05 15:39:32 +0100281# Tree interface functions. They help with retrieving data about the current
282# state of the tree:
283def GetAllTargets():
284 """Get the complete list of targets.
285
286 returns the list of cross targets for the current tree
287 """
Mike Frysingereeecfda2012-06-01 16:34:50 -0400288 targets = GetToolchainsForBoard('all')
Zdenek Behan508dcce2011-12-05 15:39:32 +0100289
290 # Remove the host target as that is not a cross-target. Replace with 'host'.
Mike Frysinger7ccee992012-06-01 21:27:59 -0400291 del targets[GetHostTuple()]
Zdenek Behan508dcce2011-12-05 15:39:32 +0100292 return targets
293
294
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100295def GetToolchainsForBoard(board):
296 """Get a list of toolchain tuples for a given board name
297
298 returns the list of toolchain tuples for the given board
299 """
David Jamese5867812012-10-19 12:02:20 -0700300 overlays = portage_utilities.FindOverlays(
301 constants.BOTH_OVERLAYS, None if board in ('all', 'sdk') else board)
Mike Frysingereaebb582012-06-19 13:04:53 -0400302 toolchains = GetTuplesForOverlays(overlays)
303 if board == 'sdk':
304 toolchains = FilterToolchains(toolchains, 'sdk', True)
305 return toolchains
306
307
308def FilterToolchains(targets, key, value):
309 """Filter out targets based on their attributes.
310
311 args:
312 targets - dict of toolchains
313 key - metadata to examine
314 value - expected value for metadata
315
316 returns a dict where all targets whose metadata key does not match value
317 have been deleted.
318 """
319 for target, metadata in targets.items():
320 if metadata[key] != value:
321 del targets[target]
322 return targets
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100323
324
David James66a09c42012-11-05 13:31:38 -0800325def GetInstalledPackageVersions(atom):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100326 """Extracts the list of current versions of a target, package pair.
327
328 args:
David James66a09c42012-11-05 13:31:38 -0800329 atom - the atom to operate on (e.g. sys-devel/gcc)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100330
331 returns the list of versions of the package currently installed.
332 """
Zdenek Behan508dcce2011-12-05 15:39:32 +0100333 versions = []
Zdenek Behan508dcce2011-12-05 15:39:32 +0100334 for pkg in portage.db['/']['vartree'].dbapi.match(atom):
335 version = portage.versions.cpv_getversion(pkg)
336 versions.append(version)
337 return versions
338
339
Zdenek Behan699ddd32012-04-13 07:14:08 +0200340def GetStablePackageVersion(target, package, installed):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100341 """Extracts the current stable version for a given package.
342
343 args:
344 target, package - the target/package to operate on eg. i686-pc-linux-gnu,gcc
Zdenek Behan699ddd32012-04-13 07:14:08 +0200345 installed - Whether we want installed packages or ebuilds
Zdenek Behan508dcce2011-12-05 15:39:32 +0100346
347 returns a string containing the latest version.
348 """
Zdenek Behan20d6ea22012-04-05 05:15:39 +0200349 def mass_portageq_splitline(entry):
350 """Splits the output of mass_best_visible into package:version tuple."""
351 # mass_best_visible returns lines of the format "package:cpv"
352 split_string = entry.split(':', 1)
Zdenek Behan699ddd32012-04-13 07:14:08 +0200353 if split_string[1]:
354 split_string[1] = portage.versions.cpv_getversion(split_string[1])
Zdenek Behan20d6ea22012-04-05 05:15:39 +0200355 return split_string
356
357 CACHE_ATTR = '_target_stable_map'
358
Zdenek Behan699ddd32012-04-13 07:14:08 +0200359 pkgtype = "installed" if installed else "ebuild"
360
Zdenek Behan20d6ea22012-04-05 05:15:39 +0200361 val = getattr(VAR_CACHE, CACHE_ATTR, {})
362 if not target in val:
Zdenek Behan699ddd32012-04-13 07:14:08 +0200363 val[target] = {}
364 if not pkgtype in val[target]:
Zdenek Behan20d6ea22012-04-05 05:15:39 +0200365 keyword = GetPortageKeyword(target)
366 extra_env = {'ACCEPT_KEYWORDS' : '-* ' + keyword}
367 # Evaluate all packages for a target in one swoop, because it's much faster.
368 pkgs = [GetPortagePackage(target, p) for p in GetTargetPackages(target)]
Zdenek Behan699ddd32012-04-13 07:14:08 +0200369 cmd = ['portageq', 'mass_best_visible', '/', pkgtype] + pkgs
Zdenek Behan20d6ea22012-04-05 05:15:39 +0200370 cpvs = cros_build_lib.RunCommand(cmd,
371 print_cmd=False, redirect_stdout=True,
372 extra_env=extra_env).output.splitlines()
Zdenek Behan699ddd32012-04-13 07:14:08 +0200373 val[target][pkgtype] = dict(map(mass_portageq_splitline, cpvs))
Zdenek Behan20d6ea22012-04-05 05:15:39 +0200374 setattr(VAR_CACHE, CACHE_ATTR, val)
375
Zdenek Behan699ddd32012-04-13 07:14:08 +0200376 return val[target][pkgtype][GetPortagePackage(target, package)]
Zdenek Behan508dcce2011-12-05 15:39:32 +0100377
378
Zdenek Behan699ddd32012-04-13 07:14:08 +0200379def VersionListToNumeric(target, package, versions, installed):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100380 """Resolves keywords in a given version list for a particular package.
381
382 Resolving means replacing PACKAGE_STABLE with the actual number.
383
384 args:
385 target, package - the target/package to operate on eg. i686-pc-linux-gnu,gcc
386 versions - list of versions to resolve
387
388 returns list of purely numeric versions equivalent to argument
389 """
390 resolved = []
391 for version in versions:
392 if version == PACKAGE_STABLE:
Zdenek Behan699ddd32012-04-13 07:14:08 +0200393 resolved.append(GetStablePackageVersion(target, package, installed))
Zdenek Behan508dcce2011-12-05 15:39:32 +0100394 elif version != PACKAGE_NONE:
395 resolved.append(version)
396 return resolved
397
398
399def GetDesiredPackageVersions(target, package):
400 """Produces the list of desired versions for each target, package pair.
401
402 The first version in the list is implicitly treated as primary, ie.
403 the version that will be initialized by crossdev and selected.
404
405 If the version is PACKAGE_STABLE, it really means the current version which
406 is emerged by using the package atom with no particular version key.
407 Since crossdev unmasks all packages by default, this will actually
408 mean 'unstable' in most cases.
409
410 args:
411 target, package - the target/package to operate on eg. i686-pc-linux-gnu,gcc
412
413 returns a list composed of either a version string, PACKAGE_STABLE
414 """
415 packagemap = GetPackageMap(target)
416
417 versions = []
418 if package in packagemap:
419 versions.append(packagemap[package])
420
421 return versions
422
423
424def TargetIsInitialized(target):
425 """Verifies if the given list of targets has been correctly initialized.
426
427 This determines whether we have to call crossdev while emerging
428 toolchain packages or can do it using emerge. Emerge is naturally
429 preferred, because all packages can be updated in a single pass.
430
431 args:
432 targets - list of individual cross targets which are checked
433
434 returns True if target is completely initialized
435 returns False otherwise
436 """
437 # Check if packages for the given target all have a proper version.
438 try:
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100439 for package in GetTargetPackages(target):
David James66a09c42012-11-05 13:31:38 -0800440 atom = GetPortagePackage(target, package)
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100441 # Do we even want this package && is it initialized?
442 if not IsPackageDisabled(target, package) and \
David James66a09c42012-11-05 13:31:38 -0800443 not GetInstalledPackageVersions(atom):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100444 return False
445 return True
446 except cros_build_lib.RunCommandError:
447 # Fails - The target has likely never been initialized before.
448 return False
449
450
451def RemovePackageMask(target):
452 """Removes a package.mask file for the given platform.
453
454 The pre-existing package.mask files can mess with the keywords.
455
456 args:
457 target - the target for which to remove the file
458 """
459 maskfile = os.path.join('/etc/portage/package.mask', 'cross-' + target)
Brian Harringaf019fb2012-05-10 15:06:13 -0700460 osutils.SafeUnlink(maskfile)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100461
462
463def CreatePackageMask(target, masks):
464 """[Re]creates a package.mask file for the given platform.
465
466 args:
467 target - the given target on which to operate
468 masks - a map of package : version,
469 where version is the highest permissible version (mask >)
470 """
471 maskfile = os.path.join('/etc/portage/package.mask', 'cross-' + target)
472 assert not os.path.exists(maskfile)
Brian Harringaf019fb2012-05-10 15:06:13 -0700473 osutils.SafeMakedirs(os.path.dirname(maskfile))
Zdenek Behan508dcce2011-12-05 15:39:32 +0100474
475 with open(maskfile, 'w') as f:
476 for pkg, m in masks.items():
477 f.write('>%s-%s\n' % (pkg, m))
478
479
480def CreatePackageKeywords(target):
481 """[Re]create a package.keywords file for the platform.
482
483 This sets all package.keywords files to unmask all stable/testing packages.
484 TODO: Note that this approach should be deprecated and is only done for
485 compatibility reasons. In the future, we'd like to stop using keywords
486 altogether, and keep just stable unmasked.
487
488 args:
489 target - target for which to recreate package.keywords
490 """
491 maskfile = os.path.join('/etc/portage/package.keywords', 'cross-' + target)
Brian Harringaf019fb2012-05-10 15:06:13 -0700492 osutils.SafeUnlink(maskfile)
493
494 osutils.SafeMakedirs(os.path.dirname(maskfile))
Zdenek Behan508dcce2011-12-05 15:39:32 +0100495
496 keyword = GetPortageKeyword(target)
497
498 with open(maskfile, 'w') as f:
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100499 for pkg in GetTargetPackages(target):
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100500 if IsPackageDisabled(target, pkg):
501 continue
502 f.write('%s %s ~%s\n' %
503 (GetPortagePackage(target, pkg), keyword, keyword))
Zdenek Behan508dcce2011-12-05 15:39:32 +0100504
505
506# Main functions performing the actual update steps.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100507def UpdateTargets(targets, usepkg):
508 """Determines which packages need update/unmerge and defers to portage.
509
510 args:
511 targets - the list of targets to update
512 usepkg - copies the commandline option
513 """
514 # TODO(zbehan): This process is rather complex due to package.* handling.
515 # With some semantic changes over the original setup_board functionality,
516 # it can be considerably cleaned up.
517 mergemap = {}
518
519 # For each target, we do two things. Figure out the list of updates,
520 # and figure out the appropriate keywords/masks. Crossdev will initialize
521 # these, but they need to be regenerated on every update.
522 print 'Determining required toolchain updates...'
523 for target in targets:
524 # Record the highest needed version for each target, for masking purposes.
525 RemovePackageMask(target)
526 CreatePackageKeywords(target)
527 packagemasks = {}
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100528 for package in GetTargetPackages(target):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100529 # Portage name for the package
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100530 if IsPackageDisabled(target, package):
531 continue
532 pkg = GetPortagePackage(target, package)
David James66a09c42012-11-05 13:31:38 -0800533 current = GetInstalledPackageVersions(pkg)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100534 desired = GetDesiredPackageVersions(target, package)
Zdenek Behan699ddd32012-04-13 07:14:08 +0200535 desired_num = VersionListToNumeric(target, package, desired, False)
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100536 mergemap[pkg] = set(desired_num).difference(current)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100537
538 # Pick the highest version for mask.
539 packagemasks[pkg] = portage.versions.best(desired_num)
540
541 CreatePackageMask(target, packagemasks)
542
543 packages = []
544 for pkg in mergemap:
545 for ver in mergemap[pkg]:
Zdenek Behan677b6d82012-04-11 05:31:47 +0200546 if ver != PACKAGE_NONE:
547 # Be a little more permissive for usepkg, the binaries may not exist.
548 packages.append('%s%s-%s' % ('<=' if usepkg else '=', pkg, ver))
Zdenek Behan508dcce2011-12-05 15:39:32 +0100549
550 if not packages:
551 print 'Nothing to update!'
David James55096062012-11-03 16:31:23 -0700552 return False
Zdenek Behan508dcce2011-12-05 15:39:32 +0100553
554 print 'Updating packages:'
555 print packages
556
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100557 cmd = [EMERGE_CMD, '--oneshot', '--update']
Zdenek Behan508dcce2011-12-05 15:39:32 +0100558 if usepkg:
559 cmd.extend(['--getbinpkg', '--usepkgonly'])
560
561 cmd.extend(packages)
562 cros_build_lib.RunCommand(cmd)
David James55096062012-11-03 16:31:23 -0700563 return True
Zdenek Behan508dcce2011-12-05 15:39:32 +0100564
565
566def CleanTargets(targets):
567 """Unmerges old packages that are assumed unnecessary."""
568 unmergemap = {}
569 for target in targets:
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100570 for package in GetTargetPackages(target):
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100571 if IsPackageDisabled(target, package):
572 continue
573 pkg = GetPortagePackage(target, package)
David James66a09c42012-11-05 13:31:38 -0800574 current = GetInstalledPackageVersions(pkg)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100575 desired = GetDesiredPackageVersions(target, package)
Zdenek Behan699ddd32012-04-13 07:14:08 +0200576 desired_num = VersionListToNumeric(target, package, desired, True)
577 if not set(desired_num).issubset(current):
578 print 'Some packages have been held back, skipping clean!'
579 return
Zdenek Behan508dcce2011-12-05 15:39:32 +0100580 unmergemap[pkg] = set(current).difference(desired_num)
581
582 # Cleaning doesn't care about consistency and rebuilding package.* files.
583 packages = []
584 for pkg, vers in unmergemap.iteritems():
585 packages.extend('=%s-%s' % (pkg, ver) for ver in vers if ver != '9999')
586
587 if packages:
588 print 'Cleaning packages:'
589 print packages
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100590 cmd = [EMERGE_CMD, '--unmerge']
Zdenek Behan508dcce2011-12-05 15:39:32 +0100591 cmd.extend(packages)
592 cros_build_lib.RunCommand(cmd)
593 else:
594 print 'Nothing to clean!'
595
596
597def SelectActiveToolchains(targets, suffixes):
598 """Runs gcc-config and binutils-config to select the desired.
599
600 args:
601 targets - the targets to select
602 """
603 for package in ['gcc', 'binutils']:
604 for target in targets:
605 # Pick the first version in the numbered list as the selected one.
606 desired = GetDesiredPackageVersions(target, package)
Zdenek Behan699ddd32012-04-13 07:14:08 +0200607 desired_num = VersionListToNumeric(target, package, desired, True)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100608 desired = desired_num[0]
609 # *-config does not play revisions, strip them, keep just PV.
610 desired = portage.versions.pkgsplit('%s-%s' % (package, desired))[1]
611
612 if target == 'host':
613 # *-config is the only tool treating host identically (by tuple).
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100614 target = GetHostTuple()
Zdenek Behan508dcce2011-12-05 15:39:32 +0100615
616 # And finally, attach target to it.
617 desired = '%s-%s' % (target, desired)
618
619 # Target specific hacks
620 if package in suffixes:
621 if target in suffixes[package]:
622 desired += suffixes[package][target]
623
624 cmd = [ package + '-config', '-c', target ]
625 current = cros_build_lib.RunCommand(cmd, print_cmd=False,
Zdenek Behan699ddd32012-04-13 07:14:08 +0200626 redirect_stdout=True).output.splitlines()[0]
Zdenek Behan508dcce2011-12-05 15:39:32 +0100627 # Do not gcc-config when the current is live or nothing needs to be done.
628 if current != desired and current != '9999':
629 cmd = [ package + '-config', desired ]
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100630 cros_build_lib.RunCommand(cmd, print_cmd=False)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100631
632
Mike Frysinger7ccee992012-06-01 21:27:59 -0400633def UpdateToolchains(usepkg, deleteold, hostonly, targets_wanted,
634 boards_wanted):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100635 """Performs all steps to create a synchronized toolchain enviroment.
636
637 args:
638 arguments correspond to the given commandline flags
639 """
Mike Frysinger7ccee992012-06-01 21:27:59 -0400640 targets = {}
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100641 if not hostonly:
642 # For hostonly, we can skip most of the below logic, much of which won't
643 # work on bare systems where this is useful.
644 alltargets = GetAllTargets()
Mike Frysinger7ccee992012-06-01 21:27:59 -0400645 targets_wanted = set(targets_wanted)
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100646 if targets_wanted == set(['all']):
Mike Frysinger7ccee992012-06-01 21:27:59 -0400647 targets = alltargets
Mike Frysingereaebb582012-06-19 13:04:53 -0400648 elif targets_wanted == set(['sdk']):
649 # Filter out all the non-sdk toolchains as we don't want to mess
650 # with those in all of our builds.
651 targets = FilterToolchains(alltargets, 'sdk', True)
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100652 else:
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100653 # Verify user input.
Mike Frysinger7ccee992012-06-01 21:27:59 -0400654 nonexistant = []
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100655 for target in targets_wanted:
656 if target not in alltargets:
657 nonexistant.append(target)
Mike Frysinger7ccee992012-06-01 21:27:59 -0400658 else:
659 targets[target] = alltargets[target]
660 if nonexistant:
661 cros_build_lib.Die('Invalid targets: ' + ','.join(nonexistant))
662
Mike Frysinger7ccee992012-06-01 21:27:59 -0400663 # Now re-add any targets that might be from this board. This is
664 # to allow unofficial boards to declare their own toolchains.
665 for board in boards_wanted:
666 targets.update(GetToolchainsForBoard(board))
Zdenek Behan508dcce2011-12-05 15:39:32 +0100667
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100668 # First check and initialize all cross targets that need to be.
Mike Frysinger7ccee992012-06-01 21:27:59 -0400669 crossdev_targets = {}
670 reconfig_targets = {}
671 for target in targets:
672 if TargetIsInitialized(target):
673 reconfig_targets[target] = targets[target]
674 else:
675 crossdev_targets[target] = targets[target]
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100676 if crossdev_targets:
Zdenek Behan8be29ba2012-05-29 23:10:34 +0200677 print 'The following targets need to be re-initialized:'
678 print crossdev_targets
David James66a09c42012-11-05 13:31:38 -0800679 Crossdev.UpdateTargets(crossdev_targets, usepkg)
Zdenek Behan8be29ba2012-05-29 23:10:34 +0200680 # Those that were not initialized may need a config update.
David James66a09c42012-11-05 13:31:38 -0800681 Crossdev.UpdateTargets(reconfig_targets, usepkg, config_only=True)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100682
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100683 # We want host updated.
Mike Frysinger7ccee992012-06-01 21:27:59 -0400684 targets['host'] = {}
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100685
686 # Now update all packages.
David James55096062012-11-03 16:31:23 -0700687 if UpdateTargets(targets, usepkg):
688 SelectActiveToolchains(targets, CONFIG_TARGET_SUFFIXES)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100689
David James55096062012-11-03 16:31:23 -0700690 if deleteold:
691 CleanTargets(targets)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100692
693
Brian Harring30675052012-02-29 12:18:22 -0800694def main(argv):
Zdenek Behan508dcce2011-12-05 15:39:32 +0100695 usage = """usage: %prog [options]
696
697 The script installs and updates the toolchains in your chroot.
698 """
699 parser = optparse.OptionParser(usage)
700 parser.add_option('-u', '--nousepkg',
701 action='store_false', dest='usepkg', default=True,
702 help=('Use prebuilt packages if possible.'))
703 parser.add_option('-d', '--deleteold',
704 action='store_true', dest='deleteold', default=False,
705 help=('Unmerge deprecated packages.'))
706 parser.add_option('-t', '--targets',
Mike Frysingereaebb582012-06-19 13:04:53 -0400707 dest='targets', default='sdk',
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100708 help=('Comma separated list of tuples. '
Mike Frysingereaebb582012-06-19 13:04:53 -0400709 'Special keyword \'host\' is allowed. Default: sdk.'))
Mike Frysinger7ccee992012-06-01 21:27:59 -0400710 parser.add_option('--include-boards',
711 dest='include_boards', default='',
712 help=('Comma separated list of boards whose toolchains we'
713 ' will always include. Default: none.'))
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100714 parser.add_option('--hostonly',
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100715 dest='hostonly', default=False, action='store_true',
716 help=('Only setup the host toolchain. '
717 'Useful for bootstrapping chroot.'))
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100718 parser.add_option('--show-board-cfg',
719 dest='board_cfg', default=None,
720 help=('Board to list toolchain tuples for'))
David James66a09c42012-11-05 13:31:38 -0800721 parser.add_option('--reconfig', default=False, action='store_true',
722 help=('Reload crossdev config'))
Zdenek Behan508dcce2011-12-05 15:39:32 +0100723
Brian Harring30675052012-02-29 12:18:22 -0800724 (options, _remaining_arguments) = parser.parse_args(argv)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100725
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100726 if options.board_cfg:
Mike Frysinger47ad3ec2012-06-04 17:41:13 -0400727 toolchains = GetToolchainsForBoard(options.board_cfg)
728 # Make sure we display the default toolchain first.
729 tuples = toolchains.keys()
730 for tuple in tuples:
731 if toolchains[tuple]['default']:
732 tuples.remove(tuple)
733 tuples.insert(0, tuple)
734 break
735 print ','.join(tuples)
Liam McLoughlinf54a0782012-05-17 23:36:52 +0100736 return 0
737
Mike Frysinger984d0622012-06-01 16:08:44 -0400738 # This has to be always run as root.
739 if not os.getuid() == 0:
740 print "%s: This script must be run as root!" % sys.argv[0]
741 sys.exit(1)
742
Zdenek Behan508dcce2011-12-05 15:39:32 +0100743 targets = set(options.targets.split(','))
Mike Frysinger7ccee992012-06-01 21:27:59 -0400744 boards = set(options.include_boards.split(',')) if options.include_boards \
745 else set()
David James66a09c42012-11-05 13:31:38 -0800746 Crossdev.Load(options.reconfig)
Mike Frysinger7ccee992012-06-01 21:27:59 -0400747 UpdateToolchains(options.usepkg, options.deleteold, options.hostonly, targets,
748 boards)
David James66a09c42012-11-05 13:31:38 -0800749 Crossdev.Save()