blob: 192f08a41ba94aefc50b939854e943c30ffe3fee [file] [log] [blame]
Allen Webb3e498aa2023-09-05 14:40:49 +00001# Copyright 2023 The ChromiumOS Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Check whether a package links libraries not in RDEPEND.
6
7If no argument is provided it will check all installed packages. It takes the
8BOARD environment variable into account.
9
10Example:
11 package_hash_missing_deps.py --board=amd64-generic --match \
12 chromeos-base/cryptohome
13"""
14
15import argparse
16import collections
Mike Frysinger494c6182023-09-21 11:44:45 -040017import logging
Allen Webb3e498aa2023-09-05 14:40:49 +000018import os
Allen Webbe8c1da02023-09-08 18:25:22 +000019from pathlib import Path
Allen Webb3e498aa2023-09-05 14:40:49 +000020import pprint
21import sys
Mike Frysinger1e9ac7c2023-09-21 14:53:48 -040022from typing import Iterable, List, Optional, Set, Union
Allen Webb3e498aa2023-09-05 14:40:49 +000023
24from chromite.lib import build_target_lib
25from chromite.lib import chroot_lib
26from chromite.lib import commandline
27from chromite.lib import cros_build_lib
28from chromite.lib import parallel
29from chromite.lib import portage_util
30from chromite.lib.parser import package_info
31
32
Allen Webb7cc7cd92023-09-11 16:16:33 +000033VIRTUALS = {
Allen Webb4c582aa2023-09-12 17:20:49 +000034 "virtual/acl": ("sys-apps/acl", "media-libs/img-ddk-bin"),
35 "virtual/arc-opengles": (
36 "media-libs/arc-img-ddk",
37 "media-libs/arc-mesa-img",
38 "media-libs/arc-mali-drivers",
39 "media-libs/arc-mali-drivers-bifrost",
40 "media-libs/arc-mali-drivers-bifrost-bin",
41 "media-libs/arc-mali-drivers-valhall",
42 "media-libs/arc-mali-drivers-valhall-bin",
43 "media-libs/arc-mesa",
44 "media-libs/arc-mesa-amd",
45 "media-libs/arc-mesa-freedreno",
46 "media-libs/arc-mesa-iris",
47 "media-libs/arc-mesa-virgl",
48 "x11-drivers/opengles-headers",
49 ),
50 "virtual/cros-camera-hal": (
51 "media-libs/cros-camera-hal-intel-ipu3",
52 "media-libs/cros-camera-hal-intel-ipu6",
53 "media-libs/cros-camera-hal-mtk",
54 "media-libs/cros-camera-hal-qti",
55 "media-libs/cros-camera-hal-rockchip-isp1",
56 "media-libs/cros-camera-hal-usb",
57 "media-libs/qti-7c-camera-tuning",
58 ),
59 "virtual/img-ddk": ("media-libs/img-ddk", "media-libs/img-ddk-bin"),
60 "virtual/jpeg": ("media-libs/libjpeg-turbo", "media-libs/jpeg"),
61 "virtual/krb5": ("app-crypt/mit-krb5", "app-crypt/heimdal"),
Allen Webb7cc7cd92023-09-11 16:16:33 +000062 "virtual/libcrypt": ("sys-libs/libxcrypt",),
63 "virtual/libelf": ("dev-libs/elfutils", "sys-freebsd/freebsd-lib"),
64 "virtual/libiconv": ("dev-libs/libiconv",),
65 "virtual/libintl": ("dev-libs/libintl",),
66 "virtual/libudev": (
67 "sys-apps/systemd-utils",
68 "sys-fs/udev",
69 "sys-fs/eudev",
70 "sys-apps/systemd",
71 ),
72 "virtual/libusb": ("dev-libs/libusb", "sys-freebsd/freebsd-lib"),
Allen Webb4c582aa2023-09-12 17:20:49 +000073 "virtual/opengles": (
74 "media-libs/img-ddk",
75 "media-libs/img-ddk-bin",
Ricky Liangd5f64432023-09-15 15:50:04 +080076 "media-libs/libglvnd",
Allen Webb4c582aa2023-09-12 17:20:49 +000077 "media-libs/mali-drivers-bin",
78 "media-libs/mali-drivers-bifrost",
79 "media-libs/mali-drivers-bifrost-bin",
80 "media-libs/mali-drivers-valhall",
81 "media-libs/mali-drivers-valhall-bin",
82 "media-libs/mesa",
83 "media-libs/mesa-amd",
84 "media-libs/mesa-freedreno",
85 "media-libs/mesa-iris",
86 "media-libs/mesa-llvmpipe",
87 "media-libs/mesa-panfrost",
88 "media-libs/mesa-reven",
89 "x11-drivers/opengles-headers",
90 ),
91 "virtual/vulkan-icd": (
92 "media-libs/img-ddk",
93 "media-libs/img-ddk-bin",
94 "media-libs/mali-drivers-bifrost",
95 "media-libs/mali-drivers-bifrost-bin",
96 "media-libs/mali-drivers-valhall",
97 "media-libs/mali-drivers-valhall-bin",
98 "media-libs/mesa",
99 "media-libs/mesa-freedreno",
100 "media-libs/mesa-iris",
101 "media-libs/mesa-llvmpipe",
102 "media-libs/mesa-radv",
103 "media-libs/vulkan-loader",
104 ),
Allen Webb7cc7cd92023-09-11 16:16:33 +0000105}
106
107
Allen Webb3e498aa2023-09-05 14:40:49 +0000108def env_to_libs(var: str) -> List[str]:
109 """Converts value of REQUIRES to a list of .so files.
110
111 For example:
112 "arm_32: libRSSupport.so libblasV8.so libc.so ..."
113 Becomes:
114 ["libRSSupport.so", "libblasV8.so", "libc.so", ...]
115 """
116 return [x for x in var.split() if not x.endswith(":")]
117
118
119class DotSoResolver:
120 """Provides shared library related dependency operations."""
121
122 def __init__(
123 self,
124 board: Optional[str] = None,
125 root: Union[os.PathLike, str] = "/",
126 chroot: Optional[chroot_lib.Chroot] = None,
127 ):
128 self.board = board
129 self.chroot = chroot if chroot else chroot_lib.Chroot()
130
131 self.sdk_db = portage_util.PortageDB()
Mike Frysinger2c65b082023-09-21 12:22:58 -0400132 self._sdk_db_packges = None
Allen Webb3e498aa2023-09-05 14:40:49 +0000133 self.db = self.sdk_db if root == "/" else portage_util.PortageDB(root)
Mike Frysinger2c65b082023-09-21 12:22:58 -0400134 self._db_packges = None
Allen Webb3e498aa2023-09-05 14:40:49 +0000135 self.provided_libs_cache = {}
136
Allen Webbe8c1da02023-09-08 18:25:22 +0000137 # Lazy initialize since it might not be needed.
138 self.lib_to_package_map = None
139
Mike Frysinger2c65b082023-09-21 12:22:58 -0400140 @property
141 def sdk_db_packages(self):
142 """Cache sdk_db.InstalledPackages().
143
144 We won't be modifying it, so it's safe for us to reuse the results.
145 """
146 if self._sdk_db_packges is None:
147 self._sdk_db_packges = self.sdk_db.InstalledPackages()
148 return self._sdk_db_packges
149
150 @property
151 def db_packages(self):
152 """Cache db.InstalledPackages().
153
154 We won't be modifying it, so it's safe for us to reuse the results.
155 """
156 if self._db_packges is None:
157 self._db_packges = self.db.InstalledPackages()
158 return self._db_packges
159
Mike Frysinger1e9ac7c2023-09-21 14:53:48 -0400160 def get_packages(
Allen Webb3e498aa2023-09-05 14:40:49 +0000161 self, query: str, from_sdk=False
Mike Frysinger1e9ac7c2023-09-21 14:53:48 -0400162 ) -> Iterable[portage_util.InstalledPackage]:
163 """Find matching InstalledPackage(s) for the |query|."""
Mike Frysinger2c65b082023-09-21 12:22:58 -0400164 packages = self.sdk_db_packages if from_sdk else self.db_packages
Allen Webb3e498aa2023-09-05 14:40:49 +0000165 info = package_info.parse(query)
166 for package in packages:
167 if info.package != package.package:
168 continue
169 if info.category != package.category:
170 continue
171 dep_info = package.package_info
172 if info.revision and info.revision != dep_info.revision:
173 continue
174 if info.pv and info.pv != dep_info.pv:
175 continue
Mike Frysinger494c6182023-09-21 11:44:45 -0400176 logging.debug("query: %s: matched %s", query, dep_info.cpvr)
Mike Frysinger1e9ac7c2023-09-21 14:53:48 -0400177 yield package
Allen Webb3e498aa2023-09-05 14:40:49 +0000178
179 def get_required_libs(self, package) -> Set[str]:
180 """Return a set of required .so files."""
Allen Webb8fc0bba2023-09-11 14:37:25 +0000181 requires = package.requires
182 if requires is not None:
183 return set(env_to_libs(package.requires))
184 # Fallback to needed if requires is not available.
185 aggregate = set()
186 needed = package.needed
187 if needed is not None:
188 for libs in needed.values():
189 aggregate.update(libs)
190 return aggregate
Allen Webb3e498aa2023-09-05 14:40:49 +0000191
Allen Webb7cc7cd92023-09-11 16:16:33 +0000192 def get_deps(self, package) -> List[portage_util.InstalledPackage]:
193 """Return a list of dependencies.
194
195 This expands the virtuals listed below.
196 """
Allen Webb3e498aa2023-09-05 14:40:49 +0000197 cpvr = f"{package.category}/{package.pf}"
Allen Webb7cc7cd92023-09-11 16:16:33 +0000198 expanded = []
199 deps = []
200 for dep in portage_util.GetFlattenedDepsForPackage(
Allen Webb3e498aa2023-09-05 14:40:49 +0000201 cpvr, board=self.board, depth=1
Allen Webb7cc7cd92023-09-11 16:16:33 +0000202 ):
Mike Frysinger494c6182023-09-21 11:44:45 -0400203 logging.debug("%s: found package dependency: %s", cpvr, dep)
Allen Webb7cc7cd92023-09-11 16:16:33 +0000204 info = package_info.parse(dep)
205 if not info:
206 continue
207
208 cp = info.cp
209 if cp in VIRTUALS:
210 expanded += VIRTUALS[cp]
211 continue
212
213 pkg = self.db.GetInstalledPackage(info.category, info.pvr)
214 if pkg:
215 deps.append(pkg)
Mike Frysinger494c6182023-09-21 11:44:45 -0400216 else:
217 logging.warning("%s: could not find installed %s", cpvr, dep)
Allen Webb7cc7cd92023-09-11 16:16:33 +0000218
219 for dep in expanded:
Mike Frysinger1e9ac7c2023-09-21 14:53:48 -0400220 deps.extend(self.get_packages(dep))
Allen Webb7cc7cd92023-09-11 16:16:33 +0000221
222 return deps
Allen Webb3e498aa2023-09-05 14:40:49 +0000223
224 def get_implicit_libs(self):
225 """Return a set of .so files that are provided by the system."""
Allen Webb611e8b12023-09-13 15:54:52 +0000226 # libstdc++ comes from the toolchain so always ignore it.
227 implicit_libs = {"libstdc++.so", "libstdc++.so.6"}
Allen Webb3e498aa2023-09-05 14:40:49 +0000228 for dep, from_sdk in (
229 ("cross-aarch64-cros-linux-gnu/glibc", True),
230 ("cross-armv7a-cros-linux-gnueabihf/glibc", True),
231 ("cross-i686-cros-linux-gnu/glibc", True),
232 ("cross-x86_64-cros-linux-gnu/glibc", True),
233 ("sys-libs/glibc", False),
234 ("sys-libs/libcxx", False),
235 ("sys-libs/llvm-libunwind", False),
236 ):
Mike Frysinger1e9ac7c2023-09-21 14:53:48 -0400237 for pkg in self.get_packages(dep, from_sdk):
238 implicit_libs.update(self.provided_libs(pkg))
Allen Webb3e498aa2023-09-05 14:40:49 +0000239 return implicit_libs
240
241 def provided_libs(self, package: portage_util.InstalledPackage) -> Set[str]:
242 """Return a set of .so files provided by |package|."""
243 cpvr = f"{package.category}/{package.pf}"
244 if cpvr in self.provided_libs_cache:
245 return self.provided_libs_cache[cpvr]
246
247 libs = set()
248 contents = package.ListContents()
249 # Keep only the .so files
250 for typ, path in contents:
251 if typ == package.DIR:
252 continue
253 filename = os.path.basename(path)
Mike Frysinger28f7b952023-09-21 11:40:01 -0400254 if filename.endswith(".so") or (
255 ".so." in filename and not filename.endswith(".debug")
256 ):
Allen Webb3e498aa2023-09-05 14:40:49 +0000257 libs.add(filename)
258 self.provided_libs_cache[cpvr] = libs
259 return libs
260
Allen Webb8fc0bba2023-09-11 14:37:25 +0000261 def cache_libs_from_build(
262 self, package: portage_util.InstalledPackage, image_dir: Path
263 ):
264 """Populate the provided_libs_cache for the package from the image dir.
265
266 When using build-info, CONTENTS might not be available yet. so provide
267 alternative using the destination directory of the ebuild.
268 """
269
270 cpvr = f"{package.category}/{package.pf}"
271 libs = set()
272 for _, _, files in os.walk(image_dir):
273 for file in files:
Mike Frysinger28f7b952023-09-21 11:40:01 -0400274 if file.endswith(".so") or (
275 ".so." in file and not file.endswith(".debug")
276 ):
Allen Webb8fc0bba2023-09-11 14:37:25 +0000277 libs.add(os.path.basename(file))
278 self.provided_libs_cache[cpvr] = libs
279
Allen Webb3e498aa2023-09-05 14:40:49 +0000280 def get_provided_from_all_deps(
281 self, package: portage_util.InstalledPackage
282 ) -> Set[str]:
283 """Return a set of .so files provided by the immediate dependencies."""
284 provided_libs = set()
Allen Webb8fc0bba2023-09-11 14:37:25 +0000285 # |package| may not actually be installed yet so manually add it to the
286 # since a package can depend on its own libs.
287 provided_libs.update(self.provided_libs(package))
Allen Webb7cc7cd92023-09-11 16:16:33 +0000288 for pkg in self.get_deps(package):
Mike Frysinger494c6182023-09-21 11:44:45 -0400289 logging.debug(
290 "%s: loading libs from dependency %s",
291 package.package_info.cpvr,
292 pkg.package_info.cpvr,
293 )
Allen Webb7cc7cd92023-09-11 16:16:33 +0000294 provided_libs.update(self.provided_libs(pkg))
Allen Webb3e498aa2023-09-05 14:40:49 +0000295 return provided_libs
296
Allen Webbe8c1da02023-09-08 18:25:22 +0000297 def lib_to_package(self, lib_filename: str = None) -> Set[str]:
298 """Return a set of packages that contain the library."""
299 if self.lib_to_package_map is None:
300 lookup = collections.defaultdict(set)
301 for pkg in self.db.InstalledPackages():
302 cpvr = f"{pkg.category}/{pkg.pf}"
303 # Packages with bundled libs for internal use and/or standaline
304 # binary packages.
305 if f"{pkg.category}/{pkg.package}" in (
306 "app-emulation/qemu",
307 "chromeos-base/aosp-frameworks-ml-nn-vts",
308 "chromeos-base/factory",
309 "chromeos-base/signingtools-bin",
310 "sys-devel/gcc-bin",
311 ):
312 continue
313 for lib in set(self.provided_libs(pkg)):
314 lookup[lib].add(cpvr)
315 self.lib_to_package_map = lookup
316 else:
317 lookup = self.lib_to_package_map
318 if not lib_filename:
319 return set()
320 try:
321 return lookup[lib_filename]
322 except KeyError:
323 return set()
Allen Webb3e498aa2023-09-05 14:40:49 +0000324
325
326def get_parser() -> commandline.ArgumentParser:
327 """Build the argument parser."""
328 parser = commandline.ArgumentParser(description=__doc__)
329
330 parser.add_argument("package", nargs="*", help="package atom")
331
332 parser.add_argument(
333 "-b",
334 "--board",
335 "--build-target",
336 default=cros_build_lib.GetDefaultBoard(),
337 help="ChromeOS board (Uses the SDK if not specified)",
338 )
339
340 parser.add_argument(
Allen Webb7d34a9a2023-09-18 09:02:15 -0500341 "--no-default-board",
342 dest="board",
343 const=None,
344 action="store_const",
345 help="Ignore the default board",
346 )
347
348 parser.add_argument(
Allen Webbe8c1da02023-09-08 18:25:22 +0000349 "-i",
350 "--build-info",
351 default=None,
352 type=Path,
353 help="Path to build-info folder post src_install",
354 )
355
356 parser.add_argument(
Allen Webb8fc0bba2023-09-11 14:37:25 +0000357 "-x",
358 "--image",
359 default=None,
360 type=Path,
361 help="Path to image folder post src_install (${D} if unspecified)",
362 )
363
364 parser.add_argument(
Allen Webb3e498aa2023-09-05 14:40:49 +0000365 "--match",
366 default=False,
367 action="store_true",
368 help="Try to match missing libraries",
369 )
370
371 parser.add_argument(
372 "-j",
373 "--jobs",
374 default=None,
375 type=int,
376 help="Number of parallel processes",
377 )
378
379 return parser
380
381
382def parse_arguments(argv: List[str]) -> argparse.Namespace:
383 """Parse and validate arguments."""
384 parser = get_parser()
385 opts = parser.parse_args(argv)
Allen Webbe8c1da02023-09-08 18:25:22 +0000386 if opts.build_info and opts.package:
Allen Webb8fc0bba2023-09-11 14:37:25 +0000387 parser.error("Do not specify a package when setting --board-info")
388 if opts.image and not opts.build_info:
389 parser.error("--image requires --board-info")
Allen Webbe8c1da02023-09-08 18:25:22 +0000390 if opts.build_info or len(opts.package) == 1:
Allen Webb3e498aa2023-09-05 14:40:49 +0000391 opts.jobs = 1
392 return opts
393
394
395def check_package(
Allen Webbe8c1da02023-09-08 18:25:22 +0000396 package: portage_util.InstalledPackage,
Allen Webb3e498aa2023-09-05 14:40:49 +0000397 implicit: Set[str],
398 resolver: DotSoResolver,
399 match: bool,
400 debug: bool,
401) -> bool:
402 """Returns false if the package has missing dependencies"""
403 if not package:
404 print("missing package")
405 return False
406
407 provided = resolver.get_provided_from_all_deps(package)
408 if debug:
409 print("provided")
Mike Frysinger494c6182023-09-21 11:44:45 -0400410 pprint.pprint(sorted(provided))
Allen Webb3e498aa2023-09-05 14:40:49 +0000411
412 available = provided.union(implicit)
413 required = resolver.get_required_libs(package)
414 if debug:
415 print("required")
Mike Frysinger494c6182023-09-21 11:44:45 -0400416 pprint.pprint(sorted(required))
Allen Webb3e498aa2023-09-05 14:40:49 +0000417 unsatisfied = required - available
418 if unsatisfied:
419 cpvr = package.package_info.cpvr
420 print(f"'{cpvr}' missing deps for: ", end="")
Mike Frysinger494c6182023-09-21 11:44:45 -0400421 pprint.pprint(sorted(unsatisfied))
Allen Webb3e498aa2023-09-05 14:40:49 +0000422 if match:
423 missing = set()
424 for lib in unsatisfied:
Allen Webbe8c1da02023-09-08 18:25:22 +0000425 missing.update(resolver.lib_to_package(lib))
Allen Webb3e498aa2023-09-05 14:40:49 +0000426 if missing:
427 print(f"'{cpvr}' needs: ", end="")
Mike Frysinger494c6182023-09-21 11:44:45 -0400428 pprint.pprint(sorted(missing))
Allen Webb3e498aa2023-09-05 14:40:49 +0000429 return False
430 return True
431
432
433def main(argv: Optional[List[str]]):
434 """Main."""
435 opts = parse_arguments(argv)
436 opts.Freeze()
437
438 board = opts.board
439 root = build_target_lib.get_default_sysroot_path(board)
440 if board:
441 os.environ["PORTAGE_CONFIGROOT"] = root
442 os.environ["SYSROOT"] = root
443 os.environ["ROOT"] = root
444
445 failed = False
446 resolver = DotSoResolver(board, root)
Allen Webb3e498aa2023-09-05 14:40:49 +0000447
448 if not opts.package:
Allen Webbe8c1da02023-09-08 18:25:22 +0000449 if opts.build_info:
Allen Webb8fc0bba2023-09-11 14:37:25 +0000450 pkg = portage_util.InstalledPackage(resolver.db, opts.build_info)
451 image_path = opts.image or os.environ.get("D")
452 if image_path:
453 resolver.cache_libs_from_build(pkg, Path(image_path))
454 packages = [pkg]
Allen Webbe8c1da02023-09-08 18:25:22 +0000455 else:
456 packages = resolver.db.InstalledPackages()
Allen Webb3e498aa2023-09-05 14:40:49 +0000457 else:
Mike Frysinger1e9ac7c2023-09-21 14:53:48 -0400458 packages = []
459 for pkg in opts.package:
460 packages.extend(resolver.get_packages(pkg))
Allen Webb3e498aa2023-09-05 14:40:49 +0000461
462 implicit = resolver.get_implicit_libs()
463 if opts.debug:
464 print("implicit")
465 pprint.pprint(implicit)
466
467 if opts.jobs == 1:
468 for package in packages:
469 if not check_package(
470 package,
Allen Webb3e498aa2023-09-05 14:40:49 +0000471 implicit,
472 resolver,
473 opts.match,
474 opts.debug,
475 ):
476 failed = True
477 else:
Allen Webbe8c1da02023-09-08 18:25:22 +0000478 if opts.match:
479 # Pre initialize the map before starting jobs.
480 resolver.lib_to_package()
Allen Webb3e498aa2023-09-05 14:40:49 +0000481 for ret in parallel.RunTasksInProcessPool(
482 lambda p: check_package(
Allen Webbe8c1da02023-09-08 18:25:22 +0000483 p, implicit, resolver, opts.match, opts.debug
Allen Webb3e498aa2023-09-05 14:40:49 +0000484 ),
485 [[p] for p in packages],
486 opts.jobs,
487 ):
488 if not ret:
489 failed = True
490
491 if failed:
492 sys.exit(1)
493
494
495if __name__ == "__main__":
496 main(sys.argv[1:])