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.
6 """This script manages the installed toolchains in the chroot.
14 from chromite.cbuildbot import constants
15 from chromite.lib import commandline
16 from chromite.lib import cros_build_lib
17 from chromite.lib import osutils
18 from chromite.lib import parallel
19 from chromite.lib import toolchain
21 # Needs to be after chromite imports.
24 if cros_build_lib.IsInsideChroot():
25 # Only import portage after we've checked that we're inside the chroot.
26 # Outside may not have portage, in which case the above may not happen.
27 # We'll check in main() if the operation needs portage.
28 # pylint: disable=F0401
32 EMERGE_CMD = os.path.join(constants.CHROMITE_BIN_DIR, 'parallel_emerge')
33 PACKAGE_STABLE = '[stable]'
34 PACKAGE_NONE = '[none]'
35 SRC_ROOT = os.path.realpath(constants.SOURCE_ROOT)
37 CHROMIUMOS_OVERLAY = '/usr/local/portage/chromiumos'
38 STABLE_OVERLAY = '/usr/local/portage/stable'
39 CROSSDEV_OVERLAY = '/usr/local/portage/crossdev'
42 # TODO: The versions are stored here very much like in setup_board.
43 # The goal for future is to differentiate these using a config file.
44 # This is done essentially by messing with GetDesiredPackageVersions()
45 DEFAULT_VERSION = PACKAGE_STABLE
46 DEFAULT_TARGET_VERSION_MAP = {
48 TARGET_VERSION_MAP = {
53 # Overrides for {gcc,binutils}-config, pick a package with particular suffix.
54 CONFIG_TARGET_SUFFIXES = {
56 'i686-pc-linux-gnu' : '-gold',
57 'x86_64-cros-linux-gnu' : '-gold',
60 # Global per-run cache that will be filled ondemand in by GetPackageMap()
62 target_version_map = {
66 class Crossdev(object):
67 """Class for interacting with crossdev and caching its output."""
69 _CACHE_FILE = os.path.join(CROSSDEV_OVERLAY, '.configured.json')
73 def Load(cls, reconfig):
74 """Load crossdev cache from disk."""
75 crossdev_version = GetStablePackageVersion('sys-devel/crossdev', True)
76 cls._CACHE = {'crossdev_version': crossdev_version}
77 if os.path.exists(cls._CACHE_FILE) and not reconfig:
78 with open(cls._CACHE_FILE) as f:
80 if crossdev_version == data.get('crossdev_version'):
85 """Store crossdev cache on disk."""
86 # Save the cache from the successful run.
87 with open(cls._CACHE_FILE, 'w') as f:
88 json.dump(cls._CACHE, f)
91 def GetConfig(cls, target):
92 """Returns a map of crossdev provided variables about a tuple."""
93 CACHE_ATTR = '_target_tuple_map'
95 val = cls._CACHE.setdefault(CACHE_ATTR, {})
97 # Find out the crossdev tuple.
100 target_tuple = toolchain.GetHostTuple()
101 # Catch output of crossdev.
102 out = cros_build_lib.RunCommand(['crossdev', '--show-target-cfg',
103 '--ex-gdb', target_tuple],
104 print_cmd=False, redirect_stdout=True).output.splitlines()
105 # List of tuples split at the first '=', converted into dict.
106 val[target] = dict([x.split('=', 1) for x in out])
110 def UpdateTargets(cls, targets, usepkg, config_only=False):
111 """Calls crossdev to initialize a cross target.
114 targets: The list of targets to initialize using crossdev.
115 usepkg: Copies the commandline opts.
116 config_only: Just update.
118 configured_targets = cls._CACHE.setdefault('configured_targets', [])
120 cmdbase = ['crossdev', '--show-fail-log']
121 cmdbase.extend(['--env', 'FEATURES=splitdebug'])
122 # Pick stable by default, and override as necessary.
123 cmdbase.extend(['-P', '--oneshot'])
125 cmdbase.extend(['-P', '--getbinpkg',
126 '-P', '--usepkgonly',
127 '--without-headers'])
129 overlays = '%s %s' % (CHROMIUMOS_OVERLAY, STABLE_OVERLAY)
130 cmdbase.extend(['--overlays', overlays])
131 cmdbase.extend(['--ov-output', CROSSDEV_OVERLAY])
133 for target in targets:
134 if config_only and target in configured_targets:
137 cmd = cmdbase + ['-t', target]
139 for pkg in GetTargetPackages(target):
141 # Gdb does not have selectable versions.
142 cmd.append('--ex-gdb')
144 # The first of the desired versions is the "primary" one.
145 version = GetDesiredPackageVersions(target, pkg)[0]
146 cmd.extend(['--%s' % pkg, version])
148 cmd.extend(targets[target]['crossdev'].split())
150 # In this case we want to just quietly reinit
151 cmd.append('--init-target')
152 cros_build_lib.RunCommand(cmd, print_cmd=False, redirect_stdout=True)
154 cros_build_lib.RunCommand(cmd)
156 configured_targets.append(target)
159 def GetPackageMap(target):
160 """Compiles a package map for the given target from the constants.
162 Uses a cache in target_version_map, that is dynamically filled in as needed,
163 since here everything is static data and the structuring is for ease of
164 configurability only.
167 target - the target for which to return a version map
169 returns a map between packages and desired versions in internal format
170 (using the PACKAGE_* constants)
172 if target in target_version_map:
173 return target_version_map[target]
175 # Start from copy of the global defaults.
176 result = copy.copy(DEFAULT_TARGET_VERSION_MAP)
178 for pkg in GetTargetPackages(target):
179 # prefer any specific overrides
180 if pkg in TARGET_VERSION_MAP.get(target, {}):
181 result[pkg] = TARGET_VERSION_MAP[target][pkg]
183 # finally, if not already set, set a sane default
184 result.setdefault(pkg, DEFAULT_VERSION)
185 target_version_map[target] = result
189 def GetTargetPackages(target):
190 """Returns a list of packages for a given target."""
191 conf = Crossdev.GetConfig(target)
192 # Undesired packages are denoted by empty ${pkg}_pn variable.
193 return [x for x in conf['crosspkgs'].strip("'").split() if conf[x+'_pn']]
196 # Portage helper functions:
197 def GetPortagePackage(target, package):
198 """Returns a package name for the given target."""
199 conf = Crossdev.GetConfig(target)
202 category = conf[package + '_category']
204 category = conf['category']
206 pn = conf[package + '_pn']
207 # Final package name:
210 return '%s/%s' % (category, pn)
213 def IsPackageDisabled(target, package):
214 """Returns if the given package is not used for the target."""
215 return GetDesiredPackageVersions(target, package) == [PACKAGE_NONE]
218 def GetInstalledPackageVersions(atom):
219 """Extracts the list of current versions of a target, package pair.
222 atom - the atom to operate on (e.g. sys-devel/gcc)
224 returns the list of versions of the package currently installed.
227 # pylint: disable=E1101
228 for pkg in portage.db['/']['vartree'].dbapi.match(atom, use_cache=0):
229 version = portage.versions.cpv_getversion(pkg)
230 versions.append(version)
234 def GetStablePackageVersion(atom, installed):
235 """Extracts the current stable version for a given package.
238 target, package - the target/package to operate on eg. i686-pc-linux-gnu,gcc
239 installed - Whether we want installed packages or ebuilds
241 returns a string containing the latest version.
243 pkgtype = 'vartree' if installed else 'porttree'
244 # pylint: disable=E1101
245 cpv = portage.best(portage.db['/'][pkgtype].dbapi.match(atom, use_cache=0))
246 return portage.versions.cpv_getversion(cpv) if cpv else None
249 def VersionListToNumeric(target, package, versions, installed):
250 """Resolves keywords in a given version list for a particular package.
252 Resolving means replacing PACKAGE_STABLE with the actual number.
255 target, package - the target/package to operate on eg. i686-pc-linux-gnu,gcc
256 versions - list of versions to resolve
258 returns list of purely numeric versions equivalent to argument
261 atom = GetPortagePackage(target, package)
262 for version in versions:
263 if version == PACKAGE_STABLE:
264 resolved.append(GetStablePackageVersion(atom, installed))
265 elif version != PACKAGE_NONE:
266 resolved.append(version)
270 def GetDesiredPackageVersions(target, package):
271 """Produces the list of desired versions for each target, package pair.
273 The first version in the list is implicitly treated as primary, ie.
274 the version that will be initialized by crossdev and selected.
276 If the version is PACKAGE_STABLE, it really means the current version which
277 is emerged by using the package atom with no particular version key.
278 Since crossdev unmasks all packages by default, this will actually
279 mean 'unstable' in most cases.
282 target, package - the target/package to operate on eg. i686-pc-linux-gnu,gcc
284 returns a list composed of either a version string, PACKAGE_STABLE
286 packagemap = GetPackageMap(target)
289 if package in packagemap:
290 versions.append(packagemap[package])
295 def TargetIsInitialized(target):
296 """Verifies if the given list of targets has been correctly initialized.
298 This determines whether we have to call crossdev while emerging
299 toolchain packages or can do it using emerge. Emerge is naturally
300 preferred, because all packages can be updated in a single pass.
303 targets - list of individual cross targets which are checked
305 returns True if target is completely initialized
306 returns False otherwise
308 # Check if packages for the given target all have a proper version.
310 for package in GetTargetPackages(target):
311 atom = GetPortagePackage(target, package)
312 # Do we even want this package && is it initialized?
313 if not IsPackageDisabled(target, package) and not (
314 GetStablePackageVersion(atom, True) and
315 GetStablePackageVersion(atom, False)):
318 except cros_build_lib.RunCommandError:
319 # Fails - The target has likely never been initialized before.
323 def RemovePackageMask(target):
324 """Removes a package.mask file for the given platform.
326 The pre-existing package.mask files can mess with the keywords.
329 target - the target for which to remove the file
331 maskfile = os.path.join('/etc/portage/package.mask', 'cross-' + target)
332 osutils.SafeUnlink(maskfile)
335 # Main functions performing the actual update steps.
336 def RebuildLibtool():
337 """Rebuild libtool as needed
339 Libtool hardcodes full paths to internal gcc files, so whenever we upgrade
340 gcc, libtool will break. We can't use binary packages either as those will
341 most likely be compiled against the previous version of gcc.
344 with open('/usr/bin/libtool') as f:
346 # Look for a line like:
347 # sys_lib_search_path_spec="..."
348 # It'll be a list of paths and gcc will be one of them.
349 if line.startswith('sys_lib_search_path_spec='):
351 for path in line.split('=', 1)[1].strip('"').split():
352 if not os.path.exists(path):
353 print 'Rebuilding libtool after gcc upgrade'
355 print ' missing path: %s' % path
363 cmd = [EMERGE_CMD, '--oneshot', 'sys-devel/libtool']
364 cros_build_lib.RunCommand(cmd)
367 def UpdateTargets(targets, usepkg):
368 """Determines which packages need update/unmerge and defers to portage.
371 targets - the list of targets to update
372 usepkg - copies the commandline option
374 # Remove keyword files created by old versions of cros_setup_toolchains.
375 osutils.SafeUnlink('/etc/portage/package.keywords/cross-host')
377 # For each target, we do two things. Figure out the list of updates,
378 # and figure out the appropriate keywords/masks. Crossdev will initialize
379 # these, but they need to be regenerated on every update.
380 print 'Determining required toolchain updates...'
382 for target in targets:
383 # Record the highest needed version for each target, for masking purposes.
384 RemovePackageMask(target)
385 for package in GetTargetPackages(target):
386 # Portage name for the package
387 if IsPackageDisabled(target, package):
389 pkg = GetPortagePackage(target, package)
390 current = GetInstalledPackageVersions(pkg)
391 desired = GetDesiredPackageVersions(target, package)
392 desired_num = VersionListToNumeric(target, package, desired, False)
393 mergemap[pkg] = set(desired_num).difference(current)
397 for ver in mergemap[pkg]:
398 if ver != PACKAGE_NONE:
402 print 'Nothing to update!'
405 print 'Updating packages:'
408 cmd = [EMERGE_CMD, '--oneshot', '--update']
410 cmd.extend(['--getbinpkg', '--usepkgonly'])
413 cros_build_lib.RunCommand(cmd)
417 def CleanTargets(targets):
418 """Unmerges old packages that are assumed unnecessary."""
420 for target in targets:
421 for package in GetTargetPackages(target):
422 if IsPackageDisabled(target, package):
424 pkg = GetPortagePackage(target, package)
425 current = GetInstalledPackageVersions(pkg)
426 desired = GetDesiredPackageVersions(target, package)
427 desired_num = VersionListToNumeric(target, package, desired, True)
428 if not set(desired_num).issubset(current):
429 print 'Some packages have been held back, skipping clean!'
431 unmergemap[pkg] = set(current).difference(desired_num)
433 # Cleaning doesn't care about consistency and rebuilding package.* files.
435 for pkg, vers in unmergemap.iteritems():
436 packages.extend('=%s-%s' % (pkg, ver) for ver in vers if ver != '9999')
439 print 'Cleaning packages:'
441 cmd = [EMERGE_CMD, '--unmerge']
443 cros_build_lib.RunCommand(cmd)
445 print 'Nothing to clean!'
448 def SelectActiveToolchains(targets, suffixes):
449 """Runs gcc-config and binutils-config to select the desired.
452 targets - the targets to select
454 for package in ['gcc', 'binutils']:
455 for target in targets:
456 # Pick the first version in the numbered list as the selected one.
457 desired = GetDesiredPackageVersions(target, package)
458 desired_num = VersionListToNumeric(target, package, desired, True)
459 desired = desired_num[0]
460 # *-config does not play revisions, strip them, keep just PV.
461 desired = portage.versions.pkgsplit('%s-%s' % (package, desired))[1]
464 # *-config is the only tool treating host identically (by tuple).
465 target = toolchain.GetHostTuple()
467 # And finally, attach target to it.
468 desired = '%s-%s' % (target, desired)
470 # Target specific hacks
471 if package in suffixes:
472 if target in suffixes[package]:
473 desired += suffixes[package][target]
475 extra_env = {'CHOST': target}
476 cmd = ['%s-config' % package, '-c', target]
477 current = cros_build_lib.RunCommand(cmd, print_cmd=False,
478 redirect_stdout=True, extra_env=extra_env).output.splitlines()[0]
479 # Do not gcc-config when the current is live or nothing needs to be done.
480 if current != desired and current != '9999':
481 cmd = [ package + '-config', desired ]
482 cros_build_lib.RunCommand(cmd, print_cmd=False)
485 def ExpandTargets(targets_wanted):
486 """Expand any possible toolchain aliases into full targets
488 This will expand 'all' and 'sdk' into the respective toolchain tuples.
491 targets_wanted: The targets specified by the user.
494 Full list of tuples with pseudo targets removed.
496 alltargets = toolchain.GetAllTargets()
497 targets_wanted = set(targets_wanted)
498 if targets_wanted == set(['all']):
500 elif targets_wanted == set(['sdk']):
501 # Filter out all the non-sdk toolchains as we don't want to mess
502 # with those in all of our builds.
503 targets = toolchain.FilterToolchains(alltargets, 'sdk', True)
506 nonexistent = targets_wanted.difference(alltargets)
508 raise ValueError('Invalid targets: %s', ','.join(nonexistent))
509 targets = dict((t, alltargets[t]) for t in targets_wanted)
513 def UpdateToolchains(usepkg, deleteold, hostonly, reconfig,
514 targets_wanted, boards_wanted):
515 """Performs all steps to create a synchronized toolchain enviroment.
518 arguments correspond to the given commandline flags
520 targets, crossdev_targets, reconfig_targets = {}, {}, {}
522 # For hostonly, we can skip most of the below logic, much of which won't
523 # work on bare systems where this is useful.
524 targets = ExpandTargets(targets_wanted)
526 # Now re-add any targets that might be from this board. This is
527 # to allow unofficial boards to declare their own toolchains.
528 for board in boards_wanted:
529 targets.update(toolchain.GetToolchainsForBoard(board))
531 # First check and initialize all cross targets that need to be.
532 for target in targets:
533 if TargetIsInitialized(target):
534 reconfig_targets[target] = targets[target]
536 crossdev_targets[target] = targets[target]
538 print 'The following targets need to be re-initialized:'
539 print crossdev_targets
540 Crossdev.UpdateTargets(crossdev_targets, usepkg)
541 # Those that were not initialized may need a config update.
542 Crossdev.UpdateTargets(reconfig_targets, usepkg, config_only=True)
544 # We want host updated.
547 # Now update all packages.
548 if UpdateTargets(targets, usepkg) or crossdev_targets or reconfig:
549 SelectActiveToolchains(targets, CONFIG_TARGET_SUFFIXES)
552 CleanTargets(targets)
554 # Now that we've cleared out old versions, see if we need to rebuild
555 # anything. Can't do this earlier as it might not be broken.
559 def ShowBoardConfig(board):
560 """Show the toolchain tuples used by |board|
563 board: The board to query.
565 toolchains = toolchain.GetToolchainsForBoard(board)
566 # Make sure we display the default toolchain first.
568 toolchain.FilterToolchains(toolchains, 'default', True).keys() +
569 toolchain.FilterToolchains(toolchains, 'default', False).keys())
572 def GeneratePathWrapper(root, wrappath, path):
573 """Generate a shell script to execute another shell script
575 Since we can't symlink a wrapped ELF (see GenerateLdsoWrapper) because the
576 argv[0] won't be pointing to the correct path, generate a shell script that
577 just executes another program with its full path.
580 root: The root tree to generate scripts inside of
581 wrappath: The full path (inside |root|) to create the wrapper
582 path: The target program which this wrapper will execute
586 'relroot': os.path.relpath('/', os.path.dirname(wrappath)),
588 wrapper = """#!/bin/sh
589 base=$(realpath "$0")
591 exec "${basedir}/%(relroot)s%(path)s" "$@"
593 root_wrapper = root + wrappath
594 if os.path.islink(root_wrapper):
595 os.unlink(root_wrapper)
597 osutils.SafeMakedirs(os.path.dirname(root_wrapper))
598 osutils.WriteFile(root_wrapper, wrapper)
599 os.chmod(root_wrapper, 0o755)
602 def FileIsCrosSdkElf(elf):
603 """Determine if |elf| is an ELF that we execute in the cros_sdk
605 We don't need this to be perfect, just quick. It makes sure the ELF
606 is a 64bit LSB x86_64 ELF. That is the native type of cros_sdk.
609 elf: The file to check
612 True if we think |elf| is a native ELF
616 # Check the magic number, EI_CLASS, EI_DATA, and e_machine.
617 return (data[0:4] == '\x7fELF' and
618 data[4] == '\x02' and
619 data[5] == '\x01' and
623 def IsPathPackagable(ptype, path):
624 """Should the specified file be included in a toolchain package?
626 We only need to handle files as we'll create dirs as we need them.
628 Further, trim files that won't be useful:
629 - non-english translations (.mo) since it'd require env vars
630 - debug files since these are for the host compiler itself
631 - info/man pages as they're big, and docs are online, and the
632 native docs should work fine for the most part (`man gcc`)
635 ptype: A string describing the path type (i.e. 'file' or 'dir' or 'sym')
636 path: The full path to inspect
639 True if we want to include this path in the package
641 return not (ptype in ('dir',) or
642 path.startswith('/usr/lib/debug/') or
643 os.path.splitext(path)[1] == '.mo' or
644 ('/man/' in path or '/info/' in path))
647 def ReadlinkRoot(path, root):
648 """Like os.readlink(), but relative to a |root|
651 path: The symlink to read
652 root: The path to use for resolving absolute symlinks
655 A fully resolved symlink path
657 while os.path.islink(root + path):
658 path = os.path.join(os.path.dirname(path), os.readlink(root + path))
662 def _GetFilesForTarget(target, root='/'):
663 """Locate all the files to package for |target|
665 This does not cover ELF dependencies.
668 target: The toolchain target name
669 root: The root path to pull all packages from
672 A tuple of a set of all packable paths, and a set of all paths which
678 # Find all the files owned by the packages for this target.
679 for pkg in GetTargetPackages(target):
680 # Ignore packages that are part of the target sysroot.
681 if pkg in ('kernel', 'libc'):
684 atom = GetPortagePackage(target, pkg)
685 cat, pn = atom.split('/')
686 ver = GetInstalledPackageVersions(atom)[0]
687 cros_build_lib.Info('packaging %s-%s', atom, ver)
689 # pylint: disable=E1101
690 dblink = portage.dblink(cat, pn + '-' + ver, myroot=root,
691 settings=portage.settings)
692 contents = dblink.getcontents()
694 ptype = contents[obj][0]
695 if not IsPathPackagable(ptype, obj):
699 # For native ELFs, we need to pull in their dependencies too.
700 if FileIsCrosSdkElf(obj):
707 def _BuildInitialPackageRoot(output_dir, paths, elfs, ldpaths,
708 path_rewrite_func=lambda x:x, root='/'):
709 """Link in all packable files and their runtime dependencies
711 This also wraps up executable ELFs with helper scripts.
714 output_dir: The output directory to store files
715 paths: All the files to include
716 elfs: All the files which are ELFs (a subset of |paths|)
717 ldpaths: A dict of static ldpath information
718 path_rewrite_func: User callback to rewrite paths in output_dir
719 root: The root path to pull all packages/files from
721 # Link in all the files.
724 new_path = path_rewrite_func(path)
725 dst = output_dir + new_path
726 osutils.SafeMakedirs(os.path.dirname(dst))
728 # Is this a symlink which we have to rewrite or wrap?
729 # Delay wrap check until after we have created all paths.
731 if os.path.islink(src):
732 tgt = os.readlink(src)
733 if os.path.sep in tgt:
734 sym_paths.append((new_path, lddtree.normpath(ReadlinkRoot(src, root))))
736 # Rewrite absolute links to relative and then generate the symlink
737 # ourselves. All other symlinks can be hardlinked below.
739 tgt = os.path.relpath(tgt, os.path.dirname(new_path))
745 # Now see if any of the symlinks need to be wrapped.
746 for sym, tgt in sym_paths:
748 GeneratePathWrapper(output_dir, sym, tgt)
750 # Locate all the dependencies for all the ELFs. Stick them all in the
751 # top level "lib" dir to make the wrapper simpler. This exact path does
752 # not matter since we execute ldso directly, and we tell the ldso the
753 # exact path to search for its libraries.
754 libdir = os.path.join(output_dir, 'lib')
755 osutils.SafeMakedirs(libdir)
758 e = lddtree.ParseELF(elf, root=root, ldpaths=ldpaths)
761 # Generate a wrapper if it is executable.
762 interp = os.path.join('/lib', os.path.basename(interp))
763 lddtree.GenerateLdsoWrapper(output_dir, path_rewrite_func(elf), interp,
764 libpaths=e['rpath'] + e['runpath'])
766 for lib, lib_data in e['libs'].iteritems():
770 src = path = lib_data['path']
772 cros_build_lib.Warning('%s: could not locate %s', elf, lib)
776 # Needed libs are the SONAME, but that is usually a symlink, not a
777 # real file. So link in the target rather than the symlink itself.
778 # We have to walk all the possible symlinks (SONAME could point to a
779 # symlink which points to a symlink), and we have to handle absolute
780 # ourselves (since we have a "root" argument).
781 dst = os.path.join(libdir, os.path.basename(path))
782 src = ReadlinkRoot(src, root)
784 os.link(root + src, dst)
787 def _EnvdGetVar(envd, var):
788 """Given a Gentoo env.d file, extract a var from it
791 envd: The env.d file to load (may be a glob path)
792 var: The var to extract
797 envds = glob.glob(envd)
798 assert len(envds) == 1, '%s: should have exactly 1 env.d file' % envd
800 return cros_build_lib.LoadKeyValueFile(envd)[var]
803 def _ProcessBinutilsConfig(target, output_dir):
804 """Do what binutils-config would have done"""
805 binpath = os.path.join('/bin', target + '-')
806 globpath = os.path.join(output_dir, 'usr', toolchain.GetHostTuple(), target,
807 'binutils-bin', '*-gold')
808 srcpath = glob.glob(globpath)
809 assert len(srcpath) == 1, '%s: did not match 1 path' % globpath
810 srcpath = srcpath[0][len(output_dir):]
811 gccpath = os.path.join('/usr', 'libexec', 'gcc')
812 for prog in os.listdir(output_dir + srcpath):
813 # Skip binaries already wrapped.
814 if not prog.endswith('.real'):
815 GeneratePathWrapper(output_dir, binpath + prog,
816 os.path.join(srcpath, prog))
817 GeneratePathWrapper(output_dir, os.path.join(gccpath, prog),
818 os.path.join(srcpath, prog))
820 libpath = os.path.join('/usr', toolchain.GetHostTuple(), target, 'lib')
821 envd = os.path.join(output_dir, 'etc', 'env.d', 'binutils', '*-gold')
822 srcpath = _EnvdGetVar(envd, 'LIBPATH')
823 os.symlink(os.path.relpath(srcpath, os.path.dirname(libpath)),
824 output_dir + libpath)
827 def _ProcessGccConfig(target, output_dir):
828 """Do what gcc-config would have done"""
830 envd = os.path.join(output_dir, 'etc', 'env.d', 'gcc', '*')
831 srcpath = _EnvdGetVar(envd, 'GCC_PATH')
832 for prog in os.listdir(output_dir + srcpath):
833 # Skip binaries already wrapped.
834 if (not prog.endswith('.real') and
835 not prog.endswith('.elf') and
836 prog.startswith(target)):
837 GeneratePathWrapper(output_dir, os.path.join(binpath, prog),
838 os.path.join(srcpath, prog))
842 def _ProcessSysrootWrapper(_target, output_dir, srcpath):
843 """Remove chroot-specific things from our sysroot wrapper"""
844 # Disable ccache since we know it won't work outside of chroot.
845 sysroot_wrapper = glob.glob(os.path.join(
846 output_dir + srcpath, 'sysroot_wrapper*'))[0]
847 contents = osutils.ReadFile(sysroot_wrapper).splitlines()
848 for num in xrange(len(contents)):
849 if '@CCACHE_DEFAULT@' in contents[num]:
850 contents[num] = 'use_ccache = False'
852 # Can't update the wrapper in place since it's a hardlink to a file in /.
853 os.unlink(sysroot_wrapper)
854 osutils.WriteFile(sysroot_wrapper, '\n'.join(contents))
855 os.chmod(sysroot_wrapper, 0o755)
858 def _ProcessDistroCleanups(target, output_dir):
859 """Clean up the tree and remove all distro-specific requirements
862 target: The toolchain target name
863 output_dir: The output directory to clean up
865 _ProcessBinutilsConfig(target, output_dir)
866 gcc_path = _ProcessGccConfig(target, output_dir)
867 _ProcessSysrootWrapper(target, output_dir, gcc_path)
869 osutils.RmDir(os.path.join(output_dir, 'etc'))
872 def CreatePackagableRoot(target, output_dir, ldpaths, root='/'):
873 """Setup a tree from the packages for the specified target
875 This populates a path with all the files from toolchain packages so that
876 a tarball can easily be generated from the result.
879 target: The target to create a packagable root from
880 output_dir: The output directory to place all the files
881 ldpaths: A dict of static ldpath information
882 root: The root path to pull all packages/files from
884 # Find all the files owned by the packages for this target.
885 paths, elfs = _GetFilesForTarget(target, root=root)
887 # Link in all the package's files, any ELF dependencies, and wrap any
888 # executable ELFs with helper scripts.
889 def MoveUsrBinToBin(path):
890 """Move /usr/bin to /bin so people can just use that toplevel dir"""
891 return path[4:] if path.startswith('/usr/bin/') else path
892 _BuildInitialPackageRoot(output_dir, paths, elfs, ldpaths,
893 path_rewrite_func=MoveUsrBinToBin, root=root)
895 # The packages, when part of the normal distro, have helper scripts
896 # that setup paths and such. Since we are making this standalone, we
897 # need to preprocess all that ourselves.
898 _ProcessDistroCleanups(target, output_dir)
901 def CreatePackages(targets_wanted, output_dir, root='/'):
902 """Create redistributable cross-compiler packages for the specified targets
904 This creates toolchain packages that should be usable in conjunction with
905 a downloaded sysroot (created elsewhere).
907 Tarballs (one per target) will be created in $PWD.
910 targets_wanted: The targets to package up.
911 output_dir: The directory to put the packages in.
912 root: The root path to pull all packages/files from.
914 osutils.SafeMakedirs(output_dir)
915 ldpaths = lddtree.LoadLdpaths(root)
916 targets = ExpandTargets(targets_wanted)
918 with osutils.TempDir() as tempdir:
919 # We have to split the root generation from the compression stages. This is
920 # because we hardlink in all the files (to avoid overhead of reading/writing
921 # the copies multiple times). But tar gets angry if a file's hardlink count
922 # changes from when it starts reading a file to when it finishes.
923 with parallel.BackgroundTaskRunner(CreatePackagableRoot) as queue:
924 for target in targets:
925 output_target_dir = os.path.join(tempdir, target)
926 queue.put([target, output_target_dir, ldpaths, root])
929 with parallel.BackgroundTaskRunner(cros_build_lib.CreateTarball) as queue:
930 for target in targets:
931 tar_file = os.path.join(output_dir, target + '.tar.xz')
932 queue.put([tar_file, os.path.join(tempdir, target)])
936 usage = """usage: %prog [options]
938 The script installs and updates the toolchains in your chroot."""
939 parser = commandline.OptionParser(usage)
940 parser.add_option('-u', '--nousepkg',
941 action='store_false', dest='usepkg', default=True,
942 help='Use prebuilt packages if possible')
943 parser.add_option('-d', '--deleteold',
944 action='store_true', dest='deleteold', default=False,
945 help='Unmerge deprecated packages')
946 parser.add_option('-t', '--targets',
947 dest='targets', default='sdk',
948 help='Comma separated list of tuples. '
949 'Special keyword \'host\' is allowed. Default: sdk')
950 parser.add_option('--include-boards',
951 dest='include_boards', default='',
952 help='Comma separated list of boards whose toolchains we'
953 ' will always include. Default: none')
954 parser.add_option('--hostonly',
955 dest='hostonly', default=False, action='store_true',
956 help='Only setup the host toolchain. '
957 'Useful for bootstrapping chroot')
958 parser.add_option('--show-board-cfg',
959 dest='board_cfg', default=None,
960 help='Board to list toolchain tuples for')
961 parser.add_option('--create-packages',
962 action='store_true', default=False,
963 help='Build redistributable packages')
964 parser.add_option('--output-dir', default=os.getcwd(), type='path',
965 help='Output directory')
966 parser.add_option('--reconfig', default=False, action='store_true',
967 help='Reload crossdev config and reselect toolchains')
969 (options, remaining_arguments) = parser.parse_args(argv)
970 if len(remaining_arguments):
971 parser.error('script does not take arguments: %s' % remaining_arguments)
973 # Figure out what we're supposed to do and reject conflicting options.
974 if options.board_cfg and options.create_packages:
975 parser.error('conflicting options: create-packages & show-board-cfg')
977 targets = set(options.targets.split(','))
978 boards = set(options.include_boards.split(',')) if options.include_boards \
981 if options.board_cfg:
982 ShowBoardConfig(options.board_cfg)
983 elif options.create_packages:
984 cros_build_lib.AssertInsideChroot()
986 CreatePackages(targets, options.output_dir)
988 cros_build_lib.AssertInsideChroot()
989 # This has to be always run as root.
990 if os.geteuid() != 0:
991 cros_build_lib.Die('this script must be run as root')
993 Crossdev.Load(options.reconfig)
994 UpdateToolchains(options.usepkg, options.deleteold, options.hostonly,
995 options.reconfig, targets, boards)