blob: 501f497e538cee96c5876b69f92af89b5c25ea3b [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
Mike Frysinger164ec032023-03-27 16:15:14 -040035EMERGE_CMD = constants.CHROMITE_BIN_DIR / "parallel_emerge"
Alex Klein1699fab2022-09-08 08:46:06 -060036PACKAGE_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 = (
Manoj Gupta45c60c02023-05-16 16:29:28 +0000110 "arm-none-eabi",
Alex Klein1699fab2022-09-08 08:46:06 -0600111 "armv7m-cros-eabi",
112 "armv7a-cros-linux-gnueabi",
113 "armv7a-cros-linux-gnueabihf",
114 "aarch64-cros-linux-gnu",
115 "i686-cros-linux-gnu",
116 "x86_64-cros-linux-gnu",
Manoj Gupta946abb42017-04-12 14:27:19 -0700117)
118
119LLVM_PKGS_TABLE = {
Alex Klein1699fab2022-09-08 08:46:06 -0600120 "ex_llvm-libunwind": ["--ex-pkg", "sys-libs/llvm-libunwind"],
121 "ex_libcxx": ["--ex-pkg", "sys-libs/libcxx"],
Manoj Gupta946abb42017-04-12 14:27:19 -0700122}
123
Alex Klein1699fab2022-09-08 08:46:06 -0600124
David James66a09c42012-11-05 13:31:38 -0800125class Crossdev(object):
Alex Klein1699fab2022-09-08 08:46:06 -0600126 """Class for interacting with crossdev and caching its output."""
David James66a09c42012-11-05 13:31:38 -0800127
Alex Klein1699fab2022-09-08 08:46:06 -0600128 _CACHE_FILE = os.path.join(CROSSDEV_OVERLAY, ".configured.json")
129 _CACHE = {}
130 # Packages that needs separate handling, in addition to what we have from
131 # crossdev.
132 MANUAL_PKGS = {
Alex Klein1699fab2022-09-08 08:46:06 -0600133 "llvm": "sys-devel",
134 "llvm-libunwind": "sys-libs",
135 "libcxx": "sys-libs",
136 "elfutils": "dev-libs",
George Burgess IV6c298782023-02-14 14:28:04 -0700137 # b/269306499: note that rust and rust-host are shipped as a part of
138 # this tarball on a best-effort basis. If you would like them to be
139 # fully supported (with an SLA), please reach out to
140 # chromeos-toolchain@google.com and chat with us.
141 "rust": "dev-lang",
142 "rust-host": "dev-lang",
Mike Frysinger3ed47722017-08-08 14:59:08 -0400143 }
144
Alex Klein1699fab2022-09-08 08:46:06 -0600145 @classmethod
146 def Load(cls, reconfig):
147 """Load crossdev cache from disk.
Mike Frysinger3ed47722017-08-08 14:59:08 -0400148
Alex Klein1699fab2022-09-08 08:46:06 -0600149 We invalidate the cache when crossdev updates or this script changes.
150 """
151 crossdev_version = GetStablePackageVersion("sys-devel/crossdev", True)
152 # If we run the compiled/cached .pyc file, we'll read/hash that when we
153 # really always want to track the source .py file.
154 script = os.path.abspath(__file__)
155 if script.endswith(".pyc"):
156 script = script[:-1]
157 setup_toolchains_hash = hashlib.md5(
158 osutils.ReadFile(script, mode="rb")
159 ).hexdigest()
Mike Frysinger3ed47722017-08-08 14:59:08 -0400160
Alex Klein1699fab2022-09-08 08:46:06 -0600161 cls._CACHE = {
162 "crossdev_version": crossdev_version,
163 "setup_toolchains_hash": setup_toolchains_hash,
Mike Frysinger66bfde52017-09-12 16:42:57 -0400164 }
Alex Klein1699fab2022-09-08 08:46:06 -0600165
166 logging.debug("cache: checking file: %s", cls._CACHE_FILE)
167 if reconfig:
168 logging.debug("cache: forcing regen due to reconfig")
169 return
170
171 try:
172 file_data = osutils.ReadFile(cls._CACHE_FILE)
173 except IOError as e:
174 if e.errno != errno.ENOENT:
175 logging.warning("cache: reading failed: %s", e)
176 osutils.SafeUnlink(cls._CACHE_FILE)
177 return
178
179 try:
180 data = json.loads(file_data)
181 except ValueError as e:
182 logging.warning("cache: ignoring invalid content: %s", e)
183 return
184
185 if crossdev_version != data.get("crossdev_version"):
186 logging.debug("cache: rebuilding after crossdev upgrade")
187 elif setup_toolchains_hash != data.get("setup_toolchains_hash"):
188 logging.debug(
189 "cache: rebuilding after cros_setup_toolchains change"
190 )
Mike Frysinger785b0c32017-09-13 01:35:59 -0400191 else:
Alex Klein1699fab2022-09-08 08:46:06 -0600192 logging.debug("cache: content is up-to-date!")
193 cls._CACHE = data
Han Shene23782f2016-02-18 12:20:00 -0800194
Alex Klein1699fab2022-09-08 08:46:06 -0600195 @classmethod
196 def Save(cls):
197 """Store crossdev cache on disk."""
198 # Save the cache from the successful run.
Mike Frysinger31fdddd2023-02-24 15:50:55 -0500199 with open(cls._CACHE_FILE, "w", encoding="utf-8") as f:
Alex Klein1699fab2022-09-08 08:46:06 -0600200 json.dump(cls._CACHE, f)
Mike Frysinger66bfde52017-09-12 16:42:57 -0400201
Alex Klein1699fab2022-09-08 08:46:06 -0600202 @classmethod
203 def GetConfig(cls, target):
204 """Returns a map of crossdev provided variables about a tuple."""
205 CACHE_ATTR = "_target_tuple_map"
Han Shene23782f2016-02-18 12:20:00 -0800206
Alex Klein1699fab2022-09-08 08:46:06 -0600207 val = cls._CACHE.setdefault(CACHE_ATTR, {})
208 if not target in val:
209 if target.startswith("host"):
210 conf = {
211 "crosspkgs": [],
212 "target": toolchain.GetHostTuple(),
213 }
214 if target == "host":
215 packages_list = HOST_PACKAGES
216 else:
217 packages_list = HOST_POST_CROSS_PACKAGES
218 manual_pkgs = dict(
219 (pkg, cat)
220 for cat, pkg in [x.split("/") for x in packages_list]
221 )
222 else:
223 # Build the crossdev command.
Jordan R Abrahams-Whiteheadc6363032023-04-06 23:30:50 +0000224 cmd = ["crossdev", "--stable", "--show-target-cfg", "--ex-gdb"]
Alex Klein1699fab2022-09-08 08:46:06 -0600225 # Enable libxcrypt for all linux-gnu targets.
226 if "cros-linux-gnu" in target:
227 cmd.extend(CROSSDEV_LIBXCRYPT_ARGS)
228 if target in TARGET_COMPILER_RT_ENABLED:
229 cmd.extend(CROSSDEV_COMPILER_RT_ARGS)
230 if target in TARGET_LLVM_PKGS_ENABLED:
Trent Apted593c0742023-05-05 03:50:20 +0000231 # TODO(b/236161656): Fix.
232 # pylint: disable-next=consider-using-dict-items
Alex Klein1699fab2022-09-08 08:46:06 -0600233 for pkg in LLVM_PKGS_TABLE:
234 cmd.extend(LLVM_PKGS_TABLE[pkg])
235 if target in TARGET_GO_ENABLED:
236 cmd.extend(CROSSDEV_GO_ARGS)
237 cmd.extend(["-t", target])
238 # Catch output of crossdev.
239 out = cros_build_lib.run(
240 cmd, print_cmd=False, stdout=True, encoding="utf-8"
241 ).stdout.splitlines()
242 # List of tuples split at the first '=', converted into dict.
243 conf = dict(
244 (k, cros_build_lib.ShellUnquote(v))
245 for k, v in (x.split("=", 1) for x in out)
246 )
247 conf["crosspkgs"] = conf["crosspkgs"].split()
Han Shene23782f2016-02-18 12:20:00 -0800248
Alex Klein1699fab2022-09-08 08:46:06 -0600249 manual_pkgs = cls.MANUAL_PKGS
David James66a09c42012-11-05 13:31:38 -0800250
Alex Klein1699fab2022-09-08 08:46:06 -0600251 for pkg, cat in manual_pkgs.items():
252 conf[pkg + "_pn"] = pkg
253 conf[pkg + "_category"] = cat
254 if pkg not in conf["crosspkgs"]:
255 conf["crosspkgs"].append(pkg)
David James66a09c42012-11-05 13:31:38 -0800256
Alex Klein1699fab2022-09-08 08:46:06 -0600257 val[target] = conf
David James66a09c42012-11-05 13:31:38 -0800258
Alex Klein1699fab2022-09-08 08:46:06 -0600259 return val[target]
Manoj Gupta4d016f62021-10-19 16:39:34 -0700260
Alex Klein1699fab2022-09-08 08:46:06 -0600261 @classmethod
262 def UpdateTargets(cls, targets, usepkg, config_only=False):
263 """Calls crossdev to initialize a cross target.
Manoj Gupta4d016f62021-10-19 16:39:34 -0700264
Alex Klein1699fab2022-09-08 08:46:06 -0600265 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700266 targets: The dict of targets to initialize using crossdev.
267 usepkg: Copies the commandline opts.
268 config_only: Just update.
Alex Klein1699fab2022-09-08 08:46:06 -0600269 """
270 configured_targets = cls._CACHE.setdefault("configured_targets", [])
271 started_targets = set()
David James66a09c42012-11-05 13:31:38 -0800272
Alex Klein1699fab2022-09-08 08:46:06 -0600273 # Schedule all of the targets in parallel, and let them run.
274 with parallel.BackgroundTaskRunner(cls._UpdateTarget) as queue:
275 for target_name in targets:
276 # We already started this target in this loop.
277 if target_name in started_targets:
278 continue
279 # The target is already configured.
280 if config_only and target_name in configured_targets:
281 continue
282 queue.put(
283 [target_name, targets[target_name], usepkg, config_only]
284 )
285 started_targets.add(target_name)
David James66a09c42012-11-05 13:31:38 -0800286
Alex Klein1699fab2022-09-08 08:46:06 -0600287 @classmethod
288 def _UpdateTarget(cls, target_name, target, usepkg, config_only):
289 """Calls crossdev to initialize a cross target.
David James66a09c42012-11-05 13:31:38 -0800290
Alex Klein1699fab2022-09-08 08:46:06 -0600291 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700292 target_name: The name of the target to initialize.
293 target: The target info for initializing.
294 usepkg: Copies the commandline opts.
295 config_only: Just update.
Alex Klein1699fab2022-09-08 08:46:06 -0600296 """
297 configured_targets = cls._CACHE.setdefault("configured_targets", [])
Jordan R Abrahams-Whiteheadc6363032023-04-06 23:30:50 +0000298 cmdbase = ["crossdev", "--stable", "--show-fail-log"]
Alex Klein1699fab2022-09-08 08:46:06 -0600299 cmdbase.extend(["--env", "FEATURES=splitdebug"])
300 # Pick stable by default, and override as necessary.
301 cmdbase.extend(["-P", "--oneshot"])
302 if usepkg:
303 cmdbase.extend(
304 ["-P", "--getbinpkg", "-P", "--usepkgonly", "--without-headers"]
305 )
David James66a09c42012-11-05 13:31:38 -0800306
Alex Klein1699fab2022-09-08 08:46:06 -0600307 overlays = " ".join(
308 (CHROMIUMOS_OVERLAY, ECLASS_OVERLAY, STABLE_OVERLAY)
309 )
310 cmdbase.extend(["--overlays", overlays])
311 cmdbase.extend(["--ov-output", CROSSDEV_OVERLAY])
Manoj Gupta4d016f62021-10-19 16:39:34 -0700312
Alex Klein1699fab2022-09-08 08:46:06 -0600313 cmd = cmdbase + ["-t", target_name]
Manoj Gupta4d016f62021-10-19 16:39:34 -0700314
Alex Klein1699fab2022-09-08 08:46:06 -0600315 for pkg in GetTargetPackages(target_name):
316 if pkg == "gdb":
317 # Gdb does not have selectable versions.
318 cmd.append("--ex-gdb")
319 elif pkg == "ex_libxcrypt":
320 cmd.extend(CROSSDEV_LIBXCRYPT_ARGS)
321 elif pkg == "ex_compiler-rt":
322 cmd.extend(CROSSDEV_COMPILER_RT_ARGS)
323 elif pkg == "ex_go":
324 # Go does not have selectable versions.
325 cmd.extend(CROSSDEV_GO_ARGS)
326 elif pkg in LLVM_PKGS_TABLE:
327 cmd.extend(LLVM_PKGS_TABLE[pkg])
328 elif pkg in cls.MANUAL_PKGS:
329 pass
330 else:
331 # The first of the desired versions is the "primary" one.
332 version = GetDesiredPackageVersions(target_name, pkg)[0]
333 cmd.extend(["--%s" % pkg, version])
334
335 cmd.extend(target["crossdev"].split())
336
337 if config_only:
338 # In this case we want to just quietly reinit
339 cmd.append("--init-target")
340 cros_build_lib.run(cmd, print_cmd=False, stdout=True)
341 else:
342 cros_build_lib.run(cmd)
343
344 configured_targets.append(target_name)
David James66a09c42012-11-05 13:31:38 -0800345
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100346
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100347def GetTargetPackages(target):
Alex Klein1699fab2022-09-08 08:46:06 -0600348 """Returns a list of packages for a given target."""
349 conf = Crossdev.GetConfig(target)
350 # Undesired packages are denoted by empty ${pkg}_pn variable.
351 return [x for x in conf["crosspkgs"] if conf.get(x + "_pn")]
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100352
353
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100354# Portage helper functions:
355def GetPortagePackage(target, package):
Alex Klein1699fab2022-09-08 08:46:06 -0600356 """Returns a package name for the given target."""
357 conf = Crossdev.GetConfig(target)
358 # Portage category:
359 if target.startswith("host") or package in Crossdev.MANUAL_PKGS:
360 category = conf[package + "_category"]
361 else:
362 category = conf["category"]
363 # Portage package:
364 pn = conf[package + "_pn"]
365 # Final package name:
366 assert category
367 assert pn
368 return "%s/%s" % (category, pn)
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100369
370
Gilad Arnold2dab78c2015-05-21 14:43:33 -0700371def PortageTrees(root):
Alex Klein1699fab2022-09-08 08:46:06 -0600372 """Return the portage trees for a given root."""
373 if root == "/":
374 return portage.db["/"]
375 # The portage logic requires the path always end in a slash.
376 root = root.rstrip("/") + "/"
377 return portage.create_trees(target_root=root, config_root=root)[root]
Gilad Arnold2dab78c2015-05-21 14:43:33 -0700378
379
Alex Klein1699fab2022-09-08 08:46:06 -0600380def GetInstalledPackageVersions(atom, root="/"):
381 """Extracts the list of current versions of a target, package pair.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100382
Alex Klein1699fab2022-09-08 08:46:06 -0600383 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700384 atom: The atom to operate on (e.g. sys-devel/gcc)
385 root: The root to check for installed packages.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100386
Alex Klein1699fab2022-09-08 08:46:06 -0600387 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700388 The list of versions of the package currently installed.
Alex Klein1699fab2022-09-08 08:46:06 -0600389 """
390 versions = []
391 for pkg in PortageTrees(root)["vartree"].dbapi.match(atom, use_cache=0):
392 version = portage.versions.cpv_getversion(pkg)
393 versions.append(version)
394 return versions
Zdenek Behan508dcce2011-12-05 15:39:32 +0100395
396
Alex Klein1699fab2022-09-08 08:46:06 -0600397def GetStablePackageVersion(atom, installed, root="/"):
398 """Extracts the current stable version for a given package.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100399
Alex Klein1699fab2022-09-08 08:46:06 -0600400 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700401 atom: The target/package to operate on e.g. i686-cros-linux-gnu/gcc
402 installed: Whether we want installed packages or ebuilds
403 root: The root to use when querying packages.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100404
Alex Klein1699fab2022-09-08 08:46:06 -0600405 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700406 A string containing the latest version.
Alex Klein1699fab2022-09-08 08:46:06 -0600407 """
408 pkgtype = "vartree" if installed else "porttree"
409 cpv = portage.best(
410 PortageTrees(root)[pkgtype].dbapi.match(atom, use_cache=0)
411 )
412 return portage.versions.cpv_getversion(cpv) if cpv else None
Zdenek Behan508dcce2011-12-05 15:39:32 +0100413
414
Alex Klein1699fab2022-09-08 08:46:06 -0600415def VersionListToNumeric(target, package, versions, installed, root="/"):
416 """Resolves keywords in a given version list for a particular package.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100417
Alex Klein1699fab2022-09-08 08:46:06 -0600418 Resolving means replacing PACKAGE_STABLE with the actual number.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100419
Alex Klein1699fab2022-09-08 08:46:06 -0600420 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700421 target: The target to operate on (e.g. i686-cros-linux-gnu)
422 package: The target/package to operate on (e.g. gcc)
423 versions: List of versions to resolve
424 installed: Query installed packages
425 root: The install root to use; ignored if |installed| is False.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100426
Alex Klein1699fab2022-09-08 08:46:06 -0600427 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700428 List of purely numeric versions equivalent to argument
Alex Klein1699fab2022-09-08 08:46:06 -0600429 """
430 resolved = []
431 atom = GetPortagePackage(target, package)
432 if not installed:
433 root = "/"
434 for version in versions:
435 if version == PACKAGE_STABLE:
436 resolved.append(GetStablePackageVersion(atom, installed, root=root))
437 else:
438 resolved.append(version)
439 return resolved
Zdenek Behan508dcce2011-12-05 15:39:32 +0100440
441
442def GetDesiredPackageVersions(target, package):
Alex Klein1699fab2022-09-08 08:46:06 -0600443 """Produces the list of desired versions for each target, package pair.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100444
Alex Klein1699fab2022-09-08 08:46:06 -0600445 The first version in the list is implicitly treated as primary, ie.
446 the version that will be initialized by crossdev and selected.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100447
Alex Klein1699fab2022-09-08 08:46:06 -0600448 If the version is PACKAGE_STABLE, it really means the current version which
449 is emerged by using the package atom with no particular version key.
450 Since crossdev unmasks all packages by default, this will actually
451 mean 'unstable' in most cases.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100452
Alex Klein1699fab2022-09-08 08:46:06 -0600453 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700454 target: The target to operate on (e.g. i686-cros-linux-gnu)
455 package: The target/package to operate on (e.g. gcc)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100456
Alex Klein1699fab2022-09-08 08:46:06 -0600457 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700458 A list composed of either a version string, PACKAGE_STABLE
Alex Klein1699fab2022-09-08 08:46:06 -0600459 """
460 if package in GetTargetPackages(target):
461 return [PACKAGE_STABLE]
462 else:
463 return []
Zdenek Behan508dcce2011-12-05 15:39:32 +0100464
465
466def TargetIsInitialized(target):
Alex Klein1699fab2022-09-08 08:46:06 -0600467 """Verifies if the given list of targets has been correctly initialized.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100468
Alex Klein1699fab2022-09-08 08:46:06 -0600469 This determines whether we have to call crossdev while emerging
470 toolchain packages or can do it using emerge. Emerge is naturally
471 preferred, because all packages can be updated in a single pass.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100472
Alex Klein1699fab2022-09-08 08:46:06 -0600473 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700474 target: The target to operate on (e.g. i686-cros-linux-gnu)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100475
Alex Klein1699fab2022-09-08 08:46:06 -0600476 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700477 True if |target| is completely initialized, else False
Alex Klein1699fab2022-09-08 08:46:06 -0600478 """
479 # Check if packages for the given target all have a proper version.
480 try:
481 for package in GetTargetPackages(target):
482 atom = GetPortagePackage(target, package)
483 # Do we even want this package && is it initialized?
484 if not (
485 GetStablePackageVersion(atom, True)
486 and GetStablePackageVersion(atom, False)
487 ):
488 return False
489 return True
490 except cros_build_lib.RunCommandError:
491 # Fails - The target has likely never been initialized before.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100492 return False
Zdenek Behan508dcce2011-12-05 15:39:32 +0100493
494
495def RemovePackageMask(target):
Alex Klein1699fab2022-09-08 08:46:06 -0600496 """Removes a package.mask file for the given platform.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100497
Alex Klein1699fab2022-09-08 08:46:06 -0600498 The pre-existing package.mask files can mess with the keywords.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100499
Alex Klein1699fab2022-09-08 08:46:06 -0600500 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700501 target: The target to operate on (e.g. i686-cros-linux-gnu)
Alex Klein1699fab2022-09-08 08:46:06 -0600502 """
503 maskfile = os.path.join("/etc/portage/package.mask", "cross-" + target)
504 osutils.SafeUnlink(maskfile)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100505
506
Zdenek Behan508dcce2011-12-05 15:39:32 +0100507# Main functions performing the actual update steps.
Alex Klein1699fab2022-09-08 08:46:06 -0600508def RebuildLibtool(root="/"):
509 """Rebuild libtool as needed
Mike Frysingerc880a962013-11-08 13:59:06 -0500510
Alex Klein1699fab2022-09-08 08:46:06 -0600511 Libtool hardcodes full paths to internal gcc files, so whenever we upgrade
512 gcc, libtool will break. We can't use binary packages either as those will
513 most likely be compiled against the previous version of gcc.
Gilad Arnold2dab78c2015-05-21 14:43:33 -0700514
Alex Klein1699fab2022-09-08 08:46:06 -0600515 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700516 root: The install root where we want libtool rebuilt.
Alex Klein1699fab2022-09-08 08:46:06 -0600517 """
518 needs_update = False
Mike Frysinger31fdddd2023-02-24 15:50:55 -0500519 with open(os.path.join(root, "usr/bin/libtool"), encoding="utf-8") as f:
Alex Klein1699fab2022-09-08 08:46:06 -0600520 for line in f:
521 # Look for a line like:
522 # sys_lib_search_path_spec="..."
523 # It'll be a list of paths and gcc will be one of them.
524 if line.startswith("sys_lib_search_path_spec="):
525 line = line.rstrip()
526 for path in line.split("=", 1)[1].strip('"').split():
527 root_path = os.path.join(root, path.lstrip(os.path.sep))
528 logging.debug("Libtool: checking %s", root_path)
529 if not os.path.exists(root_path):
530 logging.info("Rebuilding libtool after gcc upgrade")
531 logging.info(" %s", line)
532 logging.info(" missing path: %s", path)
533 needs_update = True
534 break
Mike Frysingerc880a962013-11-08 13:59:06 -0500535
Alex Klein1699fab2022-09-08 08:46:06 -0600536 if needs_update:
537 break
Mike Frysingerc880a962013-11-08 13:59:06 -0500538
Alex Klein1699fab2022-09-08 08:46:06 -0600539 if needs_update:
540 cmd = [EMERGE_CMD, "--oneshot"]
541 if root != "/":
542 cmd.extend(["--sysroot=%s" % root, "--root=%s" % root])
543 cmd.append("sys-devel/libtool")
544 cros_build_lib.run(cmd)
545 else:
546 logging.debug("Libtool is up-to-date; no need to rebuild")
Mike Frysingerc880a962013-11-08 13:59:06 -0500547
548
Alex Klein1699fab2022-09-08 08:46:06 -0600549def UpdateTargets(targets, usepkg, root="/"):
550 """Determines which packages need update/unmerge and defers to portage.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100551
Alex Klein1699fab2022-09-08 08:46:06 -0600552 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700553 targets: The list of targets to update
554 usepkg: Copies the commandline option
555 root: The install root in which we want packages updated.
Alex Klein1699fab2022-09-08 08:46:06 -0600556 """
557 # For each target, we do two things. Figure out the list of updates,
558 # and figure out the appropriate keywords/masks. Crossdev will initialize
559 # these, but they need to be regenerated on every update.
560 logging.info("Determining required toolchain updates...")
561 mergemap = {}
562 # Used to keep track of post-cross packages. These are allowed to have
563 # implicit dependencies on toolchain packages, and therefore need to
564 # be built last.
565 post_cross_pkgs = set()
Zdenek Behan508dcce2011-12-05 15:39:32 +0100566 for target in targets:
Alex Klein1699fab2022-09-08 08:46:06 -0600567 is_post_cross_target = target.endswith("-post-cross")
568 logging.debug("Updating target %s", target)
Alex Klein0e92b2c2023-01-13 11:54:15 -0700569 # Record the highest needed version for each target, for masking
570 # purposes.
Alex Klein1699fab2022-09-08 08:46:06 -0600571 RemovePackageMask(target)
572 for package in GetTargetPackages(target):
573 # Portage name for the package
574 logging.debug(" Checking package %s", package)
575 pkg = GetPortagePackage(target, package)
576 current = GetInstalledPackageVersions(pkg, root=root)
577 desired = GetDesiredPackageVersions(target, package)
578 desired_num = VersionListToNumeric(target, package, desired, False)
579 if pkg in NEW_PACKAGES and usepkg:
580 # Skip this binary package (for now).
581 continue
582 mergemap[pkg] = set(desired_num).difference(current)
583 logging.debug(" %s -> %s", current, desired_num)
584 if is_post_cross_target:
585 post_cross_pkgs.add(pkg)
Mike Frysinger785b0c32017-09-13 01:35:59 -0400586
Alex Klein1699fab2022-09-08 08:46:06 -0600587 packages = [pkg for pkg, vers in mergemap.items() if vers]
588 if not packages:
589 logging.info("Nothing to update!")
590 return False
Zdenek Behan508dcce2011-12-05 15:39:32 +0100591
Alex Klein1699fab2022-09-08 08:46:06 -0600592 logging.info("Updating packages:")
593 logging.info("%s", packages)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100594
Alex Klein1699fab2022-09-08 08:46:06 -0600595 cmd = [EMERGE_CMD, "--oneshot", "--update"]
596 if usepkg:
597 cmd.extend(["--getbinpkg", "--usepkgonly"])
598 if root != "/":
599 cmd.extend(["--sysroot=%s" % root, "--root=%s" % root])
Zdenek Behan508dcce2011-12-05 15:39:32 +0100600
Alex Klein1699fab2022-09-08 08:46:06 -0600601 if usepkg:
602 # Since we are not building from source, we can handle
603 # all packages in one go.
604 cmd.extend(packages)
605 cros_build_lib.run(cmd)
606 else:
607 pre_cross_items = [
608 pkg for pkg in packages if pkg not in post_cross_pkgs
609 ]
610 if pre_cross_items:
611 cros_build_lib.run(cmd + pre_cross_items)
612 post_cross_items = [pkg for pkg in packages if pkg in post_cross_pkgs]
613 if post_cross_items:
614 cros_build_lib.run(cmd + post_cross_items)
615 return True
Gilad Arnold2dab78c2015-05-21 14:43:33 -0700616
Alex Klein1699fab2022-09-08 08:46:06 -0600617
618def CleanTargets(targets, root="/"):
619 """Unmerges old packages that are assumed unnecessary.
620
621 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700622 targets: The list of targets to clean up.
623 root: The install root in which we want packages cleaned up.
Alex Klein1699fab2022-09-08 08:46:06 -0600624 """
625 unmergemap = {}
626 for target in targets:
627 logging.debug("Cleaning target %s", target)
628 for package in GetTargetPackages(target):
629 logging.debug(" Cleaning package %s", package)
630 pkg = GetPortagePackage(target, package)
631 current = GetInstalledPackageVersions(pkg, root=root)
632 desired = GetDesiredPackageVersions(target, package)
633 # NOTE: This refers to installed packages (vartree) rather than the
Alex Klein0e92b2c2023-01-13 11:54:15 -0700634 # Portage version (porttree and/or bintree) when determining the
635 # current version. While this isn't the most accurate thing to do,
636 # it is probably a good simple compromise, which should have the
637 # desired result of uninstalling everything but the latest installed
638 # version. In particular, using the bintree (--usebinpkg) requires a
639 # non-trivial binhost sync and is probably more complex than useful.
Alex Klein1699fab2022-09-08 08:46:06 -0600640 desired_num = VersionListToNumeric(target, package, desired, True)
641 if not set(desired_num).issubset(current):
642 logging.warning(
643 "Error detecting stable version for %s, " "skipping clean!",
644 pkg,
645 )
646 return
647 unmergemap[pkg] = set(current).difference(desired_num)
648
649 # Cleaning doesn't care about consistency and rebuilding package.* files.
650 packages = []
651 for pkg, vers in unmergemap.items():
652 packages.extend("=%s-%s" % (pkg, ver) for ver in vers if ver != "9999")
653
654 if packages:
655 logging.info("Cleaning packages:")
656 logging.info("%s", packages)
657 cmd = [EMERGE_CMD, "--unmerge"]
658 if root != "/":
659 cmd.extend(["--sysroot=%s" % root, "--root=%s" % root])
660 cmd.extend(packages)
661 cros_build_lib.run(cmd)
662 else:
663 logging.info("Nothing to clean!")
664
665
666def SelectActiveToolchains(targets, root="/"):
667 """Runs gcc-config and binutils-config to select the desired.
668
669 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700670 targets: The targets to select
671 root: The root where we want to select toolchain versions.
Alex Klein1699fab2022-09-08 08:46:06 -0600672 """
673 for package in ["gcc", "binutils"]:
674 for target in targets:
675 # See if this package is part of this target.
676 if package not in GetTargetPackages(target):
677 logging.debug("%s: %s is not used", target, package)
678 continue
679
680 # Pick the first version in the numbered list as the selected one.
681 desired = GetDesiredPackageVersions(target, package)
682 desired_num = VersionListToNumeric(
683 target, package, desired, True, root=root
684 )
685 desired = desired_num[0]
686 # *-config does not play revisions, strip them, keep just PV.
687 desired = portage.versions.pkgsplit("%s-%s" % (package, desired))[1]
688
689 if target.startswith("host"):
Alex Klein0e92b2c2023-01-13 11:54:15 -0700690 # *-config is the only tool treating host identically (by
691 # tuple).
Alex Klein1699fab2022-09-08 08:46:06 -0600692 target = toolchain.GetHostTuple()
693
694 # And finally, attach target to it.
695 desired = "%s-%s" % (target, desired)
696
697 extra_env = {"CHOST": target}
698 if root != "/":
699 extra_env["ROOT"] = root
700 cmd = ["%s-config" % package, "-c", target]
701 result = cros_build_lib.run(
702 cmd,
703 print_cmd=False,
704 stdout=True,
705 encoding="utf-8",
706 extra_env=extra_env,
707 )
708 current = result.stdout.splitlines()[0]
709
Alex Klein0e92b2c2023-01-13 11:54:15 -0700710 # Do not reconfig when the current is live or nothing needs to be
711 # done.
Alex Klein1699fab2022-09-08 08:46:06 -0600712 extra_env = {"ROOT": root} if root != "/" else None
Alex Klein64930532023-04-17 12:20:52 -0600713 if current not in (desired, "9999"):
Alex Klein1699fab2022-09-08 08:46:06 -0600714 cmd = [package + "-config", desired]
715 cros_build_lib.run(cmd, print_cmd=False, extra_env=extra_env)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100716
717
Mike Frysinger35247af2012-11-16 18:58:06 -0500718def ExpandTargets(targets_wanted):
Alex Klein1699fab2022-09-08 08:46:06 -0600719 """Expand any possible toolchain aliases into full targets
Mike Frysinger35247af2012-11-16 18:58:06 -0500720
Alex Klein1699fab2022-09-08 08:46:06 -0600721 This will expand 'all' and 'sdk' into the respective toolchain tuples.
Mike Frysinger35247af2012-11-16 18:58:06 -0500722
Alex Klein1699fab2022-09-08 08:46:06 -0600723 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700724 targets_wanted: The targets specified by the user.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500725
Alex Klein1699fab2022-09-08 08:46:06 -0600726 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700727 Dictionary of concrete targets and their toolchain tuples.
Alex Klein1699fab2022-09-08 08:46:06 -0600728 """
729 targets_wanted = set(targets_wanted)
730 if targets_wanted == set(["boards"]):
731 # Only pull targets from the included boards.
732 return {}
Gilad Arnold8195b532015-04-07 10:56:30 +0300733
Alex Klein1699fab2022-09-08 08:46:06 -0600734 all_targets = toolchain.GetAllTargets()
735 if targets_wanted == set(["all"]):
736 return all_targets
737 if targets_wanted == set(["sdk"]):
738 # Filter out all the non-sdk toolchains as we don't want to mess
739 # with those in all of our builds.
740 return toolchain.FilterToolchains(all_targets, "sdk", True)
Gilad Arnold8195b532015-04-07 10:56:30 +0300741
Alex Klein1699fab2022-09-08 08:46:06 -0600742 # Verify user input.
743 nonexistent = targets_wanted.difference(all_targets)
744 if nonexistent:
745 raise ValueError("Invalid targets: %s" % (",".join(nonexistent),))
746 return {t: all_targets[t] for t in targets_wanted}
Mike Frysinger35247af2012-11-16 18:58:06 -0500747
748
Alex Klein1699fab2022-09-08 08:46:06 -0600749def UpdateToolchains(
750 usepkg,
751 deleteold,
752 hostonly,
753 reconfig,
754 targets_wanted,
755 boards_wanted,
756 root="/",
757):
758 """Performs all steps to create a synchronized toolchain enviroment.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100759
Alex Klein1699fab2022-09-08 08:46:06 -0600760 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700761 usepkg: Use prebuilt packages
762 deleteold: Unmerge deprecated packages
763 hostonly: Only setup the host toolchain
764 reconfig: Reload crossdev config and reselect toolchains
765 targets_wanted: All the targets to update
766 boards_wanted: Load targets from these boards
767 root: The root in which to install the toolchains.
Alex Klein1699fab2022-09-08 08:46:06 -0600768 """
769 targets, crossdev_targets, reconfig_targets = {}, {}, {}
770 if not hostonly:
771 # For hostonly, we can skip most of the below logic, much of which won't
772 # work on bare systems where this is useful.
773 targets = ExpandTargets(targets_wanted)
Mike Frysinger7ccee992012-06-01 21:27:59 -0400774
Alex Klein1699fab2022-09-08 08:46:06 -0600775 # Filter out toolchains that don't (yet) have a binpkg available.
776 if usepkg:
777 for target in list(targets.keys()):
778 if not targets[target]["have-binpkg"]:
779 del targets[target]
Mike Frysingerd246fb92021-10-26 16:08:39 -0400780
Alex Klein1699fab2022-09-08 08:46:06 -0600781 # Now re-add any targets that might be from this board. This is to
782 # allow unofficial boards to declare their own toolchains.
783 for board in boards_wanted:
784 targets.update(toolchain.GetToolchainsForBoard(board))
Zdenek Behan508dcce2011-12-05 15:39:32 +0100785
Alex Klein1699fab2022-09-08 08:46:06 -0600786 # First check and initialize all cross targets that need to be.
787 for target in targets:
788 if TargetIsInitialized(target):
789 reconfig_targets[target] = targets[target]
790 else:
791 crossdev_targets[target] = targets[target]
792 if crossdev_targets:
793 logging.info("The following targets need to be re-initialized:")
794 logging.info("%s", crossdev_targets)
795 Crossdev.UpdateTargets(crossdev_targets, usepkg)
796 # Those that were not initialized may need a config update.
797 Crossdev.UpdateTargets(reconfig_targets, usepkg, config_only=True)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100798
Alex Klein0e92b2c2023-01-13 11:54:15 -0700799 # If we're building a subset of toolchains for a board, we might not
800 # have all the tuples that the packages expect. We don't define the
801 # "full" set of tuples currently other than "whatever the full sdk has
802 # normally".
Alex Klein1699fab2022-09-08 08:46:06 -0600803 if usepkg or set(("all", "sdk")) & targets_wanted:
804 # Since we have cross-compilers now, we can update these packages.
805 targets["host-post-cross"] = {}
Mike Frysinger785b0c32017-09-13 01:35:59 -0400806
Alex Klein1699fab2022-09-08 08:46:06 -0600807 # We want host updated.
808 targets["host"] = {}
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100809
Alex Klein1699fab2022-09-08 08:46:06 -0600810 # Now update all packages.
811 if (
812 UpdateTargets(targets, usepkg, root=root)
813 or crossdev_targets
814 or reconfig
815 ):
816 SelectActiveToolchains(targets, root=root)
David James7ec5efc2012-11-06 09:39:49 -0800817
Alex Klein1699fab2022-09-08 08:46:06 -0600818 if deleteold:
819 CleanTargets(targets, root=root)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100820
Alex Klein1699fab2022-09-08 08:46:06 -0600821 # Now that we've cleared out old versions, see if we need to rebuild
822 # anything. Can't do this earlier as it might not be broken.
823 RebuildLibtool(root=root)
Mike Frysingerc880a962013-11-08 13:59:06 -0500824
Zdenek Behan508dcce2011-12-05 15:39:32 +0100825
Bertrand SIMONNETcae9d5f2015-03-09 15:58:01 -0700826def ShowConfig(name):
Alex Klein1699fab2022-09-08 08:46:06 -0600827 """Show the toolchain tuples used by |name|
Mike Frysinger35247af2012-11-16 18:58:06 -0500828
Alex Klein1699fab2022-09-08 08:46:06 -0600829 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700830 name: The board name to query.
Alex Klein1699fab2022-09-08 08:46:06 -0600831 """
832 toolchains = toolchain.GetToolchainsForBoard(name)
833 # Make sure we display the default toolchain first.
834 # Note: Do not use logging here as this is meant to be used by other tools.
835 print(
836 ",".join(
837 list(toolchain.FilterToolchains(toolchains, "default", True))
838 + list(toolchain.FilterToolchains(toolchains, "default", False))
839 )
840 )
Mike Frysinger35247af2012-11-16 18:58:06 -0500841
842
Mike Frysinger35247af2012-11-16 18:58:06 -0500843def GeneratePathWrapper(root, wrappath, path):
Alex Klein1699fab2022-09-08 08:46:06 -0600844 """Generate a shell script to execute another shell script
Mike Frysinger35247af2012-11-16 18:58:06 -0500845
Alex Klein1699fab2022-09-08 08:46:06 -0600846 Since we can't symlink a wrapped ELF (see GenerateLdsoWrapper) because the
847 argv[0] won't be pointing to the correct path, generate a shell script that
848 just executes another program with its full path.
Mike Frysinger35247af2012-11-16 18:58:06 -0500849
Alex Klein1699fab2022-09-08 08:46:06 -0600850 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700851 root: The root tree to generate scripts inside of
852 wrappath: The full path (inside |root|) to create the wrapper
853 path: The target program which this wrapper will execute
Alex Klein1699fab2022-09-08 08:46:06 -0600854 """
855 replacements = {
856 "path": path,
857 "relroot": os.path.relpath("/", os.path.dirname(wrappath)),
858 }
Takuto Ikuta58403972018-08-16 18:52:51 +0900859
Alex Klein1699fab2022-09-08 08:46:06 -0600860 # Do not use exec here, because exec invokes script with absolute path in
Alex Klein0e92b2c2023-01-13 11:54:15 -0700861 # argv0. Keeping relativeness allows us to remove abs path from compile
862 # result and leads directory independent build cache sharing in some
863 # distributed build system.
Alex Klein1699fab2022-09-08 08:46:06 -0600864 wrapper = (
865 """#!/bin/sh
Takuto Ikuta58403972018-08-16 18:52:51 +0900866basedir=$(dirname "$0")
867"${basedir}/%(relroot)s%(path)s" "$@"
868exit "$?"
Alex Klein1699fab2022-09-08 08:46:06 -0600869"""
870 % replacements
871 )
872 root_wrapper = root + wrappath
873 if os.path.islink(root_wrapper):
874 os.unlink(root_wrapper)
875 else:
876 osutils.SafeMakedirs(os.path.dirname(root_wrapper))
877 osutils.WriteFile(root_wrapper, wrapper)
878 os.chmod(root_wrapper, 0o755)
Mike Frysinger35247af2012-11-16 18:58:06 -0500879
880
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700881def FixClangXXWrapper(root, path):
Alex Klein1699fab2022-09-08 08:46:06 -0600882 """Fix wrapper shell scripts and symlinks for invoking clang++
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700883
Alex Klein1699fab2022-09-08 08:46:06 -0600884 In a typical installation, clang++ symlinks to clang, which symlinks to the
885 elf executable. The executable distinguishes between clang and clang++ based
886 on argv[0].
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700887
Alex Klein0e92b2c2023-01-13 11:54:15 -0700888 When invoked through the LdsoWrapper, argv[0] always contains the path to
889 the executable elf file, making clang/clang++ invocations indistinguishable.
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700890
Alex Klein1699fab2022-09-08 08:46:06 -0600891 This function detects if the elf executable being wrapped is clang-X.Y, and
892 fixes wrappers/symlinks as necessary so that clang++ will work correctly.
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700893
Alex Klein1699fab2022-09-08 08:46:06 -0600894 The calling sequence now becomes:
895 -) clang++ invocation turns into clang++-3.9 (which is a copy of clang-3.9,
896 the Ldsowrapper).
897 -) clang++-3.9 uses the Ldso to invoke clang++-3.9.elf, which is a symlink
898 to the original clang-3.9 elf.
899 -) The difference this time is that inside the elf file execution, $0 is
900 set as .../usr/bin/clang++-3.9.elf, which contains 'clang++' in the name.
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700901
Alex Klein1699fab2022-09-08 08:46:06 -0600902 Update: Starting since clang 7, the clang and clang++ are symlinks to
903 clang-7 binary, not clang-7.0. The pattern match is extended to handle
904 both clang-7 and clang-7.0 cases for now. (https://crbug.com/837889)
Manoj Guptaae268142018-04-27 23:28:36 -0700905
Alex Klein1699fab2022-09-08 08:46:06 -0600906 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700907 root: The root tree to generate scripts / symlinks inside of
908 path: The target elf for which LdsoWrapper was created
Alex Klein1699fab2022-09-08 08:46:06 -0600909 """
910 if re.match(r"/usr/bin/clang-\d+(\.\d+)*$", path):
911 logging.info("fixing clang++ invocation for %s", path)
912 clangdir = os.path.dirname(root + path)
913 clang = os.path.basename(path)
914 clangxx = clang.replace("clang", "clang++")
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700915
Alex Klein1699fab2022-09-08 08:46:06 -0600916 # Create a symlink clang++-X.Y.elf to point to clang-X.Y.elf
917 os.symlink(clang + ".elf", os.path.join(clangdir, clangxx + ".elf"))
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700918
Alex Klein1699fab2022-09-08 08:46:06 -0600919 # Create a hardlink clang++-X.Y pointing to clang-X.Y
920 os.link(os.path.join(clangdir, clang), os.path.join(clangdir, clangxx))
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700921
Alex Klein1699fab2022-09-08 08:46:06 -0600922 # Adjust the clang++ symlink to point to clang++-X.Y
923 os.unlink(os.path.join(clangdir, "clang++"))
924 os.symlink(clangxx, os.path.join(clangdir, "clang++"))
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700925
926
Mike Frysinger35247af2012-11-16 18:58:06 -0500927def FileIsCrosSdkElf(elf):
Alex Klein1699fab2022-09-08 08:46:06 -0600928 """Determine if |elf| is an ELF that we execute in the cros_sdk
Mike Frysinger35247af2012-11-16 18:58:06 -0500929
Alex Klein1699fab2022-09-08 08:46:06 -0600930 We don't need this to be perfect, just quick. It makes sure the ELF
931 is a 64bit LSB x86_64 ELF. That is the native type of cros_sdk.
Mike Frysinger35247af2012-11-16 18:58:06 -0500932
Alex Klein1699fab2022-09-08 08:46:06 -0600933 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700934 elf: The file to check
Mike Frysinger1a736a82013-12-12 01:50:59 -0500935
Alex Klein1699fab2022-09-08 08:46:06 -0600936 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700937 True if we think |elf| is a native ELF
Alex Klein1699fab2022-09-08 08:46:06 -0600938 """
939 with open(elf, "rb") as f:
940 data = f.read(20)
941 # Check the magic number, EI_CLASS, EI_DATA, and e_machine.
942 return (
943 data[0:4] == b"\x7fELF"
944 and data[4:5] == b"\x02"
945 and data[5:6] == b"\x01"
946 and data[18:19] == b"\x3e"
947 )
Mike Frysinger35247af2012-11-16 18:58:06 -0500948
949
950def IsPathPackagable(ptype, path):
Alex Klein1699fab2022-09-08 08:46:06 -0600951 """Should the specified file be included in a toolchain package?
Mike Frysinger35247af2012-11-16 18:58:06 -0500952
Alex Klein1699fab2022-09-08 08:46:06 -0600953 We only need to handle files as we'll create dirs as we need them.
Mike Frysinger35247af2012-11-16 18:58:06 -0500954
Alex Klein1699fab2022-09-08 08:46:06 -0600955 Further, trim files that won't be useful:
956 - non-english translations (.mo) since it'd require env vars
957 - debug files since these are for the host compiler itself
958 - info/man pages as they're big, and docs are online, and the
959 native docs should work fine for the most part (`man gcc`)
Mike Frysinger35247af2012-11-16 18:58:06 -0500960
Alex Klein1699fab2022-09-08 08:46:06 -0600961 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700962 ptype: A string describing the path type (i.e. 'file' or 'dir' or 'sym')
963 path: The full path to inspect
Mike Frysinger1a736a82013-12-12 01:50:59 -0500964
Alex Klein1699fab2022-09-08 08:46:06 -0600965 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700966 True if we want to include this path in the package
Alex Klein1699fab2022-09-08 08:46:06 -0600967 """
968 return not (
969 ptype in ("dir",)
970 or path.startswith("/usr/lib/debug/")
971 or os.path.splitext(path)[1] == ".mo"
972 or ("/man/" in path or "/info/" in path)
973 )
Mike Frysinger35247af2012-11-16 18:58:06 -0500974
975
976def ReadlinkRoot(path, root):
Alex Klein1699fab2022-09-08 08:46:06 -0600977 """Like os.readlink(), but relative to a |root|
Mike Frysinger35247af2012-11-16 18:58:06 -0500978
Alex Klein1699fab2022-09-08 08:46:06 -0600979 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700980 path: The symlink to read
981 root: The path to use for resolving absolute symlinks
Mike Frysinger1a736a82013-12-12 01:50:59 -0500982
Alex Klein1699fab2022-09-08 08:46:06 -0600983 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700984 A fully resolved symlink path
Alex Klein1699fab2022-09-08 08:46:06 -0600985 """
986 while os.path.islink(root + path):
987 path = os.path.join(os.path.dirname(path), os.readlink(root + path))
988 return path
Mike Frysinger35247af2012-11-16 18:58:06 -0500989
990
Alex Klein1699fab2022-09-08 08:46:06 -0600991def _GetFilesForTarget(target, root="/"):
992 """Locate all the files to package for |target|
Mike Frysinger35247af2012-11-16 18:58:06 -0500993
Alex Klein1699fab2022-09-08 08:46:06 -0600994 This does not cover ELF dependencies.
Mike Frysinger35247af2012-11-16 18:58:06 -0500995
Alex Klein1699fab2022-09-08 08:46:06 -0600996 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700997 target: The toolchain target name
998 root: The root path to pull all packages from
Mike Frysinger1a736a82013-12-12 01:50:59 -0500999
Alex Klein1699fab2022-09-08 08:46:06 -06001000 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -07001001 A tuple of a set of all packable paths, and a set of all paths which
1002 are also native ELFs
Alex Klein1699fab2022-09-08 08:46:06 -06001003 """
1004 paths = set()
1005 elfs = set()
Mike Frysinger35247af2012-11-16 18:58:06 -05001006
Alex Klein1699fab2022-09-08 08:46:06 -06001007 # Find all the files owned by the packages for this target.
1008 for pkg in GetTargetPackages(target):
Alex Klein1699fab2022-09-08 08:46:06 -06001009 # Skip Go compiler from redistributable packages.
1010 # The "go" executable has GOROOT=/usr/lib/go/${CTARGET} hardcoded
1011 # into it. Due to this, the toolchain cannot be unpacked anywhere
1012 # else and be readily useful. To enable packaging Go, we need to:
1013 # -) Tweak the wrappers/environment to override GOROOT
1014 # automatically based on the unpack location.
1015 # -) Make sure the ELF dependency checking and wrapping logic
1016 # below skips the Go toolchain executables and libraries.
1017 # -) Make sure the packaging process maintains the relative
1018 # timestamps of precompiled standard library packages.
1019 # (see dev-lang/go ebuild for details).
1020 if pkg == "ex_go":
1021 continue
Rahul Chaudhry4b803052015-05-13 15:25:56 -07001022
Alex Klein1699fab2022-09-08 08:46:06 -06001023 # Use armv7a-cros-linux-gnueabi/compiler-rt for
Alex Klein0e92b2c2023-01-13 11:54:15 -07001024 # armv7a-cros-linux-gnueabihf/compiler-rt. Currently the
1025 # armv7a-cros-linux-gnueabi is actually the same as
1026 # armv7a-cros-linux-gnueabihf with different names. Because of that, for
1027 # compiler-rt, it generates the same binary in the same location. To
1028 # avoid the installation conflict, we do not install anything for
1029 # 'armv7a-cros-linux-gnueabihf'. This would cause problem if other
1030 # people try to use standalone armv7a-cros-linux-gnueabihf toolchain.
Alex Klein1699fab2022-09-08 08:46:06 -06001031 if "compiler-rt" in pkg and "armv7a-cros-linux-gnueabi" in target:
1032 atom = GetPortagePackage(target, pkg)
1033 cat, pn = atom.split("/")
1034 ver = GetInstalledPackageVersions(atom, root=root)[0]
1035 dblink = portage.dblink(
1036 cat, pn + "-" + ver, myroot=root, settings=portage.settings
1037 )
1038 contents = dblink.getcontents()
1039 if not contents:
1040 if "hf" in target:
1041 new_target = "armv7a-cros-linux-gnueabi"
1042 else:
1043 new_target = "armv7a-cros-linux-gnueabihf"
1044 atom = GetPortagePackage(new_target, pkg)
Yunlian Jiang36f35242018-04-27 10:18:40 -07001045 else:
Alex Klein1699fab2022-09-08 08:46:06 -06001046 atom = GetPortagePackage(target, pkg)
Yunlian Jiang36f35242018-04-27 10:18:40 -07001047
Alex Klein1699fab2022-09-08 08:46:06 -06001048 cat, pn = atom.split("/")
1049 ver = GetInstalledPackageVersions(atom, root=root)[0]
1050 logging.info("packaging %s-%s", atom, ver)
Mike Frysinger35247af2012-11-16 18:58:06 -05001051
Alex Klein1699fab2022-09-08 08:46:06 -06001052 dblink = portage.dblink(
1053 cat, pn + "-" + ver, myroot=root, settings=portage.settings
1054 )
1055 contents = dblink.getcontents()
1056 for obj in contents:
1057 ptype = contents[obj][0]
1058 if not IsPathPackagable(ptype, obj):
1059 continue
Mike Frysinger35247af2012-11-16 18:58:06 -05001060
Alex Klein1699fab2022-09-08 08:46:06 -06001061 if ptype == "obj":
1062 # For native ELFs, we need to pull in their dependencies too.
1063 if FileIsCrosSdkElf(obj):
1064 logging.debug("Adding ELF %s", obj)
1065 elfs.add(obj)
1066 logging.debug("Adding path %s", obj)
1067 paths.add(obj)
Mike Frysinger35247af2012-11-16 18:58:06 -05001068
Alex Klein1699fab2022-09-08 08:46:06 -06001069 return paths, elfs
Mike Frysinger35247af2012-11-16 18:58:06 -05001070
1071
Alex Klein1699fab2022-09-08 08:46:06 -06001072def _BuildInitialPackageRoot(
1073 output_dir, paths, elfs, ldpaths, path_rewrite_func=lambda x: x, root="/"
1074):
1075 """Link in all packable files and their runtime dependencies
Mike Frysinger35247af2012-11-16 18:58:06 -05001076
Alex Klein1699fab2022-09-08 08:46:06 -06001077 This also wraps up executable ELFs with helper scripts.
Mike Frysinger35247af2012-11-16 18:58:06 -05001078
Alex Klein1699fab2022-09-08 08:46:06 -06001079 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -07001080 output_dir: The output directory to store files
1081 paths: All the files to include
1082 elfs: All the files which are ELFs (a subset of |paths|)
1083 ldpaths: A dict of static ldpath information
1084 path_rewrite_func: User callback to rewrite paths in output_dir
1085 root: The root path to pull all packages/files from
Alex Klein1699fab2022-09-08 08:46:06 -06001086 """
1087 # Link in all the files.
1088 sym_paths = {}
1089 for path in paths:
1090 new_path = path_rewrite_func(path)
1091 logging.debug("Transformed %s to %s", path, new_path)
1092 dst = output_dir + new_path
1093 osutils.SafeMakedirs(os.path.dirname(dst))
Mike Frysinger35247af2012-11-16 18:58:06 -05001094
Alex Klein1699fab2022-09-08 08:46:06 -06001095 # Is this a symlink which we have to rewrite or wrap?
1096 # Delay wrap check until after we have created all paths.
1097 src = root + path
1098 if os.path.islink(src):
1099 tgt = os.readlink(src)
1100 if os.path.sep in tgt:
1101 sym_paths[lddtree.normpath(ReadlinkRoot(src, root))] = new_path
Mike Frysinger35247af2012-11-16 18:58:06 -05001102
Alex Klein0e92b2c2023-01-13 11:54:15 -07001103 # Rewrite absolute links to relative and then generate the
1104 # symlink ourselves. All other symlinks can be hardlinked below.
Alex Klein1699fab2022-09-08 08:46:06 -06001105 if tgt[0] == "/":
1106 tgt = os.path.relpath(tgt, os.path.dirname(new_path))
1107 os.symlink(tgt, dst)
1108 continue
Mike Frysinger35247af2012-11-16 18:58:06 -05001109
Alex Klein1699fab2022-09-08 08:46:06 -06001110 logging.debug("Linking path %s -> %s", src, dst)
1111 os.link(src, dst)
Mike Frysinger35247af2012-11-16 18:58:06 -05001112
Alex Klein1699fab2022-09-08 08:46:06 -06001113 # Locate all the dependencies for all the ELFs. Stick them all in the
1114 # top level "lib" dir to make the wrapper simpler. This exact path does
1115 # not matter since we execute ldso directly, and we tell the ldso the
1116 # exact path to search for its libraries.
1117 libdir = os.path.join(output_dir, "lib")
1118 osutils.SafeMakedirs(libdir)
1119 donelibs = set()
1120 basenamelibs = set()
Manoj Gupta98e674d2022-10-05 00:31:41 +00001121 glibc_re = re.compile(r"/lib(c|pthread)[0-9.-]*\.so[0-9.-]*")
Alex Klein1699fab2022-09-08 08:46:06 -06001122 for elf in elfs:
1123 e = lddtree.ParseELF(elf, root=root, ldpaths=ldpaths)
1124 logging.debug("Parsed elf %s data: %s", elf, e)
1125 interp = e["interp"]
Mike Frysinger221bd822017-09-29 02:51:47 -04001126
Alex Klein0e92b2c2023-01-13 11:54:15 -07001127 # TODO(b/187786323): Drop this hack once libopcodes linkage is fixed.
Alex Klein1699fab2022-09-08 08:46:06 -06001128 if os.path.basename(elf).startswith("libopcodes-"):
1129 continue
Mike Frysinger35247af2012-11-16 18:58:06 -05001130
Alex Klein0e92b2c2023-01-13 11:54:15 -07001131 # Copy all the dependencies before we copy the program & generate
1132 # wrappers.
Alex Klein1699fab2022-09-08 08:46:06 -06001133 for lib, lib_data in e["libs"].items():
1134 src = path = lib_data["path"]
1135 if path is None:
1136 logging.warning("%s: could not locate %s", elf, lib)
1137 continue
Mike Frysinger9fe02342019-12-12 17:52:53 -05001138
Alex Klein1699fab2022-09-08 08:46:06 -06001139 # No need to try and copy the same source lib multiple times.
1140 if path in donelibs:
1141 continue
1142 donelibs.add(path)
Mike Frysinger9fe02342019-12-12 17:52:53 -05001143
Alex Klein0e92b2c2023-01-13 11:54:15 -07001144 # Die if we try to normalize different source libs with the same
1145 # basename.
Alex Klein1699fab2022-09-08 08:46:06 -06001146 if lib in basenamelibs:
1147 logging.error(
1148 "Multiple sources detected for %s:\n new: %s\n old: %s",
1149 os.path.join("/lib", lib),
1150 path,
1151 " ".join(
1152 x
1153 for x in donelibs
1154 if x != path and os.path.basename(x) == lib
1155 ),
1156 )
1157 # TODO(crbug.com/917193): Make this fatal.
1158 # cros_build_lib.Die('Unable to resolve lib conflicts')
1159 continue
1160 basenamelibs.add(lib)
Mike Frysinger35247af2012-11-16 18:58:06 -05001161
Alex Klein1699fab2022-09-08 08:46:06 -06001162 # Needed libs are the SONAME, but that is usually a symlink, not a
1163 # real file. So link in the target rather than the symlink itself.
1164 # We have to walk all the possible symlinks (SONAME could point to a
1165 # symlink which points to a symlink), and we have to handle absolute
1166 # ourselves (since we have a "root" argument).
1167 dst = os.path.join(libdir, os.path.basename(path))
1168 src = ReadlinkRoot(src, root)
Mike Frysinger35247af2012-11-16 18:58:06 -05001169
Alex Klein1699fab2022-09-08 08:46:06 -06001170 logging.debug("Linking lib %s -> %s", root + src, dst)
1171 os.link(root + src, dst)
Mike Frysinger35247af2012-11-16 18:58:06 -05001172
Alex Klein1699fab2022-09-08 08:46:06 -06001173 # Do not create wrapper for libc. crbug.com/766827
1174 if interp and not glibc_re.search(elf):
1175 # Generate a wrapper if it is executable.
1176 interp = os.path.join("/lib", os.path.basename(interp))
1177 lddtree.GenerateLdsoWrapper(
1178 output_dir,
1179 path_rewrite_func(elf),
1180 interp,
1181 libpaths=e["rpath"] + e["runpath"],
1182 )
1183 FixClangXXWrapper(output_dir, path_rewrite_func(elf))
Mike Frysinger00b129f2021-04-21 18:11:48 -04001184
Alex Klein1699fab2022-09-08 08:46:06 -06001185 # Wrap any symlinks to the wrapper.
1186 if elf in sym_paths:
1187 link = sym_paths[elf]
1188 GeneratePathWrapper(output_dir, link, elf)
Mike Frysinger00b129f2021-04-21 18:11:48 -04001189
Mike Frysinger35247af2012-11-16 18:58:06 -05001190
1191def _EnvdGetVar(envd, var):
Alex Klein1699fab2022-09-08 08:46:06 -06001192 """Given a Gentoo env.d file, extract a var from it
Mike Frysinger35247af2012-11-16 18:58:06 -05001193
Alex Klein1699fab2022-09-08 08:46:06 -06001194 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -07001195 envd: The env.d file to load (may be a glob path)
1196 var: The var to extract
Mike Frysinger1a736a82013-12-12 01:50:59 -05001197
Alex Klein1699fab2022-09-08 08:46:06 -06001198 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -07001199 The value of |var|
Alex Klein1699fab2022-09-08 08:46:06 -06001200 """
1201 envds = glob.glob(envd)
1202 assert len(envds) == 1, "%s: should have exactly 1 env.d file" % envd
1203 envd = envds[0]
1204 return key_value_store.LoadFile(envd)[var]
Mike Frysinger35247af2012-11-16 18:58:06 -05001205
1206
1207def _ProcessBinutilsConfig(target, output_dir):
Alex Klein1699fab2022-09-08 08:46:06 -06001208 """Do what binutils-config would have done"""
1209 binpath = os.path.join("/bin", target + "-")
Mike Frysingerd4d40fd2014-11-06 17:30:57 -05001210
Alex Klein1699fab2022-09-08 08:46:06 -06001211 # Locate the bin dir holding the linker and perform some confidence checks
1212 binutils_bin_path = os.path.join(
1213 output_dir, "usr", toolchain.GetHostTuple(), target, "binutils-bin"
1214 )
1215 globpath = os.path.join(binutils_bin_path, "*")
1216 srcpath = glob.glob(globpath)
1217 assert len(srcpath) == 1, (
1218 "%s: matched more than one path. Is Gold enabled?" % globpath
1219 )
1220 srcpath = srcpath[0]
1221 ld_path = os.path.join(srcpath, "ld")
1222 assert os.path.exists(ld_path), "%s: linker is missing!" % ld_path
1223 ld_path = os.path.join(srcpath, "ld.bfd")
1224 assert os.path.exists(ld_path), "%s: linker is missing!" % ld_path
Rahul Chaudhry4891b4d2017-03-08 10:31:27 -08001225
Alex Klein1699fab2022-09-08 08:46:06 -06001226 srcpath = srcpath[len(output_dir) :]
1227 gccpath = os.path.join("/usr", "libexec", "gcc")
1228 for prog in os.listdir(output_dir + srcpath):
1229 # Skip binaries already wrapped.
1230 if not prog.endswith(".real"):
1231 GeneratePathWrapper(
1232 output_dir, binpath + prog, os.path.join(srcpath, prog)
1233 )
1234 GeneratePathWrapper(
1235 output_dir,
1236 os.path.join(gccpath, prog),
1237 os.path.join(srcpath, prog),
1238 )
Mike Frysinger35247af2012-11-16 18:58:06 -05001239
Alex Klein1699fab2022-09-08 08:46:06 -06001240 libpath = os.path.join("/usr", toolchain.GetHostTuple(), target, "lib")
1241 envd = os.path.join(output_dir, "etc", "env.d", "binutils", "*")
1242 srcpath = _EnvdGetVar(envd, "LIBPATH")
1243 os.symlink(
1244 os.path.relpath(srcpath, os.path.dirname(libpath)), output_dir + libpath
1245 )
Mike Frysinger35247af2012-11-16 18:58:06 -05001246
1247
1248def _ProcessGccConfig(target, output_dir):
Alex Klein1699fab2022-09-08 08:46:06 -06001249 """Do what gcc-config would have done"""
1250 binpath = "/bin"
1251 envd = os.path.join(output_dir, "etc", "env.d", "gcc", "*")
1252 srcpath = _EnvdGetVar(envd, "GCC_PATH")
1253 for prog in os.listdir(output_dir + srcpath):
1254 # Skip binaries already wrapped.
1255 if (
1256 not prog.endswith(".real")
1257 and not prog.endswith(".elf")
1258 and prog.startswith(target)
1259 ):
1260 GeneratePathWrapper(
1261 output_dir,
1262 os.path.join(binpath, prog),
1263 os.path.join(srcpath, prog),
1264 )
1265 return srcpath
Mike Frysinger35247af2012-11-16 18:58:06 -05001266
1267
Frank Henigman179ec7c2015-02-06 03:01:09 -05001268def _ProcessSysrootWrappers(_target, output_dir, srcpath):
Alex Klein1699fab2022-09-08 08:46:06 -06001269 """Remove chroot-specific things from our sysroot wrappers"""
1270 # Disable ccache since we know it won't work outside of chroot.
Tobias Boschddd16492019-08-14 09:29:54 -07001271
Alex Klein1699fab2022-09-08 08:46:06 -06001272 # Use the version of the wrapper that does not use ccache.
1273 for sysroot_wrapper in glob.glob(
1274 os.path.join(output_dir + srcpath, "sysroot_wrapper*.ccache")
1275 ):
1276 # Can't update the wrapper in place to not affect the chroot,
1277 # but only the extracted toolchain.
1278 os.unlink(sysroot_wrapper)
1279 shutil.copy(sysroot_wrapper[:-6] + "noccache", sysroot_wrapper)
1280 shutil.copy(
1281 sysroot_wrapper[:-6] + "noccache.elf", sysroot_wrapper + ".elf"
1282 )
Mike Frysinger35247af2012-11-16 18:58:06 -05001283
1284
Manoj Gupta61bf9db2020-03-23 21:28:04 -07001285def _ProcessClangWrappers(target, output_dir):
Alex Klein1699fab2022-09-08 08:46:06 -06001286 """Remove chroot-specific things from our sysroot wrappers"""
1287 clang_bin_path = "/usr/bin"
1288 # Disable ccache from clang wrappers.
1289 _ProcessSysrootWrappers(target, output_dir, clang_bin_path)
1290 GeneratePathWrapper(
1291 output_dir, f"/bin/{target}-clang", f"/usr/bin/{target}-clang"
1292 )
1293 GeneratePathWrapper(
1294 output_dir, f"/bin/{target}-clang++", f"/usr/bin/{target}-clang++"
1295 )
Manoj Gupta61bf9db2020-03-23 21:28:04 -07001296
1297
Yunlian Jiang5ad6b512017-09-20 09:27:45 -07001298def _CreateMainLibDir(target, output_dir):
Alex Klein1699fab2022-09-08 08:46:06 -06001299 """Create some lib dirs so that compiler can get the right Gcc paths"""
1300 osutils.SafeMakedirs(os.path.join(output_dir, "usr", target, "lib"))
1301 osutils.SafeMakedirs(os.path.join(output_dir, "usr", target, "usr/lib"))
Yunlian Jiang5ad6b512017-09-20 09:27:45 -07001302
1303
Manoj Guptadf8b3872022-01-13 11:57:36 -08001304def _CreateRemoteToolchainFile(output_dir):
Alex Klein1699fab2022-09-08 08:46:06 -06001305 """Create a remote_toolchain_inputs file for reclient/RBE"""
1306 # The inputs file lists all files/shared libraries needed to run clang.
1307 # All inputs are relative to location of clang binary and one input
1308 # location per line of file e.g.
1309 # clang-13.elf
1310 # clang++-13.elf
1311 # relative/path/to/clang/resource/directory
Manoj Guptadf8b3872022-01-13 11:57:36 -08001312
Alex Klein1699fab2022-09-08 08:46:06 -06001313 clang_path = os.path.join(output_dir, "usr/bin")
1314 # Add needed shared libraries and internal files e.g. allowlists.
1315 toolchain_inputs = ["../../lib"]
1316 clang_shared_dirs = glob.glob(
1317 os.path.join(output_dir, "usr/lib64/clang/*/share")
1318 )
1319 for clang_dir in clang_shared_dirs:
1320 toolchain_inputs.append(os.path.relpath(clang_dir, clang_path))
Manoj Guptadf8b3872022-01-13 11:57:36 -08001321
Alex Klein1699fab2022-09-08 08:46:06 -06001322 # Add actual clang binaries/wrappers.
1323 for clang_files in glob.glob(os.path.join(clang_path, "clang*-[0-9]*")):
1324 toolchain_inputs.append(os.path.basename(clang_files))
Manoj Guptadf8b3872022-01-13 11:57:36 -08001325
Mike Frysinger31fdddd2023-02-24 15:50:55 -05001326 with open(
1327 os.path.join(clang_path, "remote_toolchain_inputs"),
1328 "w",
1329 encoding="utf-8",
1330 ) as f:
Alex Klein1699fab2022-09-08 08:46:06 -06001331 f.writelines("%s\n" % line for line in toolchain_inputs)
Manoj Guptadf8b3872022-01-13 11:57:36 -08001332
1333
Mike Frysinger35247af2012-11-16 18:58:06 -05001334def _ProcessDistroCleanups(target, output_dir):
Alex Klein1699fab2022-09-08 08:46:06 -06001335 """Clean up the tree and remove all distro-specific requirements
Mike Frysinger35247af2012-11-16 18:58:06 -05001336
Alex Klein1699fab2022-09-08 08:46:06 -06001337 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -07001338 target: The toolchain target name
1339 output_dir: The output directory to clean up
Han Shen699ea192016-03-02 10:42:47 -08001340 """
Alex Klein1699fab2022-09-08 08:46:06 -06001341 _ProcessBinutilsConfig(target, output_dir)
1342 gcc_path = _ProcessGccConfig(target, output_dir)
1343 _ProcessSysrootWrappers(target, output_dir, gcc_path)
1344 _ProcessClangWrappers(target, output_dir)
1345 _CreateMainLibDir(target, output_dir)
1346 _CreateRemoteToolchainFile(output_dir)
Mike Frysinger35247af2012-11-16 18:58:06 -05001347
Alex Klein1699fab2022-09-08 08:46:06 -06001348 osutils.RmDir(os.path.join(output_dir, "etc"))
Mike Frysinger35247af2012-11-16 18:58:06 -05001349
1350
Alex Klein1699fab2022-09-08 08:46:06 -06001351def CreatePackagableRoot(target, output_dir, ldpaths, root="/"):
1352 """Setup a tree from the packages for the specified target
Mike Frysinger35247af2012-11-16 18:58:06 -05001353
Alex Klein1699fab2022-09-08 08:46:06 -06001354 This populates a path with all the files from toolchain packages so that
1355 a tarball can easily be generated from the result.
Mike Frysinger35247af2012-11-16 18:58:06 -05001356
Alex Klein1699fab2022-09-08 08:46:06 -06001357 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -07001358 target: The target to create a packagable root from
1359 output_dir: The output directory to place all the files
1360 ldpaths: A dict of static ldpath information
1361 root: The root path to pull all packages/files from
Alex Klein1699fab2022-09-08 08:46:06 -06001362 """
1363 # Find all the files owned by the packages for this target.
1364 paths, elfs = _GetFilesForTarget(target, root=root)
Mike Frysinger35247af2012-11-16 18:58:06 -05001365
Alex Klein1699fab2022-09-08 08:46:06 -06001366 # Link in all the package's files, any ELF dependencies, and wrap any
1367 # executable ELFs with helper scripts.
1368 def MoveUsrBinToBin(path):
1369 """Move /usr/bin to /bin so people can just use that toplevel dir
Mike Frysinger35247af2012-11-16 18:58:06 -05001370
Alex Klein0e92b2c2023-01-13 11:54:15 -07001371 Note we do not apply this to clang or rust; there is correlation between
Alex Klein1699fab2022-09-08 08:46:06 -06001372 clang's search path for libraries / inclusion and its installation path.
1373 """
1374 NO_MOVE_PATTERNS = ("clang", "rust", "cargo", "sysroot_wrapper")
1375 if path.startswith("/usr/bin/") and not any(
1376 x in path for x in NO_MOVE_PATTERNS
1377 ):
1378 return path[4:]
1379 return path
Mike Frysinger221bd822017-09-29 02:51:47 -04001380
Alex Klein1699fab2022-09-08 08:46:06 -06001381 _BuildInitialPackageRoot(
1382 output_dir,
1383 paths,
1384 elfs,
1385 ldpaths,
1386 path_rewrite_func=MoveUsrBinToBin,
1387 root=root,
1388 )
Mike Frysinger35247af2012-11-16 18:58:06 -05001389
Alex Klein1699fab2022-09-08 08:46:06 -06001390 # The packages, when part of the normal distro, have helper scripts
1391 # that setup paths and such. Since we are making this standalone, we
1392 # need to preprocess all that ourselves.
1393 _ProcessDistroCleanups(target, output_dir)
1394
1395
1396def CreatePackages(targets_wanted, output_dir, root="/"):
1397 """Create redistributable cross-compiler packages for the specified targets
1398
1399 This creates toolchain packages that should be usable in conjunction with
1400 a downloaded sysroot (created elsewhere).
1401
1402 Tarballs (one per target) will be created in $PWD.
1403
1404 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -07001405 targets_wanted: The targets to package up.
1406 output_dir: The directory to put the packages in.
1407 root: The root path to pull all packages/files from.
Alex Klein1699fab2022-09-08 08:46:06 -06001408 """
1409 logging.info("Writing tarballs to %s", output_dir)
1410 osutils.SafeMakedirs(output_dir)
1411 ldpaths = lddtree.LoadLdpaths(root)
1412 targets = ExpandTargets(targets_wanted)
1413
Brian Norrisca274dd2023-05-17 13:06:41 -07001414 # b/282231712: Stash temporary path structure at |root|, so we have control
1415 # over cross-device linking. The default base directory (/tmp) might be on
1416 # a different filesystem/mount, so hard links won't work.
1417 with osutils.TempDir(base_dir=root, prefix="create-packages") as tempdir:
Alex Klein1699fab2022-09-08 08:46:06 -06001418 logging.debug("Using tempdir: %s", tempdir)
1419
Alex Klein0e92b2c2023-01-13 11:54:15 -07001420 # We have to split the root generation from the compression stages.
1421 # This is because we hardlink in all the files (to avoid overhead of
1422 # reading/writing the copies multiple times). But tar gets angry if a
1423 # file's hardlink count changes from when it starts reading a file to
1424 # when it finishes.
Alex Klein1699fab2022-09-08 08:46:06 -06001425 with parallel.BackgroundTaskRunner(CreatePackagableRoot) as queue:
1426 for target in targets:
1427 output_target_dir = os.path.join(tempdir, target)
1428 queue.put([target, output_target_dir, ldpaths, root])
1429
1430 # Build the tarball.
1431 with parallel.BackgroundTaskRunner(
1432 cros_build_lib.CreateTarball
1433 ) as queue:
1434 for target in targets:
1435 tar_file = os.path.join(output_dir, target + ".tar.xz")
1436 queue.put([tar_file, os.path.join(tempdir, target)])
Mike Frysinger35247af2012-11-16 18:58:06 -05001437
1438
Mike Frysinger07534cf2017-09-12 17:40:21 -04001439def GetParser():
Alex Klein1699fab2022-09-08 08:46:06 -06001440 """Return a command line parser."""
1441 parser = commandline.ArgumentParser(description=__doc__)
1442 parser.add_argument(
1443 "-u",
1444 "--nousepkg",
1445 action="store_false",
1446 dest="usepkg",
1447 default=True,
1448 help="Do not use prebuilt packages",
1449 )
1450 parser.add_argument(
1451 "-d",
1452 "--deleteold",
1453 action="store_true",
1454 dest="deleteold",
1455 default=False,
1456 help="Unmerge deprecated packages",
1457 )
1458 parser.add_argument(
1459 "-t",
1460 "--targets",
1461 dest="targets",
1462 default="sdk",
1463 help="Comma separated list of tuples. Special keywords "
1464 "'host', 'sdk', 'boards', and 'all' are "
1465 "allowed. Defaults to 'sdk'.",
1466 )
1467 parser.add_argument(
1468 "--include-boards",
1469 default="",
1470 metavar="BOARDS",
1471 help="Comma separated list of boards whose toolchains we "
1472 "will always include. Default: none",
1473 )
1474 parser.add_argument(
1475 "--hostonly",
1476 dest="hostonly",
1477 default=False,
1478 action="store_true",
1479 help="Only setup the host toolchain. "
1480 "Useful for bootstrapping chroot",
1481 )
1482 parser.add_argument(
1483 "--show-board-cfg",
1484 "--show-cfg",
1485 dest="cfg_name",
1486 default=None,
1487 help="Board to list toolchains tuples for",
1488 )
1489 parser.add_argument(
1490 "--show-packages",
1491 default=None,
1492 help="List all packages the specified target uses",
1493 )
1494 parser.add_argument(
1495 "--create-packages",
1496 action="store_true",
1497 default=False,
1498 help="Build redistributable packages",
1499 )
1500 parser.add_argument(
1501 "--output-dir",
1502 default=os.getcwd(),
1503 type="path",
1504 help="Output directory",
1505 )
1506 parser.add_argument(
1507 "--reconfig",
1508 default=False,
1509 action="store_true",
1510 help="Reload crossdev config and reselect toolchains",
1511 )
1512 parser.add_argument(
1513 "--sysroot",
1514 type="path",
1515 help="The sysroot in which to install the toolchains",
1516 )
1517 return parser
Zdenek Behan508dcce2011-12-05 15:39:32 +01001518
Mike Frysinger07534cf2017-09-12 17:40:21 -04001519
1520def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001521 parser = GetParser()
1522 options = parser.parse_args(argv)
1523 options.Freeze()
Zdenek Behan508dcce2011-12-05 15:39:32 +01001524
Alex Klein1699fab2022-09-08 08:46:06 -06001525 # Figure out what we're supposed to do and reject conflicting options.
1526 conflicting_options = (
1527 options.cfg_name,
1528 options.show_packages,
1529 options.create_packages,
1530 )
1531 if sum(bool(x) for x in conflicting_options) > 1:
1532 parser.error(
1533 "conflicting options: create-packages & show-packages & "
1534 "show-board-cfg"
1535 )
Mike Frysinger984d0622012-06-01 16:08:44 -04001536
Alex Klein1699fab2022-09-08 08:46:06 -06001537 targets_wanted = set(options.targets.split(","))
1538 boards_wanted = (
1539 set(options.include_boards.split(","))
1540 if options.include_boards
1541 else set()
1542 )
Mike Frysinger35247af2012-11-16 18:58:06 -05001543
Alex Klein1699fab2022-09-08 08:46:06 -06001544 if options.cfg_name:
1545 ShowConfig(options.cfg_name)
1546 elif options.show_packages is not None:
1547 cros_build_lib.AssertInsideChroot()
1548 target = options.show_packages
1549 Crossdev.Load(False)
1550 for package in GetTargetPackages(target):
1551 print(GetPortagePackage(target, package))
1552 elif options.create_packages:
1553 cros_build_lib.AssertInsideChroot()
1554 Crossdev.Load(False)
1555 CreatePackages(targets_wanted, options.output_dir)
1556 else:
1557 cros_build_lib.AssertInsideChroot()
1558 # This has to be always run as root.
1559 if osutils.IsNonRootUser():
1560 cros_build_lib.Die("this script must be run as root")
Mike Frysinger35247af2012-11-16 18:58:06 -05001561
Alex Klein1699fab2022-09-08 08:46:06 -06001562 Crossdev.Load(options.reconfig)
1563 root = options.sysroot or "/"
1564 UpdateToolchains(
1565 options.usepkg,
1566 options.deleteold,
1567 options.hostonly,
1568 options.reconfig,
1569 targets_wanted,
1570 boards_wanted,
1571 root=root,
1572 )
1573 Crossdev.Save()
Mike Frysinger35247af2012-11-16 18:58:06 -05001574
Alex Klein1699fab2022-09-08 08:46:06 -06001575 return 0