Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / chromite / scripts / cros_setup_toolchains.py
1 #!/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
9 import copy
10 import glob
11 import json
12 import os
13
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
20
21 # Needs to be after chromite imports.
22 import lddtree
23
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
29   import portage
30
31
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)
36
37 CHROMIUMOS_OVERLAY = '/usr/local/portage/chromiumos'
38 STABLE_OVERLAY = '/usr/local/portage/stable'
39 CROSSDEV_OVERLAY = '/usr/local/portage/crossdev'
40
41
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 = {
47 }
48 TARGET_VERSION_MAP = {
49   'host' : {
50     'gdb' : PACKAGE_NONE,
51   },
52 }
53 # Overrides for {gcc,binutils}-config, pick a package with particular suffix.
54 CONFIG_TARGET_SUFFIXES = {
55   'binutils' : {
56     'i686-pc-linux-gnu' : '-gold',
57     'x86_64-cros-linux-gnu' : '-gold',
58   },
59 }
60 # Global per-run cache that will be filled ondemand in by GetPackageMap()
61 # function as needed.
62 target_version_map = {
63 }
64
65
66 class Crossdev(object):
67   """Class for interacting with crossdev and caching its output."""
68
69   _CACHE_FILE = os.path.join(CROSSDEV_OVERLAY, '.configured.json')
70   _CACHE = {}
71
72   @classmethod
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:
79         data = json.load(f)
80         if crossdev_version == data.get('crossdev_version'):
81           cls._CACHE = data
82
83   @classmethod
84   def Save(cls):
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)
89
90   @classmethod
91   def GetConfig(cls, target):
92     """Returns a map of crossdev provided variables about a tuple."""
93     CACHE_ATTR = '_target_tuple_map'
94
95     val = cls._CACHE.setdefault(CACHE_ATTR, {})
96     if not target in val:
97       # Find out the crossdev tuple.
98       target_tuple = target
99       if target == 'host':
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])
107     return val[target]
108
109   @classmethod
110   def UpdateTargets(cls, targets, usepkg, config_only=False):
111     """Calls crossdev to initialize a cross target.
112
113     Args:
114       targets: The list of targets to initialize using crossdev.
115       usepkg: Copies the commandline opts.
116       config_only: Just update.
117     """
118     configured_targets = cls._CACHE.setdefault('configured_targets', [])
119
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'])
124     if usepkg:
125       cmdbase.extend(['-P', '--getbinpkg',
126                       '-P', '--usepkgonly',
127                       '--without-headers'])
128
129     overlays = '%s %s' % (CHROMIUMOS_OVERLAY, STABLE_OVERLAY)
130     cmdbase.extend(['--overlays', overlays])
131     cmdbase.extend(['--ov-output', CROSSDEV_OVERLAY])
132
133     for target in targets:
134       if config_only and target in configured_targets:
135         continue
136
137       cmd = cmdbase + ['-t', target]
138
139       for pkg in GetTargetPackages(target):
140         if pkg == 'gdb':
141           # Gdb does not have selectable versions.
142           cmd.append('--ex-gdb')
143           continue
144         # The first of the desired versions is the "primary" one.
145         version = GetDesiredPackageVersions(target, pkg)[0]
146         cmd.extend(['--%s' % pkg, version])
147
148       cmd.extend(targets[target]['crossdev'].split())
149       if config_only:
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)
153       else:
154         cros_build_lib.RunCommand(cmd)
155
156       configured_targets.append(target)
157
158
159 def GetPackageMap(target):
160   """Compiles a package map for the given target from the constants.
161
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.
165
166   args:
167     target - the target for which to return a version map
168
169   returns a map between packages and desired versions in internal format
170   (using the PACKAGE_* constants)
171   """
172   if target in target_version_map:
173     return target_version_map[target]
174
175   # Start from copy of the global defaults.
176   result = copy.copy(DEFAULT_TARGET_VERSION_MAP)
177
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]
182     else:
183       # finally, if not already set, set a sane default
184       result.setdefault(pkg, DEFAULT_VERSION)
185   target_version_map[target] = result
186   return result
187
188
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']]
194
195
196 # Portage helper functions:
197 def GetPortagePackage(target, package):
198   """Returns a package name for the given target."""
199   conf = Crossdev.GetConfig(target)
200   # Portage category:
201   if target == 'host':
202     category = conf[package + '_category']
203   else:
204     category = conf['category']
205   # Portage package:
206   pn = conf[package + '_pn']
207   # Final package name:
208   assert(category)
209   assert(pn)
210   return '%s/%s' % (category, pn)
211
212
213 def IsPackageDisabled(target, package):
214   """Returns if the given package is not used for the target."""
215   return GetDesiredPackageVersions(target, package) == [PACKAGE_NONE]
216
217
218 def GetInstalledPackageVersions(atom):
219   """Extracts the list of current versions of a target, package pair.
220
221   args:
222     atom - the atom to operate on (e.g. sys-devel/gcc)
223
224   returns the list of versions of the package currently installed.
225   """
226   versions = []
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)
231   return versions
232
233
234 def GetStablePackageVersion(atom, installed):
235   """Extracts the current stable version for a given package.
236
237   args:
238     target, package - the target/package to operate on eg. i686-pc-linux-gnu,gcc
239     installed - Whether we want installed packages or ebuilds
240
241   returns a string containing the latest version.
242   """
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
247
248
249 def VersionListToNumeric(target, package, versions, installed):
250   """Resolves keywords in a given version list for a particular package.
251
252   Resolving means replacing PACKAGE_STABLE with the actual number.
253
254   args:
255     target, package - the target/package to operate on eg. i686-pc-linux-gnu,gcc
256     versions - list of versions to resolve
257
258   returns list of purely numeric versions equivalent to argument
259   """
260   resolved = []
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)
267   return resolved
268
269
270 def GetDesiredPackageVersions(target, package):
271   """Produces the list of desired versions for each target, package pair.
272
273   The first version in the list is implicitly treated as primary, ie.
274   the version that will be initialized by crossdev and selected.
275
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.
280
281   args:
282     target, package - the target/package to operate on eg. i686-pc-linux-gnu,gcc
283
284   returns a list composed of either a version string, PACKAGE_STABLE
285   """
286   packagemap = GetPackageMap(target)
287
288   versions = []
289   if package in packagemap:
290     versions.append(packagemap[package])
291
292   return versions
293
294
295 def TargetIsInitialized(target):
296   """Verifies if the given list of targets has been correctly initialized.
297
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.
301
302   args:
303     targets - list of individual cross targets which are checked
304
305   returns True if target is completely initialized
306   returns False otherwise
307   """
308   # Check if packages for the given target all have a proper version.
309   try:
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)):
316         return False
317     return True
318   except cros_build_lib.RunCommandError:
319     # Fails - The target has likely never been initialized before.
320     return False
321
322
323 def RemovePackageMask(target):
324   """Removes a package.mask file for the given platform.
325
326   The pre-existing package.mask files can mess with the keywords.
327
328   args:
329     target - the target for which to remove the file
330   """
331   maskfile = os.path.join('/etc/portage/package.mask', 'cross-' + target)
332   osutils.SafeUnlink(maskfile)
333
334
335 # Main functions performing the actual update steps.
336 def RebuildLibtool():
337   """Rebuild libtool as needed
338
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.
342   """
343   needs_update = False
344   with open('/usr/bin/libtool') as f:
345     for line in 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='):
350         line = line.rstrip()
351         for path in line.split('=', 1)[1].strip('"').split():
352           if not os.path.exists(path):
353             print 'Rebuilding libtool after gcc upgrade'
354             print ' %s' % line
355             print ' missing path: %s' % path
356             needs_update = True
357             break
358
359       if needs_update:
360         break
361
362   if needs_update:
363     cmd = [EMERGE_CMD, '--oneshot', 'sys-devel/libtool']
364     cros_build_lib.RunCommand(cmd)
365
366
367 def UpdateTargets(targets, usepkg):
368   """Determines which packages need update/unmerge and defers to portage.
369
370   args:
371     targets - the list of targets to update
372     usepkg - copies the commandline option
373   """
374   # Remove keyword files created by old versions of cros_setup_toolchains.
375   osutils.SafeUnlink('/etc/portage/package.keywords/cross-host')
376
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...'
381   mergemap = {}
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):
388         continue
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)
394
395   packages = []
396   for pkg in mergemap:
397     for ver in mergemap[pkg]:
398       if ver != PACKAGE_NONE:
399         packages.append(pkg)
400
401   if not packages:
402     print 'Nothing to update!'
403     return False
404
405   print 'Updating packages:'
406   print packages
407
408   cmd = [EMERGE_CMD, '--oneshot', '--update']
409   if usepkg:
410     cmd.extend(['--getbinpkg', '--usepkgonly'])
411
412   cmd.extend(packages)
413   cros_build_lib.RunCommand(cmd)
414   return True
415
416
417 def CleanTargets(targets):
418   """Unmerges old packages that are assumed unnecessary."""
419   unmergemap = {}
420   for target in targets:
421     for package in GetTargetPackages(target):
422       if IsPackageDisabled(target, package):
423         continue
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!'
430         return
431       unmergemap[pkg] = set(current).difference(desired_num)
432
433   # Cleaning doesn't care about consistency and rebuilding package.* files.
434   packages = []
435   for pkg, vers in unmergemap.iteritems():
436     packages.extend('=%s-%s' % (pkg, ver) for ver in vers if ver != '9999')
437
438   if packages:
439     print 'Cleaning packages:'
440     print packages
441     cmd = [EMERGE_CMD, '--unmerge']
442     cmd.extend(packages)
443     cros_build_lib.RunCommand(cmd)
444   else:
445     print 'Nothing to clean!'
446
447
448 def SelectActiveToolchains(targets, suffixes):
449   """Runs gcc-config and binutils-config to select the desired.
450
451   args:
452     targets - the targets to select
453   """
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]
462
463       if target == 'host':
464         # *-config is the only tool treating host identically (by tuple).
465         target = toolchain.GetHostTuple()
466
467       # And finally, attach target to it.
468       desired = '%s-%s' % (target, desired)
469
470       # Target specific hacks
471       if package in suffixes:
472         if target in suffixes[package]:
473           desired += suffixes[package][target]
474
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)
483
484
485 def ExpandTargets(targets_wanted):
486   """Expand any possible toolchain aliases into full targets
487
488   This will expand 'all' and 'sdk' into the respective toolchain tuples.
489
490   Args:
491     targets_wanted: The targets specified by the user.
492
493   Returns:
494     Full list of tuples with pseudo targets removed.
495   """
496   alltargets = toolchain.GetAllTargets()
497   targets_wanted = set(targets_wanted)
498   if targets_wanted == set(['all']):
499     targets = alltargets
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)
504   else:
505     # Verify user input.
506     nonexistent = targets_wanted.difference(alltargets)
507     if nonexistent:
508       raise ValueError('Invalid targets: %s', ','.join(nonexistent))
509     targets = dict((t, alltargets[t]) for t in targets_wanted)
510   return targets
511
512
513 def UpdateToolchains(usepkg, deleteold, hostonly, reconfig,
514                      targets_wanted, boards_wanted):
515   """Performs all steps to create a synchronized toolchain enviroment.
516
517   args:
518     arguments correspond to the given commandline flags
519   """
520   targets, crossdev_targets, reconfig_targets = {}, {}, {}
521   if not hostonly:
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)
525
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))
530
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]
535       else:
536         crossdev_targets[target] = targets[target]
537     if crossdev_targets:
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)
543
544   # We want host updated.
545   targets['host'] = {}
546
547   # Now update all packages.
548   if UpdateTargets(targets, usepkg) or crossdev_targets or reconfig:
549     SelectActiveToolchains(targets, CONFIG_TARGET_SUFFIXES)
550
551   if deleteold:
552     CleanTargets(targets)
553
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.
556   RebuildLibtool()
557
558
559 def ShowBoardConfig(board):
560   """Show the toolchain tuples used by |board|
561
562   Args:
563     board: The board to query.
564   """
565   toolchains = toolchain.GetToolchainsForBoard(board)
566   # Make sure we display the default toolchain first.
567   print ','.join(
568       toolchain.FilterToolchains(toolchains, 'default', True).keys() +
569       toolchain.FilterToolchains(toolchains, 'default', False).keys())
570
571
572 def GeneratePathWrapper(root, wrappath, path):
573   """Generate a shell script to execute another shell script
574
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.
578
579   Args:
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
583   """
584   replacements = {
585       'path': path,
586       'relroot': os.path.relpath('/', os.path.dirname(wrappath)),
587   }
588   wrapper = """#!/bin/sh
589 base=$(realpath "$0")
590 basedir=${base%%/*}
591 exec "${basedir}/%(relroot)s%(path)s" "$@"
592 """ % replacements
593   root_wrapper = root + wrappath
594   if os.path.islink(root_wrapper):
595     os.unlink(root_wrapper)
596   else:
597     osutils.SafeMakedirs(os.path.dirname(root_wrapper))
598   osutils.WriteFile(root_wrapper, wrapper)
599   os.chmod(root_wrapper, 0o755)
600
601
602 def FileIsCrosSdkElf(elf):
603   """Determine if |elf| is an ELF that we execute in the cros_sdk
604
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.
607
608   Args:
609     elf: The file to check
610
611   Returns:
612     True if we think |elf| is a native ELF
613   """
614   with open(elf) as f:
615     data = f.read(20)
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
620             data[18] == '\x3e')
621
622
623 def IsPathPackagable(ptype, path):
624   """Should the specified file be included in a toolchain package?
625
626   We only need to handle files as we'll create dirs as we need them.
627
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`)
633
634   Args:
635     ptype: A string describing the path type (i.e. 'file' or 'dir' or 'sym')
636     path: The full path to inspect
637
638   Returns:
639     True if we want to include this path in the package
640   """
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))
645
646
647 def ReadlinkRoot(path, root):
648   """Like os.readlink(), but relative to a |root|
649
650   Args:
651     path: The symlink to read
652     root: The path to use for resolving absolute symlinks
653
654   Returns:
655     A fully resolved symlink path
656   """
657   while os.path.islink(root + path):
658     path = os.path.join(os.path.dirname(path), os.readlink(root + path))
659   return path
660
661
662 def _GetFilesForTarget(target, root='/'):
663   """Locate all the files to package for |target|
664
665   This does not cover ELF dependencies.
666
667   Args:
668     target: The toolchain target name
669     root: The root path to pull all packages from
670
671   Returns:
672     A tuple of a set of all packable paths, and a set of all paths which
673     are also native ELFs
674   """
675   paths = set()
676   elfs = set()
677
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'):
682       continue
683
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)
688
689     # pylint: disable=E1101
690     dblink = portage.dblink(cat, pn + '-' + ver, myroot=root,
691                             settings=portage.settings)
692     contents = dblink.getcontents()
693     for obj in contents:
694       ptype = contents[obj][0]
695       if not IsPathPackagable(ptype, obj):
696         continue
697
698       if ptype == 'obj':
699         # For native ELFs, we need to pull in their dependencies too.
700         if FileIsCrosSdkElf(obj):
701           elfs.add(obj)
702       paths.add(obj)
703
704   return paths, elfs
705
706
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
710
711   This also wraps up executable ELFs with helper scripts.
712
713   Args:
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
720   """
721   # Link in all the files.
722   sym_paths = []
723   for path in paths:
724     new_path = path_rewrite_func(path)
725     dst = output_dir + new_path
726     osutils.SafeMakedirs(os.path.dirname(dst))
727
728     # Is this a symlink which we have to rewrite or wrap?
729     # Delay wrap check until after we have created all paths.
730     src = root + path
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))))
735
736         # Rewrite absolute links to relative and then generate the symlink
737         # ourselves.  All other symlinks can be hardlinked below.
738         if tgt[0] == '/':
739           tgt = os.path.relpath(tgt, os.path.dirname(new_path))
740           os.symlink(tgt, dst)
741           continue
742
743     os.link(src, dst)
744
745   # Now see if any of the symlinks need to be wrapped.
746   for sym, tgt in sym_paths:
747     if tgt in elfs:
748       GeneratePathWrapper(output_dir, sym, tgt)
749
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)
756   donelibs = set()
757   for elf in elfs:
758     e = lddtree.ParseELF(elf, root=root, ldpaths=ldpaths)
759     interp = e['interp']
760     if interp:
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'])
765
766     for lib, lib_data in e['libs'].iteritems():
767       if lib in donelibs:
768         continue
769
770       src = path = lib_data['path']
771       if path is None:
772         cros_build_lib.Warning('%s: could not locate %s', elf, lib)
773         continue
774       donelibs.add(lib)
775
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)
783
784       os.link(root + src, dst)
785
786
787 def _EnvdGetVar(envd, var):
788   """Given a Gentoo env.d file, extract a var from it
789
790   Args:
791     envd: The env.d file to load (may be a glob path)
792     var: The var to extract
793
794   Returns:
795     The value of |var|
796   """
797   envds = glob.glob(envd)
798   assert len(envds) == 1, '%s: should have exactly 1 env.d file' % envd
799   envd = envds[0]
800   return cros_build_lib.LoadKeyValueFile(envd)[var]
801
802
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))
819
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)
825
826
827 def _ProcessGccConfig(target, output_dir):
828   """Do what gcc-config would have done"""
829   binpath = '/bin'
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))
839   return srcpath
840
841
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'
851       break
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)
856
857
858 def _ProcessDistroCleanups(target, output_dir):
859   """Clean up the tree and remove all distro-specific requirements
860
861   Args:
862     target: The toolchain target name
863     output_dir: The output directory to clean up
864   """
865   _ProcessBinutilsConfig(target, output_dir)
866   gcc_path = _ProcessGccConfig(target, output_dir)
867   _ProcessSysrootWrapper(target, output_dir, gcc_path)
868
869   osutils.RmDir(os.path.join(output_dir, 'etc'))
870
871
872 def CreatePackagableRoot(target, output_dir, ldpaths, root='/'):
873   """Setup a tree from the packages for the specified target
874
875   This populates a path with all the files from toolchain packages so that
876   a tarball can easily be generated from the result.
877
878   Args:
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
883   """
884   # Find all the files owned by the packages for this target.
885   paths, elfs = _GetFilesForTarget(target, root=root)
886
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)
894
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)
899
900
901 def CreatePackages(targets_wanted, output_dir, root='/'):
902   """Create redistributable cross-compiler packages for the specified targets
903
904   This creates toolchain packages that should be usable in conjunction with
905   a downloaded sysroot (created elsewhere).
906
907   Tarballs (one per target) will be created in $PWD.
908
909   Args:
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.
913   """
914   osutils.SafeMakedirs(output_dir)
915   ldpaths = lddtree.LoadLdpaths(root)
916   targets = ExpandTargets(targets_wanted)
917
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])
927
928     # Build the tarball.
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)])
933
934
935 def main(argv):
936   usage = """usage: %prog [options]
937
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')
968
969   (options, remaining_arguments) = parser.parse_args(argv)
970   if len(remaining_arguments):
971     parser.error('script does not take arguments: %s' % remaining_arguments)
972
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')
976
977   targets = set(options.targets.split(','))
978   boards = set(options.include_boards.split(',')) if options.include_boards \
979       else set()
980
981   if options.board_cfg:
982     ShowBoardConfig(options.board_cfg)
983   elif options.create_packages:
984     cros_build_lib.AssertInsideChroot()
985     Crossdev.Load(False)
986     CreatePackages(targets, options.output_dir)
987   else:
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')
992
993     Crossdev.Load(options.reconfig)
994     UpdateToolchains(options.usepkg, options.deleteold, options.hostonly,
995                      options.reconfig, targets, boards)
996     Crossdev.Save()
997
998   return 0