blob: 38cff6121b80d1777856169963cb63aa8cc5c6d0 [file] [log] [blame]
Mike Frysingerf1ba7ad2022-09-12 05:42:57 -04001# Copyright 2012 The ChromiumOS Authors
Zdenek Behan508dcce2011-12-05 15:39:32 +01002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Mike Frysinger750c5f52014-09-16 16:16:57 -04005"""This script manages the installed toolchains in the chroot."""
Zdenek Behan508dcce2011-12-05 15:39:32 +01006
Mike Frysinger3ed47722017-08-08 14:59:08 -04007import errno
Mike Frysinger35247af2012-11-16 18:58:06 -05008import glob
Mike Frysinger3ed47722017-08-08 14:59:08 -04009import hashlib
Mike Frysinger7ccee992012-06-01 21:27:59 -040010import json
Chris McDonald59650c32021-07-20 15:29:28 -060011import logging
Zdenek Behan508dcce2011-12-05 15:39:32 +010012import os
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -070013import re
Tobias Boschddd16492019-08-14 09:29:54 -070014import shutil
Zdenek Behan508dcce2011-12-05 15:39:32 +010015
Chris McDonald59650c32021-07-20 15:29:28 -060016from chromite.third_party import lddtree
17
Mike Frysinger506e75f2012-12-17 14:21:13 -050018from chromite.lib import commandline
Mike Frysinger95452702021-01-23 00:07:22 -050019from chromite.lib import constants
Brian Harring503f3ab2012-03-09 21:39:41 -080020from chromite.lib import cros_build_lib
Brian Harringaf019fb2012-05-10 15:06:13 -070021from chromite.lib import osutils
Mike Frysinger35247af2012-11-16 18:58:06 -050022from chromite.lib import parallel
David James27ac4ae2012-12-03 23:16:15 -080023from chromite.lib import toolchain
Mike Frysingere652ba12019-09-08 00:57:43 -040024from chromite.utils import key_value_store
Mike Frysinger35247af2012-11-16 18:58:06 -050025
Zdenek Behan508dcce2011-12-05 15:39:32 +010026
Mike Frysinger31596002012-12-03 23:54:24 -050027if cros_build_lib.IsInsideChroot():
Alex Klein1699fab2022-09-08 08:46:06 -060028 # Only import portage after we've checked that we're inside the chroot.
29 # Outside may not have portage, in which case the above may not happen.
30 # We'll check in main() if the operation needs portage.
31 # pylint: disable=import-error
32 import portage
Zdenek Behan508dcce2011-12-05 15:39:32 +010033
34
Alex Klein1699fab2022-09-08 08:46:06 -060035EMERGE_CMD = os.path.join(constants.CHROMITE_BIN_DIR, "parallel_emerge")
36PACKAGE_STABLE = "[stable]"
Zdenek Behan4eb6fd22012-03-12 17:00:56 +010037
Mike Frysinger4cd80b62022-05-04 20:39:01 -040038CHROMIUMOS_OVERLAY = os.path.join(
Alex Klein1699fab2022-09-08 08:46:06 -060039 constants.CHROOT_SOURCE_ROOT, constants.CHROMIUMOS_OVERLAY_DIR
40)
Mike Frysinger4cd80b62022-05-04 20:39:01 -040041ECLASS_OVERLAY = os.path.join(
Alex Klein1699fab2022-09-08 08:46:06 -060042 constants.CHROOT_SOURCE_ROOT, constants.ECLASS_OVERLAY_DIR
43)
Mike Frysinger4cd80b62022-05-04 20:39:01 -040044STABLE_OVERLAY = os.path.join(
Alex Klein1699fab2022-09-08 08:46:06 -060045 constants.CHROOT_SOURCE_ROOT, constants.PORTAGE_STABLE_OVERLAY_DIR
46)
47CROSSDEV_OVERLAY = "/usr/local/portage/crossdev"
Zdenek Behan508dcce2011-12-05 15:39:32 +010048
49
Mike Frysinger66bfde52017-09-12 16:42:57 -040050# The exact list of host toolchain packages we care about. These are the
51# packages that bots/devs install only from binpkgs and rely on the SDK bot
52# (chromiumos-sdk) to validate+uprev.
53#
Mike Frysinger66bfde52017-09-12 16:42:57 -040054# We don't use crossdev to manage the host toolchain for us, especially since
55# we diverge significantly now (with llvm/clang/etc...), and we don't need or
56# want crossdev managing /etc/portage config files for the sdk
57HOST_PACKAGES = (
Alex Klein1699fab2022-09-08 08:46:06 -060058 "dev-lang/go",
59 "dev-lang/rust-bootstrap",
60 "dev-lang/rust-host",
61 "dev-libs/elfutils",
62 "sys-devel/binutils",
63 "sys-devel/gcc",
64 "sys-devel/llvm",
65 "sys-kernel/linux-headers",
66 "sys-libs/glibc",
67 "sys-libs/libcxx",
68 "sys-libs/llvm-libunwind",
Mike Frysinger66bfde52017-09-12 16:42:57 -040069)
70
Mike Frysinger785b0c32017-09-13 01:35:59 -040071# These packages are also installed into the host SDK. However, they require
72# the cross-compilers to be installed first (because they need them to actually
73# build), so we have to delay their installation.
74HOST_POST_CROSS_PACKAGES = (
Alex Klein1699fab2022-09-08 08:46:06 -060075 "dev-lang/rust",
76 "virtual/target-sdk-post-cross",
77 "dev-embedded/coreboot-sdk",
78 "dev-embedded/hps-sdk",
79 "dev-embedded/ti50-sdk",
Mike Frysinger785b0c32017-09-13 01:35:59 -040080)
81
82# New packages that we're in the process of adding to the SDK. Since the SDK
83# bot hasn't had a chance to run yet, there are no binary packages available,
84# so we have to list them here and wait. Once it completes, entries here can
85# be removed so they'll end up on bots & dev's systems.
George Burgess IV66e199c2021-05-05 15:38:40 -070086NEW_PACKAGES = ()
Mike Frysinger785b0c32017-09-13 01:35:59 -040087
Rahul Chaudhry4b803052015-05-13 15:25:56 -070088# Enable the Go compiler for these targets.
89TARGET_GO_ENABLED = (
Alex Klein1699fab2022-09-08 08:46:06 -060090 "x86_64-cros-linux-gnu",
91 "armv7a-cros-linux-gnueabi",
92 "armv7a-cros-linux-gnueabihf",
93 "aarch64-cros-linux-gnu",
Rahul Chaudhry4b803052015-05-13 15:25:56 -070094)
Alex Klein1699fab2022-09-08 08:46:06 -060095CROSSDEV_GO_ARGS = ["--ex-pkg", "dev-lang/go"]
Rahul Chaudhry4b803052015-05-13 15:25:56 -070096
Alex Klein1699fab2022-09-08 08:46:06 -060097CROSSDEV_LIBXCRYPT_ARGS = ["--ex-pkg", "sys-libs/libxcrypt"]
Adrian Ratiubf0b9af2022-05-02 14:48:15 +030098
Manoj Gupta1b5642e2017-03-08 16:44:12 -080099# Enable llvm's compiler-rt for these targets.
100TARGET_COMPILER_RT_ENABLED = (
Alex Klein1699fab2022-09-08 08:46:06 -0600101 "armv7a-cros-linux-gnueabi",
102 "armv7a-cros-linux-gnueabihf",
103 "aarch64-cros-linux-gnu",
104 "arm-none-eabi",
105 "armv7m-cros-eabi",
Manoj Gupta1b5642e2017-03-08 16:44:12 -0800106)
Alex Klein1699fab2022-09-08 08:46:06 -0600107CROSSDEV_COMPILER_RT_ARGS = ["--ex-pkg", "sys-libs/compiler-rt"]
Manoj Gupta1b5642e2017-03-08 16:44:12 -0800108
Manoj Gupta946abb42017-04-12 14:27:19 -0700109TARGET_LLVM_PKGS_ENABLED = (
Alex Klein1699fab2022-09-08 08:46:06 -0600110 "armv7m-cros-eabi",
111 "armv7a-cros-linux-gnueabi",
112 "armv7a-cros-linux-gnueabihf",
113 "aarch64-cros-linux-gnu",
114 "i686-cros-linux-gnu",
115 "x86_64-cros-linux-gnu",
Manoj Gupta946abb42017-04-12 14:27:19 -0700116)
117
118LLVM_PKGS_TABLE = {
Alex Klein1699fab2022-09-08 08:46:06 -0600119 "ex_llvm-libunwind": ["--ex-pkg", "sys-libs/llvm-libunwind"],
120 "ex_libcxx": ["--ex-pkg", "sys-libs/libcxx"],
Manoj Gupta946abb42017-04-12 14:27:19 -0700121}
122
Alex Klein1699fab2022-09-08 08:46:06 -0600123
David James66a09c42012-11-05 13:31:38 -0800124class Crossdev(object):
Alex Klein1699fab2022-09-08 08:46:06 -0600125 """Class for interacting with crossdev and caching its output."""
David James66a09c42012-11-05 13:31:38 -0800126
Alex Klein1699fab2022-09-08 08:46:06 -0600127 _CACHE_FILE = os.path.join(CROSSDEV_OVERLAY, ".configured.json")
128 _CACHE = {}
129 # Packages that needs separate handling, in addition to what we have from
130 # crossdev.
131 MANUAL_PKGS = {
132 "rust": "dev-lang",
133 "llvm": "sys-devel",
134 "llvm-libunwind": "sys-libs",
135 "libcxx": "sys-libs",
136 "elfutils": "dev-libs",
Mike Frysinger3ed47722017-08-08 14:59:08 -0400137 }
138
Alex Klein1699fab2022-09-08 08:46:06 -0600139 @classmethod
140 def Load(cls, reconfig):
141 """Load crossdev cache from disk.
Mike Frysinger3ed47722017-08-08 14:59:08 -0400142
Alex Klein1699fab2022-09-08 08:46:06 -0600143 We invalidate the cache when crossdev updates or this script changes.
144 """
145 crossdev_version = GetStablePackageVersion("sys-devel/crossdev", True)
146 # If we run the compiled/cached .pyc file, we'll read/hash that when we
147 # really always want to track the source .py file.
148 script = os.path.abspath(__file__)
149 if script.endswith(".pyc"):
150 script = script[:-1]
151 setup_toolchains_hash = hashlib.md5(
152 osutils.ReadFile(script, mode="rb")
153 ).hexdigest()
Mike Frysinger3ed47722017-08-08 14:59:08 -0400154
Alex Klein1699fab2022-09-08 08:46:06 -0600155 cls._CACHE = {
156 "crossdev_version": crossdev_version,
157 "setup_toolchains_hash": setup_toolchains_hash,
Mike Frysinger66bfde52017-09-12 16:42:57 -0400158 }
Alex Klein1699fab2022-09-08 08:46:06 -0600159
160 logging.debug("cache: checking file: %s", cls._CACHE_FILE)
161 if reconfig:
162 logging.debug("cache: forcing regen due to reconfig")
163 return
164
165 try:
166 file_data = osutils.ReadFile(cls._CACHE_FILE)
167 except IOError as e:
168 if e.errno != errno.ENOENT:
169 logging.warning("cache: reading failed: %s", e)
170 osutils.SafeUnlink(cls._CACHE_FILE)
171 return
172
173 try:
174 data = json.loads(file_data)
175 except ValueError as e:
176 logging.warning("cache: ignoring invalid content: %s", e)
177 return
178
179 if crossdev_version != data.get("crossdev_version"):
180 logging.debug("cache: rebuilding after crossdev upgrade")
181 elif setup_toolchains_hash != data.get("setup_toolchains_hash"):
182 logging.debug(
183 "cache: rebuilding after cros_setup_toolchains change"
184 )
Mike Frysinger785b0c32017-09-13 01:35:59 -0400185 else:
Alex Klein1699fab2022-09-08 08:46:06 -0600186 logging.debug("cache: content is up-to-date!")
187 cls._CACHE = data
Han Shene23782f2016-02-18 12:20:00 -0800188
Alex Klein1699fab2022-09-08 08:46:06 -0600189 @classmethod
190 def Save(cls):
191 """Store crossdev cache on disk."""
192 # Save the cache from the successful run.
193 with open(cls._CACHE_FILE, "w") as f:
194 json.dump(cls._CACHE, f)
Mike Frysinger66bfde52017-09-12 16:42:57 -0400195
Alex Klein1699fab2022-09-08 08:46:06 -0600196 @classmethod
197 def GetConfig(cls, target):
198 """Returns a map of crossdev provided variables about a tuple."""
199 CACHE_ATTR = "_target_tuple_map"
Han Shene23782f2016-02-18 12:20:00 -0800200
Alex Klein1699fab2022-09-08 08:46:06 -0600201 val = cls._CACHE.setdefault(CACHE_ATTR, {})
202 if not target in val:
203 if target.startswith("host"):
204 conf = {
205 "crosspkgs": [],
206 "target": toolchain.GetHostTuple(),
207 }
208 if target == "host":
209 packages_list = HOST_PACKAGES
210 else:
211 packages_list = HOST_POST_CROSS_PACKAGES
212 manual_pkgs = dict(
213 (pkg, cat)
214 for cat, pkg in [x.split("/") for x in packages_list]
215 )
216 else:
217 # Build the crossdev command.
218 cmd = ["crossdev", "--show-target-cfg", "--ex-gdb"]
219 # Enable libxcrypt for all linux-gnu targets.
220 if "cros-linux-gnu" in target:
221 cmd.extend(CROSSDEV_LIBXCRYPT_ARGS)
222 if target in TARGET_COMPILER_RT_ENABLED:
223 cmd.extend(CROSSDEV_COMPILER_RT_ARGS)
224 if target in TARGET_LLVM_PKGS_ENABLED:
225 for pkg in LLVM_PKGS_TABLE:
226 cmd.extend(LLVM_PKGS_TABLE[pkg])
227 if target in TARGET_GO_ENABLED:
228 cmd.extend(CROSSDEV_GO_ARGS)
229 cmd.extend(["-t", target])
230 # Catch output of crossdev.
231 out = cros_build_lib.run(
232 cmd, print_cmd=False, stdout=True, encoding="utf-8"
233 ).stdout.splitlines()
234 # List of tuples split at the first '=', converted into dict.
235 conf = dict(
236 (k, cros_build_lib.ShellUnquote(v))
237 for k, v in (x.split("=", 1) for x in out)
238 )
239 conf["crosspkgs"] = conf["crosspkgs"].split()
Han Shene23782f2016-02-18 12:20:00 -0800240
Alex Klein1699fab2022-09-08 08:46:06 -0600241 manual_pkgs = cls.MANUAL_PKGS
David James66a09c42012-11-05 13:31:38 -0800242
Alex Klein1699fab2022-09-08 08:46:06 -0600243 for pkg, cat in manual_pkgs.items():
244 conf[pkg + "_pn"] = pkg
245 conf[pkg + "_category"] = cat
246 if pkg not in conf["crosspkgs"]:
247 conf["crosspkgs"].append(pkg)
David James66a09c42012-11-05 13:31:38 -0800248
Alex Klein1699fab2022-09-08 08:46:06 -0600249 val[target] = conf
David James66a09c42012-11-05 13:31:38 -0800250
Alex Klein1699fab2022-09-08 08:46:06 -0600251 return val[target]
Manoj Gupta4d016f62021-10-19 16:39:34 -0700252
Alex Klein1699fab2022-09-08 08:46:06 -0600253 @classmethod
254 def UpdateTargets(cls, targets, usepkg, config_only=False):
255 """Calls crossdev to initialize a cross target.
Manoj Gupta4d016f62021-10-19 16:39:34 -0700256
Alex Klein1699fab2022-09-08 08:46:06 -0600257 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700258 targets: The dict of targets to initialize using crossdev.
259 usepkg: Copies the commandline opts.
260 config_only: Just update.
Alex Klein1699fab2022-09-08 08:46:06 -0600261 """
262 configured_targets = cls._CACHE.setdefault("configured_targets", [])
263 started_targets = set()
David James66a09c42012-11-05 13:31:38 -0800264
Alex Klein1699fab2022-09-08 08:46:06 -0600265 # Schedule all of the targets in parallel, and let them run.
266 with parallel.BackgroundTaskRunner(cls._UpdateTarget) as queue:
267 for target_name in targets:
268 # We already started this target in this loop.
269 if target_name in started_targets:
270 continue
271 # The target is already configured.
272 if config_only and target_name in configured_targets:
273 continue
274 queue.put(
275 [target_name, targets[target_name], usepkg, config_only]
276 )
277 started_targets.add(target_name)
David James66a09c42012-11-05 13:31:38 -0800278
Alex Klein1699fab2022-09-08 08:46:06 -0600279 @classmethod
280 def _UpdateTarget(cls, target_name, target, usepkg, config_only):
281 """Calls crossdev to initialize a cross target.
David James66a09c42012-11-05 13:31:38 -0800282
Alex Klein1699fab2022-09-08 08:46:06 -0600283 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700284 target_name: The name of the target to initialize.
285 target: The target info for initializing.
286 usepkg: Copies the commandline opts.
287 config_only: Just update.
Alex Klein1699fab2022-09-08 08:46:06 -0600288 """
289 configured_targets = cls._CACHE.setdefault("configured_targets", [])
290 cmdbase = ["crossdev", "--show-fail-log"]
291 cmdbase.extend(["--env", "FEATURES=splitdebug"])
292 # Pick stable by default, and override as necessary.
293 cmdbase.extend(["-P", "--oneshot"])
294 if usepkg:
295 cmdbase.extend(
296 ["-P", "--getbinpkg", "-P", "--usepkgonly", "--without-headers"]
297 )
David James66a09c42012-11-05 13:31:38 -0800298
Alex Klein1699fab2022-09-08 08:46:06 -0600299 overlays = " ".join(
300 (CHROMIUMOS_OVERLAY, ECLASS_OVERLAY, STABLE_OVERLAY)
301 )
302 cmdbase.extend(["--overlays", overlays])
303 cmdbase.extend(["--ov-output", CROSSDEV_OVERLAY])
Manoj Gupta4d016f62021-10-19 16:39:34 -0700304
Alex Klein1699fab2022-09-08 08:46:06 -0600305 cmd = cmdbase + ["-t", target_name]
Manoj Gupta4d016f62021-10-19 16:39:34 -0700306
Alex Klein1699fab2022-09-08 08:46:06 -0600307 for pkg in GetTargetPackages(target_name):
308 if pkg == "gdb":
309 # Gdb does not have selectable versions.
310 cmd.append("--ex-gdb")
311 elif pkg == "ex_libxcrypt":
312 cmd.extend(CROSSDEV_LIBXCRYPT_ARGS)
313 elif pkg == "ex_compiler-rt":
314 cmd.extend(CROSSDEV_COMPILER_RT_ARGS)
315 elif pkg == "ex_go":
316 # Go does not have selectable versions.
317 cmd.extend(CROSSDEV_GO_ARGS)
318 elif pkg in LLVM_PKGS_TABLE:
319 cmd.extend(LLVM_PKGS_TABLE[pkg])
320 elif pkg in cls.MANUAL_PKGS:
321 pass
322 else:
323 # The first of the desired versions is the "primary" one.
324 version = GetDesiredPackageVersions(target_name, pkg)[0]
325 cmd.extend(["--%s" % pkg, version])
326
327 cmd.extend(target["crossdev"].split())
328
329 if config_only:
330 # In this case we want to just quietly reinit
331 cmd.append("--init-target")
332 cros_build_lib.run(cmd, print_cmd=False, stdout=True)
333 else:
334 cros_build_lib.run(cmd)
335
336 configured_targets.append(target_name)
David James66a09c42012-11-05 13:31:38 -0800337
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100338
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100339def GetTargetPackages(target):
Alex Klein1699fab2022-09-08 08:46:06 -0600340 """Returns a list of packages for a given target."""
341 conf = Crossdev.GetConfig(target)
342 # Undesired packages are denoted by empty ${pkg}_pn variable.
343 return [x for x in conf["crosspkgs"] if conf.get(x + "_pn")]
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100344
345
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100346# Portage helper functions:
347def GetPortagePackage(target, package):
Alex Klein1699fab2022-09-08 08:46:06 -0600348 """Returns a package name for the given target."""
349 conf = Crossdev.GetConfig(target)
350 # Portage category:
351 if target.startswith("host") or package in Crossdev.MANUAL_PKGS:
352 category = conf[package + "_category"]
353 else:
354 category = conf["category"]
355 # Portage package:
356 pn = conf[package + "_pn"]
357 # Final package name:
358 assert category
359 assert pn
360 return "%s/%s" % (category, pn)
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100361
362
Gilad Arnold2dab78c2015-05-21 14:43:33 -0700363def PortageTrees(root):
Alex Klein1699fab2022-09-08 08:46:06 -0600364 """Return the portage trees for a given root."""
365 if root == "/":
366 return portage.db["/"]
367 # The portage logic requires the path always end in a slash.
368 root = root.rstrip("/") + "/"
369 return portage.create_trees(target_root=root, config_root=root)[root]
Gilad Arnold2dab78c2015-05-21 14:43:33 -0700370
371
Alex Klein1699fab2022-09-08 08:46:06 -0600372def GetInstalledPackageVersions(atom, root="/"):
373 """Extracts the list of current versions of a target, package pair.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100374
Alex Klein1699fab2022-09-08 08:46:06 -0600375 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700376 atom: The atom to operate on (e.g. sys-devel/gcc)
377 root: The root to check for installed packages.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100378
Alex Klein1699fab2022-09-08 08:46:06 -0600379 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700380 The list of versions of the package currently installed.
Alex Klein1699fab2022-09-08 08:46:06 -0600381 """
382 versions = []
383 for pkg in PortageTrees(root)["vartree"].dbapi.match(atom, use_cache=0):
384 version = portage.versions.cpv_getversion(pkg)
385 versions.append(version)
386 return versions
Zdenek Behan508dcce2011-12-05 15:39:32 +0100387
388
Alex Klein1699fab2022-09-08 08:46:06 -0600389def GetStablePackageVersion(atom, installed, root="/"):
390 """Extracts the current stable version for a given package.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100391
Alex Klein1699fab2022-09-08 08:46:06 -0600392 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700393 atom: The target/package to operate on e.g. i686-cros-linux-gnu/gcc
394 installed: Whether we want installed packages or ebuilds
395 root: The root to use when querying packages.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100396
Alex Klein1699fab2022-09-08 08:46:06 -0600397 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700398 A string containing the latest version.
Alex Klein1699fab2022-09-08 08:46:06 -0600399 """
400 pkgtype = "vartree" if installed else "porttree"
401 cpv = portage.best(
402 PortageTrees(root)[pkgtype].dbapi.match(atom, use_cache=0)
403 )
404 return portage.versions.cpv_getversion(cpv) if cpv else None
Zdenek Behan508dcce2011-12-05 15:39:32 +0100405
406
Alex Klein1699fab2022-09-08 08:46:06 -0600407def VersionListToNumeric(target, package, versions, installed, root="/"):
408 """Resolves keywords in a given version list for a particular package.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100409
Alex Klein1699fab2022-09-08 08:46:06 -0600410 Resolving means replacing PACKAGE_STABLE with the actual number.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100411
Alex Klein1699fab2022-09-08 08:46:06 -0600412 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700413 target: The target to operate on (e.g. i686-cros-linux-gnu)
414 package: The target/package to operate on (e.g. gcc)
415 versions: List of versions to resolve
416 installed: Query installed packages
417 root: The install root to use; ignored if |installed| is False.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100418
Alex Klein1699fab2022-09-08 08:46:06 -0600419 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700420 List of purely numeric versions equivalent to argument
Alex Klein1699fab2022-09-08 08:46:06 -0600421 """
422 resolved = []
423 atom = GetPortagePackage(target, package)
424 if not installed:
425 root = "/"
426 for version in versions:
427 if version == PACKAGE_STABLE:
428 resolved.append(GetStablePackageVersion(atom, installed, root=root))
429 else:
430 resolved.append(version)
431 return resolved
Zdenek Behan508dcce2011-12-05 15:39:32 +0100432
433
434def GetDesiredPackageVersions(target, package):
Alex Klein1699fab2022-09-08 08:46:06 -0600435 """Produces the list of desired versions for each target, package pair.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100436
Alex Klein1699fab2022-09-08 08:46:06 -0600437 The first version in the list is implicitly treated as primary, ie.
438 the version that will be initialized by crossdev and selected.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100439
Alex Klein1699fab2022-09-08 08:46:06 -0600440 If the version is PACKAGE_STABLE, it really means the current version which
441 is emerged by using the package atom with no particular version key.
442 Since crossdev unmasks all packages by default, this will actually
443 mean 'unstable' in most cases.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100444
Alex Klein1699fab2022-09-08 08:46:06 -0600445 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700446 target: The target to operate on (e.g. i686-cros-linux-gnu)
447 package: The target/package to operate on (e.g. gcc)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100448
Alex Klein1699fab2022-09-08 08:46:06 -0600449 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700450 A list composed of either a version string, PACKAGE_STABLE
Alex Klein1699fab2022-09-08 08:46:06 -0600451 """
452 if package in GetTargetPackages(target):
453 return [PACKAGE_STABLE]
454 else:
455 return []
Zdenek Behan508dcce2011-12-05 15:39:32 +0100456
457
458def TargetIsInitialized(target):
Alex Klein1699fab2022-09-08 08:46:06 -0600459 """Verifies if the given list of targets has been correctly initialized.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100460
Alex Klein1699fab2022-09-08 08:46:06 -0600461 This determines whether we have to call crossdev while emerging
462 toolchain packages or can do it using emerge. Emerge is naturally
463 preferred, because all packages can be updated in a single pass.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100464
Alex Klein1699fab2022-09-08 08:46:06 -0600465 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700466 target: The target to operate on (e.g. i686-cros-linux-gnu)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100467
Alex Klein1699fab2022-09-08 08:46:06 -0600468 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700469 True if |target| is completely initialized, else False
Alex Klein1699fab2022-09-08 08:46:06 -0600470 """
471 # Check if packages for the given target all have a proper version.
472 try:
473 for package in GetTargetPackages(target):
474 atom = GetPortagePackage(target, package)
475 # Do we even want this package && is it initialized?
476 if not (
477 GetStablePackageVersion(atom, True)
478 and GetStablePackageVersion(atom, False)
479 ):
480 return False
481 return True
482 except cros_build_lib.RunCommandError:
483 # Fails - The target has likely never been initialized before.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100484 return False
Zdenek Behan508dcce2011-12-05 15:39:32 +0100485
486
487def RemovePackageMask(target):
Alex Klein1699fab2022-09-08 08:46:06 -0600488 """Removes a package.mask file for the given platform.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100489
Alex Klein1699fab2022-09-08 08:46:06 -0600490 The pre-existing package.mask files can mess with the keywords.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100491
Alex Klein1699fab2022-09-08 08:46:06 -0600492 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700493 target: The target to operate on (e.g. i686-cros-linux-gnu)
Alex Klein1699fab2022-09-08 08:46:06 -0600494 """
495 maskfile = os.path.join("/etc/portage/package.mask", "cross-" + target)
496 osutils.SafeUnlink(maskfile)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100497
498
Zdenek Behan508dcce2011-12-05 15:39:32 +0100499# Main functions performing the actual update steps.
Alex Klein1699fab2022-09-08 08:46:06 -0600500def RebuildLibtool(root="/"):
501 """Rebuild libtool as needed
Mike Frysingerc880a962013-11-08 13:59:06 -0500502
Alex Klein1699fab2022-09-08 08:46:06 -0600503 Libtool hardcodes full paths to internal gcc files, so whenever we upgrade
504 gcc, libtool will break. We can't use binary packages either as those will
505 most likely be compiled against the previous version of gcc.
Gilad Arnold2dab78c2015-05-21 14:43:33 -0700506
Alex Klein1699fab2022-09-08 08:46:06 -0600507 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700508 root: The install root where we want libtool rebuilt.
Alex Klein1699fab2022-09-08 08:46:06 -0600509 """
510 needs_update = False
511 with open(os.path.join(root, "usr/bin/libtool")) as f:
512 for line in f:
513 # Look for a line like:
514 # sys_lib_search_path_spec="..."
515 # It'll be a list of paths and gcc will be one of them.
516 if line.startswith("sys_lib_search_path_spec="):
517 line = line.rstrip()
518 for path in line.split("=", 1)[1].strip('"').split():
519 root_path = os.path.join(root, path.lstrip(os.path.sep))
520 logging.debug("Libtool: checking %s", root_path)
521 if not os.path.exists(root_path):
522 logging.info("Rebuilding libtool after gcc upgrade")
523 logging.info(" %s", line)
524 logging.info(" missing path: %s", path)
525 needs_update = True
526 break
Mike Frysingerc880a962013-11-08 13:59:06 -0500527
Alex Klein1699fab2022-09-08 08:46:06 -0600528 if needs_update:
529 break
Mike Frysingerc880a962013-11-08 13:59:06 -0500530
Alex Klein1699fab2022-09-08 08:46:06 -0600531 if needs_update:
532 cmd = [EMERGE_CMD, "--oneshot"]
533 if root != "/":
534 cmd.extend(["--sysroot=%s" % root, "--root=%s" % root])
535 cmd.append("sys-devel/libtool")
536 cros_build_lib.run(cmd)
537 else:
538 logging.debug("Libtool is up-to-date; no need to rebuild")
Mike Frysingerc880a962013-11-08 13:59:06 -0500539
540
Alex Klein1699fab2022-09-08 08:46:06 -0600541def UpdateTargets(targets, usepkg, root="/"):
542 """Determines which packages need update/unmerge and defers to portage.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100543
Alex Klein1699fab2022-09-08 08:46:06 -0600544 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700545 targets: The list of targets to update
546 usepkg: Copies the commandline option
547 root: The install root in which we want packages updated.
Alex Klein1699fab2022-09-08 08:46:06 -0600548 """
549 # For each target, we do two things. Figure out the list of updates,
550 # and figure out the appropriate keywords/masks. Crossdev will initialize
551 # these, but they need to be regenerated on every update.
552 logging.info("Determining required toolchain updates...")
553 mergemap = {}
554 # Used to keep track of post-cross packages. These are allowed to have
555 # implicit dependencies on toolchain packages, and therefore need to
556 # be built last.
557 post_cross_pkgs = set()
Zdenek Behan508dcce2011-12-05 15:39:32 +0100558 for target in targets:
Alex Klein1699fab2022-09-08 08:46:06 -0600559 is_post_cross_target = target.endswith("-post-cross")
560 logging.debug("Updating target %s", target)
Alex Klein0e92b2c2023-01-13 11:54:15 -0700561 # Record the highest needed version for each target, for masking
562 # purposes.
Alex Klein1699fab2022-09-08 08:46:06 -0600563 RemovePackageMask(target)
564 for package in GetTargetPackages(target):
565 # Portage name for the package
566 logging.debug(" Checking package %s", package)
567 pkg = GetPortagePackage(target, package)
568 current = GetInstalledPackageVersions(pkg, root=root)
569 desired = GetDesiredPackageVersions(target, package)
570 desired_num = VersionListToNumeric(target, package, desired, False)
571 if pkg in NEW_PACKAGES and usepkg:
572 # Skip this binary package (for now).
573 continue
574 mergemap[pkg] = set(desired_num).difference(current)
575 logging.debug(" %s -> %s", current, desired_num)
576 if is_post_cross_target:
577 post_cross_pkgs.add(pkg)
Mike Frysinger785b0c32017-09-13 01:35:59 -0400578
Alex Klein1699fab2022-09-08 08:46:06 -0600579 packages = [pkg for pkg, vers in mergemap.items() if vers]
580 if not packages:
581 logging.info("Nothing to update!")
582 return False
Zdenek Behan508dcce2011-12-05 15:39:32 +0100583
Alex Klein1699fab2022-09-08 08:46:06 -0600584 logging.info("Updating packages:")
585 logging.info("%s", packages)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100586
Alex Klein1699fab2022-09-08 08:46:06 -0600587 cmd = [EMERGE_CMD, "--oneshot", "--update"]
588 if usepkg:
589 cmd.extend(["--getbinpkg", "--usepkgonly"])
590 if root != "/":
591 cmd.extend(["--sysroot=%s" % root, "--root=%s" % root])
Zdenek Behan508dcce2011-12-05 15:39:32 +0100592
Alex Klein1699fab2022-09-08 08:46:06 -0600593 if usepkg:
594 # Since we are not building from source, we can handle
595 # all packages in one go.
596 cmd.extend(packages)
597 cros_build_lib.run(cmd)
598 else:
599 pre_cross_items = [
600 pkg for pkg in packages if pkg not in post_cross_pkgs
601 ]
602 if pre_cross_items:
603 cros_build_lib.run(cmd + pre_cross_items)
604 post_cross_items = [pkg for pkg in packages if pkg in post_cross_pkgs]
605 if post_cross_items:
606 cros_build_lib.run(cmd + post_cross_items)
607 return True
Gilad Arnold2dab78c2015-05-21 14:43:33 -0700608
Alex Klein1699fab2022-09-08 08:46:06 -0600609
610def CleanTargets(targets, root="/"):
611 """Unmerges old packages that are assumed unnecessary.
612
613 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700614 targets: The list of targets to clean up.
615 root: The install root in which we want packages cleaned up.
Alex Klein1699fab2022-09-08 08:46:06 -0600616 """
617 unmergemap = {}
618 for target in targets:
619 logging.debug("Cleaning target %s", target)
620 for package in GetTargetPackages(target):
621 logging.debug(" Cleaning package %s", package)
622 pkg = GetPortagePackage(target, package)
623 current = GetInstalledPackageVersions(pkg, root=root)
624 desired = GetDesiredPackageVersions(target, package)
625 # NOTE: This refers to installed packages (vartree) rather than the
Alex Klein0e92b2c2023-01-13 11:54:15 -0700626 # Portage version (porttree and/or bintree) when determining the
627 # current version. While this isn't the most accurate thing to do,
628 # it is probably a good simple compromise, which should have the
629 # desired result of uninstalling everything but the latest installed
630 # version. In particular, using the bintree (--usebinpkg) requires a
631 # non-trivial binhost sync and is probably more complex than useful.
Alex Klein1699fab2022-09-08 08:46:06 -0600632 desired_num = VersionListToNumeric(target, package, desired, True)
633 if not set(desired_num).issubset(current):
634 logging.warning(
635 "Error detecting stable version for %s, " "skipping clean!",
636 pkg,
637 )
638 return
639 unmergemap[pkg] = set(current).difference(desired_num)
640
641 # Cleaning doesn't care about consistency and rebuilding package.* files.
642 packages = []
643 for pkg, vers in unmergemap.items():
644 packages.extend("=%s-%s" % (pkg, ver) for ver in vers if ver != "9999")
645
646 if packages:
647 logging.info("Cleaning packages:")
648 logging.info("%s", packages)
649 cmd = [EMERGE_CMD, "--unmerge"]
650 if root != "/":
651 cmd.extend(["--sysroot=%s" % root, "--root=%s" % root])
652 cmd.extend(packages)
653 cros_build_lib.run(cmd)
654 else:
655 logging.info("Nothing to clean!")
656
657
658def SelectActiveToolchains(targets, root="/"):
659 """Runs gcc-config and binutils-config to select the desired.
660
661 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700662 targets: The targets to select
663 root: The root where we want to select toolchain versions.
Alex Klein1699fab2022-09-08 08:46:06 -0600664 """
665 for package in ["gcc", "binutils"]:
666 for target in targets:
667 # See if this package is part of this target.
668 if package not in GetTargetPackages(target):
669 logging.debug("%s: %s is not used", target, package)
670 continue
671
672 # Pick the first version in the numbered list as the selected one.
673 desired = GetDesiredPackageVersions(target, package)
674 desired_num = VersionListToNumeric(
675 target, package, desired, True, root=root
676 )
677 desired = desired_num[0]
678 # *-config does not play revisions, strip them, keep just PV.
679 desired = portage.versions.pkgsplit("%s-%s" % (package, desired))[1]
680
681 if target.startswith("host"):
Alex Klein0e92b2c2023-01-13 11:54:15 -0700682 # *-config is the only tool treating host identically (by
683 # tuple).
Alex Klein1699fab2022-09-08 08:46:06 -0600684 target = toolchain.GetHostTuple()
685
686 # And finally, attach target to it.
687 desired = "%s-%s" % (target, desired)
688
689 extra_env = {"CHOST": target}
690 if root != "/":
691 extra_env["ROOT"] = root
692 cmd = ["%s-config" % package, "-c", target]
693 result = cros_build_lib.run(
694 cmd,
695 print_cmd=False,
696 stdout=True,
697 encoding="utf-8",
698 extra_env=extra_env,
699 )
700 current = result.stdout.splitlines()[0]
701
Alex Klein0e92b2c2023-01-13 11:54:15 -0700702 # Do not reconfig when the current is live or nothing needs to be
703 # done.
Alex Klein1699fab2022-09-08 08:46:06 -0600704 extra_env = {"ROOT": root} if root != "/" else None
705 if current != desired and current != "9999":
706 cmd = [package + "-config", desired]
707 cros_build_lib.run(cmd, print_cmd=False, extra_env=extra_env)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100708
709
Mike Frysinger35247af2012-11-16 18:58:06 -0500710def ExpandTargets(targets_wanted):
Alex Klein1699fab2022-09-08 08:46:06 -0600711 """Expand any possible toolchain aliases into full targets
Mike Frysinger35247af2012-11-16 18:58:06 -0500712
Alex Klein1699fab2022-09-08 08:46:06 -0600713 This will expand 'all' and 'sdk' into the respective toolchain tuples.
Mike Frysinger35247af2012-11-16 18:58:06 -0500714
Alex Klein1699fab2022-09-08 08:46:06 -0600715 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700716 targets_wanted: The targets specified by the user.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500717
Alex Klein1699fab2022-09-08 08:46:06 -0600718 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700719 Dictionary of concrete targets and their toolchain tuples.
Alex Klein1699fab2022-09-08 08:46:06 -0600720 """
721 targets_wanted = set(targets_wanted)
722 if targets_wanted == set(["boards"]):
723 # Only pull targets from the included boards.
724 return {}
Gilad Arnold8195b532015-04-07 10:56:30 +0300725
Alex Klein1699fab2022-09-08 08:46:06 -0600726 all_targets = toolchain.GetAllTargets()
727 if targets_wanted == set(["all"]):
728 return all_targets
729 if targets_wanted == set(["sdk"]):
730 # Filter out all the non-sdk toolchains as we don't want to mess
731 # with those in all of our builds.
732 return toolchain.FilterToolchains(all_targets, "sdk", True)
Gilad Arnold8195b532015-04-07 10:56:30 +0300733
Alex Klein1699fab2022-09-08 08:46:06 -0600734 # Verify user input.
735 nonexistent = targets_wanted.difference(all_targets)
736 if nonexistent:
737 raise ValueError("Invalid targets: %s" % (",".join(nonexistent),))
738 return {t: all_targets[t] for t in targets_wanted}
Mike Frysinger35247af2012-11-16 18:58:06 -0500739
740
Alex Klein1699fab2022-09-08 08:46:06 -0600741def UpdateToolchains(
742 usepkg,
743 deleteold,
744 hostonly,
745 reconfig,
746 targets_wanted,
747 boards_wanted,
748 root="/",
749):
750 """Performs all steps to create a synchronized toolchain enviroment.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100751
Alex Klein1699fab2022-09-08 08:46:06 -0600752 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700753 usepkg: Use prebuilt packages
754 deleteold: Unmerge deprecated packages
755 hostonly: Only setup the host toolchain
756 reconfig: Reload crossdev config and reselect toolchains
757 targets_wanted: All the targets to update
758 boards_wanted: Load targets from these boards
759 root: The root in which to install the toolchains.
Alex Klein1699fab2022-09-08 08:46:06 -0600760 """
761 targets, crossdev_targets, reconfig_targets = {}, {}, {}
762 if not hostonly:
763 # For hostonly, we can skip most of the below logic, much of which won't
764 # work on bare systems where this is useful.
765 targets = ExpandTargets(targets_wanted)
Mike Frysinger7ccee992012-06-01 21:27:59 -0400766
Alex Klein1699fab2022-09-08 08:46:06 -0600767 # Filter out toolchains that don't (yet) have a binpkg available.
768 if usepkg:
769 for target in list(targets.keys()):
770 if not targets[target]["have-binpkg"]:
771 del targets[target]
Mike Frysingerd246fb92021-10-26 16:08:39 -0400772
Alex Klein1699fab2022-09-08 08:46:06 -0600773 # Now re-add any targets that might be from this board. This is to
774 # allow unofficial boards to declare their own toolchains.
775 for board in boards_wanted:
776 targets.update(toolchain.GetToolchainsForBoard(board))
Zdenek Behan508dcce2011-12-05 15:39:32 +0100777
Alex Klein1699fab2022-09-08 08:46:06 -0600778 # First check and initialize all cross targets that need to be.
779 for target in targets:
780 if TargetIsInitialized(target):
781 reconfig_targets[target] = targets[target]
782 else:
783 crossdev_targets[target] = targets[target]
784 if crossdev_targets:
785 logging.info("The following targets need to be re-initialized:")
786 logging.info("%s", crossdev_targets)
787 Crossdev.UpdateTargets(crossdev_targets, usepkg)
788 # Those that were not initialized may need a config update.
789 Crossdev.UpdateTargets(reconfig_targets, usepkg, config_only=True)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100790
Alex Klein0e92b2c2023-01-13 11:54:15 -0700791 # If we're building a subset of toolchains for a board, we might not
792 # have all the tuples that the packages expect. We don't define the
793 # "full" set of tuples currently other than "whatever the full sdk has
794 # normally".
Alex Klein1699fab2022-09-08 08:46:06 -0600795 if usepkg or set(("all", "sdk")) & targets_wanted:
796 # Since we have cross-compilers now, we can update these packages.
797 targets["host-post-cross"] = {}
Mike Frysinger785b0c32017-09-13 01:35:59 -0400798
Alex Klein1699fab2022-09-08 08:46:06 -0600799 # We want host updated.
800 targets["host"] = {}
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100801
Alex Klein1699fab2022-09-08 08:46:06 -0600802 # Now update all packages.
803 if (
804 UpdateTargets(targets, usepkg, root=root)
805 or crossdev_targets
806 or reconfig
807 ):
808 SelectActiveToolchains(targets, root=root)
David James7ec5efc2012-11-06 09:39:49 -0800809
Alex Klein1699fab2022-09-08 08:46:06 -0600810 if deleteold:
811 CleanTargets(targets, root=root)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100812
Alex Klein1699fab2022-09-08 08:46:06 -0600813 # Now that we've cleared out old versions, see if we need to rebuild
814 # anything. Can't do this earlier as it might not be broken.
815 RebuildLibtool(root=root)
Mike Frysingerc880a962013-11-08 13:59:06 -0500816
Zdenek Behan508dcce2011-12-05 15:39:32 +0100817
Bertrand SIMONNETcae9d5f2015-03-09 15:58:01 -0700818def ShowConfig(name):
Alex Klein1699fab2022-09-08 08:46:06 -0600819 """Show the toolchain tuples used by |name|
Mike Frysinger35247af2012-11-16 18:58:06 -0500820
Alex Klein1699fab2022-09-08 08:46:06 -0600821 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700822 name: The board name to query.
Alex Klein1699fab2022-09-08 08:46:06 -0600823 """
824 toolchains = toolchain.GetToolchainsForBoard(name)
825 # Make sure we display the default toolchain first.
826 # Note: Do not use logging here as this is meant to be used by other tools.
827 print(
828 ",".join(
829 list(toolchain.FilterToolchains(toolchains, "default", True))
830 + list(toolchain.FilterToolchains(toolchains, "default", False))
831 )
832 )
Mike Frysinger35247af2012-11-16 18:58:06 -0500833
834
Mike Frysinger35247af2012-11-16 18:58:06 -0500835def GeneratePathWrapper(root, wrappath, path):
Alex Klein1699fab2022-09-08 08:46:06 -0600836 """Generate a shell script to execute another shell script
Mike Frysinger35247af2012-11-16 18:58:06 -0500837
Alex Klein1699fab2022-09-08 08:46:06 -0600838 Since we can't symlink a wrapped ELF (see GenerateLdsoWrapper) because the
839 argv[0] won't be pointing to the correct path, generate a shell script that
840 just executes another program with its full path.
Mike Frysinger35247af2012-11-16 18:58:06 -0500841
Alex Klein1699fab2022-09-08 08:46:06 -0600842 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700843 root: The root tree to generate scripts inside of
844 wrappath: The full path (inside |root|) to create the wrapper
845 path: The target program which this wrapper will execute
Alex Klein1699fab2022-09-08 08:46:06 -0600846 """
847 replacements = {
848 "path": path,
849 "relroot": os.path.relpath("/", os.path.dirname(wrappath)),
850 }
Takuto Ikuta58403972018-08-16 18:52:51 +0900851
Alex Klein1699fab2022-09-08 08:46:06 -0600852 # Do not use exec here, because exec invokes script with absolute path in
Alex Klein0e92b2c2023-01-13 11:54:15 -0700853 # argv0. Keeping relativeness allows us to remove abs path from compile
854 # result and leads directory independent build cache sharing in some
855 # distributed build system.
Alex Klein1699fab2022-09-08 08:46:06 -0600856 wrapper = (
857 """#!/bin/sh
Takuto Ikuta58403972018-08-16 18:52:51 +0900858basedir=$(dirname "$0")
859"${basedir}/%(relroot)s%(path)s" "$@"
860exit "$?"
Alex Klein1699fab2022-09-08 08:46:06 -0600861"""
862 % replacements
863 )
864 root_wrapper = root + wrappath
865 if os.path.islink(root_wrapper):
866 os.unlink(root_wrapper)
867 else:
868 osutils.SafeMakedirs(os.path.dirname(root_wrapper))
869 osutils.WriteFile(root_wrapper, wrapper)
870 os.chmod(root_wrapper, 0o755)
Mike Frysinger35247af2012-11-16 18:58:06 -0500871
872
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700873def FixClangXXWrapper(root, path):
Alex Klein1699fab2022-09-08 08:46:06 -0600874 """Fix wrapper shell scripts and symlinks for invoking clang++
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700875
Alex Klein1699fab2022-09-08 08:46:06 -0600876 In a typical installation, clang++ symlinks to clang, which symlinks to the
877 elf executable. The executable distinguishes between clang and clang++ based
878 on argv[0].
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700879
Alex Klein0e92b2c2023-01-13 11:54:15 -0700880 When invoked through the LdsoWrapper, argv[0] always contains the path to
881 the executable elf file, making clang/clang++ invocations indistinguishable.
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700882
Alex Klein1699fab2022-09-08 08:46:06 -0600883 This function detects if the elf executable being wrapped is clang-X.Y, and
884 fixes wrappers/symlinks as necessary so that clang++ will work correctly.
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700885
Alex Klein1699fab2022-09-08 08:46:06 -0600886 The calling sequence now becomes:
887 -) clang++ invocation turns into clang++-3.9 (which is a copy of clang-3.9,
888 the Ldsowrapper).
889 -) clang++-3.9 uses the Ldso to invoke clang++-3.9.elf, which is a symlink
890 to the original clang-3.9 elf.
891 -) The difference this time is that inside the elf file execution, $0 is
892 set as .../usr/bin/clang++-3.9.elf, which contains 'clang++' in the name.
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700893
Alex Klein1699fab2022-09-08 08:46:06 -0600894 Update: Starting since clang 7, the clang and clang++ are symlinks to
895 clang-7 binary, not clang-7.0. The pattern match is extended to handle
896 both clang-7 and clang-7.0 cases for now. (https://crbug.com/837889)
Manoj Guptaae268142018-04-27 23:28:36 -0700897
Alex Klein1699fab2022-09-08 08:46:06 -0600898 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700899 root: The root tree to generate scripts / symlinks inside of
900 path: The target elf for which LdsoWrapper was created
Alex Klein1699fab2022-09-08 08:46:06 -0600901 """
902 if re.match(r"/usr/bin/clang-\d+(\.\d+)*$", path):
903 logging.info("fixing clang++ invocation for %s", path)
904 clangdir = os.path.dirname(root + path)
905 clang = os.path.basename(path)
906 clangxx = clang.replace("clang", "clang++")
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700907
Alex Klein1699fab2022-09-08 08:46:06 -0600908 # Create a symlink clang++-X.Y.elf to point to clang-X.Y.elf
909 os.symlink(clang + ".elf", os.path.join(clangdir, clangxx + ".elf"))
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700910
Alex Klein1699fab2022-09-08 08:46:06 -0600911 # Create a hardlink clang++-X.Y pointing to clang-X.Y
912 os.link(os.path.join(clangdir, clang), os.path.join(clangdir, clangxx))
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700913
Alex Klein1699fab2022-09-08 08:46:06 -0600914 # Adjust the clang++ symlink to point to clang++-X.Y
915 os.unlink(os.path.join(clangdir, "clang++"))
916 os.symlink(clangxx, os.path.join(clangdir, "clang++"))
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700917
918
Mike Frysinger35247af2012-11-16 18:58:06 -0500919def FileIsCrosSdkElf(elf):
Alex Klein1699fab2022-09-08 08:46:06 -0600920 """Determine if |elf| is an ELF that we execute in the cros_sdk
Mike Frysinger35247af2012-11-16 18:58:06 -0500921
Alex Klein1699fab2022-09-08 08:46:06 -0600922 We don't need this to be perfect, just quick. It makes sure the ELF
923 is a 64bit LSB x86_64 ELF. That is the native type of cros_sdk.
Mike Frysinger35247af2012-11-16 18:58:06 -0500924
Alex Klein1699fab2022-09-08 08:46:06 -0600925 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700926 elf: The file to check
Mike Frysinger1a736a82013-12-12 01:50:59 -0500927
Alex Klein1699fab2022-09-08 08:46:06 -0600928 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700929 True if we think |elf| is a native ELF
Alex Klein1699fab2022-09-08 08:46:06 -0600930 """
931 with open(elf, "rb") as f:
932 data = f.read(20)
933 # Check the magic number, EI_CLASS, EI_DATA, and e_machine.
934 return (
935 data[0:4] == b"\x7fELF"
936 and data[4:5] == b"\x02"
937 and data[5:6] == b"\x01"
938 and data[18:19] == b"\x3e"
939 )
Mike Frysinger35247af2012-11-16 18:58:06 -0500940
941
942def IsPathPackagable(ptype, path):
Alex Klein1699fab2022-09-08 08:46:06 -0600943 """Should the specified file be included in a toolchain package?
Mike Frysinger35247af2012-11-16 18:58:06 -0500944
Alex Klein1699fab2022-09-08 08:46:06 -0600945 We only need to handle files as we'll create dirs as we need them.
Mike Frysinger35247af2012-11-16 18:58:06 -0500946
Alex Klein1699fab2022-09-08 08:46:06 -0600947 Further, trim files that won't be useful:
948 - non-english translations (.mo) since it'd require env vars
949 - debug files since these are for the host compiler itself
950 - info/man pages as they're big, and docs are online, and the
951 native docs should work fine for the most part (`man gcc`)
Mike Frysinger35247af2012-11-16 18:58:06 -0500952
Alex Klein1699fab2022-09-08 08:46:06 -0600953 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700954 ptype: A string describing the path type (i.e. 'file' or 'dir' or 'sym')
955 path: The full path to inspect
Mike Frysinger1a736a82013-12-12 01:50:59 -0500956
Alex Klein1699fab2022-09-08 08:46:06 -0600957 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700958 True if we want to include this path in the package
Alex Klein1699fab2022-09-08 08:46:06 -0600959 """
960 return not (
961 ptype in ("dir",)
962 or path.startswith("/usr/lib/debug/")
963 or os.path.splitext(path)[1] == ".mo"
964 or ("/man/" in path or "/info/" in path)
965 )
Mike Frysinger35247af2012-11-16 18:58:06 -0500966
967
968def ReadlinkRoot(path, root):
Alex Klein1699fab2022-09-08 08:46:06 -0600969 """Like os.readlink(), but relative to a |root|
Mike Frysinger35247af2012-11-16 18:58:06 -0500970
Alex Klein1699fab2022-09-08 08:46:06 -0600971 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700972 path: The symlink to read
973 root: The path to use for resolving absolute symlinks
Mike Frysinger1a736a82013-12-12 01:50:59 -0500974
Alex Klein1699fab2022-09-08 08:46:06 -0600975 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700976 A fully resolved symlink path
Alex Klein1699fab2022-09-08 08:46:06 -0600977 """
978 while os.path.islink(root + path):
979 path = os.path.join(os.path.dirname(path), os.readlink(root + path))
980 return path
Mike Frysinger35247af2012-11-16 18:58:06 -0500981
982
Alex Klein1699fab2022-09-08 08:46:06 -0600983def _GetFilesForTarget(target, root="/"):
984 """Locate all the files to package for |target|
Mike Frysinger35247af2012-11-16 18:58:06 -0500985
Alex Klein1699fab2022-09-08 08:46:06 -0600986 This does not cover ELF dependencies.
Mike Frysinger35247af2012-11-16 18:58:06 -0500987
Alex Klein1699fab2022-09-08 08:46:06 -0600988 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700989 target: The toolchain target name
990 root: The root path to pull all packages from
Mike Frysinger1a736a82013-12-12 01:50:59 -0500991
Alex Klein1699fab2022-09-08 08:46:06 -0600992 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700993 A tuple of a set of all packable paths, and a set of all paths which
994 are also native ELFs
Alex Klein1699fab2022-09-08 08:46:06 -0600995 """
996 paths = set()
997 elfs = set()
Mike Frysinger35247af2012-11-16 18:58:06 -0500998
Alex Klein1699fab2022-09-08 08:46:06 -0600999 # Find all the files owned by the packages for this target.
1000 for pkg in GetTargetPackages(target):
Mike Frysinger35247af2012-11-16 18:58:06 -05001001
Alex Klein1699fab2022-09-08 08:46:06 -06001002 # Skip Go compiler from redistributable packages.
1003 # The "go" executable has GOROOT=/usr/lib/go/${CTARGET} hardcoded
1004 # into it. Due to this, the toolchain cannot be unpacked anywhere
1005 # else and be readily useful. To enable packaging Go, we need to:
1006 # -) Tweak the wrappers/environment to override GOROOT
1007 # automatically based on the unpack location.
1008 # -) Make sure the ELF dependency checking and wrapping logic
1009 # below skips the Go toolchain executables and libraries.
1010 # -) Make sure the packaging process maintains the relative
1011 # timestamps of precompiled standard library packages.
1012 # (see dev-lang/go ebuild for details).
1013 if pkg == "ex_go":
1014 continue
Rahul Chaudhry4b803052015-05-13 15:25:56 -07001015
Alex Klein1699fab2022-09-08 08:46:06 -06001016 # Use armv7a-cros-linux-gnueabi/compiler-rt for
Alex Klein0e92b2c2023-01-13 11:54:15 -07001017 # armv7a-cros-linux-gnueabihf/compiler-rt. Currently the
1018 # armv7a-cros-linux-gnueabi is actually the same as
1019 # armv7a-cros-linux-gnueabihf with different names. Because of that, for
1020 # compiler-rt, it generates the same binary in the same location. To
1021 # avoid the installation conflict, we do not install anything for
1022 # 'armv7a-cros-linux-gnueabihf'. This would cause problem if other
1023 # people try to use standalone armv7a-cros-linux-gnueabihf toolchain.
Alex Klein1699fab2022-09-08 08:46:06 -06001024 if "compiler-rt" in pkg and "armv7a-cros-linux-gnueabi" in target:
1025 atom = GetPortagePackage(target, pkg)
1026 cat, pn = atom.split("/")
1027 ver = GetInstalledPackageVersions(atom, root=root)[0]
1028 dblink = portage.dblink(
1029 cat, pn + "-" + ver, myroot=root, settings=portage.settings
1030 )
1031 contents = dblink.getcontents()
1032 if not contents:
1033 if "hf" in target:
1034 new_target = "armv7a-cros-linux-gnueabi"
1035 else:
1036 new_target = "armv7a-cros-linux-gnueabihf"
1037 atom = GetPortagePackage(new_target, pkg)
Yunlian Jiang36f35242018-04-27 10:18:40 -07001038 else:
Alex Klein1699fab2022-09-08 08:46:06 -06001039 atom = GetPortagePackage(target, pkg)
Yunlian Jiang36f35242018-04-27 10:18:40 -07001040
Alex Klein1699fab2022-09-08 08:46:06 -06001041 cat, pn = atom.split("/")
1042 ver = GetInstalledPackageVersions(atom, root=root)[0]
1043 logging.info("packaging %s-%s", atom, ver)
Mike Frysinger35247af2012-11-16 18:58:06 -05001044
Alex Klein1699fab2022-09-08 08:46:06 -06001045 dblink = portage.dblink(
1046 cat, pn + "-" + ver, myroot=root, settings=portage.settings
1047 )
1048 contents = dblink.getcontents()
1049 for obj in contents:
1050 ptype = contents[obj][0]
1051 if not IsPathPackagable(ptype, obj):
1052 continue
Mike Frysinger35247af2012-11-16 18:58:06 -05001053
Alex Klein1699fab2022-09-08 08:46:06 -06001054 if ptype == "obj":
1055 # For native ELFs, we need to pull in their dependencies too.
1056 if FileIsCrosSdkElf(obj):
1057 logging.debug("Adding ELF %s", obj)
1058 elfs.add(obj)
1059 logging.debug("Adding path %s", obj)
1060 paths.add(obj)
Mike Frysinger35247af2012-11-16 18:58:06 -05001061
Alex Klein1699fab2022-09-08 08:46:06 -06001062 return paths, elfs
Mike Frysinger35247af2012-11-16 18:58:06 -05001063
1064
Alex Klein1699fab2022-09-08 08:46:06 -06001065def _BuildInitialPackageRoot(
1066 output_dir, paths, elfs, ldpaths, path_rewrite_func=lambda x: x, root="/"
1067):
1068 """Link in all packable files and their runtime dependencies
Mike Frysinger35247af2012-11-16 18:58:06 -05001069
Alex Klein1699fab2022-09-08 08:46:06 -06001070 This also wraps up executable ELFs with helper scripts.
Mike Frysinger35247af2012-11-16 18:58:06 -05001071
Alex Klein1699fab2022-09-08 08:46:06 -06001072 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -07001073 output_dir: The output directory to store files
1074 paths: All the files to include
1075 elfs: All the files which are ELFs (a subset of |paths|)
1076 ldpaths: A dict of static ldpath information
1077 path_rewrite_func: User callback to rewrite paths in output_dir
1078 root: The root path to pull all packages/files from
Alex Klein1699fab2022-09-08 08:46:06 -06001079 """
1080 # Link in all the files.
1081 sym_paths = {}
1082 for path in paths:
1083 new_path = path_rewrite_func(path)
1084 logging.debug("Transformed %s to %s", path, new_path)
1085 dst = output_dir + new_path
1086 osutils.SafeMakedirs(os.path.dirname(dst))
Mike Frysinger35247af2012-11-16 18:58:06 -05001087
Alex Klein1699fab2022-09-08 08:46:06 -06001088 # Is this a symlink which we have to rewrite or wrap?
1089 # Delay wrap check until after we have created all paths.
1090 src = root + path
1091 if os.path.islink(src):
1092 tgt = os.readlink(src)
1093 if os.path.sep in tgt:
1094 sym_paths[lddtree.normpath(ReadlinkRoot(src, root))] = new_path
Mike Frysinger35247af2012-11-16 18:58:06 -05001095
Alex Klein0e92b2c2023-01-13 11:54:15 -07001096 # Rewrite absolute links to relative and then generate the
1097 # symlink ourselves. All other symlinks can be hardlinked below.
Alex Klein1699fab2022-09-08 08:46:06 -06001098 if tgt[0] == "/":
1099 tgt = os.path.relpath(tgt, os.path.dirname(new_path))
1100 os.symlink(tgt, dst)
1101 continue
Mike Frysinger35247af2012-11-16 18:58:06 -05001102
Alex Klein1699fab2022-09-08 08:46:06 -06001103 logging.debug("Linking path %s -> %s", src, dst)
1104 os.link(src, dst)
Mike Frysinger35247af2012-11-16 18:58:06 -05001105
Alex Klein1699fab2022-09-08 08:46:06 -06001106 # Locate all the dependencies for all the ELFs. Stick them all in the
1107 # top level "lib" dir to make the wrapper simpler. This exact path does
1108 # not matter since we execute ldso directly, and we tell the ldso the
1109 # exact path to search for its libraries.
1110 libdir = os.path.join(output_dir, "lib")
1111 osutils.SafeMakedirs(libdir)
1112 donelibs = set()
1113 basenamelibs = set()
Manoj Gupta98e674d2022-10-05 00:31:41 +00001114 glibc_re = re.compile(r"/lib(c|pthread)[0-9.-]*\.so[0-9.-]*")
Alex Klein1699fab2022-09-08 08:46:06 -06001115 for elf in elfs:
1116 e = lddtree.ParseELF(elf, root=root, ldpaths=ldpaths)
1117 logging.debug("Parsed elf %s data: %s", elf, e)
1118 interp = e["interp"]
Mike Frysinger221bd822017-09-29 02:51:47 -04001119
Alex Klein0e92b2c2023-01-13 11:54:15 -07001120 # TODO(b/187786323): Drop this hack once libopcodes linkage is fixed.
Alex Klein1699fab2022-09-08 08:46:06 -06001121 if os.path.basename(elf).startswith("libopcodes-"):
1122 continue
Mike Frysinger35247af2012-11-16 18:58:06 -05001123
Alex Klein0e92b2c2023-01-13 11:54:15 -07001124 # Copy all the dependencies before we copy the program & generate
1125 # wrappers.
Alex Klein1699fab2022-09-08 08:46:06 -06001126 for lib, lib_data in e["libs"].items():
1127 src = path = lib_data["path"]
1128 if path is None:
1129 logging.warning("%s: could not locate %s", elf, lib)
1130 continue
Mike Frysinger9fe02342019-12-12 17:52:53 -05001131
Alex Klein1699fab2022-09-08 08:46:06 -06001132 # No need to try and copy the same source lib multiple times.
1133 if path in donelibs:
1134 continue
1135 donelibs.add(path)
Mike Frysinger9fe02342019-12-12 17:52:53 -05001136
Alex Klein0e92b2c2023-01-13 11:54:15 -07001137 # Die if we try to normalize different source libs with the same
1138 # basename.
Alex Klein1699fab2022-09-08 08:46:06 -06001139 if lib in basenamelibs:
1140 logging.error(
1141 "Multiple sources detected for %s:\n new: %s\n old: %s",
1142 os.path.join("/lib", lib),
1143 path,
1144 " ".join(
1145 x
1146 for x in donelibs
1147 if x != path and os.path.basename(x) == lib
1148 ),
1149 )
1150 # TODO(crbug.com/917193): Make this fatal.
1151 # cros_build_lib.Die('Unable to resolve lib conflicts')
1152 continue
1153 basenamelibs.add(lib)
Mike Frysinger35247af2012-11-16 18:58:06 -05001154
Alex Klein1699fab2022-09-08 08:46:06 -06001155 # Needed libs are the SONAME, but that is usually a symlink, not a
1156 # real file. So link in the target rather than the symlink itself.
1157 # We have to walk all the possible symlinks (SONAME could point to a
1158 # symlink which points to a symlink), and we have to handle absolute
1159 # ourselves (since we have a "root" argument).
1160 dst = os.path.join(libdir, os.path.basename(path))
1161 src = ReadlinkRoot(src, root)
Mike Frysinger35247af2012-11-16 18:58:06 -05001162
Alex Klein1699fab2022-09-08 08:46:06 -06001163 logging.debug("Linking lib %s -> %s", root + src, dst)
1164 os.link(root + src, dst)
Mike Frysinger35247af2012-11-16 18:58:06 -05001165
Alex Klein1699fab2022-09-08 08:46:06 -06001166 # Do not create wrapper for libc. crbug.com/766827
1167 if interp and not glibc_re.search(elf):
1168 # Generate a wrapper if it is executable.
1169 interp = os.path.join("/lib", os.path.basename(interp))
1170 lddtree.GenerateLdsoWrapper(
1171 output_dir,
1172 path_rewrite_func(elf),
1173 interp,
1174 libpaths=e["rpath"] + e["runpath"],
1175 )
1176 FixClangXXWrapper(output_dir, path_rewrite_func(elf))
Mike Frysinger00b129f2021-04-21 18:11:48 -04001177
Alex Klein1699fab2022-09-08 08:46:06 -06001178 # Wrap any symlinks to the wrapper.
1179 if elf in sym_paths:
1180 link = sym_paths[elf]
1181 GeneratePathWrapper(output_dir, link, elf)
Mike Frysinger00b129f2021-04-21 18:11:48 -04001182
Mike Frysinger35247af2012-11-16 18:58:06 -05001183
1184def _EnvdGetVar(envd, var):
Alex Klein1699fab2022-09-08 08:46:06 -06001185 """Given a Gentoo env.d file, extract a var from it
Mike Frysinger35247af2012-11-16 18:58:06 -05001186
Alex Klein1699fab2022-09-08 08:46:06 -06001187 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -07001188 envd: The env.d file to load (may be a glob path)
1189 var: The var to extract
Mike Frysinger1a736a82013-12-12 01:50:59 -05001190
Alex Klein1699fab2022-09-08 08:46:06 -06001191 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -07001192 The value of |var|
Alex Klein1699fab2022-09-08 08:46:06 -06001193 """
1194 envds = glob.glob(envd)
1195 assert len(envds) == 1, "%s: should have exactly 1 env.d file" % envd
1196 envd = envds[0]
1197 return key_value_store.LoadFile(envd)[var]
Mike Frysinger35247af2012-11-16 18:58:06 -05001198
1199
1200def _ProcessBinutilsConfig(target, output_dir):
Alex Klein1699fab2022-09-08 08:46:06 -06001201 """Do what binutils-config would have done"""
1202 binpath = os.path.join("/bin", target + "-")
Mike Frysingerd4d40fd2014-11-06 17:30:57 -05001203
Alex Klein1699fab2022-09-08 08:46:06 -06001204 # Locate the bin dir holding the linker and perform some confidence checks
1205 binutils_bin_path = os.path.join(
1206 output_dir, "usr", toolchain.GetHostTuple(), target, "binutils-bin"
1207 )
1208 globpath = os.path.join(binutils_bin_path, "*")
1209 srcpath = glob.glob(globpath)
1210 assert len(srcpath) == 1, (
1211 "%s: matched more than one path. Is Gold enabled?" % globpath
1212 )
1213 srcpath = srcpath[0]
1214 ld_path = os.path.join(srcpath, "ld")
1215 assert os.path.exists(ld_path), "%s: linker is missing!" % ld_path
1216 ld_path = os.path.join(srcpath, "ld.bfd")
1217 assert os.path.exists(ld_path), "%s: linker is missing!" % ld_path
Rahul Chaudhry4891b4d2017-03-08 10:31:27 -08001218
Alex Klein1699fab2022-09-08 08:46:06 -06001219 srcpath = srcpath[len(output_dir) :]
1220 gccpath = os.path.join("/usr", "libexec", "gcc")
1221 for prog in os.listdir(output_dir + srcpath):
1222 # Skip binaries already wrapped.
1223 if not prog.endswith(".real"):
1224 GeneratePathWrapper(
1225 output_dir, binpath + prog, os.path.join(srcpath, prog)
1226 )
1227 GeneratePathWrapper(
1228 output_dir,
1229 os.path.join(gccpath, prog),
1230 os.path.join(srcpath, prog),
1231 )
Mike Frysinger35247af2012-11-16 18:58:06 -05001232
Alex Klein1699fab2022-09-08 08:46:06 -06001233 libpath = os.path.join("/usr", toolchain.GetHostTuple(), target, "lib")
1234 envd = os.path.join(output_dir, "etc", "env.d", "binutils", "*")
1235 srcpath = _EnvdGetVar(envd, "LIBPATH")
1236 os.symlink(
1237 os.path.relpath(srcpath, os.path.dirname(libpath)), output_dir + libpath
1238 )
Mike Frysinger35247af2012-11-16 18:58:06 -05001239
1240
1241def _ProcessGccConfig(target, output_dir):
Alex Klein1699fab2022-09-08 08:46:06 -06001242 """Do what gcc-config would have done"""
1243 binpath = "/bin"
1244 envd = os.path.join(output_dir, "etc", "env.d", "gcc", "*")
1245 srcpath = _EnvdGetVar(envd, "GCC_PATH")
1246 for prog in os.listdir(output_dir + srcpath):
1247 # Skip binaries already wrapped.
1248 if (
1249 not prog.endswith(".real")
1250 and not prog.endswith(".elf")
1251 and prog.startswith(target)
1252 ):
1253 GeneratePathWrapper(
1254 output_dir,
1255 os.path.join(binpath, prog),
1256 os.path.join(srcpath, prog),
1257 )
1258 return srcpath
Mike Frysinger35247af2012-11-16 18:58:06 -05001259
1260
Frank Henigman179ec7c2015-02-06 03:01:09 -05001261def _ProcessSysrootWrappers(_target, output_dir, srcpath):
Alex Klein1699fab2022-09-08 08:46:06 -06001262 """Remove chroot-specific things from our sysroot wrappers"""
1263 # Disable ccache since we know it won't work outside of chroot.
Tobias Boschddd16492019-08-14 09:29:54 -07001264
Alex Klein1699fab2022-09-08 08:46:06 -06001265 # Use the version of the wrapper that does not use ccache.
1266 for sysroot_wrapper in glob.glob(
1267 os.path.join(output_dir + srcpath, "sysroot_wrapper*.ccache")
1268 ):
1269 # Can't update the wrapper in place to not affect the chroot,
1270 # but only the extracted toolchain.
1271 os.unlink(sysroot_wrapper)
1272 shutil.copy(sysroot_wrapper[:-6] + "noccache", sysroot_wrapper)
1273 shutil.copy(
1274 sysroot_wrapper[:-6] + "noccache.elf", sysroot_wrapper + ".elf"
1275 )
Mike Frysinger35247af2012-11-16 18:58:06 -05001276
1277
Manoj Gupta61bf9db2020-03-23 21:28:04 -07001278def _ProcessClangWrappers(target, output_dir):
Alex Klein1699fab2022-09-08 08:46:06 -06001279 """Remove chroot-specific things from our sysroot wrappers"""
1280 clang_bin_path = "/usr/bin"
1281 # Disable ccache from clang wrappers.
1282 _ProcessSysrootWrappers(target, output_dir, clang_bin_path)
1283 GeneratePathWrapper(
1284 output_dir, f"/bin/{target}-clang", f"/usr/bin/{target}-clang"
1285 )
1286 GeneratePathWrapper(
1287 output_dir, f"/bin/{target}-clang++", f"/usr/bin/{target}-clang++"
1288 )
Manoj Gupta61bf9db2020-03-23 21:28:04 -07001289
1290
Yunlian Jiang5ad6b512017-09-20 09:27:45 -07001291def _CreateMainLibDir(target, output_dir):
Alex Klein1699fab2022-09-08 08:46:06 -06001292 """Create some lib dirs so that compiler can get the right Gcc paths"""
1293 osutils.SafeMakedirs(os.path.join(output_dir, "usr", target, "lib"))
1294 osutils.SafeMakedirs(os.path.join(output_dir, "usr", target, "usr/lib"))
Yunlian Jiang5ad6b512017-09-20 09:27:45 -07001295
1296
Manoj Guptadf8b3872022-01-13 11:57:36 -08001297def _CreateRemoteToolchainFile(output_dir):
Alex Klein1699fab2022-09-08 08:46:06 -06001298 """Create a remote_toolchain_inputs file for reclient/RBE"""
1299 # The inputs file lists all files/shared libraries needed to run clang.
1300 # All inputs are relative to location of clang binary and one input
1301 # location per line of file e.g.
1302 # clang-13.elf
1303 # clang++-13.elf
1304 # relative/path/to/clang/resource/directory
Manoj Guptadf8b3872022-01-13 11:57:36 -08001305
Alex Klein1699fab2022-09-08 08:46:06 -06001306 clang_path = os.path.join(output_dir, "usr/bin")
1307 # Add needed shared libraries and internal files e.g. allowlists.
1308 toolchain_inputs = ["../../lib"]
1309 clang_shared_dirs = glob.glob(
1310 os.path.join(output_dir, "usr/lib64/clang/*/share")
1311 )
1312 for clang_dir in clang_shared_dirs:
1313 toolchain_inputs.append(os.path.relpath(clang_dir, clang_path))
Manoj Guptadf8b3872022-01-13 11:57:36 -08001314
Alex Klein1699fab2022-09-08 08:46:06 -06001315 # Add actual clang binaries/wrappers.
1316 for clang_files in glob.glob(os.path.join(clang_path, "clang*-[0-9]*")):
1317 toolchain_inputs.append(os.path.basename(clang_files))
Manoj Guptadf8b3872022-01-13 11:57:36 -08001318
Alex Klein1699fab2022-09-08 08:46:06 -06001319 with open(os.path.join(clang_path, "remote_toolchain_inputs"), "w") as f:
1320 f.writelines("%s\n" % line for line in toolchain_inputs)
Manoj Guptadf8b3872022-01-13 11:57:36 -08001321
1322
Mike Frysinger35247af2012-11-16 18:58:06 -05001323def _ProcessDistroCleanups(target, output_dir):
Alex Klein1699fab2022-09-08 08:46:06 -06001324 """Clean up the tree and remove all distro-specific requirements
Mike Frysinger35247af2012-11-16 18:58:06 -05001325
Alex Klein1699fab2022-09-08 08:46:06 -06001326 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -07001327 target: The toolchain target name
1328 output_dir: The output directory to clean up
Han Shen699ea192016-03-02 10:42:47 -08001329 """
Alex Klein1699fab2022-09-08 08:46:06 -06001330 _ProcessBinutilsConfig(target, output_dir)
1331 gcc_path = _ProcessGccConfig(target, output_dir)
1332 _ProcessSysrootWrappers(target, output_dir, gcc_path)
1333 _ProcessClangWrappers(target, output_dir)
1334 _CreateMainLibDir(target, output_dir)
1335 _CreateRemoteToolchainFile(output_dir)
Mike Frysinger35247af2012-11-16 18:58:06 -05001336
Alex Klein1699fab2022-09-08 08:46:06 -06001337 osutils.RmDir(os.path.join(output_dir, "etc"))
Mike Frysinger35247af2012-11-16 18:58:06 -05001338
1339
Alex Klein1699fab2022-09-08 08:46:06 -06001340def CreatePackagableRoot(target, output_dir, ldpaths, root="/"):
1341 """Setup a tree from the packages for the specified target
Mike Frysinger35247af2012-11-16 18:58:06 -05001342
Alex Klein1699fab2022-09-08 08:46:06 -06001343 This populates a path with all the files from toolchain packages so that
1344 a tarball can easily be generated from the result.
Mike Frysinger35247af2012-11-16 18:58:06 -05001345
Alex Klein1699fab2022-09-08 08:46:06 -06001346 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -07001347 target: The target to create a packagable root from
1348 output_dir: The output directory to place all the files
1349 ldpaths: A dict of static ldpath information
1350 root: The root path to pull all packages/files from
Alex Klein1699fab2022-09-08 08:46:06 -06001351 """
1352 # Find all the files owned by the packages for this target.
1353 paths, elfs = _GetFilesForTarget(target, root=root)
Mike Frysinger35247af2012-11-16 18:58:06 -05001354
Alex Klein1699fab2022-09-08 08:46:06 -06001355 # Link in all the package's files, any ELF dependencies, and wrap any
1356 # executable ELFs with helper scripts.
1357 def MoveUsrBinToBin(path):
1358 """Move /usr/bin to /bin so people can just use that toplevel dir
Mike Frysinger35247af2012-11-16 18:58:06 -05001359
Alex Klein0e92b2c2023-01-13 11:54:15 -07001360 Note we do not apply this to clang or rust; there is correlation between
Alex Klein1699fab2022-09-08 08:46:06 -06001361 clang's search path for libraries / inclusion and its installation path.
1362 """
1363 NO_MOVE_PATTERNS = ("clang", "rust", "cargo", "sysroot_wrapper")
1364 if path.startswith("/usr/bin/") and not any(
1365 x in path for x in NO_MOVE_PATTERNS
1366 ):
1367 return path[4:]
1368 return path
Mike Frysinger221bd822017-09-29 02:51:47 -04001369
Alex Klein1699fab2022-09-08 08:46:06 -06001370 _BuildInitialPackageRoot(
1371 output_dir,
1372 paths,
1373 elfs,
1374 ldpaths,
1375 path_rewrite_func=MoveUsrBinToBin,
1376 root=root,
1377 )
Mike Frysinger35247af2012-11-16 18:58:06 -05001378
Alex Klein1699fab2022-09-08 08:46:06 -06001379 # The packages, when part of the normal distro, have helper scripts
1380 # that setup paths and such. Since we are making this standalone, we
1381 # need to preprocess all that ourselves.
1382 _ProcessDistroCleanups(target, output_dir)
1383
1384
1385def CreatePackages(targets_wanted, output_dir, root="/"):
1386 """Create redistributable cross-compiler packages for the specified targets
1387
1388 This creates toolchain packages that should be usable in conjunction with
1389 a downloaded sysroot (created elsewhere).
1390
1391 Tarballs (one per target) will be created in $PWD.
1392
1393 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -07001394 targets_wanted: The targets to package up.
1395 output_dir: The directory to put the packages in.
1396 root: The root path to pull all packages/files from.
Alex Klein1699fab2022-09-08 08:46:06 -06001397 """
1398 logging.info("Writing tarballs to %s", output_dir)
1399 osutils.SafeMakedirs(output_dir)
1400 ldpaths = lddtree.LoadLdpaths(root)
1401 targets = ExpandTargets(targets_wanted)
1402
1403 with osutils.TempDir(prefix="create-packages") as tempdir:
1404 logging.debug("Using tempdir: %s", tempdir)
1405
Alex Klein0e92b2c2023-01-13 11:54:15 -07001406 # We have to split the root generation from the compression stages.
1407 # This is because we hardlink in all the files (to avoid overhead of
1408 # reading/writing the copies multiple times). But tar gets angry if a
1409 # file's hardlink count changes from when it starts reading a file to
1410 # when it finishes.
Alex Klein1699fab2022-09-08 08:46:06 -06001411 with parallel.BackgroundTaskRunner(CreatePackagableRoot) as queue:
1412 for target in targets:
1413 output_target_dir = os.path.join(tempdir, target)
1414 queue.put([target, output_target_dir, ldpaths, root])
1415
1416 # Build the tarball.
1417 with parallel.BackgroundTaskRunner(
1418 cros_build_lib.CreateTarball
1419 ) as queue:
1420 for target in targets:
1421 tar_file = os.path.join(output_dir, target + ".tar.xz")
1422 queue.put([tar_file, os.path.join(tempdir, target)])
Mike Frysinger35247af2012-11-16 18:58:06 -05001423
1424
Mike Frysinger07534cf2017-09-12 17:40:21 -04001425def GetParser():
Alex Klein1699fab2022-09-08 08:46:06 -06001426 """Return a command line parser."""
1427 parser = commandline.ArgumentParser(description=__doc__)
1428 parser.add_argument(
1429 "-u",
1430 "--nousepkg",
1431 action="store_false",
1432 dest="usepkg",
1433 default=True,
1434 help="Do not use prebuilt packages",
1435 )
1436 parser.add_argument(
1437 "-d",
1438 "--deleteold",
1439 action="store_true",
1440 dest="deleteold",
1441 default=False,
1442 help="Unmerge deprecated packages",
1443 )
1444 parser.add_argument(
1445 "-t",
1446 "--targets",
1447 dest="targets",
1448 default="sdk",
1449 help="Comma separated list of tuples. Special keywords "
1450 "'host', 'sdk', 'boards', and 'all' are "
1451 "allowed. Defaults to 'sdk'.",
1452 )
1453 parser.add_argument(
1454 "--include-boards",
1455 default="",
1456 metavar="BOARDS",
1457 help="Comma separated list of boards whose toolchains we "
1458 "will always include. Default: none",
1459 )
1460 parser.add_argument(
1461 "--hostonly",
1462 dest="hostonly",
1463 default=False,
1464 action="store_true",
1465 help="Only setup the host toolchain. "
1466 "Useful for bootstrapping chroot",
1467 )
1468 parser.add_argument(
1469 "--show-board-cfg",
1470 "--show-cfg",
1471 dest="cfg_name",
1472 default=None,
1473 help="Board to list toolchains tuples for",
1474 )
1475 parser.add_argument(
1476 "--show-packages",
1477 default=None,
1478 help="List all packages the specified target uses",
1479 )
1480 parser.add_argument(
1481 "--create-packages",
1482 action="store_true",
1483 default=False,
1484 help="Build redistributable packages",
1485 )
1486 parser.add_argument(
1487 "--output-dir",
1488 default=os.getcwd(),
1489 type="path",
1490 help="Output directory",
1491 )
1492 parser.add_argument(
1493 "--reconfig",
1494 default=False,
1495 action="store_true",
1496 help="Reload crossdev config and reselect toolchains",
1497 )
1498 parser.add_argument(
1499 "--sysroot",
1500 type="path",
1501 help="The sysroot in which to install the toolchains",
1502 )
1503 return parser
Zdenek Behan508dcce2011-12-05 15:39:32 +01001504
Mike Frysinger07534cf2017-09-12 17:40:21 -04001505
1506def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001507 parser = GetParser()
1508 options = parser.parse_args(argv)
1509 options.Freeze()
Zdenek Behan508dcce2011-12-05 15:39:32 +01001510
Alex Klein1699fab2022-09-08 08:46:06 -06001511 # Figure out what we're supposed to do and reject conflicting options.
1512 conflicting_options = (
1513 options.cfg_name,
1514 options.show_packages,
1515 options.create_packages,
1516 )
1517 if sum(bool(x) for x in conflicting_options) > 1:
1518 parser.error(
1519 "conflicting options: create-packages & show-packages & "
1520 "show-board-cfg"
1521 )
Mike Frysinger984d0622012-06-01 16:08:44 -04001522
Alex Klein1699fab2022-09-08 08:46:06 -06001523 targets_wanted = set(options.targets.split(","))
1524 boards_wanted = (
1525 set(options.include_boards.split(","))
1526 if options.include_boards
1527 else set()
1528 )
Mike Frysinger35247af2012-11-16 18:58:06 -05001529
Alex Klein1699fab2022-09-08 08:46:06 -06001530 if options.cfg_name:
1531 ShowConfig(options.cfg_name)
1532 elif options.show_packages is not None:
1533 cros_build_lib.AssertInsideChroot()
1534 target = options.show_packages
1535 Crossdev.Load(False)
1536 for package in GetTargetPackages(target):
1537 print(GetPortagePackage(target, package))
1538 elif options.create_packages:
1539 cros_build_lib.AssertInsideChroot()
1540 Crossdev.Load(False)
1541 CreatePackages(targets_wanted, options.output_dir)
1542 else:
1543 cros_build_lib.AssertInsideChroot()
1544 # This has to be always run as root.
1545 if osutils.IsNonRootUser():
1546 cros_build_lib.Die("this script must be run as root")
Mike Frysinger35247af2012-11-16 18:58:06 -05001547
Alex Klein1699fab2022-09-08 08:46:06 -06001548 Crossdev.Load(options.reconfig)
1549 root = options.sysroot or "/"
1550 UpdateToolchains(
1551 options.usepkg,
1552 options.deleteold,
1553 options.hostonly,
1554 options.reconfig,
1555 targets_wanted,
1556 boards_wanted,
1557 root=root,
1558 )
1559 Crossdev.Save()
Mike Frysinger35247af2012-11-16 18:58:06 -05001560
Alex Klein1699fab2022-09-08 08:46:06 -06001561 return 0