blob: d3bf0ef0b8d72de27b65f0fed0509fb50acd6726 [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 = (
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 = {
Alex Klein1699fab2022-09-08 08:46:06 -0600132 "llvm": "sys-devel",
133 "llvm-libunwind": "sys-libs",
134 "libcxx": "sys-libs",
135 "elfutils": "dev-libs",
George Burgess IV6c298782023-02-14 14:28:04 -0700136 # b/269306499: note that rust and rust-host are shipped as a part of
137 # this tarball on a best-effort basis. If you would like them to be
138 # fully supported (with an SLA), please reach out to
139 # chromeos-toolchain@google.com and chat with us.
140 "rust": "dev-lang",
141 "rust-host": "dev-lang",
Mike Frysinger3ed47722017-08-08 14:59:08 -0400142 }
143
Alex Klein1699fab2022-09-08 08:46:06 -0600144 @classmethod
145 def Load(cls, reconfig):
146 """Load crossdev cache from disk.
Mike Frysinger3ed47722017-08-08 14:59:08 -0400147
Alex Klein1699fab2022-09-08 08:46:06 -0600148 We invalidate the cache when crossdev updates or this script changes.
149 """
150 crossdev_version = GetStablePackageVersion("sys-devel/crossdev", True)
151 # If we run the compiled/cached .pyc file, we'll read/hash that when we
152 # really always want to track the source .py file.
153 script = os.path.abspath(__file__)
154 if script.endswith(".pyc"):
155 script = script[:-1]
156 setup_toolchains_hash = hashlib.md5(
157 osutils.ReadFile(script, mode="rb")
158 ).hexdigest()
Mike Frysinger3ed47722017-08-08 14:59:08 -0400159
Alex Klein1699fab2022-09-08 08:46:06 -0600160 cls._CACHE = {
161 "crossdev_version": crossdev_version,
162 "setup_toolchains_hash": setup_toolchains_hash,
Mike Frysinger66bfde52017-09-12 16:42:57 -0400163 }
Alex Klein1699fab2022-09-08 08:46:06 -0600164
165 logging.debug("cache: checking file: %s", cls._CACHE_FILE)
166 if reconfig:
167 logging.debug("cache: forcing regen due to reconfig")
168 return
169
170 try:
171 file_data = osutils.ReadFile(cls._CACHE_FILE)
172 except IOError as e:
173 if e.errno != errno.ENOENT:
174 logging.warning("cache: reading failed: %s", e)
175 osutils.SafeUnlink(cls._CACHE_FILE)
176 return
177
178 try:
179 data = json.loads(file_data)
180 except ValueError as e:
181 logging.warning("cache: ignoring invalid content: %s", e)
182 return
183
184 if crossdev_version != data.get("crossdev_version"):
185 logging.debug("cache: rebuilding after crossdev upgrade")
186 elif setup_toolchains_hash != data.get("setup_toolchains_hash"):
187 logging.debug(
188 "cache: rebuilding after cros_setup_toolchains change"
189 )
Mike Frysinger785b0c32017-09-13 01:35:59 -0400190 else:
Alex Klein1699fab2022-09-08 08:46:06 -0600191 logging.debug("cache: content is up-to-date!")
192 cls._CACHE = data
Han Shene23782f2016-02-18 12:20:00 -0800193
Alex Klein1699fab2022-09-08 08:46:06 -0600194 @classmethod
195 def Save(cls):
196 """Store crossdev cache on disk."""
197 # Save the cache from the successful run.
Mike Frysinger31fdddd2023-02-24 15:50:55 -0500198 with open(cls._CACHE_FILE, "w", encoding="utf-8") as f:
Alex Klein1699fab2022-09-08 08:46:06 -0600199 json.dump(cls._CACHE, f)
Mike Frysinger66bfde52017-09-12 16:42:57 -0400200
Alex Klein1699fab2022-09-08 08:46:06 -0600201 @classmethod
202 def GetConfig(cls, target):
203 """Returns a map of crossdev provided variables about a tuple."""
204 CACHE_ATTR = "_target_tuple_map"
Han Shene23782f2016-02-18 12:20:00 -0800205
Alex Klein1699fab2022-09-08 08:46:06 -0600206 val = cls._CACHE.setdefault(CACHE_ATTR, {})
207 if not target in val:
208 if target.startswith("host"):
209 conf = {
210 "crosspkgs": [],
211 "target": toolchain.GetHostTuple(),
212 }
213 if target == "host":
214 packages_list = HOST_PACKAGES
215 else:
216 packages_list = HOST_POST_CROSS_PACKAGES
217 manual_pkgs = dict(
218 (pkg, cat)
219 for cat, pkg in [x.split("/") for x in packages_list]
220 )
221 else:
222 # Build the crossdev command.
Jordan R Abrahams-Whiteheadc6363032023-04-06 23:30:50 +0000223 cmd = ["crossdev", "--stable", "--show-target-cfg", "--ex-gdb"]
Alex Klein1699fab2022-09-08 08:46:06 -0600224 # Enable libxcrypt for all linux-gnu targets.
225 if "cros-linux-gnu" in target:
226 cmd.extend(CROSSDEV_LIBXCRYPT_ARGS)
227 if target in TARGET_COMPILER_RT_ENABLED:
228 cmd.extend(CROSSDEV_COMPILER_RT_ARGS)
229 if target in TARGET_LLVM_PKGS_ENABLED:
Trent Apted593c0742023-05-05 03:50:20 +0000230 # TODO(b/236161656): Fix.
231 # pylint: disable-next=consider-using-dict-items
Alex Klein1699fab2022-09-08 08:46:06 -0600232 for pkg in LLVM_PKGS_TABLE:
233 cmd.extend(LLVM_PKGS_TABLE[pkg])
234 if target in TARGET_GO_ENABLED:
235 cmd.extend(CROSSDEV_GO_ARGS)
236 cmd.extend(["-t", target])
237 # Catch output of crossdev.
238 out = cros_build_lib.run(
239 cmd, print_cmd=False, stdout=True, encoding="utf-8"
240 ).stdout.splitlines()
241 # List of tuples split at the first '=', converted into dict.
242 conf = dict(
243 (k, cros_build_lib.ShellUnquote(v))
244 for k, v in (x.split("=", 1) for x in out)
245 )
246 conf["crosspkgs"] = conf["crosspkgs"].split()
Han Shene23782f2016-02-18 12:20:00 -0800247
Alex Klein1699fab2022-09-08 08:46:06 -0600248 manual_pkgs = cls.MANUAL_PKGS
David James66a09c42012-11-05 13:31:38 -0800249
Alex Klein1699fab2022-09-08 08:46:06 -0600250 for pkg, cat in manual_pkgs.items():
251 conf[pkg + "_pn"] = pkg
252 conf[pkg + "_category"] = cat
253 if pkg not in conf["crosspkgs"]:
254 conf["crosspkgs"].append(pkg)
David James66a09c42012-11-05 13:31:38 -0800255
Alex Klein1699fab2022-09-08 08:46:06 -0600256 val[target] = conf
David James66a09c42012-11-05 13:31:38 -0800257
Alex Klein1699fab2022-09-08 08:46:06 -0600258 return val[target]
Manoj Gupta4d016f62021-10-19 16:39:34 -0700259
Alex Klein1699fab2022-09-08 08:46:06 -0600260 @classmethod
261 def UpdateTargets(cls, targets, usepkg, config_only=False):
262 """Calls crossdev to initialize a cross target.
Manoj Gupta4d016f62021-10-19 16:39:34 -0700263
Alex Klein1699fab2022-09-08 08:46:06 -0600264 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700265 targets: The dict of targets to initialize using crossdev.
266 usepkg: Copies the commandline opts.
267 config_only: Just update.
Alex Klein1699fab2022-09-08 08:46:06 -0600268 """
269 configured_targets = cls._CACHE.setdefault("configured_targets", [])
270 started_targets = set()
David James66a09c42012-11-05 13:31:38 -0800271
Alex Klein1699fab2022-09-08 08:46:06 -0600272 # Schedule all of the targets in parallel, and let them run.
273 with parallel.BackgroundTaskRunner(cls._UpdateTarget) as queue:
274 for target_name in targets:
275 # We already started this target in this loop.
276 if target_name in started_targets:
277 continue
278 # The target is already configured.
279 if config_only and target_name in configured_targets:
280 continue
281 queue.put(
282 [target_name, targets[target_name], usepkg, config_only]
283 )
284 started_targets.add(target_name)
David James66a09c42012-11-05 13:31:38 -0800285
Alex Klein1699fab2022-09-08 08:46:06 -0600286 @classmethod
287 def _UpdateTarget(cls, target_name, target, usepkg, config_only):
288 """Calls crossdev to initialize a cross target.
David James66a09c42012-11-05 13:31:38 -0800289
Alex Klein1699fab2022-09-08 08:46:06 -0600290 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700291 target_name: The name of the target to initialize.
292 target: The target info for initializing.
293 usepkg: Copies the commandline opts.
294 config_only: Just update.
Alex Klein1699fab2022-09-08 08:46:06 -0600295 """
296 configured_targets = cls._CACHE.setdefault("configured_targets", [])
Jordan R Abrahams-Whiteheadc6363032023-04-06 23:30:50 +0000297 cmdbase = ["crossdev", "--stable", "--show-fail-log"]
Alex Klein1699fab2022-09-08 08:46:06 -0600298 cmdbase.extend(["--env", "FEATURES=splitdebug"])
299 # Pick stable by default, and override as necessary.
300 cmdbase.extend(["-P", "--oneshot"])
301 if usepkg:
302 cmdbase.extend(
303 ["-P", "--getbinpkg", "-P", "--usepkgonly", "--without-headers"]
304 )
David James66a09c42012-11-05 13:31:38 -0800305
Alex Klein1699fab2022-09-08 08:46:06 -0600306 overlays = " ".join(
307 (CHROMIUMOS_OVERLAY, ECLASS_OVERLAY, STABLE_OVERLAY)
308 )
309 cmdbase.extend(["--overlays", overlays])
310 cmdbase.extend(["--ov-output", CROSSDEV_OVERLAY])
Manoj Gupta4d016f62021-10-19 16:39:34 -0700311
Alex Klein1699fab2022-09-08 08:46:06 -0600312 cmd = cmdbase + ["-t", target_name]
Manoj Gupta4d016f62021-10-19 16:39:34 -0700313
Alex Klein1699fab2022-09-08 08:46:06 -0600314 for pkg in GetTargetPackages(target_name):
315 if pkg == "gdb":
316 # Gdb does not have selectable versions.
317 cmd.append("--ex-gdb")
318 elif pkg == "ex_libxcrypt":
319 cmd.extend(CROSSDEV_LIBXCRYPT_ARGS)
320 elif pkg == "ex_compiler-rt":
321 cmd.extend(CROSSDEV_COMPILER_RT_ARGS)
322 elif pkg == "ex_go":
323 # Go does not have selectable versions.
324 cmd.extend(CROSSDEV_GO_ARGS)
325 elif pkg in LLVM_PKGS_TABLE:
326 cmd.extend(LLVM_PKGS_TABLE[pkg])
327 elif pkg in cls.MANUAL_PKGS:
328 pass
329 else:
330 # The first of the desired versions is the "primary" one.
331 version = GetDesiredPackageVersions(target_name, pkg)[0]
332 cmd.extend(["--%s" % pkg, version])
333
334 cmd.extend(target["crossdev"].split())
335
336 if config_only:
337 # In this case we want to just quietly reinit
338 cmd.append("--init-target")
339 cros_build_lib.run(cmd, print_cmd=False, stdout=True)
340 else:
341 cros_build_lib.run(cmd)
342
343 configured_targets.append(target_name)
David James66a09c42012-11-05 13:31:38 -0800344
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100345
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100346def GetTargetPackages(target):
Alex Klein1699fab2022-09-08 08:46:06 -0600347 """Returns a list of packages for a given target."""
348 conf = Crossdev.GetConfig(target)
349 # Undesired packages are denoted by empty ${pkg}_pn variable.
350 return [x for x in conf["crosspkgs"] if conf.get(x + "_pn")]
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100351
352
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100353# Portage helper functions:
354def GetPortagePackage(target, package):
Alex Klein1699fab2022-09-08 08:46:06 -0600355 """Returns a package name for the given target."""
356 conf = Crossdev.GetConfig(target)
357 # Portage category:
358 if target.startswith("host") or package in Crossdev.MANUAL_PKGS:
359 category = conf[package + "_category"]
360 else:
361 category = conf["category"]
362 # Portage package:
363 pn = conf[package + "_pn"]
364 # Final package name:
365 assert category
366 assert pn
367 return "%s/%s" % (category, pn)
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100368
369
Gilad Arnold2dab78c2015-05-21 14:43:33 -0700370def PortageTrees(root):
Alex Klein1699fab2022-09-08 08:46:06 -0600371 """Return the portage trees for a given root."""
372 if root == "/":
373 return portage.db["/"]
374 # The portage logic requires the path always end in a slash.
375 root = root.rstrip("/") + "/"
376 return portage.create_trees(target_root=root, config_root=root)[root]
Gilad Arnold2dab78c2015-05-21 14:43:33 -0700377
378
Alex Klein1699fab2022-09-08 08:46:06 -0600379def GetInstalledPackageVersions(atom, root="/"):
380 """Extracts the list of current versions of a target, package pair.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100381
Alex Klein1699fab2022-09-08 08:46:06 -0600382 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700383 atom: The atom to operate on (e.g. sys-devel/gcc)
384 root: The root to check for installed packages.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100385
Alex Klein1699fab2022-09-08 08:46:06 -0600386 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700387 The list of versions of the package currently installed.
Alex Klein1699fab2022-09-08 08:46:06 -0600388 """
389 versions = []
390 for pkg in PortageTrees(root)["vartree"].dbapi.match(atom, use_cache=0):
391 version = portage.versions.cpv_getversion(pkg)
392 versions.append(version)
393 return versions
Zdenek Behan508dcce2011-12-05 15:39:32 +0100394
395
Alex Klein1699fab2022-09-08 08:46:06 -0600396def GetStablePackageVersion(atom, installed, root="/"):
397 """Extracts the current stable version for a given package.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100398
Alex Klein1699fab2022-09-08 08:46:06 -0600399 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700400 atom: The target/package to operate on e.g. i686-cros-linux-gnu/gcc
401 installed: Whether we want installed packages or ebuilds
402 root: The root to use when querying packages.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100403
Alex Klein1699fab2022-09-08 08:46:06 -0600404 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700405 A string containing the latest version.
Alex Klein1699fab2022-09-08 08:46:06 -0600406 """
407 pkgtype = "vartree" if installed else "porttree"
408 cpv = portage.best(
409 PortageTrees(root)[pkgtype].dbapi.match(atom, use_cache=0)
410 )
411 return portage.versions.cpv_getversion(cpv) if cpv else None
Zdenek Behan508dcce2011-12-05 15:39:32 +0100412
413
Alex Klein1699fab2022-09-08 08:46:06 -0600414def VersionListToNumeric(target, package, versions, installed, root="/"):
415 """Resolves keywords in a given version list for a particular package.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100416
Alex Klein1699fab2022-09-08 08:46:06 -0600417 Resolving means replacing PACKAGE_STABLE with the actual number.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100418
Alex Klein1699fab2022-09-08 08:46:06 -0600419 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700420 target: The target to operate on (e.g. i686-cros-linux-gnu)
421 package: The target/package to operate on (e.g. gcc)
422 versions: List of versions to resolve
423 installed: Query installed packages
424 root: The install root to use; ignored if |installed| is False.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100425
Alex Klein1699fab2022-09-08 08:46:06 -0600426 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700427 List of purely numeric versions equivalent to argument
Alex Klein1699fab2022-09-08 08:46:06 -0600428 """
429 resolved = []
430 atom = GetPortagePackage(target, package)
431 if not installed:
432 root = "/"
433 for version in versions:
434 if version == PACKAGE_STABLE:
435 resolved.append(GetStablePackageVersion(atom, installed, root=root))
436 else:
437 resolved.append(version)
438 return resolved
Zdenek Behan508dcce2011-12-05 15:39:32 +0100439
440
441def GetDesiredPackageVersions(target, package):
Alex Klein1699fab2022-09-08 08:46:06 -0600442 """Produces the list of desired versions for each target, package pair.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100443
Alex Klein1699fab2022-09-08 08:46:06 -0600444 The first version in the list is implicitly treated as primary, ie.
445 the version that will be initialized by crossdev and selected.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100446
Alex Klein1699fab2022-09-08 08:46:06 -0600447 If the version is PACKAGE_STABLE, it really means the current version which
448 is emerged by using the package atom with no particular version key.
449 Since crossdev unmasks all packages by default, this will actually
450 mean 'unstable' in most cases.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100451
Alex Klein1699fab2022-09-08 08:46:06 -0600452 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700453 target: The target to operate on (e.g. i686-cros-linux-gnu)
454 package: The target/package to operate on (e.g. gcc)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100455
Alex Klein1699fab2022-09-08 08:46:06 -0600456 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700457 A list composed of either a version string, PACKAGE_STABLE
Alex Klein1699fab2022-09-08 08:46:06 -0600458 """
459 if package in GetTargetPackages(target):
460 return [PACKAGE_STABLE]
461 else:
462 return []
Zdenek Behan508dcce2011-12-05 15:39:32 +0100463
464
465def TargetIsInitialized(target):
Alex Klein1699fab2022-09-08 08:46:06 -0600466 """Verifies if the given list of targets has been correctly initialized.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100467
Alex Klein1699fab2022-09-08 08:46:06 -0600468 This determines whether we have to call crossdev while emerging
469 toolchain packages or can do it using emerge. Emerge is naturally
470 preferred, because all packages can be updated in a single pass.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100471
Alex Klein1699fab2022-09-08 08:46:06 -0600472 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700473 target: The target to operate on (e.g. i686-cros-linux-gnu)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100474
Alex Klein1699fab2022-09-08 08:46:06 -0600475 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700476 True if |target| is completely initialized, else False
Alex Klein1699fab2022-09-08 08:46:06 -0600477 """
478 # Check if packages for the given target all have a proper version.
479 try:
480 for package in GetTargetPackages(target):
481 atom = GetPortagePackage(target, package)
482 # Do we even want this package && is it initialized?
483 if not (
484 GetStablePackageVersion(atom, True)
485 and GetStablePackageVersion(atom, False)
486 ):
487 return False
488 return True
489 except cros_build_lib.RunCommandError:
490 # Fails - The target has likely never been initialized before.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100491 return False
Zdenek Behan508dcce2011-12-05 15:39:32 +0100492
493
494def RemovePackageMask(target):
Alex Klein1699fab2022-09-08 08:46:06 -0600495 """Removes a package.mask file for the given platform.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100496
Alex Klein1699fab2022-09-08 08:46:06 -0600497 The pre-existing package.mask files can mess with the keywords.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100498
Alex Klein1699fab2022-09-08 08:46:06 -0600499 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700500 target: The target to operate on (e.g. i686-cros-linux-gnu)
Alex Klein1699fab2022-09-08 08:46:06 -0600501 """
502 maskfile = os.path.join("/etc/portage/package.mask", "cross-" + target)
503 osutils.SafeUnlink(maskfile)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100504
505
Zdenek Behan508dcce2011-12-05 15:39:32 +0100506# Main functions performing the actual update steps.
Alex Klein1699fab2022-09-08 08:46:06 -0600507def RebuildLibtool(root="/"):
508 """Rebuild libtool as needed
Mike Frysingerc880a962013-11-08 13:59:06 -0500509
Alex Klein1699fab2022-09-08 08:46:06 -0600510 Libtool hardcodes full paths to internal gcc files, so whenever we upgrade
511 gcc, libtool will break. We can't use binary packages either as those will
512 most likely be compiled against the previous version of gcc.
Gilad Arnold2dab78c2015-05-21 14:43:33 -0700513
Alex Klein1699fab2022-09-08 08:46:06 -0600514 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700515 root: The install root where we want libtool rebuilt.
Alex Klein1699fab2022-09-08 08:46:06 -0600516 """
517 needs_update = False
Mike Frysinger31fdddd2023-02-24 15:50:55 -0500518 with open(os.path.join(root, "usr/bin/libtool"), encoding="utf-8") as f:
Alex Klein1699fab2022-09-08 08:46:06 -0600519 for line in f:
520 # Look for a line like:
521 # sys_lib_search_path_spec="..."
522 # It'll be a list of paths and gcc will be one of them.
523 if line.startswith("sys_lib_search_path_spec="):
524 line = line.rstrip()
525 for path in line.split("=", 1)[1].strip('"').split():
526 root_path = os.path.join(root, path.lstrip(os.path.sep))
527 logging.debug("Libtool: checking %s", root_path)
528 if not os.path.exists(root_path):
529 logging.info("Rebuilding libtool after gcc upgrade")
530 logging.info(" %s", line)
531 logging.info(" missing path: %s", path)
532 needs_update = True
533 break
Mike Frysingerc880a962013-11-08 13:59:06 -0500534
Alex Klein1699fab2022-09-08 08:46:06 -0600535 if needs_update:
536 break
Mike Frysingerc880a962013-11-08 13:59:06 -0500537
Alex Klein1699fab2022-09-08 08:46:06 -0600538 if needs_update:
539 cmd = [EMERGE_CMD, "--oneshot"]
540 if root != "/":
541 cmd.extend(["--sysroot=%s" % root, "--root=%s" % root])
542 cmd.append("sys-devel/libtool")
543 cros_build_lib.run(cmd)
544 else:
545 logging.debug("Libtool is up-to-date; no need to rebuild")
Mike Frysingerc880a962013-11-08 13:59:06 -0500546
547
Alex Klein1699fab2022-09-08 08:46:06 -0600548def UpdateTargets(targets, usepkg, root="/"):
549 """Determines which packages need update/unmerge and defers to portage.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100550
Alex Klein1699fab2022-09-08 08:46:06 -0600551 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700552 targets: The list of targets to update
553 usepkg: Copies the commandline option
554 root: The install root in which we want packages updated.
Alex Klein1699fab2022-09-08 08:46:06 -0600555 """
556 # For each target, we do two things. Figure out the list of updates,
557 # and figure out the appropriate keywords/masks. Crossdev will initialize
558 # these, but they need to be regenerated on every update.
559 logging.info("Determining required toolchain updates...")
560 mergemap = {}
561 # Used to keep track of post-cross packages. These are allowed to have
562 # implicit dependencies on toolchain packages, and therefore need to
563 # be built last.
564 post_cross_pkgs = set()
Zdenek Behan508dcce2011-12-05 15:39:32 +0100565 for target in targets:
Alex Klein1699fab2022-09-08 08:46:06 -0600566 is_post_cross_target = target.endswith("-post-cross")
567 logging.debug("Updating target %s", target)
Alex Klein0e92b2c2023-01-13 11:54:15 -0700568 # Record the highest needed version for each target, for masking
569 # purposes.
Alex Klein1699fab2022-09-08 08:46:06 -0600570 RemovePackageMask(target)
571 for package in GetTargetPackages(target):
572 # Portage name for the package
573 logging.debug(" Checking package %s", package)
574 pkg = GetPortagePackage(target, package)
575 current = GetInstalledPackageVersions(pkg, root=root)
576 desired = GetDesiredPackageVersions(target, package)
577 desired_num = VersionListToNumeric(target, package, desired, False)
578 if pkg in NEW_PACKAGES and usepkg:
579 # Skip this binary package (for now).
580 continue
581 mergemap[pkg] = set(desired_num).difference(current)
582 logging.debug(" %s -> %s", current, desired_num)
583 if is_post_cross_target:
584 post_cross_pkgs.add(pkg)
Mike Frysinger785b0c32017-09-13 01:35:59 -0400585
Alex Klein1699fab2022-09-08 08:46:06 -0600586 packages = [pkg for pkg, vers in mergemap.items() if vers]
587 if not packages:
588 logging.info("Nothing to update!")
589 return False
Zdenek Behan508dcce2011-12-05 15:39:32 +0100590
Alex Klein1699fab2022-09-08 08:46:06 -0600591 logging.info("Updating packages:")
592 logging.info("%s", packages)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100593
Alex Klein1699fab2022-09-08 08:46:06 -0600594 cmd = [EMERGE_CMD, "--oneshot", "--update"]
595 if usepkg:
596 cmd.extend(["--getbinpkg", "--usepkgonly"])
597 if root != "/":
598 cmd.extend(["--sysroot=%s" % root, "--root=%s" % root])
Zdenek Behan508dcce2011-12-05 15:39:32 +0100599
Alex Klein1699fab2022-09-08 08:46:06 -0600600 if usepkg:
601 # Since we are not building from source, we can handle
602 # all packages in one go.
603 cmd.extend(packages)
604 cros_build_lib.run(cmd)
605 else:
606 pre_cross_items = [
607 pkg for pkg in packages if pkg not in post_cross_pkgs
608 ]
609 if pre_cross_items:
610 cros_build_lib.run(cmd + pre_cross_items)
611 post_cross_items = [pkg for pkg in packages if pkg in post_cross_pkgs]
612 if post_cross_items:
613 cros_build_lib.run(cmd + post_cross_items)
614 return True
Gilad Arnold2dab78c2015-05-21 14:43:33 -0700615
Alex Klein1699fab2022-09-08 08:46:06 -0600616
617def CleanTargets(targets, root="/"):
618 """Unmerges old packages that are assumed unnecessary.
619
620 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700621 targets: The list of targets to clean up.
622 root: The install root in which we want packages cleaned up.
Alex Klein1699fab2022-09-08 08:46:06 -0600623 """
624 unmergemap = {}
625 for target in targets:
626 logging.debug("Cleaning target %s", target)
627 for package in GetTargetPackages(target):
628 logging.debug(" Cleaning package %s", package)
629 pkg = GetPortagePackage(target, package)
630 current = GetInstalledPackageVersions(pkg, root=root)
631 desired = GetDesiredPackageVersions(target, package)
632 # NOTE: This refers to installed packages (vartree) rather than the
Alex Klein0e92b2c2023-01-13 11:54:15 -0700633 # Portage version (porttree and/or bintree) when determining the
634 # current version. While this isn't the most accurate thing to do,
635 # it is probably a good simple compromise, which should have the
636 # desired result of uninstalling everything but the latest installed
637 # version. In particular, using the bintree (--usebinpkg) requires a
638 # non-trivial binhost sync and is probably more complex than useful.
Alex Klein1699fab2022-09-08 08:46:06 -0600639 desired_num = VersionListToNumeric(target, package, desired, True)
640 if not set(desired_num).issubset(current):
641 logging.warning(
642 "Error detecting stable version for %s, " "skipping clean!",
643 pkg,
644 )
645 return
646 unmergemap[pkg] = set(current).difference(desired_num)
647
648 # Cleaning doesn't care about consistency and rebuilding package.* files.
649 packages = []
650 for pkg, vers in unmergemap.items():
651 packages.extend("=%s-%s" % (pkg, ver) for ver in vers if ver != "9999")
652
653 if packages:
654 logging.info("Cleaning packages:")
655 logging.info("%s", packages)
656 cmd = [EMERGE_CMD, "--unmerge"]
657 if root != "/":
658 cmd.extend(["--sysroot=%s" % root, "--root=%s" % root])
659 cmd.extend(packages)
660 cros_build_lib.run(cmd)
661 else:
662 logging.info("Nothing to clean!")
663
664
665def SelectActiveToolchains(targets, root="/"):
666 """Runs gcc-config and binutils-config to select the desired.
667
668 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700669 targets: The targets to select
670 root: The root where we want to select toolchain versions.
Alex Klein1699fab2022-09-08 08:46:06 -0600671 """
672 for package in ["gcc", "binutils"]:
673 for target in targets:
674 # See if this package is part of this target.
675 if package not in GetTargetPackages(target):
676 logging.debug("%s: %s is not used", target, package)
677 continue
678
679 # Pick the first version in the numbered list as the selected one.
680 desired = GetDesiredPackageVersions(target, package)
681 desired_num = VersionListToNumeric(
682 target, package, desired, True, root=root
683 )
684 desired = desired_num[0]
685 # *-config does not play revisions, strip them, keep just PV.
686 desired = portage.versions.pkgsplit("%s-%s" % (package, desired))[1]
687
688 if target.startswith("host"):
Alex Klein0e92b2c2023-01-13 11:54:15 -0700689 # *-config is the only tool treating host identically (by
690 # tuple).
Alex Klein1699fab2022-09-08 08:46:06 -0600691 target = toolchain.GetHostTuple()
692
693 # And finally, attach target to it.
694 desired = "%s-%s" % (target, desired)
695
696 extra_env = {"CHOST": target}
697 if root != "/":
698 extra_env["ROOT"] = root
699 cmd = ["%s-config" % package, "-c", target]
700 result = cros_build_lib.run(
701 cmd,
702 print_cmd=False,
703 stdout=True,
704 encoding="utf-8",
705 extra_env=extra_env,
706 )
707 current = result.stdout.splitlines()[0]
708
Alex Klein0e92b2c2023-01-13 11:54:15 -0700709 # Do not reconfig when the current is live or nothing needs to be
710 # done.
Alex Klein1699fab2022-09-08 08:46:06 -0600711 extra_env = {"ROOT": root} if root != "/" else None
Alex Klein64930532023-04-17 12:20:52 -0600712 if current not in (desired, "9999"):
Alex Klein1699fab2022-09-08 08:46:06 -0600713 cmd = [package + "-config", desired]
714 cros_build_lib.run(cmd, print_cmd=False, extra_env=extra_env)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100715
716
Mike Frysinger35247af2012-11-16 18:58:06 -0500717def ExpandTargets(targets_wanted):
Alex Klein1699fab2022-09-08 08:46:06 -0600718 """Expand any possible toolchain aliases into full targets
Mike Frysinger35247af2012-11-16 18:58:06 -0500719
Alex Klein1699fab2022-09-08 08:46:06 -0600720 This will expand 'all' and 'sdk' into the respective toolchain tuples.
Mike Frysinger35247af2012-11-16 18:58:06 -0500721
Alex Klein1699fab2022-09-08 08:46:06 -0600722 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700723 targets_wanted: The targets specified by the user.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500724
Alex Klein1699fab2022-09-08 08:46:06 -0600725 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700726 Dictionary of concrete targets and their toolchain tuples.
Alex Klein1699fab2022-09-08 08:46:06 -0600727 """
728 targets_wanted = set(targets_wanted)
729 if targets_wanted == set(["boards"]):
730 # Only pull targets from the included boards.
731 return {}
Gilad Arnold8195b532015-04-07 10:56:30 +0300732
Alex Klein1699fab2022-09-08 08:46:06 -0600733 all_targets = toolchain.GetAllTargets()
734 if targets_wanted == set(["all"]):
735 return all_targets
736 if targets_wanted == set(["sdk"]):
737 # Filter out all the non-sdk toolchains as we don't want to mess
738 # with those in all of our builds.
739 return toolchain.FilterToolchains(all_targets, "sdk", True)
Gilad Arnold8195b532015-04-07 10:56:30 +0300740
Alex Klein1699fab2022-09-08 08:46:06 -0600741 # Verify user input.
742 nonexistent = targets_wanted.difference(all_targets)
743 if nonexistent:
744 raise ValueError("Invalid targets: %s" % (",".join(nonexistent),))
745 return {t: all_targets[t] for t in targets_wanted}
Mike Frysinger35247af2012-11-16 18:58:06 -0500746
747
Alex Klein1699fab2022-09-08 08:46:06 -0600748def UpdateToolchains(
749 usepkg,
750 deleteold,
751 hostonly,
752 reconfig,
753 targets_wanted,
754 boards_wanted,
755 root="/",
756):
757 """Performs all steps to create a synchronized toolchain enviroment.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100758
Alex Klein1699fab2022-09-08 08:46:06 -0600759 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700760 usepkg: Use prebuilt packages
761 deleteold: Unmerge deprecated packages
762 hostonly: Only setup the host toolchain
763 reconfig: Reload crossdev config and reselect toolchains
764 targets_wanted: All the targets to update
765 boards_wanted: Load targets from these boards
766 root: The root in which to install the toolchains.
Alex Klein1699fab2022-09-08 08:46:06 -0600767 """
768 targets, crossdev_targets, reconfig_targets = {}, {}, {}
769 if not hostonly:
770 # For hostonly, we can skip most of the below logic, much of which won't
771 # work on bare systems where this is useful.
772 targets = ExpandTargets(targets_wanted)
Mike Frysinger7ccee992012-06-01 21:27:59 -0400773
Alex Klein1699fab2022-09-08 08:46:06 -0600774 # Filter out toolchains that don't (yet) have a binpkg available.
775 if usepkg:
776 for target in list(targets.keys()):
777 if not targets[target]["have-binpkg"]:
778 del targets[target]
Mike Frysingerd246fb92021-10-26 16:08:39 -0400779
Alex Klein1699fab2022-09-08 08:46:06 -0600780 # Now re-add any targets that might be from this board. This is to
781 # allow unofficial boards to declare their own toolchains.
782 for board in boards_wanted:
783 targets.update(toolchain.GetToolchainsForBoard(board))
Zdenek Behan508dcce2011-12-05 15:39:32 +0100784
Alex Klein1699fab2022-09-08 08:46:06 -0600785 # First check and initialize all cross targets that need to be.
786 for target in targets:
787 if TargetIsInitialized(target):
788 reconfig_targets[target] = targets[target]
789 else:
790 crossdev_targets[target] = targets[target]
791 if crossdev_targets:
792 logging.info("The following targets need to be re-initialized:")
793 logging.info("%s", crossdev_targets)
794 Crossdev.UpdateTargets(crossdev_targets, usepkg)
795 # Those that were not initialized may need a config update.
796 Crossdev.UpdateTargets(reconfig_targets, usepkg, config_only=True)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100797
Alex Klein0e92b2c2023-01-13 11:54:15 -0700798 # If we're building a subset of toolchains for a board, we might not
799 # have all the tuples that the packages expect. We don't define the
800 # "full" set of tuples currently other than "whatever the full sdk has
801 # normally".
Alex Klein1699fab2022-09-08 08:46:06 -0600802 if usepkg or set(("all", "sdk")) & targets_wanted:
803 # Since we have cross-compilers now, we can update these packages.
804 targets["host-post-cross"] = {}
Mike Frysinger785b0c32017-09-13 01:35:59 -0400805
Alex Klein1699fab2022-09-08 08:46:06 -0600806 # We want host updated.
807 targets["host"] = {}
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100808
Alex Klein1699fab2022-09-08 08:46:06 -0600809 # Now update all packages.
810 if (
811 UpdateTargets(targets, usepkg, root=root)
812 or crossdev_targets
813 or reconfig
814 ):
815 SelectActiveToolchains(targets, root=root)
David James7ec5efc2012-11-06 09:39:49 -0800816
Alex Klein1699fab2022-09-08 08:46:06 -0600817 if deleteold:
818 CleanTargets(targets, root=root)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100819
Alex Klein1699fab2022-09-08 08:46:06 -0600820 # Now that we've cleared out old versions, see if we need to rebuild
821 # anything. Can't do this earlier as it might not be broken.
822 RebuildLibtool(root=root)
Mike Frysingerc880a962013-11-08 13:59:06 -0500823
Zdenek Behan508dcce2011-12-05 15:39:32 +0100824
Bertrand SIMONNETcae9d5f2015-03-09 15:58:01 -0700825def ShowConfig(name):
Alex Klein1699fab2022-09-08 08:46:06 -0600826 """Show the toolchain tuples used by |name|
Mike Frysinger35247af2012-11-16 18:58:06 -0500827
Alex Klein1699fab2022-09-08 08:46:06 -0600828 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700829 name: The board name to query.
Alex Klein1699fab2022-09-08 08:46:06 -0600830 """
831 toolchains = toolchain.GetToolchainsForBoard(name)
832 # Make sure we display the default toolchain first.
833 # Note: Do not use logging here as this is meant to be used by other tools.
834 print(
835 ",".join(
836 list(toolchain.FilterToolchains(toolchains, "default", True))
837 + list(toolchain.FilterToolchains(toolchains, "default", False))
838 )
839 )
Mike Frysinger35247af2012-11-16 18:58:06 -0500840
841
Mike Frysinger35247af2012-11-16 18:58:06 -0500842def GeneratePathWrapper(root, wrappath, path):
Alex Klein1699fab2022-09-08 08:46:06 -0600843 """Generate a shell script to execute another shell script
Mike Frysinger35247af2012-11-16 18:58:06 -0500844
Alex Klein1699fab2022-09-08 08:46:06 -0600845 Since we can't symlink a wrapped ELF (see GenerateLdsoWrapper) because the
846 argv[0] won't be pointing to the correct path, generate a shell script that
847 just executes another program with its full path.
Mike Frysinger35247af2012-11-16 18:58:06 -0500848
Alex Klein1699fab2022-09-08 08:46:06 -0600849 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700850 root: The root tree to generate scripts inside of
851 wrappath: The full path (inside |root|) to create the wrapper
852 path: The target program which this wrapper will execute
Alex Klein1699fab2022-09-08 08:46:06 -0600853 """
854 replacements = {
855 "path": path,
856 "relroot": os.path.relpath("/", os.path.dirname(wrappath)),
857 }
Takuto Ikuta58403972018-08-16 18:52:51 +0900858
Alex Klein1699fab2022-09-08 08:46:06 -0600859 # Do not use exec here, because exec invokes script with absolute path in
Alex Klein0e92b2c2023-01-13 11:54:15 -0700860 # argv0. Keeping relativeness allows us to remove abs path from compile
861 # result and leads directory independent build cache sharing in some
862 # distributed build system.
Alex Klein1699fab2022-09-08 08:46:06 -0600863 wrapper = (
864 """#!/bin/sh
Takuto Ikuta58403972018-08-16 18:52:51 +0900865basedir=$(dirname "$0")
866"${basedir}/%(relroot)s%(path)s" "$@"
867exit "$?"
Alex Klein1699fab2022-09-08 08:46:06 -0600868"""
869 % replacements
870 )
871 root_wrapper = root + wrappath
872 if os.path.islink(root_wrapper):
873 os.unlink(root_wrapper)
874 else:
875 osutils.SafeMakedirs(os.path.dirname(root_wrapper))
876 osutils.WriteFile(root_wrapper, wrapper)
877 os.chmod(root_wrapper, 0o755)
Mike Frysinger35247af2012-11-16 18:58:06 -0500878
879
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700880def FixClangXXWrapper(root, path):
Alex Klein1699fab2022-09-08 08:46:06 -0600881 """Fix wrapper shell scripts and symlinks for invoking clang++
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700882
Alex Klein1699fab2022-09-08 08:46:06 -0600883 In a typical installation, clang++ symlinks to clang, which symlinks to the
884 elf executable. The executable distinguishes between clang and clang++ based
885 on argv[0].
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700886
Alex Klein0e92b2c2023-01-13 11:54:15 -0700887 When invoked through the LdsoWrapper, argv[0] always contains the path to
888 the executable elf file, making clang/clang++ invocations indistinguishable.
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700889
Alex Klein1699fab2022-09-08 08:46:06 -0600890 This function detects if the elf executable being wrapped is clang-X.Y, and
891 fixes wrappers/symlinks as necessary so that clang++ will work correctly.
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700892
Alex Klein1699fab2022-09-08 08:46:06 -0600893 The calling sequence now becomes:
894 -) clang++ invocation turns into clang++-3.9 (which is a copy of clang-3.9,
895 the Ldsowrapper).
896 -) clang++-3.9 uses the Ldso to invoke clang++-3.9.elf, which is a symlink
897 to the original clang-3.9 elf.
898 -) The difference this time is that inside the elf file execution, $0 is
899 set as .../usr/bin/clang++-3.9.elf, which contains 'clang++' in the name.
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700900
Alex Klein1699fab2022-09-08 08:46:06 -0600901 Update: Starting since clang 7, the clang and clang++ are symlinks to
902 clang-7 binary, not clang-7.0. The pattern match is extended to handle
903 both clang-7 and clang-7.0 cases for now. (https://crbug.com/837889)
Manoj Guptaae268142018-04-27 23:28:36 -0700904
Alex Klein1699fab2022-09-08 08:46:06 -0600905 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700906 root: The root tree to generate scripts / symlinks inside of
907 path: The target elf for which LdsoWrapper was created
Alex Klein1699fab2022-09-08 08:46:06 -0600908 """
909 if re.match(r"/usr/bin/clang-\d+(\.\d+)*$", path):
910 logging.info("fixing clang++ invocation for %s", path)
911 clangdir = os.path.dirname(root + path)
912 clang = os.path.basename(path)
913 clangxx = clang.replace("clang", "clang++")
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700914
Alex Klein1699fab2022-09-08 08:46:06 -0600915 # Create a symlink clang++-X.Y.elf to point to clang-X.Y.elf
916 os.symlink(clang + ".elf", os.path.join(clangdir, clangxx + ".elf"))
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700917
Alex Klein1699fab2022-09-08 08:46:06 -0600918 # Create a hardlink clang++-X.Y pointing to clang-X.Y
919 os.link(os.path.join(clangdir, clang), os.path.join(clangdir, clangxx))
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700920
Alex Klein1699fab2022-09-08 08:46:06 -0600921 # Adjust the clang++ symlink to point to clang++-X.Y
922 os.unlink(os.path.join(clangdir, "clang++"))
923 os.symlink(clangxx, os.path.join(clangdir, "clang++"))
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700924
925
Mike Frysinger35247af2012-11-16 18:58:06 -0500926def FileIsCrosSdkElf(elf):
Alex Klein1699fab2022-09-08 08:46:06 -0600927 """Determine if |elf| is an ELF that we execute in the cros_sdk
Mike Frysinger35247af2012-11-16 18:58:06 -0500928
Alex Klein1699fab2022-09-08 08:46:06 -0600929 We don't need this to be perfect, just quick. It makes sure the ELF
930 is a 64bit LSB x86_64 ELF. That is the native type of cros_sdk.
Mike Frysinger35247af2012-11-16 18:58:06 -0500931
Alex Klein1699fab2022-09-08 08:46:06 -0600932 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700933 elf: The file to check
Mike Frysinger1a736a82013-12-12 01:50:59 -0500934
Alex Klein1699fab2022-09-08 08:46:06 -0600935 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700936 True if we think |elf| is a native ELF
Alex Klein1699fab2022-09-08 08:46:06 -0600937 """
938 with open(elf, "rb") as f:
939 data = f.read(20)
940 # Check the magic number, EI_CLASS, EI_DATA, and e_machine.
941 return (
942 data[0:4] == b"\x7fELF"
943 and data[4:5] == b"\x02"
944 and data[5:6] == b"\x01"
945 and data[18:19] == b"\x3e"
946 )
Mike Frysinger35247af2012-11-16 18:58:06 -0500947
948
949def IsPathPackagable(ptype, path):
Alex Klein1699fab2022-09-08 08:46:06 -0600950 """Should the specified file be included in a toolchain package?
Mike Frysinger35247af2012-11-16 18:58:06 -0500951
Alex Klein1699fab2022-09-08 08:46:06 -0600952 We only need to handle files as we'll create dirs as we need them.
Mike Frysinger35247af2012-11-16 18:58:06 -0500953
Alex Klein1699fab2022-09-08 08:46:06 -0600954 Further, trim files that won't be useful:
955 - non-english translations (.mo) since it'd require env vars
956 - debug files since these are for the host compiler itself
957 - info/man pages as they're big, and docs are online, and the
958 native docs should work fine for the most part (`man gcc`)
Mike Frysinger35247af2012-11-16 18:58:06 -0500959
Alex Klein1699fab2022-09-08 08:46:06 -0600960 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700961 ptype: A string describing the path type (i.e. 'file' or 'dir' or 'sym')
962 path: The full path to inspect
Mike Frysinger1a736a82013-12-12 01:50:59 -0500963
Alex Klein1699fab2022-09-08 08:46:06 -0600964 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700965 True if we want to include this path in the package
Alex Klein1699fab2022-09-08 08:46:06 -0600966 """
967 return not (
968 ptype in ("dir",)
969 or path.startswith("/usr/lib/debug/")
970 or os.path.splitext(path)[1] == ".mo"
971 or ("/man/" in path or "/info/" in path)
972 )
Mike Frysinger35247af2012-11-16 18:58:06 -0500973
974
975def ReadlinkRoot(path, root):
Alex Klein1699fab2022-09-08 08:46:06 -0600976 """Like os.readlink(), but relative to a |root|
Mike Frysinger35247af2012-11-16 18:58:06 -0500977
Alex Klein1699fab2022-09-08 08:46:06 -0600978 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700979 path: The symlink to read
980 root: The path to use for resolving absolute symlinks
Mike Frysinger1a736a82013-12-12 01:50:59 -0500981
Alex Klein1699fab2022-09-08 08:46:06 -0600982 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700983 A fully resolved symlink path
Alex Klein1699fab2022-09-08 08:46:06 -0600984 """
985 while os.path.islink(root + path):
986 path = os.path.join(os.path.dirname(path), os.readlink(root + path))
987 return path
Mike Frysinger35247af2012-11-16 18:58:06 -0500988
989
Alex Klein1699fab2022-09-08 08:46:06 -0600990def _GetFilesForTarget(target, root="/"):
991 """Locate all the files to package for |target|
Mike Frysinger35247af2012-11-16 18:58:06 -0500992
Alex Klein1699fab2022-09-08 08:46:06 -0600993 This does not cover ELF dependencies.
Mike Frysinger35247af2012-11-16 18:58:06 -0500994
Alex Klein1699fab2022-09-08 08:46:06 -0600995 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700996 target: The toolchain target name
997 root: The root path to pull all packages from
Mike Frysinger1a736a82013-12-12 01:50:59 -0500998
Alex Klein1699fab2022-09-08 08:46:06 -0600999 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -07001000 A tuple of a set of all packable paths, and a set of all paths which
1001 are also native ELFs
Alex Klein1699fab2022-09-08 08:46:06 -06001002 """
1003 paths = set()
1004 elfs = set()
Mike Frysinger35247af2012-11-16 18:58:06 -05001005
Alex Klein1699fab2022-09-08 08:46:06 -06001006 # Find all the files owned by the packages for this target.
1007 for pkg in GetTargetPackages(target):
Alex Klein1699fab2022-09-08 08:46:06 -06001008 # Skip Go compiler from redistributable packages.
1009 # The "go" executable has GOROOT=/usr/lib/go/${CTARGET} hardcoded
1010 # into it. Due to this, the toolchain cannot be unpacked anywhere
1011 # else and be readily useful. To enable packaging Go, we need to:
1012 # -) Tweak the wrappers/environment to override GOROOT
1013 # automatically based on the unpack location.
1014 # -) Make sure the ELF dependency checking and wrapping logic
1015 # below skips the Go toolchain executables and libraries.
1016 # -) Make sure the packaging process maintains the relative
1017 # timestamps of precompiled standard library packages.
1018 # (see dev-lang/go ebuild for details).
1019 if pkg == "ex_go":
1020 continue
Rahul Chaudhry4b803052015-05-13 15:25:56 -07001021
Alex Klein1699fab2022-09-08 08:46:06 -06001022 # Use armv7a-cros-linux-gnueabi/compiler-rt for
Alex Klein0e92b2c2023-01-13 11:54:15 -07001023 # armv7a-cros-linux-gnueabihf/compiler-rt. Currently the
1024 # armv7a-cros-linux-gnueabi is actually the same as
1025 # armv7a-cros-linux-gnueabihf with different names. Because of that, for
1026 # compiler-rt, it generates the same binary in the same location. To
1027 # avoid the installation conflict, we do not install anything for
1028 # 'armv7a-cros-linux-gnueabihf'. This would cause problem if other
1029 # people try to use standalone armv7a-cros-linux-gnueabihf toolchain.
Alex Klein1699fab2022-09-08 08:46:06 -06001030 if "compiler-rt" in pkg and "armv7a-cros-linux-gnueabi" in target:
1031 atom = GetPortagePackage(target, pkg)
1032 cat, pn = atom.split("/")
1033 ver = GetInstalledPackageVersions(atom, root=root)[0]
1034 dblink = portage.dblink(
1035 cat, pn + "-" + ver, myroot=root, settings=portage.settings
1036 )
1037 contents = dblink.getcontents()
1038 if not contents:
1039 if "hf" in target:
1040 new_target = "armv7a-cros-linux-gnueabi"
1041 else:
1042 new_target = "armv7a-cros-linux-gnueabihf"
1043 atom = GetPortagePackage(new_target, pkg)
Yunlian Jiang36f35242018-04-27 10:18:40 -07001044 else:
Alex Klein1699fab2022-09-08 08:46:06 -06001045 atom = GetPortagePackage(target, pkg)
Yunlian Jiang36f35242018-04-27 10:18:40 -07001046
Alex Klein1699fab2022-09-08 08:46:06 -06001047 cat, pn = atom.split("/")
1048 ver = GetInstalledPackageVersions(atom, root=root)[0]
1049 logging.info("packaging %s-%s", atom, ver)
Mike Frysinger35247af2012-11-16 18:58:06 -05001050
Alex Klein1699fab2022-09-08 08:46:06 -06001051 dblink = portage.dblink(
1052 cat, pn + "-" + ver, myroot=root, settings=portage.settings
1053 )
1054 contents = dblink.getcontents()
1055 for obj in contents:
1056 ptype = contents[obj][0]
1057 if not IsPathPackagable(ptype, obj):
1058 continue
Mike Frysinger35247af2012-11-16 18:58:06 -05001059
Alex Klein1699fab2022-09-08 08:46:06 -06001060 if ptype == "obj":
1061 # For native ELFs, we need to pull in their dependencies too.
1062 if FileIsCrosSdkElf(obj):
1063 logging.debug("Adding ELF %s", obj)
1064 elfs.add(obj)
1065 logging.debug("Adding path %s", obj)
1066 paths.add(obj)
Mike Frysinger35247af2012-11-16 18:58:06 -05001067
Alex Klein1699fab2022-09-08 08:46:06 -06001068 return paths, elfs
Mike Frysinger35247af2012-11-16 18:58:06 -05001069
1070
Alex Klein1699fab2022-09-08 08:46:06 -06001071def _BuildInitialPackageRoot(
1072 output_dir, paths, elfs, ldpaths, path_rewrite_func=lambda x: x, root="/"
1073):
1074 """Link in all packable files and their runtime dependencies
Mike Frysinger35247af2012-11-16 18:58:06 -05001075
Alex Klein1699fab2022-09-08 08:46:06 -06001076 This also wraps up executable ELFs with helper scripts.
Mike Frysinger35247af2012-11-16 18:58:06 -05001077
Alex Klein1699fab2022-09-08 08:46:06 -06001078 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -07001079 output_dir: The output directory to store files
1080 paths: All the files to include
1081 elfs: All the files which are ELFs (a subset of |paths|)
1082 ldpaths: A dict of static ldpath information
1083 path_rewrite_func: User callback to rewrite paths in output_dir
1084 root: The root path to pull all packages/files from
Alex Klein1699fab2022-09-08 08:46:06 -06001085 """
1086 # Link in all the files.
1087 sym_paths = {}
1088 for path in paths:
1089 new_path = path_rewrite_func(path)
1090 logging.debug("Transformed %s to %s", path, new_path)
1091 dst = output_dir + new_path
1092 osutils.SafeMakedirs(os.path.dirname(dst))
Mike Frysinger35247af2012-11-16 18:58:06 -05001093
Alex Klein1699fab2022-09-08 08:46:06 -06001094 # Is this a symlink which we have to rewrite or wrap?
1095 # Delay wrap check until after we have created all paths.
1096 src = root + path
1097 if os.path.islink(src):
1098 tgt = os.readlink(src)
1099 if os.path.sep in tgt:
1100 sym_paths[lddtree.normpath(ReadlinkRoot(src, root))] = new_path
Mike Frysinger35247af2012-11-16 18:58:06 -05001101
Alex Klein0e92b2c2023-01-13 11:54:15 -07001102 # Rewrite absolute links to relative and then generate the
1103 # symlink ourselves. All other symlinks can be hardlinked below.
Alex Klein1699fab2022-09-08 08:46:06 -06001104 if tgt[0] == "/":
1105 tgt = os.path.relpath(tgt, os.path.dirname(new_path))
1106 os.symlink(tgt, dst)
1107 continue
Mike Frysinger35247af2012-11-16 18:58:06 -05001108
Alex Klein1699fab2022-09-08 08:46:06 -06001109 logging.debug("Linking path %s -> %s", src, dst)
1110 os.link(src, dst)
Mike Frysinger35247af2012-11-16 18:58:06 -05001111
Alex Klein1699fab2022-09-08 08:46:06 -06001112 # Locate all the dependencies for all the ELFs. Stick them all in the
1113 # top level "lib" dir to make the wrapper simpler. This exact path does
1114 # not matter since we execute ldso directly, and we tell the ldso the
1115 # exact path to search for its libraries.
1116 libdir = os.path.join(output_dir, "lib")
1117 osutils.SafeMakedirs(libdir)
1118 donelibs = set()
1119 basenamelibs = set()
Manoj Gupta98e674d2022-10-05 00:31:41 +00001120 glibc_re = re.compile(r"/lib(c|pthread)[0-9.-]*\.so[0-9.-]*")
Alex Klein1699fab2022-09-08 08:46:06 -06001121 for elf in elfs:
1122 e = lddtree.ParseELF(elf, root=root, ldpaths=ldpaths)
1123 logging.debug("Parsed elf %s data: %s", elf, e)
1124 interp = e["interp"]
Mike Frysinger221bd822017-09-29 02:51:47 -04001125
Alex Klein0e92b2c2023-01-13 11:54:15 -07001126 # TODO(b/187786323): Drop this hack once libopcodes linkage is fixed.
Alex Klein1699fab2022-09-08 08:46:06 -06001127 if os.path.basename(elf).startswith("libopcodes-"):
1128 continue
Mike Frysinger35247af2012-11-16 18:58:06 -05001129
Alex Klein0e92b2c2023-01-13 11:54:15 -07001130 # Copy all the dependencies before we copy the program & generate
1131 # wrappers.
Alex Klein1699fab2022-09-08 08:46:06 -06001132 for lib, lib_data in e["libs"].items():
1133 src = path = lib_data["path"]
1134 if path is None:
1135 logging.warning("%s: could not locate %s", elf, lib)
1136 continue
Mike Frysinger9fe02342019-12-12 17:52:53 -05001137
Alex Klein1699fab2022-09-08 08:46:06 -06001138 # No need to try and copy the same source lib multiple times.
1139 if path in donelibs:
1140 continue
1141 donelibs.add(path)
Mike Frysinger9fe02342019-12-12 17:52:53 -05001142
Alex Klein0e92b2c2023-01-13 11:54:15 -07001143 # Die if we try to normalize different source libs with the same
1144 # basename.
Alex Klein1699fab2022-09-08 08:46:06 -06001145 if lib in basenamelibs:
1146 logging.error(
1147 "Multiple sources detected for %s:\n new: %s\n old: %s",
1148 os.path.join("/lib", lib),
1149 path,
1150 " ".join(
1151 x
1152 for x in donelibs
1153 if x != path and os.path.basename(x) == lib
1154 ),
1155 )
1156 # TODO(crbug.com/917193): Make this fatal.
1157 # cros_build_lib.Die('Unable to resolve lib conflicts')
1158 continue
1159 basenamelibs.add(lib)
Mike Frysinger35247af2012-11-16 18:58:06 -05001160
Alex Klein1699fab2022-09-08 08:46:06 -06001161 # Needed libs are the SONAME, but that is usually a symlink, not a
1162 # real file. So link in the target rather than the symlink itself.
1163 # We have to walk all the possible symlinks (SONAME could point to a
1164 # symlink which points to a symlink), and we have to handle absolute
1165 # ourselves (since we have a "root" argument).
1166 dst = os.path.join(libdir, os.path.basename(path))
1167 src = ReadlinkRoot(src, root)
Mike Frysinger35247af2012-11-16 18:58:06 -05001168
Alex Klein1699fab2022-09-08 08:46:06 -06001169 logging.debug("Linking lib %s -> %s", root + src, dst)
1170 os.link(root + src, dst)
Mike Frysinger35247af2012-11-16 18:58:06 -05001171
Alex Klein1699fab2022-09-08 08:46:06 -06001172 # Do not create wrapper for libc. crbug.com/766827
1173 if interp and not glibc_re.search(elf):
1174 # Generate a wrapper if it is executable.
1175 interp = os.path.join("/lib", os.path.basename(interp))
1176 lddtree.GenerateLdsoWrapper(
1177 output_dir,
1178 path_rewrite_func(elf),
1179 interp,
1180 libpaths=e["rpath"] + e["runpath"],
1181 )
1182 FixClangXXWrapper(output_dir, path_rewrite_func(elf))
Mike Frysinger00b129f2021-04-21 18:11:48 -04001183
Alex Klein1699fab2022-09-08 08:46:06 -06001184 # Wrap any symlinks to the wrapper.
1185 if elf in sym_paths:
1186 link = sym_paths[elf]
1187 GeneratePathWrapper(output_dir, link, elf)
Mike Frysinger00b129f2021-04-21 18:11:48 -04001188
Mike Frysinger35247af2012-11-16 18:58:06 -05001189
1190def _EnvdGetVar(envd, var):
Alex Klein1699fab2022-09-08 08:46:06 -06001191 """Given a Gentoo env.d file, extract a var from it
Mike Frysinger35247af2012-11-16 18:58:06 -05001192
Alex Klein1699fab2022-09-08 08:46:06 -06001193 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -07001194 envd: The env.d file to load (may be a glob path)
1195 var: The var to extract
Mike Frysinger1a736a82013-12-12 01:50:59 -05001196
Alex Klein1699fab2022-09-08 08:46:06 -06001197 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -07001198 The value of |var|
Alex Klein1699fab2022-09-08 08:46:06 -06001199 """
1200 envds = glob.glob(envd)
1201 assert len(envds) == 1, "%s: should have exactly 1 env.d file" % envd
1202 envd = envds[0]
1203 return key_value_store.LoadFile(envd)[var]
Mike Frysinger35247af2012-11-16 18:58:06 -05001204
1205
1206def _ProcessBinutilsConfig(target, output_dir):
Alex Klein1699fab2022-09-08 08:46:06 -06001207 """Do what binutils-config would have done"""
1208 binpath = os.path.join("/bin", target + "-")
Mike Frysingerd4d40fd2014-11-06 17:30:57 -05001209
Alex Klein1699fab2022-09-08 08:46:06 -06001210 # Locate the bin dir holding the linker and perform some confidence checks
1211 binutils_bin_path = os.path.join(
1212 output_dir, "usr", toolchain.GetHostTuple(), target, "binutils-bin"
1213 )
1214 globpath = os.path.join(binutils_bin_path, "*")
1215 srcpath = glob.glob(globpath)
1216 assert len(srcpath) == 1, (
1217 "%s: matched more than one path. Is Gold enabled?" % globpath
1218 )
1219 srcpath = srcpath[0]
1220 ld_path = os.path.join(srcpath, "ld")
1221 assert os.path.exists(ld_path), "%s: linker is missing!" % ld_path
1222 ld_path = os.path.join(srcpath, "ld.bfd")
1223 assert os.path.exists(ld_path), "%s: linker is missing!" % ld_path
Rahul Chaudhry4891b4d2017-03-08 10:31:27 -08001224
Alex Klein1699fab2022-09-08 08:46:06 -06001225 srcpath = srcpath[len(output_dir) :]
1226 gccpath = os.path.join("/usr", "libexec", "gcc")
1227 for prog in os.listdir(output_dir + srcpath):
1228 # Skip binaries already wrapped.
1229 if not prog.endswith(".real"):
1230 GeneratePathWrapper(
1231 output_dir, binpath + prog, os.path.join(srcpath, prog)
1232 )
1233 GeneratePathWrapper(
1234 output_dir,
1235 os.path.join(gccpath, prog),
1236 os.path.join(srcpath, prog),
1237 )
Mike Frysinger35247af2012-11-16 18:58:06 -05001238
Alex Klein1699fab2022-09-08 08:46:06 -06001239 libpath = os.path.join("/usr", toolchain.GetHostTuple(), target, "lib")
1240 envd = os.path.join(output_dir, "etc", "env.d", "binutils", "*")
1241 srcpath = _EnvdGetVar(envd, "LIBPATH")
1242 os.symlink(
1243 os.path.relpath(srcpath, os.path.dirname(libpath)), output_dir + libpath
1244 )
Mike Frysinger35247af2012-11-16 18:58:06 -05001245
1246
1247def _ProcessGccConfig(target, output_dir):
Alex Klein1699fab2022-09-08 08:46:06 -06001248 """Do what gcc-config would have done"""
1249 binpath = "/bin"
1250 envd = os.path.join(output_dir, "etc", "env.d", "gcc", "*")
1251 srcpath = _EnvdGetVar(envd, "GCC_PATH")
1252 for prog in os.listdir(output_dir + srcpath):
1253 # Skip binaries already wrapped.
1254 if (
1255 not prog.endswith(".real")
1256 and not prog.endswith(".elf")
1257 and prog.startswith(target)
1258 ):
1259 GeneratePathWrapper(
1260 output_dir,
1261 os.path.join(binpath, prog),
1262 os.path.join(srcpath, prog),
1263 )
1264 return srcpath
Mike Frysinger35247af2012-11-16 18:58:06 -05001265
1266
Frank Henigman179ec7c2015-02-06 03:01:09 -05001267def _ProcessSysrootWrappers(_target, output_dir, srcpath):
Alex Klein1699fab2022-09-08 08:46:06 -06001268 """Remove chroot-specific things from our sysroot wrappers"""
1269 # Disable ccache since we know it won't work outside of chroot.
Tobias Boschddd16492019-08-14 09:29:54 -07001270
Alex Klein1699fab2022-09-08 08:46:06 -06001271 # Use the version of the wrapper that does not use ccache.
1272 for sysroot_wrapper in glob.glob(
1273 os.path.join(output_dir + srcpath, "sysroot_wrapper*.ccache")
1274 ):
1275 # Can't update the wrapper in place to not affect the chroot,
1276 # but only the extracted toolchain.
1277 os.unlink(sysroot_wrapper)
1278 shutil.copy(sysroot_wrapper[:-6] + "noccache", sysroot_wrapper)
1279 shutil.copy(
1280 sysroot_wrapper[:-6] + "noccache.elf", sysroot_wrapper + ".elf"
1281 )
Mike Frysinger35247af2012-11-16 18:58:06 -05001282
1283
Manoj Gupta61bf9db2020-03-23 21:28:04 -07001284def _ProcessClangWrappers(target, output_dir):
Alex Klein1699fab2022-09-08 08:46:06 -06001285 """Remove chroot-specific things from our sysroot wrappers"""
1286 clang_bin_path = "/usr/bin"
1287 # Disable ccache from clang wrappers.
1288 _ProcessSysrootWrappers(target, output_dir, clang_bin_path)
1289 GeneratePathWrapper(
1290 output_dir, f"/bin/{target}-clang", f"/usr/bin/{target}-clang"
1291 )
1292 GeneratePathWrapper(
1293 output_dir, f"/bin/{target}-clang++", f"/usr/bin/{target}-clang++"
1294 )
Manoj Gupta61bf9db2020-03-23 21:28:04 -07001295
1296
Yunlian Jiang5ad6b512017-09-20 09:27:45 -07001297def _CreateMainLibDir(target, output_dir):
Alex Klein1699fab2022-09-08 08:46:06 -06001298 """Create some lib dirs so that compiler can get the right Gcc paths"""
1299 osutils.SafeMakedirs(os.path.join(output_dir, "usr", target, "lib"))
1300 osutils.SafeMakedirs(os.path.join(output_dir, "usr", target, "usr/lib"))
Yunlian Jiang5ad6b512017-09-20 09:27:45 -07001301
1302
Manoj Guptadf8b3872022-01-13 11:57:36 -08001303def _CreateRemoteToolchainFile(output_dir):
Alex Klein1699fab2022-09-08 08:46:06 -06001304 """Create a remote_toolchain_inputs file for reclient/RBE"""
1305 # The inputs file lists all files/shared libraries needed to run clang.
1306 # All inputs are relative to location of clang binary and one input
1307 # location per line of file e.g.
1308 # clang-13.elf
1309 # clang++-13.elf
1310 # relative/path/to/clang/resource/directory
Manoj Guptadf8b3872022-01-13 11:57:36 -08001311
Alex Klein1699fab2022-09-08 08:46:06 -06001312 clang_path = os.path.join(output_dir, "usr/bin")
1313 # Add needed shared libraries and internal files e.g. allowlists.
1314 toolchain_inputs = ["../../lib"]
1315 clang_shared_dirs = glob.glob(
1316 os.path.join(output_dir, "usr/lib64/clang/*/share")
1317 )
1318 for clang_dir in clang_shared_dirs:
1319 toolchain_inputs.append(os.path.relpath(clang_dir, clang_path))
Manoj Guptadf8b3872022-01-13 11:57:36 -08001320
Alex Klein1699fab2022-09-08 08:46:06 -06001321 # Add actual clang binaries/wrappers.
1322 for clang_files in glob.glob(os.path.join(clang_path, "clang*-[0-9]*")):
1323 toolchain_inputs.append(os.path.basename(clang_files))
Manoj Guptadf8b3872022-01-13 11:57:36 -08001324
Mike Frysinger31fdddd2023-02-24 15:50:55 -05001325 with open(
1326 os.path.join(clang_path, "remote_toolchain_inputs"),
1327 "w",
1328 encoding="utf-8",
1329 ) as f:
Alex Klein1699fab2022-09-08 08:46:06 -06001330 f.writelines("%s\n" % line for line in toolchain_inputs)
Manoj Guptadf8b3872022-01-13 11:57:36 -08001331
1332
Mike Frysinger35247af2012-11-16 18:58:06 -05001333def _ProcessDistroCleanups(target, output_dir):
Alex Klein1699fab2022-09-08 08:46:06 -06001334 """Clean up the tree and remove all distro-specific requirements
Mike Frysinger35247af2012-11-16 18:58:06 -05001335
Alex Klein1699fab2022-09-08 08:46:06 -06001336 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -07001337 target: The toolchain target name
1338 output_dir: The output directory to clean up
Han Shen699ea192016-03-02 10:42:47 -08001339 """
Alex Klein1699fab2022-09-08 08:46:06 -06001340 _ProcessBinutilsConfig(target, output_dir)
1341 gcc_path = _ProcessGccConfig(target, output_dir)
1342 _ProcessSysrootWrappers(target, output_dir, gcc_path)
1343 _ProcessClangWrappers(target, output_dir)
1344 _CreateMainLibDir(target, output_dir)
1345 _CreateRemoteToolchainFile(output_dir)
Mike Frysinger35247af2012-11-16 18:58:06 -05001346
Alex Klein1699fab2022-09-08 08:46:06 -06001347 osutils.RmDir(os.path.join(output_dir, "etc"))
Mike Frysinger35247af2012-11-16 18:58:06 -05001348
1349
Alex Klein1699fab2022-09-08 08:46:06 -06001350def CreatePackagableRoot(target, output_dir, ldpaths, root="/"):
1351 """Setup a tree from the packages for the specified target
Mike Frysinger35247af2012-11-16 18:58:06 -05001352
Alex Klein1699fab2022-09-08 08:46:06 -06001353 This populates a path with all the files from toolchain packages so that
1354 a tarball can easily be generated from the result.
Mike Frysinger35247af2012-11-16 18:58:06 -05001355
Alex Klein1699fab2022-09-08 08:46:06 -06001356 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -07001357 target: The target to create a packagable root from
1358 output_dir: The output directory to place all the files
1359 ldpaths: A dict of static ldpath information
1360 root: The root path to pull all packages/files from
Alex Klein1699fab2022-09-08 08:46:06 -06001361 """
1362 # Find all the files owned by the packages for this target.
1363 paths, elfs = _GetFilesForTarget(target, root=root)
Mike Frysinger35247af2012-11-16 18:58:06 -05001364
Alex Klein1699fab2022-09-08 08:46:06 -06001365 # Link in all the package's files, any ELF dependencies, and wrap any
1366 # executable ELFs with helper scripts.
1367 def MoveUsrBinToBin(path):
1368 """Move /usr/bin to /bin so people can just use that toplevel dir
Mike Frysinger35247af2012-11-16 18:58:06 -05001369
Alex Klein0e92b2c2023-01-13 11:54:15 -07001370 Note we do not apply this to clang or rust; there is correlation between
Alex Klein1699fab2022-09-08 08:46:06 -06001371 clang's search path for libraries / inclusion and its installation path.
1372 """
1373 NO_MOVE_PATTERNS = ("clang", "rust", "cargo", "sysroot_wrapper")
1374 if path.startswith("/usr/bin/") and not any(
1375 x in path for x in NO_MOVE_PATTERNS
1376 ):
1377 return path[4:]
1378 return path
Mike Frysinger221bd822017-09-29 02:51:47 -04001379
Alex Klein1699fab2022-09-08 08:46:06 -06001380 _BuildInitialPackageRoot(
1381 output_dir,
1382 paths,
1383 elfs,
1384 ldpaths,
1385 path_rewrite_func=MoveUsrBinToBin,
1386 root=root,
1387 )
Mike Frysinger35247af2012-11-16 18:58:06 -05001388
Alex Klein1699fab2022-09-08 08:46:06 -06001389 # The packages, when part of the normal distro, have helper scripts
1390 # that setup paths and such. Since we are making this standalone, we
1391 # need to preprocess all that ourselves.
1392 _ProcessDistroCleanups(target, output_dir)
1393
1394
1395def CreatePackages(targets_wanted, output_dir, root="/"):
1396 """Create redistributable cross-compiler packages for the specified targets
1397
1398 This creates toolchain packages that should be usable in conjunction with
1399 a downloaded sysroot (created elsewhere).
1400
1401 Tarballs (one per target) will be created in $PWD.
1402
1403 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -07001404 targets_wanted: The targets to package up.
1405 output_dir: The directory to put the packages in.
1406 root: The root path to pull all packages/files from.
Alex Klein1699fab2022-09-08 08:46:06 -06001407 """
1408 logging.info("Writing tarballs to %s", output_dir)
1409 osutils.SafeMakedirs(output_dir)
1410 ldpaths = lddtree.LoadLdpaths(root)
1411 targets = ExpandTargets(targets_wanted)
1412
1413 with osutils.TempDir(prefix="create-packages") as tempdir:
1414 logging.debug("Using tempdir: %s", tempdir)
1415
Alex Klein0e92b2c2023-01-13 11:54:15 -07001416 # We have to split the root generation from the compression stages.
1417 # This is because we hardlink in all the files (to avoid overhead of
1418 # reading/writing the copies multiple times). But tar gets angry if a
1419 # file's hardlink count changes from when it starts reading a file to
1420 # when it finishes.
Alex Klein1699fab2022-09-08 08:46:06 -06001421 with parallel.BackgroundTaskRunner(CreatePackagableRoot) as queue:
1422 for target in targets:
1423 output_target_dir = os.path.join(tempdir, target)
1424 queue.put([target, output_target_dir, ldpaths, root])
1425
1426 # Build the tarball.
1427 with parallel.BackgroundTaskRunner(
1428 cros_build_lib.CreateTarball
1429 ) as queue:
1430 for target in targets:
1431 tar_file = os.path.join(output_dir, target + ".tar.xz")
1432 queue.put([tar_file, os.path.join(tempdir, target)])
Mike Frysinger35247af2012-11-16 18:58:06 -05001433
1434
Mike Frysinger07534cf2017-09-12 17:40:21 -04001435def GetParser():
Alex Klein1699fab2022-09-08 08:46:06 -06001436 """Return a command line parser."""
1437 parser = commandline.ArgumentParser(description=__doc__)
1438 parser.add_argument(
1439 "-u",
1440 "--nousepkg",
1441 action="store_false",
1442 dest="usepkg",
1443 default=True,
1444 help="Do not use prebuilt packages",
1445 )
1446 parser.add_argument(
1447 "-d",
1448 "--deleteold",
1449 action="store_true",
1450 dest="deleteold",
1451 default=False,
1452 help="Unmerge deprecated packages",
1453 )
1454 parser.add_argument(
1455 "-t",
1456 "--targets",
1457 dest="targets",
1458 default="sdk",
1459 help="Comma separated list of tuples. Special keywords "
1460 "'host', 'sdk', 'boards', and 'all' are "
1461 "allowed. Defaults to 'sdk'.",
1462 )
1463 parser.add_argument(
1464 "--include-boards",
1465 default="",
1466 metavar="BOARDS",
1467 help="Comma separated list of boards whose toolchains we "
1468 "will always include. Default: none",
1469 )
1470 parser.add_argument(
1471 "--hostonly",
1472 dest="hostonly",
1473 default=False,
1474 action="store_true",
1475 help="Only setup the host toolchain. "
1476 "Useful for bootstrapping chroot",
1477 )
1478 parser.add_argument(
1479 "--show-board-cfg",
1480 "--show-cfg",
1481 dest="cfg_name",
1482 default=None,
1483 help="Board to list toolchains tuples for",
1484 )
1485 parser.add_argument(
1486 "--show-packages",
1487 default=None,
1488 help="List all packages the specified target uses",
1489 )
1490 parser.add_argument(
1491 "--create-packages",
1492 action="store_true",
1493 default=False,
1494 help="Build redistributable packages",
1495 )
1496 parser.add_argument(
1497 "--output-dir",
1498 default=os.getcwd(),
1499 type="path",
1500 help="Output directory",
1501 )
1502 parser.add_argument(
1503 "--reconfig",
1504 default=False,
1505 action="store_true",
1506 help="Reload crossdev config and reselect toolchains",
1507 )
1508 parser.add_argument(
1509 "--sysroot",
1510 type="path",
1511 help="The sysroot in which to install the toolchains",
1512 )
1513 return parser
Zdenek Behan508dcce2011-12-05 15:39:32 +01001514
Mike Frysinger07534cf2017-09-12 17:40:21 -04001515
1516def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001517 parser = GetParser()
1518 options = parser.parse_args(argv)
1519 options.Freeze()
Zdenek Behan508dcce2011-12-05 15:39:32 +01001520
Alex Klein1699fab2022-09-08 08:46:06 -06001521 # Figure out what we're supposed to do and reject conflicting options.
1522 conflicting_options = (
1523 options.cfg_name,
1524 options.show_packages,
1525 options.create_packages,
1526 )
1527 if sum(bool(x) for x in conflicting_options) > 1:
1528 parser.error(
1529 "conflicting options: create-packages & show-packages & "
1530 "show-board-cfg"
1531 )
Mike Frysinger984d0622012-06-01 16:08:44 -04001532
Alex Klein1699fab2022-09-08 08:46:06 -06001533 targets_wanted = set(options.targets.split(","))
1534 boards_wanted = (
1535 set(options.include_boards.split(","))
1536 if options.include_boards
1537 else set()
1538 )
Mike Frysinger35247af2012-11-16 18:58:06 -05001539
Alex Klein1699fab2022-09-08 08:46:06 -06001540 if options.cfg_name:
1541 ShowConfig(options.cfg_name)
1542 elif options.show_packages is not None:
1543 cros_build_lib.AssertInsideChroot()
1544 target = options.show_packages
1545 Crossdev.Load(False)
1546 for package in GetTargetPackages(target):
1547 print(GetPortagePackage(target, package))
1548 elif options.create_packages:
1549 cros_build_lib.AssertInsideChroot()
1550 Crossdev.Load(False)
1551 CreatePackages(targets_wanted, options.output_dir)
1552 else:
1553 cros_build_lib.AssertInsideChroot()
1554 # This has to be always run as root.
1555 if osutils.IsNonRootUser():
1556 cros_build_lib.Die("this script must be run as root")
Mike Frysinger35247af2012-11-16 18:58:06 -05001557
Alex Klein1699fab2022-09-08 08:46:06 -06001558 Crossdev.Load(options.reconfig)
1559 root = options.sysroot or "/"
1560 UpdateToolchains(
1561 options.usepkg,
1562 options.deleteold,
1563 options.hostonly,
1564 options.reconfig,
1565 targets_wanted,
1566 boards_wanted,
1567 root=root,
1568 )
1569 Crossdev.Save()
Mike Frysinger35247af2012-11-16 18:58:06 -05001570
Alex Klein1699fab2022-09-08 08:46:06 -06001571 return 0