blob: 74b1239802782701d146339c87221583afdd56c1 [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",
Alex Klein1699fab2022-09-08 08:46:06 -060077 "dev-embedded/hps-sdk",
Mike Frysinger785b0c32017-09-13 01:35:59 -040078)
79
80# New packages that we're in the process of adding to the SDK. Since the SDK
81# bot hasn't had a chance to run yet, there are no binary packages available,
82# so we have to list them here and wait. Once it completes, entries here can
83# be removed so they'll end up on bots & dev's systems.
George Burgess IV66e199c2021-05-05 15:38:40 -070084NEW_PACKAGES = ()
Mike Frysinger785b0c32017-09-13 01:35:59 -040085
Rahul Chaudhry4b803052015-05-13 15:25:56 -070086# Enable the Go compiler for these targets.
87TARGET_GO_ENABLED = (
Alex Klein1699fab2022-09-08 08:46:06 -060088 "x86_64-cros-linux-gnu",
89 "armv7a-cros-linux-gnueabi",
90 "armv7a-cros-linux-gnueabihf",
91 "aarch64-cros-linux-gnu",
Rahul Chaudhry4b803052015-05-13 15:25:56 -070092)
Alex Klein1699fab2022-09-08 08:46:06 -060093CROSSDEV_GO_ARGS = ["--ex-pkg", "dev-lang/go"]
Rahul Chaudhry4b803052015-05-13 15:25:56 -070094
Alex Klein1699fab2022-09-08 08:46:06 -060095CROSSDEV_LIBXCRYPT_ARGS = ["--ex-pkg", "sys-libs/libxcrypt"]
Adrian Ratiubf0b9af2022-05-02 14:48:15 +030096
Manoj Gupta1b5642e2017-03-08 16:44:12 -080097# Enable llvm's compiler-rt for these targets.
98TARGET_COMPILER_RT_ENABLED = (
Alex Klein1699fab2022-09-08 08:46:06 -060099 "armv7a-cros-linux-gnueabi",
100 "armv7a-cros-linux-gnueabihf",
101 "aarch64-cros-linux-gnu",
102 "arm-none-eabi",
103 "armv7m-cros-eabi",
Manoj Gupta1b5642e2017-03-08 16:44:12 -0800104)
Alex Klein1699fab2022-09-08 08:46:06 -0600105CROSSDEV_COMPILER_RT_ARGS = ["--ex-pkg", "sys-libs/compiler-rt"]
Manoj Gupta1b5642e2017-03-08 16:44:12 -0800106
Manoj Gupta946abb42017-04-12 14:27:19 -0700107TARGET_LLVM_PKGS_ENABLED = (
Manoj Gupta45c60c02023-05-16 16:29:28 +0000108 "arm-none-eabi",
Alex Klein1699fab2022-09-08 08:46:06 -0600109 "armv7m-cros-eabi",
110 "armv7a-cros-linux-gnueabi",
111 "armv7a-cros-linux-gnueabihf",
112 "aarch64-cros-linux-gnu",
113 "i686-cros-linux-gnu",
114 "x86_64-cros-linux-gnu",
Manoj Gupta946abb42017-04-12 14:27:19 -0700115)
116
117LLVM_PKGS_TABLE = {
Alex Klein1699fab2022-09-08 08:46:06 -0600118 "ex_llvm-libunwind": ["--ex-pkg", "sys-libs/llvm-libunwind"],
119 "ex_libcxx": ["--ex-pkg", "sys-libs/libcxx"],
Manoj Gupta946abb42017-04-12 14:27:19 -0700120}
121
Alex Klein1699fab2022-09-08 08:46:06 -0600122
Alex Klein074f94f2023-06-22 10:32:06 -0600123class Crossdev:
Alex Klein1699fab2022-09-08 08:46:06 -0600124 """Class for interacting with crossdev and caching its output."""
David James66a09c42012-11-05 13:31:38 -0800125
Alex Klein1699fab2022-09-08 08:46:06 -0600126 _CACHE_FILE = os.path.join(CROSSDEV_OVERLAY, ".configured.json")
127 _CACHE = {}
128 # Packages that needs separate handling, in addition to what we have from
129 # crossdev.
130 MANUAL_PKGS = {
Alex Klein1699fab2022-09-08 08:46:06 -0600131 "llvm": "sys-devel",
132 "llvm-libunwind": "sys-libs",
133 "libcxx": "sys-libs",
134 "elfutils": "dev-libs",
George Burgess IV6c298782023-02-14 14:28:04 -0700135 # b/269306499: note that rust and rust-host are shipped as a part of
136 # this tarball on a best-effort basis. If you would like them to be
137 # fully supported (with an SLA), please reach out to
138 # chromeos-toolchain@google.com and chat with us.
139 "rust": "dev-lang",
140 "rust-host": "dev-lang",
Mike Frysinger3ed47722017-08-08 14:59:08 -0400141 }
142
Alex Klein1699fab2022-09-08 08:46:06 -0600143 @classmethod
144 def Load(cls, reconfig):
145 """Load crossdev cache from disk.
Mike Frysinger3ed47722017-08-08 14:59:08 -0400146
Alex Klein1699fab2022-09-08 08:46:06 -0600147 We invalidate the cache when crossdev updates or this script changes.
148 """
149 crossdev_version = GetStablePackageVersion("sys-devel/crossdev", True)
150 # If we run the compiled/cached .pyc file, we'll read/hash that when we
151 # really always want to track the source .py file.
152 script = os.path.abspath(__file__)
153 if script.endswith(".pyc"):
154 script = script[:-1]
155 setup_toolchains_hash = hashlib.md5(
156 osutils.ReadFile(script, mode="rb")
157 ).hexdigest()
Mike Frysinger3ed47722017-08-08 14:59:08 -0400158
Alex Klein1699fab2022-09-08 08:46:06 -0600159 cls._CACHE = {
160 "crossdev_version": crossdev_version,
161 "setup_toolchains_hash": setup_toolchains_hash,
Mike Frysinger66bfde52017-09-12 16:42:57 -0400162 }
Alex Klein1699fab2022-09-08 08:46:06 -0600163
164 logging.debug("cache: checking file: %s", cls._CACHE_FILE)
165 if reconfig:
166 logging.debug("cache: forcing regen due to reconfig")
167 return
168
169 try:
170 file_data = osutils.ReadFile(cls._CACHE_FILE)
171 except IOError as e:
172 if e.errno != errno.ENOENT:
173 logging.warning("cache: reading failed: %s", e)
174 osutils.SafeUnlink(cls._CACHE_FILE)
175 return
176
177 try:
178 data = json.loads(file_data)
179 except ValueError as e:
180 logging.warning("cache: ignoring invalid content: %s", e)
181 return
182
183 if crossdev_version != data.get("crossdev_version"):
184 logging.debug("cache: rebuilding after crossdev upgrade")
185 elif setup_toolchains_hash != data.get("setup_toolchains_hash"):
186 logging.debug(
187 "cache: rebuilding after cros_setup_toolchains change"
188 )
Mike Frysinger785b0c32017-09-13 01:35:59 -0400189 else:
Alex Klein1699fab2022-09-08 08:46:06 -0600190 logging.debug("cache: content is up-to-date!")
191 cls._CACHE = data
Han Shene23782f2016-02-18 12:20:00 -0800192
Alex Klein1699fab2022-09-08 08:46:06 -0600193 @classmethod
194 def Save(cls):
195 """Store crossdev cache on disk."""
196 # Save the cache from the successful run.
Mike Frysinger31fdddd2023-02-24 15:50:55 -0500197 with open(cls._CACHE_FILE, "w", encoding="utf-8") as f:
Alex Klein1699fab2022-09-08 08:46:06 -0600198 json.dump(cls._CACHE, f)
Mike Frysinger66bfde52017-09-12 16:42:57 -0400199
Alex Klein1699fab2022-09-08 08:46:06 -0600200 @classmethod
201 def GetConfig(cls, target):
202 """Returns a map of crossdev provided variables about a tuple."""
203 CACHE_ATTR = "_target_tuple_map"
Han Shene23782f2016-02-18 12:20:00 -0800204
Alex Klein1699fab2022-09-08 08:46:06 -0600205 val = cls._CACHE.setdefault(CACHE_ATTR, {})
206 if not target in val:
207 if target.startswith("host"):
208 conf = {
209 "crosspkgs": [],
210 "target": toolchain.GetHostTuple(),
211 }
212 if target == "host":
213 packages_list = HOST_PACKAGES
214 else:
215 packages_list = HOST_POST_CROSS_PACKAGES
216 manual_pkgs = dict(
217 (pkg, cat)
218 for cat, pkg in [x.split("/") for x in packages_list]
219 )
220 else:
221 # Build the crossdev command.
Jordan R Abrahams-Whiteheadc6363032023-04-06 23:30:50 +0000222 cmd = ["crossdev", "--stable", "--show-target-cfg", "--ex-gdb"]
Alex Klein1699fab2022-09-08 08:46:06 -0600223 # Enable libxcrypt for all linux-gnu targets.
224 if "cros-linux-gnu" in target:
225 cmd.extend(CROSSDEV_LIBXCRYPT_ARGS)
226 if target in TARGET_COMPILER_RT_ENABLED:
227 cmd.extend(CROSSDEV_COMPILER_RT_ARGS)
228 if target in TARGET_LLVM_PKGS_ENABLED:
Trent Apted593c0742023-05-05 03:50:20 +0000229 # TODO(b/236161656): Fix.
230 # pylint: disable-next=consider-using-dict-items
Alex Klein1699fab2022-09-08 08:46:06 -0600231 for pkg in LLVM_PKGS_TABLE:
232 cmd.extend(LLVM_PKGS_TABLE[pkg])
233 if target in TARGET_GO_ENABLED:
234 cmd.extend(CROSSDEV_GO_ARGS)
235 cmd.extend(["-t", target])
236 # Catch output of crossdev.
237 out = cros_build_lib.run(
238 cmd, print_cmd=False, stdout=True, encoding="utf-8"
239 ).stdout.splitlines()
240 # List of tuples split at the first '=', converted into dict.
241 conf = dict(
242 (k, cros_build_lib.ShellUnquote(v))
243 for k, v in (x.split("=", 1) for x in out)
244 )
245 conf["crosspkgs"] = conf["crosspkgs"].split()
Han Shene23782f2016-02-18 12:20:00 -0800246
Alex Klein1699fab2022-09-08 08:46:06 -0600247 manual_pkgs = cls.MANUAL_PKGS
David James66a09c42012-11-05 13:31:38 -0800248
Alex Klein1699fab2022-09-08 08:46:06 -0600249 for pkg, cat in manual_pkgs.items():
250 conf[pkg + "_pn"] = pkg
251 conf[pkg + "_category"] = cat
252 if pkg not in conf["crosspkgs"]:
253 conf["crosspkgs"].append(pkg)
David James66a09c42012-11-05 13:31:38 -0800254
Alex Klein1699fab2022-09-08 08:46:06 -0600255 val[target] = conf
David James66a09c42012-11-05 13:31:38 -0800256
Alex Klein1699fab2022-09-08 08:46:06 -0600257 return val[target]
Manoj Gupta4d016f62021-10-19 16:39:34 -0700258
Alex Klein1699fab2022-09-08 08:46:06 -0600259 @classmethod
260 def UpdateTargets(cls, targets, usepkg, config_only=False):
261 """Calls crossdev to initialize a cross target.
Manoj Gupta4d016f62021-10-19 16:39:34 -0700262
Alex Klein1699fab2022-09-08 08:46:06 -0600263 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700264 targets: The dict of targets to initialize using crossdev.
265 usepkg: Copies the commandline opts.
266 config_only: Just update.
Alex Klein1699fab2022-09-08 08:46:06 -0600267 """
268 configured_targets = cls._CACHE.setdefault("configured_targets", [])
269 started_targets = set()
David James66a09c42012-11-05 13:31:38 -0800270
Alex Klein1699fab2022-09-08 08:46:06 -0600271 # Schedule all of the targets in parallel, and let them run.
272 with parallel.BackgroundTaskRunner(cls._UpdateTarget) as queue:
273 for target_name in targets:
274 # We already started this target in this loop.
275 if target_name in started_targets:
276 continue
277 # The target is already configured.
278 if config_only and target_name in configured_targets:
279 continue
280 queue.put(
281 [target_name, targets[target_name], usepkg, config_only]
282 )
283 started_targets.add(target_name)
David James66a09c42012-11-05 13:31:38 -0800284
Alex Klein1699fab2022-09-08 08:46:06 -0600285 @classmethod
286 def _UpdateTarget(cls, target_name, target, usepkg, config_only):
287 """Calls crossdev to initialize a cross target.
David James66a09c42012-11-05 13:31:38 -0800288
Alex Klein1699fab2022-09-08 08:46:06 -0600289 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700290 target_name: The name of the target to initialize.
291 target: The target info for initializing.
292 usepkg: Copies the commandline opts.
293 config_only: Just update.
Alex Klein1699fab2022-09-08 08:46:06 -0600294 """
295 configured_targets = cls._CACHE.setdefault("configured_targets", [])
Jordan R Abrahams-Whiteheadc6363032023-04-06 23:30:50 +0000296 cmdbase = ["crossdev", "--stable", "--show-fail-log"]
Alex Klein1699fab2022-09-08 08:46:06 -0600297 cmdbase.extend(["--env", "FEATURES=splitdebug"])
298 # Pick stable by default, and override as necessary.
299 cmdbase.extend(["-P", "--oneshot"])
300 if usepkg:
301 cmdbase.extend(
302 ["-P", "--getbinpkg", "-P", "--usepkgonly", "--without-headers"]
303 )
David James66a09c42012-11-05 13:31:38 -0800304
Alex Klein1699fab2022-09-08 08:46:06 -0600305 overlays = " ".join(
306 (CHROMIUMOS_OVERLAY, ECLASS_OVERLAY, STABLE_OVERLAY)
307 )
308 cmdbase.extend(["--overlays", overlays])
309 cmdbase.extend(["--ov-output", CROSSDEV_OVERLAY])
Manoj Gupta4d016f62021-10-19 16:39:34 -0700310
Alex Klein1699fab2022-09-08 08:46:06 -0600311 cmd = cmdbase + ["-t", target_name]
Manoj Gupta4d016f62021-10-19 16:39:34 -0700312
Alex Klein1699fab2022-09-08 08:46:06 -0600313 for pkg in GetTargetPackages(target_name):
314 if pkg == "gdb":
315 # Gdb does not have selectable versions.
316 cmd.append("--ex-gdb")
317 elif pkg == "ex_libxcrypt":
318 cmd.extend(CROSSDEV_LIBXCRYPT_ARGS)
319 elif pkg == "ex_compiler-rt":
320 cmd.extend(CROSSDEV_COMPILER_RT_ARGS)
321 elif pkg == "ex_go":
322 # Go does not have selectable versions.
323 cmd.extend(CROSSDEV_GO_ARGS)
324 elif pkg in LLVM_PKGS_TABLE:
325 cmd.extend(LLVM_PKGS_TABLE[pkg])
326 elif pkg in cls.MANUAL_PKGS:
327 pass
328 else:
329 # The first of the desired versions is the "primary" one.
330 version = GetDesiredPackageVersions(target_name, pkg)[0]
331 cmd.extend(["--%s" % pkg, version])
332
333 cmd.extend(target["crossdev"].split())
334
335 if config_only:
336 # In this case we want to just quietly reinit
337 cmd.append("--init-target")
338 cros_build_lib.run(cmd, print_cmd=False, stdout=True)
339 else:
340 cros_build_lib.run(cmd)
341
342 configured_targets.append(target_name)
David James66a09c42012-11-05 13:31:38 -0800343
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100344
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100345def GetTargetPackages(target):
Alex Klein1699fab2022-09-08 08:46:06 -0600346 """Returns a list of packages for a given target."""
347 conf = Crossdev.GetConfig(target)
348 # Undesired packages are denoted by empty ${pkg}_pn variable.
349 return [x for x in conf["crosspkgs"] if conf.get(x + "_pn")]
Zdenek Behanf4d18a02012-03-22 15:45:05 +0100350
351
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100352# Portage helper functions:
353def GetPortagePackage(target, package):
Alex Klein1699fab2022-09-08 08:46:06 -0600354 """Returns a package name for the given target."""
355 conf = Crossdev.GetConfig(target)
356 # Portage category:
357 if target.startswith("host") or package in Crossdev.MANUAL_PKGS:
358 category = conf[package + "_category"]
359 else:
360 category = conf["category"]
361 # Portage package:
362 pn = conf[package + "_pn"]
363 # Final package name:
364 assert category
365 assert pn
366 return "%s/%s" % (category, pn)
Zdenek Behan4eb6fd22012-03-12 17:00:56 +0100367
368
Gilad Arnold2dab78c2015-05-21 14:43:33 -0700369def PortageTrees(root):
Alex Klein1699fab2022-09-08 08:46:06 -0600370 """Return the portage trees for a given root."""
371 if root == "/":
372 return portage.db["/"]
373 # The portage logic requires the path always end in a slash.
374 root = root.rstrip("/") + "/"
375 return portage.create_trees(target_root=root, config_root=root)[root]
Gilad Arnold2dab78c2015-05-21 14:43:33 -0700376
377
Alex Klein1699fab2022-09-08 08:46:06 -0600378def GetInstalledPackageVersions(atom, root="/"):
379 """Extracts the list of current versions of a target, package pair.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100380
Alex Klein1699fab2022-09-08 08:46:06 -0600381 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700382 atom: The atom to operate on (e.g. sys-devel/gcc)
383 root: The root to check for installed packages.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100384
Alex Klein1699fab2022-09-08 08:46:06 -0600385 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700386 The list of versions of the package currently installed.
Alex Klein1699fab2022-09-08 08:46:06 -0600387 """
388 versions = []
389 for pkg in PortageTrees(root)["vartree"].dbapi.match(atom, use_cache=0):
390 version = portage.versions.cpv_getversion(pkg)
391 versions.append(version)
392 return versions
Zdenek Behan508dcce2011-12-05 15:39:32 +0100393
394
Alex Klein1699fab2022-09-08 08:46:06 -0600395def GetStablePackageVersion(atom, installed, root="/"):
396 """Extracts the current stable version for a given package.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100397
Alex Klein1699fab2022-09-08 08:46:06 -0600398 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700399 atom: The target/package to operate on e.g. i686-cros-linux-gnu/gcc
400 installed: Whether we want installed packages or ebuilds
401 root: The root to use when querying packages.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100402
Alex Klein1699fab2022-09-08 08:46:06 -0600403 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700404 A string containing the latest version.
Alex Klein1699fab2022-09-08 08:46:06 -0600405 """
406 pkgtype = "vartree" if installed else "porttree"
407 cpv = portage.best(
408 PortageTrees(root)[pkgtype].dbapi.match(atom, use_cache=0)
409 )
410 return portage.versions.cpv_getversion(cpv) if cpv else None
Zdenek Behan508dcce2011-12-05 15:39:32 +0100411
412
Alex Klein1699fab2022-09-08 08:46:06 -0600413def VersionListToNumeric(target, package, versions, installed, root="/"):
414 """Resolves keywords in a given version list for a particular package.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100415
Alex Klein1699fab2022-09-08 08:46:06 -0600416 Resolving means replacing PACKAGE_STABLE with the actual number.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100417
Alex Klein1699fab2022-09-08 08:46:06 -0600418 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700419 target: The target to operate on (e.g. i686-cros-linux-gnu)
420 package: The target/package to operate on (e.g. gcc)
421 versions: List of versions to resolve
422 installed: Query installed packages
423 root: The install root to use; ignored if |installed| is False.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100424
Alex Klein1699fab2022-09-08 08:46:06 -0600425 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700426 List of purely numeric versions equivalent to argument
Alex Klein1699fab2022-09-08 08:46:06 -0600427 """
428 resolved = []
429 atom = GetPortagePackage(target, package)
430 if not installed:
431 root = "/"
432 for version in versions:
433 if version == PACKAGE_STABLE:
434 resolved.append(GetStablePackageVersion(atom, installed, root=root))
435 else:
436 resolved.append(version)
437 return resolved
Zdenek Behan508dcce2011-12-05 15:39:32 +0100438
439
440def GetDesiredPackageVersions(target, package):
Alex Klein1699fab2022-09-08 08:46:06 -0600441 """Produces the list of desired versions for each target, package pair.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100442
Alex Klein1699fab2022-09-08 08:46:06 -0600443 The first version in the list is implicitly treated as primary, ie.
444 the version that will be initialized by crossdev and selected.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100445
Alex Klein1699fab2022-09-08 08:46:06 -0600446 If the version is PACKAGE_STABLE, it really means the current version which
447 is emerged by using the package atom with no particular version key.
448 Since crossdev unmasks all packages by default, this will actually
449 mean 'unstable' in most cases.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100450
Alex Klein1699fab2022-09-08 08:46:06 -0600451 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700452 target: The target to operate on (e.g. i686-cros-linux-gnu)
453 package: The target/package to operate on (e.g. gcc)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100454
Alex Klein1699fab2022-09-08 08:46:06 -0600455 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700456 A list composed of either a version string, PACKAGE_STABLE
Alex Klein1699fab2022-09-08 08:46:06 -0600457 """
458 if package in GetTargetPackages(target):
459 return [PACKAGE_STABLE]
460 else:
461 return []
Zdenek Behan508dcce2011-12-05 15:39:32 +0100462
463
464def TargetIsInitialized(target):
Alex Klein1699fab2022-09-08 08:46:06 -0600465 """Verifies if the given list of targets has been correctly initialized.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100466
Alex Klein1699fab2022-09-08 08:46:06 -0600467 This determines whether we have to call crossdev while emerging
468 toolchain packages or can do it using emerge. Emerge is naturally
469 preferred, because all packages can be updated in a single pass.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100470
Alex Klein1699fab2022-09-08 08:46:06 -0600471 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700472 target: The target to operate on (e.g. i686-cros-linux-gnu)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100473
Alex Klein1699fab2022-09-08 08:46:06 -0600474 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700475 True if |target| is completely initialized, else False
Alex Klein1699fab2022-09-08 08:46:06 -0600476 """
477 # Check if packages for the given target all have a proper version.
478 try:
479 for package in GetTargetPackages(target):
480 atom = GetPortagePackage(target, package)
481 # Do we even want this package && is it initialized?
482 if not (
483 GetStablePackageVersion(atom, True)
484 and GetStablePackageVersion(atom, False)
485 ):
486 return False
487 return True
488 except cros_build_lib.RunCommandError:
489 # Fails - The target has likely never been initialized before.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100490 return False
Zdenek Behan508dcce2011-12-05 15:39:32 +0100491
492
493def RemovePackageMask(target):
Alex Klein1699fab2022-09-08 08:46:06 -0600494 """Removes a package.mask file for the given platform.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100495
Alex Klein1699fab2022-09-08 08:46:06 -0600496 The pre-existing package.mask files can mess with the keywords.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100497
Alex Klein1699fab2022-09-08 08:46:06 -0600498 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700499 target: The target to operate on (e.g. i686-cros-linux-gnu)
Alex Klein1699fab2022-09-08 08:46:06 -0600500 """
501 maskfile = os.path.join("/etc/portage/package.mask", "cross-" + target)
502 osutils.SafeUnlink(maskfile)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100503
504
Zdenek Behan508dcce2011-12-05 15:39:32 +0100505# Main functions performing the actual update steps.
Alex Klein1699fab2022-09-08 08:46:06 -0600506def RebuildLibtool(root="/"):
507 """Rebuild libtool as needed
Mike Frysingerc880a962013-11-08 13:59:06 -0500508
Alex Klein1699fab2022-09-08 08:46:06 -0600509 Libtool hardcodes full paths to internal gcc files, so whenever we upgrade
510 gcc, libtool will break. We can't use binary packages either as those will
511 most likely be compiled against the previous version of gcc.
Gilad Arnold2dab78c2015-05-21 14:43:33 -0700512
Alex Klein1699fab2022-09-08 08:46:06 -0600513 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700514 root: The install root where we want libtool rebuilt.
Alex Klein1699fab2022-09-08 08:46:06 -0600515 """
516 needs_update = False
Mike Frysinger31fdddd2023-02-24 15:50:55 -0500517 with open(os.path.join(root, "usr/bin/libtool"), encoding="utf-8") as f:
Alex Klein1699fab2022-09-08 08:46:06 -0600518 for line in f:
519 # Look for a line like:
520 # sys_lib_search_path_spec="..."
521 # It'll be a list of paths and gcc will be one of them.
522 if line.startswith("sys_lib_search_path_spec="):
523 line = line.rstrip()
524 for path in line.split("=", 1)[1].strip('"').split():
525 root_path = os.path.join(root, path.lstrip(os.path.sep))
526 logging.debug("Libtool: checking %s", root_path)
527 if not os.path.exists(root_path):
528 logging.info("Rebuilding libtool after gcc upgrade")
529 logging.info(" %s", line)
530 logging.info(" missing path: %s", path)
531 needs_update = True
532 break
Mike Frysingerc880a962013-11-08 13:59:06 -0500533
Alex Klein1699fab2022-09-08 08:46:06 -0600534 if needs_update:
535 break
Mike Frysingerc880a962013-11-08 13:59:06 -0500536
Alex Klein1699fab2022-09-08 08:46:06 -0600537 if needs_update:
538 cmd = [EMERGE_CMD, "--oneshot"]
539 if root != "/":
540 cmd.extend(["--sysroot=%s" % root, "--root=%s" % root])
541 cmd.append("sys-devel/libtool")
542 cros_build_lib.run(cmd)
543 else:
544 logging.debug("Libtool is up-to-date; no need to rebuild")
Mike Frysingerc880a962013-11-08 13:59:06 -0500545
546
Alex Klein1699fab2022-09-08 08:46:06 -0600547def UpdateTargets(targets, usepkg, root="/"):
548 """Determines which packages need update/unmerge and defers to portage.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100549
Alex Klein1699fab2022-09-08 08:46:06 -0600550 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700551 targets: The list of targets to update
552 usepkg: Copies the commandline option
553 root: The install root in which we want packages updated.
Alex Klein1699fab2022-09-08 08:46:06 -0600554 """
555 # For each target, we do two things. Figure out the list of updates,
556 # and figure out the appropriate keywords/masks. Crossdev will initialize
557 # these, but they need to be regenerated on every update.
558 logging.info("Determining required toolchain updates...")
559 mergemap = {}
560 # Used to keep track of post-cross packages. These are allowed to have
561 # implicit dependencies on toolchain packages, and therefore need to
562 # be built last.
563 post_cross_pkgs = set()
Zdenek Behan508dcce2011-12-05 15:39:32 +0100564 for target in targets:
Alex Klein1699fab2022-09-08 08:46:06 -0600565 is_post_cross_target = target.endswith("-post-cross")
566 logging.debug("Updating target %s", target)
Alex Klein0e92b2c2023-01-13 11:54:15 -0700567 # Record the highest needed version for each target, for masking
568 # purposes.
Alex Klein1699fab2022-09-08 08:46:06 -0600569 RemovePackageMask(target)
570 for package in GetTargetPackages(target):
571 # Portage name for the package
572 logging.debug(" Checking package %s", package)
573 pkg = GetPortagePackage(target, package)
574 current = GetInstalledPackageVersions(pkg, root=root)
575 desired = GetDesiredPackageVersions(target, package)
576 desired_num = VersionListToNumeric(target, package, desired, False)
577 if pkg in NEW_PACKAGES and usepkg:
578 # Skip this binary package (for now).
579 continue
580 mergemap[pkg] = set(desired_num).difference(current)
581 logging.debug(" %s -> %s", current, desired_num)
582 if is_post_cross_target:
583 post_cross_pkgs.add(pkg)
Mike Frysinger785b0c32017-09-13 01:35:59 -0400584
Alex Klein1699fab2022-09-08 08:46:06 -0600585 packages = [pkg for pkg, vers in mergemap.items() if vers]
586 if not packages:
587 logging.info("Nothing to update!")
588 return False
Zdenek Behan508dcce2011-12-05 15:39:32 +0100589
Alex Klein1699fab2022-09-08 08:46:06 -0600590 logging.info("Updating packages:")
591 logging.info("%s", packages)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100592
Alex Klein1699fab2022-09-08 08:46:06 -0600593 cmd = [EMERGE_CMD, "--oneshot", "--update"]
594 if usepkg:
595 cmd.extend(["--getbinpkg", "--usepkgonly"])
596 if root != "/":
597 cmd.extend(["--sysroot=%s" % root, "--root=%s" % root])
Zdenek Behan508dcce2011-12-05 15:39:32 +0100598
Alex Klein1699fab2022-09-08 08:46:06 -0600599 if usepkg:
600 # Since we are not building from source, we can handle
601 # all packages in one go.
602 cmd.extend(packages)
603 cros_build_lib.run(cmd)
604 else:
605 pre_cross_items = [
606 pkg for pkg in packages if pkg not in post_cross_pkgs
607 ]
608 if pre_cross_items:
609 cros_build_lib.run(cmd + pre_cross_items)
610 post_cross_items = [pkg for pkg in packages if pkg in post_cross_pkgs]
611 if post_cross_items:
612 cros_build_lib.run(cmd + post_cross_items)
613 return True
Gilad Arnold2dab78c2015-05-21 14:43:33 -0700614
Alex Klein1699fab2022-09-08 08:46:06 -0600615
616def CleanTargets(targets, root="/"):
617 """Unmerges old packages that are assumed unnecessary.
618
619 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700620 targets: The list of targets to clean up.
621 root: The install root in which we want packages cleaned up.
Alex Klein1699fab2022-09-08 08:46:06 -0600622 """
623 unmergemap = {}
624 for target in targets:
625 logging.debug("Cleaning target %s", target)
626 for package in GetTargetPackages(target):
627 logging.debug(" Cleaning package %s", package)
628 pkg = GetPortagePackage(target, package)
629 current = GetInstalledPackageVersions(pkg, root=root)
630 desired = GetDesiredPackageVersions(target, package)
631 # NOTE: This refers to installed packages (vartree) rather than the
Alex Klein0e92b2c2023-01-13 11:54:15 -0700632 # Portage version (porttree and/or bintree) when determining the
633 # current version. While this isn't the most accurate thing to do,
634 # it is probably a good simple compromise, which should have the
635 # desired result of uninstalling everything but the latest installed
636 # version. In particular, using the bintree (--usebinpkg) requires a
637 # non-trivial binhost sync and is probably more complex than useful.
Alex Klein1699fab2022-09-08 08:46:06 -0600638 desired_num = VersionListToNumeric(target, package, desired, True)
639 if not set(desired_num).issubset(current):
640 logging.warning(
641 "Error detecting stable version for %s, " "skipping clean!",
642 pkg,
643 )
644 return
645 unmergemap[pkg] = set(current).difference(desired_num)
646
647 # Cleaning doesn't care about consistency and rebuilding package.* files.
648 packages = []
649 for pkg, vers in unmergemap.items():
650 packages.extend("=%s-%s" % (pkg, ver) for ver in vers if ver != "9999")
651
652 if packages:
653 logging.info("Cleaning packages:")
654 logging.info("%s", packages)
655 cmd = [EMERGE_CMD, "--unmerge"]
656 if root != "/":
657 cmd.extend(["--sysroot=%s" % root, "--root=%s" % root])
658 cmd.extend(packages)
659 cros_build_lib.run(cmd)
660 else:
661 logging.info("Nothing to clean!")
662
663
664def SelectActiveToolchains(targets, root="/"):
665 """Runs gcc-config and binutils-config to select the desired.
666
667 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700668 targets: The targets to select
669 root: The root where we want to select toolchain versions.
Alex Klein1699fab2022-09-08 08:46:06 -0600670 """
671 for package in ["gcc", "binutils"]:
672 for target in targets:
673 # See if this package is part of this target.
674 if package not in GetTargetPackages(target):
675 logging.debug("%s: %s is not used", target, package)
676 continue
677
678 # Pick the first version in the numbered list as the selected one.
679 desired = GetDesiredPackageVersions(target, package)
680 desired_num = VersionListToNumeric(
681 target, package, desired, True, root=root
682 )
683 desired = desired_num[0]
684 # *-config does not play revisions, strip them, keep just PV.
685 desired = portage.versions.pkgsplit("%s-%s" % (package, desired))[1]
686
687 if target.startswith("host"):
Alex Klein0e92b2c2023-01-13 11:54:15 -0700688 # *-config is the only tool treating host identically (by
689 # tuple).
Alex Klein1699fab2022-09-08 08:46:06 -0600690 target = toolchain.GetHostTuple()
691
692 # And finally, attach target to it.
693 desired = "%s-%s" % (target, desired)
694
695 extra_env = {"CHOST": target}
696 if root != "/":
697 extra_env["ROOT"] = root
698 cmd = ["%s-config" % package, "-c", target]
699 result = cros_build_lib.run(
700 cmd,
701 print_cmd=False,
702 stdout=True,
703 encoding="utf-8",
704 extra_env=extra_env,
705 )
706 current = result.stdout.splitlines()[0]
707
Alex Klein0e92b2c2023-01-13 11:54:15 -0700708 # Do not reconfig when the current is live or nothing needs to be
709 # done.
Alex Klein1699fab2022-09-08 08:46:06 -0600710 extra_env = {"ROOT": root} if root != "/" else None
Alex Klein64930532023-04-17 12:20:52 -0600711 if current not in (desired, "9999"):
Alex Klein1699fab2022-09-08 08:46:06 -0600712 cmd = [package + "-config", desired]
713 cros_build_lib.run(cmd, print_cmd=False, extra_env=extra_env)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100714
715
Mike Frysinger35247af2012-11-16 18:58:06 -0500716def ExpandTargets(targets_wanted):
Alex Klein1699fab2022-09-08 08:46:06 -0600717 """Expand any possible toolchain aliases into full targets
Mike Frysinger35247af2012-11-16 18:58:06 -0500718
Alex Klein1699fab2022-09-08 08:46:06 -0600719 This will expand 'all' and 'sdk' into the respective toolchain tuples.
Mike Frysinger35247af2012-11-16 18:58:06 -0500720
Alex Klein1699fab2022-09-08 08:46:06 -0600721 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700722 targets_wanted: The targets specified by the user.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500723
Alex Klein1699fab2022-09-08 08:46:06 -0600724 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700725 Dictionary of concrete targets and their toolchain tuples.
Alex Klein1699fab2022-09-08 08:46:06 -0600726 """
727 targets_wanted = set(targets_wanted)
728 if targets_wanted == set(["boards"]):
729 # Only pull targets from the included boards.
730 return {}
Gilad Arnold8195b532015-04-07 10:56:30 +0300731
Alex Klein1699fab2022-09-08 08:46:06 -0600732 all_targets = toolchain.GetAllTargets()
733 if targets_wanted == set(["all"]):
734 return all_targets
735 if targets_wanted == set(["sdk"]):
736 # Filter out all the non-sdk toolchains as we don't want to mess
737 # with those in all of our builds.
738 return toolchain.FilterToolchains(all_targets, "sdk", True)
Gilad Arnold8195b532015-04-07 10:56:30 +0300739
Alex Klein1699fab2022-09-08 08:46:06 -0600740 # Verify user input.
741 nonexistent = targets_wanted.difference(all_targets)
742 if nonexistent:
743 raise ValueError("Invalid targets: %s" % (",".join(nonexistent),))
744 return {t: all_targets[t] for t in targets_wanted}
Mike Frysinger35247af2012-11-16 18:58:06 -0500745
746
Alex Klein1699fab2022-09-08 08:46:06 -0600747def UpdateToolchains(
748 usepkg,
749 deleteold,
750 hostonly,
751 reconfig,
752 targets_wanted,
753 boards_wanted,
754 root="/",
755):
756 """Performs all steps to create a synchronized toolchain enviroment.
Zdenek Behan508dcce2011-12-05 15:39:32 +0100757
Alex Klein1699fab2022-09-08 08:46:06 -0600758 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700759 usepkg: Use prebuilt packages
760 deleteold: Unmerge deprecated packages
761 hostonly: Only setup the host toolchain
762 reconfig: Reload crossdev config and reselect toolchains
763 targets_wanted: All the targets to update
764 boards_wanted: Load targets from these boards
765 root: The root in which to install the toolchains.
Alex Klein1699fab2022-09-08 08:46:06 -0600766 """
767 targets, crossdev_targets, reconfig_targets = {}, {}, {}
768 if not hostonly:
769 # For hostonly, we can skip most of the below logic, much of which won't
770 # work on bare systems where this is useful.
771 targets = ExpandTargets(targets_wanted)
Mike Frysinger7ccee992012-06-01 21:27:59 -0400772
Alex Klein1699fab2022-09-08 08:46:06 -0600773 # Filter out toolchains that don't (yet) have a binpkg available.
774 if usepkg:
775 for target in list(targets.keys()):
776 if not targets[target]["have-binpkg"]:
777 del targets[target]
Mike Frysingerd246fb92021-10-26 16:08:39 -0400778
Alex Klein1699fab2022-09-08 08:46:06 -0600779 # Now re-add any targets that might be from this board. This is to
780 # allow unofficial boards to declare their own toolchains.
781 for board in boards_wanted:
782 targets.update(toolchain.GetToolchainsForBoard(board))
Zdenek Behan508dcce2011-12-05 15:39:32 +0100783
Alex Klein1699fab2022-09-08 08:46:06 -0600784 # First check and initialize all cross targets that need to be.
785 for target in targets:
786 if TargetIsInitialized(target):
787 reconfig_targets[target] = targets[target]
788 else:
789 crossdev_targets[target] = targets[target]
790 if crossdev_targets:
791 logging.info("The following targets need to be re-initialized:")
792 logging.info("%s", crossdev_targets)
793 Crossdev.UpdateTargets(crossdev_targets, usepkg)
794 # Those that were not initialized may need a config update.
795 Crossdev.UpdateTargets(reconfig_targets, usepkg, config_only=True)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100796
Alex Klein0e92b2c2023-01-13 11:54:15 -0700797 # If we're building a subset of toolchains for a board, we might not
798 # have all the tuples that the packages expect. We don't define the
799 # "full" set of tuples currently other than "whatever the full sdk has
800 # normally".
Alex Klein1699fab2022-09-08 08:46:06 -0600801 if usepkg or set(("all", "sdk")) & targets_wanted:
802 # Since we have cross-compilers now, we can update these packages.
803 targets["host-post-cross"] = {}
Mike Frysinger785b0c32017-09-13 01:35:59 -0400804
Alex Klein1699fab2022-09-08 08:46:06 -0600805 # We want host updated.
806 targets["host"] = {}
Zdenek Behan7e33b4e2012-03-12 17:00:56 +0100807
Alex Klein1699fab2022-09-08 08:46:06 -0600808 # Now update all packages.
809 if (
810 UpdateTargets(targets, usepkg, root=root)
811 or crossdev_targets
812 or reconfig
813 ):
814 SelectActiveToolchains(targets, root=root)
David James7ec5efc2012-11-06 09:39:49 -0800815
Alex Klein1699fab2022-09-08 08:46:06 -0600816 if deleteold:
817 CleanTargets(targets, root=root)
Zdenek Behan508dcce2011-12-05 15:39:32 +0100818
Alex Klein1699fab2022-09-08 08:46:06 -0600819 # Now that we've cleared out old versions, see if we need to rebuild
820 # anything. Can't do this earlier as it might not be broken.
821 RebuildLibtool(root=root)
Mike Frysingerc880a962013-11-08 13:59:06 -0500822
Zdenek Behan508dcce2011-12-05 15:39:32 +0100823
Bertrand SIMONNETcae9d5f2015-03-09 15:58:01 -0700824def ShowConfig(name):
Alex Klein1699fab2022-09-08 08:46:06 -0600825 """Show the toolchain tuples used by |name|
Mike Frysinger35247af2012-11-16 18:58:06 -0500826
Alex Klein1699fab2022-09-08 08:46:06 -0600827 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700828 name: The board name to query.
Alex Klein1699fab2022-09-08 08:46:06 -0600829 """
830 toolchains = toolchain.GetToolchainsForBoard(name)
831 # Make sure we display the default toolchain first.
832 # Note: Do not use logging here as this is meant to be used by other tools.
833 print(
834 ",".join(
835 list(toolchain.FilterToolchains(toolchains, "default", True))
836 + list(toolchain.FilterToolchains(toolchains, "default", False))
837 )
838 )
Mike Frysinger35247af2012-11-16 18:58:06 -0500839
840
Mike Frysinger35247af2012-11-16 18:58:06 -0500841def GeneratePathWrapper(root, wrappath, path):
Alex Klein1699fab2022-09-08 08:46:06 -0600842 """Generate a shell script to execute another shell script
Mike Frysinger35247af2012-11-16 18:58:06 -0500843
Alex Klein1699fab2022-09-08 08:46:06 -0600844 Since we can't symlink a wrapped ELF (see GenerateLdsoWrapper) because the
845 argv[0] won't be pointing to the correct path, generate a shell script that
846 just executes another program with its full path.
Mike Frysinger35247af2012-11-16 18:58:06 -0500847
Alex Klein1699fab2022-09-08 08:46:06 -0600848 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700849 root: The root tree to generate scripts inside of
850 wrappath: The full path (inside |root|) to create the wrapper
851 path: The target program which this wrapper will execute
Alex Klein1699fab2022-09-08 08:46:06 -0600852 """
853 replacements = {
854 "path": path,
855 "relroot": os.path.relpath("/", os.path.dirname(wrappath)),
856 }
Takuto Ikuta58403972018-08-16 18:52:51 +0900857
Alex Klein1699fab2022-09-08 08:46:06 -0600858 # Do not use exec here, because exec invokes script with absolute path in
Alex Klein0e92b2c2023-01-13 11:54:15 -0700859 # argv0. Keeping relativeness allows us to remove abs path from compile
860 # result and leads directory independent build cache sharing in some
861 # distributed build system.
Alex Klein1699fab2022-09-08 08:46:06 -0600862 wrapper = (
863 """#!/bin/sh
Takuto Ikuta58403972018-08-16 18:52:51 +0900864basedir=$(dirname "$0")
865"${basedir}/%(relroot)s%(path)s" "$@"
866exit "$?"
Alex Klein1699fab2022-09-08 08:46:06 -0600867"""
868 % replacements
869 )
870 root_wrapper = root + wrappath
871 if os.path.islink(root_wrapper):
872 os.unlink(root_wrapper)
873 else:
874 osutils.SafeMakedirs(os.path.dirname(root_wrapper))
875 osutils.WriteFile(root_wrapper, wrapper)
876 os.chmod(root_wrapper, 0o755)
Mike Frysinger35247af2012-11-16 18:58:06 -0500877
878
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700879def FixClangXXWrapper(root, path):
Alex Klein1699fab2022-09-08 08:46:06 -0600880 """Fix wrapper shell scripts and symlinks for invoking clang++
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700881
Alex Klein1699fab2022-09-08 08:46:06 -0600882 In a typical installation, clang++ symlinks to clang, which symlinks to the
883 elf executable. The executable distinguishes between clang and clang++ based
884 on argv[0].
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700885
Alex Klein0e92b2c2023-01-13 11:54:15 -0700886 When invoked through the LdsoWrapper, argv[0] always contains the path to
887 the executable elf file, making clang/clang++ invocations indistinguishable.
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700888
Alex Klein1699fab2022-09-08 08:46:06 -0600889 This function detects if the elf executable being wrapped is clang-X.Y, and
890 fixes wrappers/symlinks as necessary so that clang++ will work correctly.
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700891
Alex Klein1699fab2022-09-08 08:46:06 -0600892 The calling sequence now becomes:
893 -) clang++ invocation turns into clang++-3.9 (which is a copy of clang-3.9,
894 the Ldsowrapper).
895 -) clang++-3.9 uses the Ldso to invoke clang++-3.9.elf, which is a symlink
896 to the original clang-3.9 elf.
897 -) The difference this time is that inside the elf file execution, $0 is
898 set as .../usr/bin/clang++-3.9.elf, which contains 'clang++' in the name.
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700899
Alex Klein1699fab2022-09-08 08:46:06 -0600900 Update: Starting since clang 7, the clang and clang++ are symlinks to
901 clang-7 binary, not clang-7.0. The pattern match is extended to handle
902 both clang-7 and clang-7.0 cases for now. (https://crbug.com/837889)
Manoj Guptaae268142018-04-27 23:28:36 -0700903
Alex Klein1699fab2022-09-08 08:46:06 -0600904 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700905 root: The root tree to generate scripts / symlinks inside of
906 path: The target elf for which LdsoWrapper was created
Alex Klein1699fab2022-09-08 08:46:06 -0600907 """
908 if re.match(r"/usr/bin/clang-\d+(\.\d+)*$", path):
909 logging.info("fixing clang++ invocation for %s", path)
910 clangdir = os.path.dirname(root + path)
911 clang = os.path.basename(path)
912 clangxx = clang.replace("clang", "clang++")
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700913
Alex Klein1699fab2022-09-08 08:46:06 -0600914 # Create a symlink clang++-X.Y.elf to point to clang-X.Y.elf
915 os.symlink(clang + ".elf", os.path.join(clangdir, clangxx + ".elf"))
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700916
Alex Klein1699fab2022-09-08 08:46:06 -0600917 # Create a hardlink clang++-X.Y pointing to clang-X.Y
918 os.link(os.path.join(clangdir, clang), os.path.join(clangdir, clangxx))
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700919
Alex Klein1699fab2022-09-08 08:46:06 -0600920 # Adjust the clang++ symlink to point to clang++-X.Y
921 os.unlink(os.path.join(clangdir, "clang++"))
922 os.symlink(clangxx, os.path.join(clangdir, "clang++"))
Rahul Chaudhrya8127bb2016-07-22 15:53:17 -0700923
924
Mike Frysinger35247af2012-11-16 18:58:06 -0500925def FileIsCrosSdkElf(elf):
Alex Klein1699fab2022-09-08 08:46:06 -0600926 """Determine if |elf| is an ELF that we execute in the cros_sdk
Mike Frysinger35247af2012-11-16 18:58:06 -0500927
Alex Klein1699fab2022-09-08 08:46:06 -0600928 We don't need this to be perfect, just quick. It makes sure the ELF
929 is a 64bit LSB x86_64 ELF. That is the native type of cros_sdk.
Mike Frysinger35247af2012-11-16 18:58:06 -0500930
Alex Klein1699fab2022-09-08 08:46:06 -0600931 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700932 elf: The file to check
Mike Frysinger1a736a82013-12-12 01:50:59 -0500933
Alex Klein1699fab2022-09-08 08:46:06 -0600934 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700935 True if we think |elf| is a native ELF
Alex Klein1699fab2022-09-08 08:46:06 -0600936 """
937 with open(elf, "rb") as f:
938 data = f.read(20)
939 # Check the magic number, EI_CLASS, EI_DATA, and e_machine.
940 return (
941 data[0:4] == b"\x7fELF"
942 and data[4:5] == b"\x02"
943 and data[5:6] == b"\x01"
944 and data[18:19] == b"\x3e"
945 )
Mike Frysinger35247af2012-11-16 18:58:06 -0500946
947
948def IsPathPackagable(ptype, path):
Alex Klein1699fab2022-09-08 08:46:06 -0600949 """Should the specified file be included in a toolchain package?
Mike Frysinger35247af2012-11-16 18:58:06 -0500950
Alex Klein1699fab2022-09-08 08:46:06 -0600951 We only need to handle files as we'll create dirs as we need them.
Mike Frysinger35247af2012-11-16 18:58:06 -0500952
Alex Klein1699fab2022-09-08 08:46:06 -0600953 Further, trim files that won't be useful:
954 - non-english translations (.mo) since it'd require env vars
955 - debug files since these are for the host compiler itself
956 - info/man pages as they're big, and docs are online, and the
957 native docs should work fine for the most part (`man gcc`)
Mike Frysinger35247af2012-11-16 18:58:06 -0500958
Alex Klein1699fab2022-09-08 08:46:06 -0600959 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700960 ptype: A string describing the path type (i.e. 'file' or 'dir' or 'sym')
961 path: The full path to inspect
Mike Frysinger1a736a82013-12-12 01:50:59 -0500962
Alex Klein1699fab2022-09-08 08:46:06 -0600963 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700964 True if we want to include this path in the package
Alex Klein1699fab2022-09-08 08:46:06 -0600965 """
966 return not (
967 ptype in ("dir",)
968 or path.startswith("/usr/lib/debug/")
969 or os.path.splitext(path)[1] == ".mo"
970 or ("/man/" in path or "/info/" in path)
971 )
Mike Frysinger35247af2012-11-16 18:58:06 -0500972
973
974def ReadlinkRoot(path, root):
Alex Klein1699fab2022-09-08 08:46:06 -0600975 """Like os.readlink(), but relative to a |root|
Mike Frysinger35247af2012-11-16 18:58:06 -0500976
Alex Klein1699fab2022-09-08 08:46:06 -0600977 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700978 path: The symlink to read
979 root: The path to use for resolving absolute symlinks
Mike Frysinger1a736a82013-12-12 01:50:59 -0500980
Alex Klein1699fab2022-09-08 08:46:06 -0600981 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700982 A fully resolved symlink path
Alex Klein1699fab2022-09-08 08:46:06 -0600983 """
984 while os.path.islink(root + path):
985 path = os.path.join(os.path.dirname(path), os.readlink(root + path))
986 return path
Mike Frysinger35247af2012-11-16 18:58:06 -0500987
988
Alex Klein1699fab2022-09-08 08:46:06 -0600989def _GetFilesForTarget(target, root="/"):
990 """Locate all the files to package for |target|
Mike Frysinger35247af2012-11-16 18:58:06 -0500991
Alex Klein1699fab2022-09-08 08:46:06 -0600992 This does not cover ELF dependencies.
Mike Frysinger35247af2012-11-16 18:58:06 -0500993
Alex Klein1699fab2022-09-08 08:46:06 -0600994 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700995 target: The toolchain target name
996 root: The root path to pull all packages from
Mike Frysinger1a736a82013-12-12 01:50:59 -0500997
Alex Klein1699fab2022-09-08 08:46:06 -0600998 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -0700999 A tuple of a set of all packable paths, and a set of all paths which
1000 are also native ELFs
Alex Klein1699fab2022-09-08 08:46:06 -06001001 """
1002 paths = set()
1003 elfs = set()
Mike Frysinger35247af2012-11-16 18:58:06 -05001004
Alex Klein1699fab2022-09-08 08:46:06 -06001005 # Find all the files owned by the packages for this target.
1006 for pkg in GetTargetPackages(target):
Alex Klein1699fab2022-09-08 08:46:06 -06001007 # Skip Go compiler from redistributable packages.
1008 # The "go" executable has GOROOT=/usr/lib/go/${CTARGET} hardcoded
1009 # into it. Due to this, the toolchain cannot be unpacked anywhere
1010 # else and be readily useful. To enable packaging Go, we need to:
1011 # -) Tweak the wrappers/environment to override GOROOT
1012 # automatically based on the unpack location.
1013 # -) Make sure the ELF dependency checking and wrapping logic
1014 # below skips the Go toolchain executables and libraries.
1015 # -) Make sure the packaging process maintains the relative
1016 # timestamps of precompiled standard library packages.
1017 # (see dev-lang/go ebuild for details).
1018 if pkg == "ex_go":
1019 continue
Rahul Chaudhry4b803052015-05-13 15:25:56 -07001020
Alex Klein1699fab2022-09-08 08:46:06 -06001021 # Use armv7a-cros-linux-gnueabi/compiler-rt for
Alex Klein0e92b2c2023-01-13 11:54:15 -07001022 # armv7a-cros-linux-gnueabihf/compiler-rt. Currently the
1023 # armv7a-cros-linux-gnueabi is actually the same as
1024 # armv7a-cros-linux-gnueabihf with different names. Because of that, for
1025 # compiler-rt, it generates the same binary in the same location. To
1026 # avoid the installation conflict, we do not install anything for
1027 # 'armv7a-cros-linux-gnueabihf'. This would cause problem if other
1028 # people try to use standalone armv7a-cros-linux-gnueabihf toolchain.
Alex Klein1699fab2022-09-08 08:46:06 -06001029 if "compiler-rt" in pkg and "armv7a-cros-linux-gnueabi" in target:
1030 atom = GetPortagePackage(target, pkg)
1031 cat, pn = atom.split("/")
1032 ver = GetInstalledPackageVersions(atom, root=root)[0]
1033 dblink = portage.dblink(
1034 cat, pn + "-" + ver, myroot=root, settings=portage.settings
1035 )
1036 contents = dblink.getcontents()
1037 if not contents:
1038 if "hf" in target:
1039 new_target = "armv7a-cros-linux-gnueabi"
1040 else:
1041 new_target = "armv7a-cros-linux-gnueabihf"
1042 atom = GetPortagePackage(new_target, pkg)
Yunlian Jiang36f35242018-04-27 10:18:40 -07001043 else:
Alex Klein1699fab2022-09-08 08:46:06 -06001044 atom = GetPortagePackage(target, pkg)
Yunlian Jiang36f35242018-04-27 10:18:40 -07001045
Alex Klein1699fab2022-09-08 08:46:06 -06001046 cat, pn = atom.split("/")
1047 ver = GetInstalledPackageVersions(atom, root=root)[0]
1048 logging.info("packaging %s-%s", atom, ver)
Mike Frysinger35247af2012-11-16 18:58:06 -05001049
Alex Klein1699fab2022-09-08 08:46:06 -06001050 dblink = portage.dblink(
1051 cat, pn + "-" + ver, myroot=root, settings=portage.settings
1052 )
1053 contents = dblink.getcontents()
1054 for obj in contents:
1055 ptype = contents[obj][0]
1056 if not IsPathPackagable(ptype, obj):
1057 continue
Mike Frysinger35247af2012-11-16 18:58:06 -05001058
Alex Klein1699fab2022-09-08 08:46:06 -06001059 if ptype == "obj":
1060 # For native ELFs, we need to pull in their dependencies too.
1061 if FileIsCrosSdkElf(obj):
1062 logging.debug("Adding ELF %s", obj)
1063 elfs.add(obj)
1064 logging.debug("Adding path %s", obj)
1065 paths.add(obj)
Mike Frysinger35247af2012-11-16 18:58:06 -05001066
Alex Klein1699fab2022-09-08 08:46:06 -06001067 return paths, elfs
Mike Frysinger35247af2012-11-16 18:58:06 -05001068
1069
Alex Klein1699fab2022-09-08 08:46:06 -06001070def _BuildInitialPackageRoot(
1071 output_dir, paths, elfs, ldpaths, path_rewrite_func=lambda x: x, root="/"
1072):
1073 """Link in all packable files and their runtime dependencies
Mike Frysinger35247af2012-11-16 18:58:06 -05001074
Alex Klein1699fab2022-09-08 08:46:06 -06001075 This also wraps up executable ELFs with helper scripts.
Mike Frysinger35247af2012-11-16 18:58:06 -05001076
Alex Klein1699fab2022-09-08 08:46:06 -06001077 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -07001078 output_dir: The output directory to store files
1079 paths: All the files to include
1080 elfs: All the files which are ELFs (a subset of |paths|)
1081 ldpaths: A dict of static ldpath information
1082 path_rewrite_func: User callback to rewrite paths in output_dir
1083 root: The root path to pull all packages/files from
Alex Klein1699fab2022-09-08 08:46:06 -06001084 """
1085 # Link in all the files.
1086 sym_paths = {}
1087 for path in paths:
1088 new_path = path_rewrite_func(path)
1089 logging.debug("Transformed %s to %s", path, new_path)
1090 dst = output_dir + new_path
1091 osutils.SafeMakedirs(os.path.dirname(dst))
Mike Frysinger35247af2012-11-16 18:58:06 -05001092
Alex Klein1699fab2022-09-08 08:46:06 -06001093 # Is this a symlink which we have to rewrite or wrap?
1094 # Delay wrap check until after we have created all paths.
1095 src = root + path
1096 if os.path.islink(src):
1097 tgt = os.readlink(src)
1098 if os.path.sep in tgt:
1099 sym_paths[lddtree.normpath(ReadlinkRoot(src, root))] = new_path
Mike Frysinger35247af2012-11-16 18:58:06 -05001100
Alex Klein0e92b2c2023-01-13 11:54:15 -07001101 # Rewrite absolute links to relative and then generate the
1102 # symlink ourselves. All other symlinks can be hardlinked below.
Alex Klein1699fab2022-09-08 08:46:06 -06001103 if tgt[0] == "/":
1104 tgt = os.path.relpath(tgt, os.path.dirname(new_path))
1105 os.symlink(tgt, dst)
1106 continue
Mike Frysinger35247af2012-11-16 18:58:06 -05001107
Alex Klein1699fab2022-09-08 08:46:06 -06001108 logging.debug("Linking path %s -> %s", src, dst)
1109 os.link(src, dst)
Mike Frysinger35247af2012-11-16 18:58:06 -05001110
Alex Klein1699fab2022-09-08 08:46:06 -06001111 # Locate all the dependencies for all the ELFs. Stick them all in the
1112 # top level "lib" dir to make the wrapper simpler. This exact path does
1113 # not matter since we execute ldso directly, and we tell the ldso the
1114 # exact path to search for its libraries.
1115 libdir = os.path.join(output_dir, "lib")
1116 osutils.SafeMakedirs(libdir)
1117 donelibs = set()
1118 basenamelibs = set()
Manoj Gupta98e674d2022-10-05 00:31:41 +00001119 glibc_re = re.compile(r"/lib(c|pthread)[0-9.-]*\.so[0-9.-]*")
Alex Klein1699fab2022-09-08 08:46:06 -06001120 for elf in elfs:
1121 e = lddtree.ParseELF(elf, root=root, ldpaths=ldpaths)
1122 logging.debug("Parsed elf %s data: %s", elf, e)
1123 interp = e["interp"]
Mike Frysinger221bd822017-09-29 02:51:47 -04001124
Alex Klein0e92b2c2023-01-13 11:54:15 -07001125 # TODO(b/187786323): Drop this hack once libopcodes linkage is fixed.
Alex Klein1699fab2022-09-08 08:46:06 -06001126 if os.path.basename(elf).startswith("libopcodes-"):
1127 continue
Mike Frysinger35247af2012-11-16 18:58:06 -05001128
Alex Klein0e92b2c2023-01-13 11:54:15 -07001129 # Copy all the dependencies before we copy the program & generate
1130 # wrappers.
Alex Klein1699fab2022-09-08 08:46:06 -06001131 for lib, lib_data in e["libs"].items():
1132 src = path = lib_data["path"]
1133 if path is None:
1134 logging.warning("%s: could not locate %s", elf, lib)
1135 continue
Mike Frysinger9fe02342019-12-12 17:52:53 -05001136
Alex Klein1699fab2022-09-08 08:46:06 -06001137 # No need to try and copy the same source lib multiple times.
1138 if path in donelibs:
1139 continue
1140 donelibs.add(path)
Mike Frysinger9fe02342019-12-12 17:52:53 -05001141
Alex Klein0e92b2c2023-01-13 11:54:15 -07001142 # Die if we try to normalize different source libs with the same
1143 # basename.
Alex Klein1699fab2022-09-08 08:46:06 -06001144 if lib in basenamelibs:
1145 logging.error(
1146 "Multiple sources detected for %s:\n new: %s\n old: %s",
1147 os.path.join("/lib", lib),
1148 path,
1149 " ".join(
1150 x
1151 for x in donelibs
1152 if x != path and os.path.basename(x) == lib
1153 ),
1154 )
1155 # TODO(crbug.com/917193): Make this fatal.
1156 # cros_build_lib.Die('Unable to resolve lib conflicts')
1157 continue
1158 basenamelibs.add(lib)
Mike Frysinger35247af2012-11-16 18:58:06 -05001159
Alex Klein1699fab2022-09-08 08:46:06 -06001160 # Needed libs are the SONAME, but that is usually a symlink, not a
1161 # real file. So link in the target rather than the symlink itself.
1162 # We have to walk all the possible symlinks (SONAME could point to a
1163 # symlink which points to a symlink), and we have to handle absolute
1164 # ourselves (since we have a "root" argument).
1165 dst = os.path.join(libdir, os.path.basename(path))
1166 src = ReadlinkRoot(src, root)
Mike Frysinger35247af2012-11-16 18:58:06 -05001167
Alex Klein1699fab2022-09-08 08:46:06 -06001168 logging.debug("Linking lib %s -> %s", root + src, dst)
1169 os.link(root + src, dst)
Mike Frysinger35247af2012-11-16 18:58:06 -05001170
Alex Klein1699fab2022-09-08 08:46:06 -06001171 # Do not create wrapper for libc. crbug.com/766827
1172 if interp and not glibc_re.search(elf):
1173 # Generate a wrapper if it is executable.
1174 interp = os.path.join("/lib", os.path.basename(interp))
1175 lddtree.GenerateLdsoWrapper(
1176 output_dir,
1177 path_rewrite_func(elf),
1178 interp,
1179 libpaths=e["rpath"] + e["runpath"],
1180 )
1181 FixClangXXWrapper(output_dir, path_rewrite_func(elf))
Mike Frysinger00b129f2021-04-21 18:11:48 -04001182
Alex Klein1699fab2022-09-08 08:46:06 -06001183 # Wrap any symlinks to the wrapper.
1184 if elf in sym_paths:
1185 link = sym_paths[elf]
1186 GeneratePathWrapper(output_dir, link, elf)
Mike Frysinger00b129f2021-04-21 18:11:48 -04001187
Mike Frysinger35247af2012-11-16 18:58:06 -05001188
1189def _EnvdGetVar(envd, var):
Alex Klein1699fab2022-09-08 08:46:06 -06001190 """Given a Gentoo env.d file, extract a var from it
Mike Frysinger35247af2012-11-16 18:58:06 -05001191
Alex Klein1699fab2022-09-08 08:46:06 -06001192 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -07001193 envd: The env.d file to load (may be a glob path)
1194 var: The var to extract
Mike Frysinger1a736a82013-12-12 01:50:59 -05001195
Alex Klein1699fab2022-09-08 08:46:06 -06001196 Returns:
Alex Klein0e92b2c2023-01-13 11:54:15 -07001197 The value of |var|
Alex Klein1699fab2022-09-08 08:46:06 -06001198 """
1199 envds = glob.glob(envd)
1200 assert len(envds) == 1, "%s: should have exactly 1 env.d file" % envd
1201 envd = envds[0]
1202 return key_value_store.LoadFile(envd)[var]
Mike Frysinger35247af2012-11-16 18:58:06 -05001203
1204
1205def _ProcessBinutilsConfig(target, output_dir):
Alex Klein1699fab2022-09-08 08:46:06 -06001206 """Do what binutils-config would have done"""
1207 binpath = os.path.join("/bin", target + "-")
Mike Frysingerd4d40fd2014-11-06 17:30:57 -05001208
Alex Klein1699fab2022-09-08 08:46:06 -06001209 # Locate the bin dir holding the linker and perform some confidence checks
1210 binutils_bin_path = os.path.join(
1211 output_dir, "usr", toolchain.GetHostTuple(), target, "binutils-bin"
1212 )
1213 globpath = os.path.join(binutils_bin_path, "*")
1214 srcpath = glob.glob(globpath)
1215 assert len(srcpath) == 1, (
1216 "%s: matched more than one path. Is Gold enabled?" % globpath
1217 )
1218 srcpath = srcpath[0]
1219 ld_path = os.path.join(srcpath, "ld")
1220 assert os.path.exists(ld_path), "%s: linker is missing!" % ld_path
1221 ld_path = os.path.join(srcpath, "ld.bfd")
1222 assert os.path.exists(ld_path), "%s: linker is missing!" % ld_path
Rahul Chaudhry4891b4d2017-03-08 10:31:27 -08001223
Alex Klein1699fab2022-09-08 08:46:06 -06001224 srcpath = srcpath[len(output_dir) :]
1225 gccpath = os.path.join("/usr", "libexec", "gcc")
1226 for prog in os.listdir(output_dir + srcpath):
1227 # Skip binaries already wrapped.
1228 if not prog.endswith(".real"):
1229 GeneratePathWrapper(
1230 output_dir, binpath + prog, os.path.join(srcpath, prog)
1231 )
1232 GeneratePathWrapper(
1233 output_dir,
1234 os.path.join(gccpath, prog),
1235 os.path.join(srcpath, prog),
1236 )
Mike Frysinger35247af2012-11-16 18:58:06 -05001237
Alex Klein1699fab2022-09-08 08:46:06 -06001238 libpath = os.path.join("/usr", toolchain.GetHostTuple(), target, "lib")
1239 envd = os.path.join(output_dir, "etc", "env.d", "binutils", "*")
1240 srcpath = _EnvdGetVar(envd, "LIBPATH")
1241 os.symlink(
1242 os.path.relpath(srcpath, os.path.dirname(libpath)), output_dir + libpath
1243 )
Mike Frysinger35247af2012-11-16 18:58:06 -05001244
1245
1246def _ProcessGccConfig(target, output_dir):
Alex Klein1699fab2022-09-08 08:46:06 -06001247 """Do what gcc-config would have done"""
1248 binpath = "/bin"
1249 envd = os.path.join(output_dir, "etc", "env.d", "gcc", "*")
1250 srcpath = _EnvdGetVar(envd, "GCC_PATH")
1251 for prog in os.listdir(output_dir + srcpath):
1252 # Skip binaries already wrapped.
1253 if (
1254 not prog.endswith(".real")
1255 and not prog.endswith(".elf")
1256 and prog.startswith(target)
1257 ):
1258 GeneratePathWrapper(
1259 output_dir,
1260 os.path.join(binpath, prog),
1261 os.path.join(srcpath, prog),
1262 )
1263 return srcpath
Mike Frysinger35247af2012-11-16 18:58:06 -05001264
1265
Frank Henigman179ec7c2015-02-06 03:01:09 -05001266def _ProcessSysrootWrappers(_target, output_dir, srcpath):
Alex Klein1699fab2022-09-08 08:46:06 -06001267 """Remove chroot-specific things from our sysroot wrappers"""
1268 # Disable ccache since we know it won't work outside of chroot.
Tobias Boschddd16492019-08-14 09:29:54 -07001269
Alex Klein1699fab2022-09-08 08:46:06 -06001270 # Use the version of the wrapper that does not use ccache.
1271 for sysroot_wrapper in glob.glob(
1272 os.path.join(output_dir + srcpath, "sysroot_wrapper*.ccache")
1273 ):
1274 # Can't update the wrapper in place to not affect the chroot,
1275 # but only the extracted toolchain.
1276 os.unlink(sysroot_wrapper)
1277 shutil.copy(sysroot_wrapper[:-6] + "noccache", sysroot_wrapper)
1278 shutil.copy(
1279 sysroot_wrapper[:-6] + "noccache.elf", sysroot_wrapper + ".elf"
1280 )
Mike Frysinger35247af2012-11-16 18:58:06 -05001281
1282
Manoj Gupta61bf9db2020-03-23 21:28:04 -07001283def _ProcessClangWrappers(target, output_dir):
Alex Klein1699fab2022-09-08 08:46:06 -06001284 """Remove chroot-specific things from our sysroot wrappers"""
1285 clang_bin_path = "/usr/bin"
1286 # Disable ccache from clang wrappers.
1287 _ProcessSysrootWrappers(target, output_dir, clang_bin_path)
1288 GeneratePathWrapper(
1289 output_dir, f"/bin/{target}-clang", f"/usr/bin/{target}-clang"
1290 )
1291 GeneratePathWrapper(
1292 output_dir, f"/bin/{target}-clang++", f"/usr/bin/{target}-clang++"
1293 )
Manoj Gupta61bf9db2020-03-23 21:28:04 -07001294
1295
Yunlian Jiang5ad6b512017-09-20 09:27:45 -07001296def _CreateMainLibDir(target, output_dir):
Alex Klein1699fab2022-09-08 08:46:06 -06001297 """Create some lib dirs so that compiler can get the right Gcc paths"""
1298 osutils.SafeMakedirs(os.path.join(output_dir, "usr", target, "lib"))
1299 osutils.SafeMakedirs(os.path.join(output_dir, "usr", target, "usr/lib"))
Yunlian Jiang5ad6b512017-09-20 09:27:45 -07001300
1301
Manoj Guptadf8b3872022-01-13 11:57:36 -08001302def _CreateRemoteToolchainFile(output_dir):
Alex Klein1699fab2022-09-08 08:46:06 -06001303 """Create a remote_toolchain_inputs file for reclient/RBE"""
1304 # The inputs file lists all files/shared libraries needed to run clang.
1305 # All inputs are relative to location of clang binary and one input
1306 # location per line of file e.g.
1307 # clang-13.elf
1308 # clang++-13.elf
1309 # relative/path/to/clang/resource/directory
Manoj Guptadf8b3872022-01-13 11:57:36 -08001310
Alex Klein1699fab2022-09-08 08:46:06 -06001311 clang_path = os.path.join(output_dir, "usr/bin")
1312 # Add needed shared libraries and internal files e.g. allowlists.
1313 toolchain_inputs = ["../../lib"]
1314 clang_shared_dirs = glob.glob(
1315 os.path.join(output_dir, "usr/lib64/clang/*/share")
1316 )
1317 for clang_dir in clang_shared_dirs:
1318 toolchain_inputs.append(os.path.relpath(clang_dir, clang_path))
Manoj Guptadf8b3872022-01-13 11:57:36 -08001319
Alex Klein1699fab2022-09-08 08:46:06 -06001320 # Add actual clang binaries/wrappers.
1321 for clang_files in glob.glob(os.path.join(clang_path, "clang*-[0-9]*")):
1322 toolchain_inputs.append(os.path.basename(clang_files))
Manoj Guptadf8b3872022-01-13 11:57:36 -08001323
Mike Frysinger31fdddd2023-02-24 15:50:55 -05001324 with open(
1325 os.path.join(clang_path, "remote_toolchain_inputs"),
1326 "w",
1327 encoding="utf-8",
1328 ) as f:
Alex Klein1699fab2022-09-08 08:46:06 -06001329 f.writelines("%s\n" % line for line in toolchain_inputs)
Manoj Guptadf8b3872022-01-13 11:57:36 -08001330
1331
Mike Frysinger35247af2012-11-16 18:58:06 -05001332def _ProcessDistroCleanups(target, output_dir):
Alex Klein1699fab2022-09-08 08:46:06 -06001333 """Clean up the tree and remove all distro-specific requirements
Mike Frysinger35247af2012-11-16 18:58:06 -05001334
Alex Klein1699fab2022-09-08 08:46:06 -06001335 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -07001336 target: The toolchain target name
1337 output_dir: The output directory to clean up
Han Shen699ea192016-03-02 10:42:47 -08001338 """
Alex Klein1699fab2022-09-08 08:46:06 -06001339 _ProcessBinutilsConfig(target, output_dir)
1340 gcc_path = _ProcessGccConfig(target, output_dir)
1341 _ProcessSysrootWrappers(target, output_dir, gcc_path)
1342 _ProcessClangWrappers(target, output_dir)
1343 _CreateMainLibDir(target, output_dir)
1344 _CreateRemoteToolchainFile(output_dir)
Mike Frysinger35247af2012-11-16 18:58:06 -05001345
Alex Klein1699fab2022-09-08 08:46:06 -06001346 osutils.RmDir(os.path.join(output_dir, "etc"))
Mike Frysinger35247af2012-11-16 18:58:06 -05001347
1348
Alex Klein1699fab2022-09-08 08:46:06 -06001349def CreatePackagableRoot(target, output_dir, ldpaths, root="/"):
1350 """Setup a tree from the packages for the specified target
Mike Frysinger35247af2012-11-16 18:58:06 -05001351
Alex Klein1699fab2022-09-08 08:46:06 -06001352 This populates a path with all the files from toolchain packages so that
1353 a tarball can easily be generated from the result.
Mike Frysinger35247af2012-11-16 18:58:06 -05001354
Alex Klein1699fab2022-09-08 08:46:06 -06001355 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -07001356 target: The target to create a packagable root from
1357 output_dir: The output directory to place all the files
1358 ldpaths: A dict of static ldpath information
1359 root: The root path to pull all packages/files from
Alex Klein1699fab2022-09-08 08:46:06 -06001360 """
1361 # Find all the files owned by the packages for this target.
1362 paths, elfs = _GetFilesForTarget(target, root=root)
Mike Frysinger35247af2012-11-16 18:58:06 -05001363
Alex Klein1699fab2022-09-08 08:46:06 -06001364 # Link in all the package's files, any ELF dependencies, and wrap any
1365 # executable ELFs with helper scripts.
1366 def MoveUsrBinToBin(path):
1367 """Move /usr/bin to /bin so people can just use that toplevel dir
Mike Frysinger35247af2012-11-16 18:58:06 -05001368
Alex Klein0e92b2c2023-01-13 11:54:15 -07001369 Note we do not apply this to clang or rust; there is correlation between
Alex Klein1699fab2022-09-08 08:46:06 -06001370 clang's search path for libraries / inclusion and its installation path.
1371 """
1372 NO_MOVE_PATTERNS = ("clang", "rust", "cargo", "sysroot_wrapper")
1373 if path.startswith("/usr/bin/") and not any(
1374 x in path for x in NO_MOVE_PATTERNS
1375 ):
1376 return path[4:]
1377 return path
Mike Frysinger221bd822017-09-29 02:51:47 -04001378
Alex Klein1699fab2022-09-08 08:46:06 -06001379 _BuildInitialPackageRoot(
1380 output_dir,
1381 paths,
1382 elfs,
1383 ldpaths,
1384 path_rewrite_func=MoveUsrBinToBin,
1385 root=root,
1386 )
Mike Frysinger35247af2012-11-16 18:58:06 -05001387
Alex Klein1699fab2022-09-08 08:46:06 -06001388 # The packages, when part of the normal distro, have helper scripts
1389 # that setup paths and such. Since we are making this standalone, we
1390 # need to preprocess all that ourselves.
1391 _ProcessDistroCleanups(target, output_dir)
1392
1393
1394def CreatePackages(targets_wanted, output_dir, root="/"):
1395 """Create redistributable cross-compiler packages for the specified targets
1396
1397 This creates toolchain packages that should be usable in conjunction with
1398 a downloaded sysroot (created elsewhere).
1399
1400 Tarballs (one per target) will be created in $PWD.
1401
1402 Args:
Alex Klein0e92b2c2023-01-13 11:54:15 -07001403 targets_wanted: The targets to package up.
1404 output_dir: The directory to put the packages in.
1405 root: The root path to pull all packages/files from.
Alex Klein1699fab2022-09-08 08:46:06 -06001406 """
1407 logging.info("Writing tarballs to %s", output_dir)
1408 osutils.SafeMakedirs(output_dir)
1409 ldpaths = lddtree.LoadLdpaths(root)
1410 targets = ExpandTargets(targets_wanted)
1411
Brian Norrisca274dd2023-05-17 13:06:41 -07001412 # b/282231712: Stash temporary path structure at |root|, so we have control
1413 # over cross-device linking. The default base directory (/tmp) might be on
1414 # a different filesystem/mount, so hard links won't work.
1415 with osutils.TempDir(base_dir=root, prefix="create-packages") as tempdir:
Alex Klein1699fab2022-09-08 08:46:06 -06001416 logging.debug("Using tempdir: %s", tempdir)
1417
Alex Klein0e92b2c2023-01-13 11:54:15 -07001418 # We have to split the root generation from the compression stages.
1419 # This is because we hardlink in all the files (to avoid overhead of
1420 # reading/writing the copies multiple times). But tar gets angry if a
1421 # file's hardlink count changes from when it starts reading a file to
1422 # when it finishes.
Alex Klein1699fab2022-09-08 08:46:06 -06001423 with parallel.BackgroundTaskRunner(CreatePackagableRoot) as queue:
1424 for target in targets:
1425 output_target_dir = os.path.join(tempdir, target)
1426 queue.put([target, output_target_dir, ldpaths, root])
1427
1428 # Build the tarball.
1429 with parallel.BackgroundTaskRunner(
1430 cros_build_lib.CreateTarball
1431 ) as queue:
1432 for target in targets:
1433 tar_file = os.path.join(output_dir, target + ".tar.xz")
1434 queue.put([tar_file, os.path.join(tempdir, target)])
Mike Frysinger35247af2012-11-16 18:58:06 -05001435
1436
Mike Frysinger07534cf2017-09-12 17:40:21 -04001437def GetParser():
Alex Klein1699fab2022-09-08 08:46:06 -06001438 """Return a command line parser."""
1439 parser = commandline.ArgumentParser(description=__doc__)
1440 parser.add_argument(
1441 "-u",
1442 "--nousepkg",
1443 action="store_false",
1444 dest="usepkg",
1445 default=True,
1446 help="Do not use prebuilt packages",
1447 )
1448 parser.add_argument(
1449 "-d",
1450 "--deleteold",
1451 action="store_true",
1452 dest="deleteold",
1453 default=False,
1454 help="Unmerge deprecated packages",
1455 )
1456 parser.add_argument(
1457 "-t",
1458 "--targets",
1459 dest="targets",
1460 default="sdk",
1461 help="Comma separated list of tuples. Special keywords "
1462 "'host', 'sdk', 'boards', and 'all' are "
1463 "allowed. Defaults to 'sdk'.",
1464 )
1465 parser.add_argument(
1466 "--include-boards",
1467 default="",
1468 metavar="BOARDS",
1469 help="Comma separated list of boards whose toolchains we "
1470 "will always include. Default: none",
1471 )
1472 parser.add_argument(
1473 "--hostonly",
1474 dest="hostonly",
1475 default=False,
1476 action="store_true",
1477 help="Only setup the host toolchain. "
1478 "Useful for bootstrapping chroot",
1479 )
1480 parser.add_argument(
1481 "--show-board-cfg",
1482 "--show-cfg",
1483 dest="cfg_name",
1484 default=None,
1485 help="Board to list toolchains tuples for",
1486 )
1487 parser.add_argument(
1488 "--show-packages",
1489 default=None,
1490 help="List all packages the specified target uses",
1491 )
1492 parser.add_argument(
1493 "--create-packages",
1494 action="store_true",
1495 default=False,
1496 help="Build redistributable packages",
1497 )
1498 parser.add_argument(
1499 "--output-dir",
1500 default=os.getcwd(),
1501 type="path",
1502 help="Output directory",
1503 )
1504 parser.add_argument(
1505 "--reconfig",
1506 default=False,
1507 action="store_true",
1508 help="Reload crossdev config and reselect toolchains",
1509 )
1510 parser.add_argument(
1511 "--sysroot",
1512 type="path",
1513 help="The sysroot in which to install the toolchains",
1514 )
1515 return parser
Zdenek Behan508dcce2011-12-05 15:39:32 +01001516
Mike Frysinger07534cf2017-09-12 17:40:21 -04001517
1518def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001519 parser = GetParser()
1520 options = parser.parse_args(argv)
1521 options.Freeze()
Zdenek Behan508dcce2011-12-05 15:39:32 +01001522
Alex Klein1699fab2022-09-08 08:46:06 -06001523 # Figure out what we're supposed to do and reject conflicting options.
1524 conflicting_options = (
1525 options.cfg_name,
1526 options.show_packages,
1527 options.create_packages,
1528 )
1529 if sum(bool(x) for x in conflicting_options) > 1:
1530 parser.error(
1531 "conflicting options: create-packages & show-packages & "
1532 "show-board-cfg"
1533 )
Mike Frysinger984d0622012-06-01 16:08:44 -04001534
Alex Klein1699fab2022-09-08 08:46:06 -06001535 targets_wanted = set(options.targets.split(","))
1536 boards_wanted = (
1537 set(options.include_boards.split(","))
1538 if options.include_boards
1539 else set()
1540 )
Mike Frysinger35247af2012-11-16 18:58:06 -05001541
Alex Klein1699fab2022-09-08 08:46:06 -06001542 if options.cfg_name:
1543 ShowConfig(options.cfg_name)
1544 elif options.show_packages is not None:
1545 cros_build_lib.AssertInsideChroot()
1546 target = options.show_packages
1547 Crossdev.Load(False)
1548 for package in GetTargetPackages(target):
1549 print(GetPortagePackage(target, package))
1550 elif options.create_packages:
1551 cros_build_lib.AssertInsideChroot()
1552 Crossdev.Load(False)
1553 CreatePackages(targets_wanted, options.output_dir)
1554 else:
1555 cros_build_lib.AssertInsideChroot()
1556 # This has to be always run as root.
1557 if osutils.IsNonRootUser():
1558 cros_build_lib.Die("this script must be run as root")
Mike Frysinger35247af2012-11-16 18:58:06 -05001559
Alex Klein1699fab2022-09-08 08:46:06 -06001560 Crossdev.Load(options.reconfig)
1561 root = options.sysroot or "/"
1562 UpdateToolchains(
1563 options.usepkg,
1564 options.deleteold,
1565 options.hostonly,
1566 options.reconfig,
1567 targets_wanted,
1568 boards_wanted,
1569 root=root,
1570 )
1571 Crossdev.Save()
Mike Frysinger35247af2012-11-16 18:58:06 -05001572
Alex Klein1699fab2022-09-08 08:46:06 -06001573 return 0