blob: 3c62caaee47eb5c45ad9af366244c13026ad95c5 [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
17import os
Allen Webbe8c1da02023-09-08 18:25:22 +000018from pathlib import Path
Allen Webb3e498aa2023-09-05 14:40:49 +000019import pprint
20import sys
Allen Webbe8c1da02023-09-08 18:25:22 +000021from typing import List, Optional, Set, Union
Allen Webb3e498aa2023-09-05 14:40:49 +000022
23from chromite.lib import build_target_lib
24from chromite.lib import chroot_lib
25from chromite.lib import commandline
26from chromite.lib import cros_build_lib
27from chromite.lib import parallel
28from chromite.lib import portage_util
29from chromite.lib.parser import package_info
30
31
Allen Webb7cc7cd92023-09-11 16:16:33 +000032VIRTUALS = {
Allen Webb4c582aa2023-09-12 17:20:49 +000033 "virtual/acl": ("sys-apps/acl", "media-libs/img-ddk-bin"),
34 "virtual/arc-opengles": (
35 "media-libs/arc-img-ddk",
36 "media-libs/arc-mesa-img",
37 "media-libs/arc-mali-drivers",
38 "media-libs/arc-mali-drivers-bifrost",
39 "media-libs/arc-mali-drivers-bifrost-bin",
40 "media-libs/arc-mali-drivers-valhall",
41 "media-libs/arc-mali-drivers-valhall-bin",
42 "media-libs/arc-mesa",
43 "media-libs/arc-mesa-amd",
44 "media-libs/arc-mesa-freedreno",
45 "media-libs/arc-mesa-iris",
46 "media-libs/arc-mesa-virgl",
47 "x11-drivers/opengles-headers",
48 ),
49 "virtual/cros-camera-hal": (
50 "media-libs/cros-camera-hal-intel-ipu3",
51 "media-libs/cros-camera-hal-intel-ipu6",
52 "media-libs/cros-camera-hal-mtk",
53 "media-libs/cros-camera-hal-qti",
54 "media-libs/cros-camera-hal-rockchip-isp1",
55 "media-libs/cros-camera-hal-usb",
56 "media-libs/qti-7c-camera-tuning",
57 ),
58 "virtual/img-ddk": ("media-libs/img-ddk", "media-libs/img-ddk-bin"),
59 "virtual/jpeg": ("media-libs/libjpeg-turbo", "media-libs/jpeg"),
60 "virtual/krb5": ("app-crypt/mit-krb5", "app-crypt/heimdal"),
Allen Webb7cc7cd92023-09-11 16:16:33 +000061 "virtual/libcrypt": ("sys-libs/libxcrypt",),
62 "virtual/libelf": ("dev-libs/elfutils", "sys-freebsd/freebsd-lib"),
63 "virtual/libiconv": ("dev-libs/libiconv",),
64 "virtual/libintl": ("dev-libs/libintl",),
65 "virtual/libudev": (
66 "sys-apps/systemd-utils",
67 "sys-fs/udev",
68 "sys-fs/eudev",
69 "sys-apps/systemd",
70 ),
71 "virtual/libusb": ("dev-libs/libusb", "sys-freebsd/freebsd-lib"),
Allen Webb4c582aa2023-09-12 17:20:49 +000072 "virtual/opengles": (
73 "media-libs/img-ddk",
74 "media-libs/img-ddk-bin",
75 "media-libs/mali-drivers-bin",
76 "media-libs/mali-drivers-bifrost",
77 "media-libs/mali-drivers-bifrost-bin",
78 "media-libs/mali-drivers-valhall",
79 "media-libs/mali-drivers-valhall-bin",
80 "media-libs/mesa",
81 "media-libs/mesa-amd",
82 "media-libs/mesa-freedreno",
83 "media-libs/mesa-iris",
84 "media-libs/mesa-llvmpipe",
85 "media-libs/mesa-panfrost",
86 "media-libs/mesa-reven",
87 "x11-drivers/opengles-headers",
88 ),
89 "virtual/vulkan-icd": (
90 "media-libs/img-ddk",
91 "media-libs/img-ddk-bin",
92 "media-libs/mali-drivers-bifrost",
93 "media-libs/mali-drivers-bifrost-bin",
94 "media-libs/mali-drivers-valhall",
95 "media-libs/mali-drivers-valhall-bin",
96 "media-libs/mesa",
97 "media-libs/mesa-freedreno",
98 "media-libs/mesa-iris",
99 "media-libs/mesa-llvmpipe",
100 "media-libs/mesa-radv",
101 "media-libs/vulkan-loader",
102 ),
Allen Webb7cc7cd92023-09-11 16:16:33 +0000103}
104
105
Allen Webb3e498aa2023-09-05 14:40:49 +0000106def env_to_libs(var: str) -> List[str]:
107 """Converts value of REQUIRES to a list of .so files.
108
109 For example:
110 "arm_32: libRSSupport.so libblasV8.so libc.so ..."
111 Becomes:
112 ["libRSSupport.so", "libblasV8.so", "libc.so", ...]
113 """
114 return [x for x in var.split() if not x.endswith(":")]
115
116
117class DotSoResolver:
118 """Provides shared library related dependency operations."""
119
120 def __init__(
121 self,
122 board: Optional[str] = None,
123 root: Union[os.PathLike, str] = "/",
124 chroot: Optional[chroot_lib.Chroot] = None,
125 ):
126 self.board = board
127 self.chroot = chroot if chroot else chroot_lib.Chroot()
128
129 self.sdk_db = portage_util.PortageDB()
130 self.db = self.sdk_db if root == "/" else portage_util.PortageDB(root)
131 self.provided_libs_cache = {}
132
Allen Webbe8c1da02023-09-08 18:25:22 +0000133 # Lazy initialize since it might not be needed.
134 self.lib_to_package_map = None
135
Allen Webb3e498aa2023-09-05 14:40:49 +0000136 def get_package(
137 self, query: str, from_sdk=False
138 ) -> Optional[portage_util.InstalledPackage]:
139 """Try to find an InstalledPackage for the provided package string"""
140 packages = (self.sdk_db if from_sdk else self.db).InstalledPackages()
141 info = package_info.parse(query)
142 for package in packages:
143 if info.package != package.package:
144 continue
145 if info.category != package.category:
146 continue
147 dep_info = package.package_info
148 if info.revision and info.revision != dep_info.revision:
149 continue
150 if info.pv and info.pv != dep_info.pv:
151 continue
152 return package
153 return None
154
155 def get_required_libs(self, package) -> Set[str]:
156 """Return a set of required .so files."""
Allen Webb8fc0bba2023-09-11 14:37:25 +0000157 requires = package.requires
158 if requires is not None:
159 return set(env_to_libs(package.requires))
160 # Fallback to needed if requires is not available.
161 aggregate = set()
162 needed = package.needed
163 if needed is not None:
164 for libs in needed.values():
165 aggregate.update(libs)
166 return aggregate
Allen Webb3e498aa2023-09-05 14:40:49 +0000167
Allen Webb7cc7cd92023-09-11 16:16:33 +0000168 def get_deps(self, package) -> List[portage_util.InstalledPackage]:
169 """Return a list of dependencies.
170
171 This expands the virtuals listed below.
172 """
Allen Webb3e498aa2023-09-05 14:40:49 +0000173 cpvr = f"{package.category}/{package.pf}"
Allen Webb7cc7cd92023-09-11 16:16:33 +0000174 expanded = []
175 deps = []
176 for dep in portage_util.GetFlattenedDepsForPackage(
Allen Webb3e498aa2023-09-05 14:40:49 +0000177 cpvr, board=self.board, depth=1
Allen Webb7cc7cd92023-09-11 16:16:33 +0000178 ):
179 info = package_info.parse(dep)
180 if not info:
181 continue
182
183 cp = info.cp
184 if cp in VIRTUALS:
185 expanded += VIRTUALS[cp]
186 continue
187
188 pkg = self.db.GetInstalledPackage(info.category, info.pvr)
189 if pkg:
190 deps.append(pkg)
191
192 for dep in expanded:
193 pkg = self.get_package(dep)
194 if pkg:
195 deps.append(pkg)
196
197 return deps
Allen Webb3e498aa2023-09-05 14:40:49 +0000198
199 def get_implicit_libs(self):
200 """Return a set of .so files that are provided by the system."""
Allen Webb611e8b12023-09-13 15:54:52 +0000201 # libstdc++ comes from the toolchain so always ignore it.
202 implicit_libs = {"libstdc++.so", "libstdc++.so.6"}
Allen Webb3e498aa2023-09-05 14:40:49 +0000203 for dep, from_sdk in (
204 ("cross-aarch64-cros-linux-gnu/glibc", True),
205 ("cross-armv7a-cros-linux-gnueabihf/glibc", True),
206 ("cross-i686-cros-linux-gnu/glibc", True),
207 ("cross-x86_64-cros-linux-gnu/glibc", True),
208 ("sys-libs/glibc", False),
209 ("sys-libs/libcxx", False),
210 ("sys-libs/llvm-libunwind", False),
211 ):
212 pkg = self.get_package(dep, from_sdk)
213 if not pkg:
214 continue
215 implicit_libs.update(self.provided_libs(pkg))
216 return implicit_libs
217
218 def provided_libs(self, package: portage_util.InstalledPackage) -> Set[str]:
219 """Return a set of .so files provided by |package|."""
220 cpvr = f"{package.category}/{package.pf}"
221 if cpvr in self.provided_libs_cache:
222 return self.provided_libs_cache[cpvr]
223
224 libs = set()
225 contents = package.ListContents()
226 # Keep only the .so files
227 for typ, path in contents:
228 if typ == package.DIR:
229 continue
230 filename = os.path.basename(path)
231 if filename.endswith(".so") or ".so." in filename:
232 libs.add(filename)
233 self.provided_libs_cache[cpvr] = libs
234 return libs
235
Allen Webb8fc0bba2023-09-11 14:37:25 +0000236 def cache_libs_from_build(
237 self, package: portage_util.InstalledPackage, image_dir: Path
238 ):
239 """Populate the provided_libs_cache for the package from the image dir.
240
241 When using build-info, CONTENTS might not be available yet. so provide
242 alternative using the destination directory of the ebuild.
243 """
244
245 cpvr = f"{package.category}/{package.pf}"
246 libs = set()
247 for _, _, files in os.walk(image_dir):
248 for file in files:
249 if file.endswith(".so") or ".so." in file:
250 libs.add(os.path.basename(file))
251 self.provided_libs_cache[cpvr] = libs
252
Allen Webb3e498aa2023-09-05 14:40:49 +0000253 def get_provided_from_all_deps(
254 self, package: portage_util.InstalledPackage
255 ) -> Set[str]:
256 """Return a set of .so files provided by the immediate dependencies."""
257 provided_libs = set()
Allen Webb8fc0bba2023-09-11 14:37:25 +0000258 # |package| may not actually be installed yet so manually add it to the
259 # since a package can depend on its own libs.
260 provided_libs.update(self.provided_libs(package))
Allen Webb7cc7cd92023-09-11 16:16:33 +0000261 for pkg in self.get_deps(package):
262 provided_libs.update(self.provided_libs(pkg))
Allen Webb3e498aa2023-09-05 14:40:49 +0000263 return provided_libs
264
Allen Webbe8c1da02023-09-08 18:25:22 +0000265 def lib_to_package(self, lib_filename: str = None) -> Set[str]:
266 """Return a set of packages that contain the library."""
267 if self.lib_to_package_map is None:
268 lookup = collections.defaultdict(set)
269 for pkg in self.db.InstalledPackages():
270 cpvr = f"{pkg.category}/{pkg.pf}"
271 # Packages with bundled libs for internal use and/or standaline
272 # binary packages.
273 if f"{pkg.category}/{pkg.package}" in (
274 "app-emulation/qemu",
275 "chromeos-base/aosp-frameworks-ml-nn-vts",
276 "chromeos-base/factory",
277 "chromeos-base/signingtools-bin",
278 "sys-devel/gcc-bin",
279 ):
280 continue
281 for lib in set(self.provided_libs(pkg)):
282 lookup[lib].add(cpvr)
283 self.lib_to_package_map = lookup
284 else:
285 lookup = self.lib_to_package_map
286 if not lib_filename:
287 return set()
288 try:
289 return lookup[lib_filename]
290 except KeyError:
291 return set()
Allen Webb3e498aa2023-09-05 14:40:49 +0000292
293
294def get_parser() -> commandline.ArgumentParser:
295 """Build the argument parser."""
296 parser = commandline.ArgumentParser(description=__doc__)
297
298 parser.add_argument("package", nargs="*", help="package atom")
299
300 parser.add_argument(
301 "-b",
302 "--board",
303 "--build-target",
304 default=cros_build_lib.GetDefaultBoard(),
305 help="ChromeOS board (Uses the SDK if not specified)",
306 )
307
308 parser.add_argument(
Allen Webbe8c1da02023-09-08 18:25:22 +0000309 "-i",
310 "--build-info",
311 default=None,
312 type=Path,
313 help="Path to build-info folder post src_install",
314 )
315
316 parser.add_argument(
Allen Webb8fc0bba2023-09-11 14:37:25 +0000317 "-x",
318 "--image",
319 default=None,
320 type=Path,
321 help="Path to image folder post src_install (${D} if unspecified)",
322 )
323
324 parser.add_argument(
Allen Webb3e498aa2023-09-05 14:40:49 +0000325 "--match",
326 default=False,
327 action="store_true",
328 help="Try to match missing libraries",
329 )
330
331 parser.add_argument(
332 "-j",
333 "--jobs",
334 default=None,
335 type=int,
336 help="Number of parallel processes",
337 )
338
339 return parser
340
341
342def parse_arguments(argv: List[str]) -> argparse.Namespace:
343 """Parse and validate arguments."""
344 parser = get_parser()
345 opts = parser.parse_args(argv)
Allen Webbe8c1da02023-09-08 18:25:22 +0000346 if opts.build_info and opts.package:
Allen Webb8fc0bba2023-09-11 14:37:25 +0000347 parser.error("Do not specify a package when setting --board-info")
348 if opts.image and not opts.build_info:
349 parser.error("--image requires --board-info")
Allen Webbe8c1da02023-09-08 18:25:22 +0000350 if opts.build_info or len(opts.package) == 1:
Allen Webb3e498aa2023-09-05 14:40:49 +0000351 opts.jobs = 1
352 return opts
353
354
355def check_package(
Allen Webbe8c1da02023-09-08 18:25:22 +0000356 package: portage_util.InstalledPackage,
Allen Webb3e498aa2023-09-05 14:40:49 +0000357 implicit: Set[str],
358 resolver: DotSoResolver,
359 match: bool,
360 debug: bool,
361) -> bool:
362 """Returns false if the package has missing dependencies"""
363 if not package:
364 print("missing package")
365 return False
366
367 provided = resolver.get_provided_from_all_deps(package)
368 if debug:
369 print("provided")
370 pprint.pprint(provided)
371
372 available = provided.union(implicit)
373 required = resolver.get_required_libs(package)
374 if debug:
375 print("required")
376 pprint.pprint(required)
377 unsatisfied = required - available
378 if unsatisfied:
379 cpvr = package.package_info.cpvr
380 print(f"'{cpvr}' missing deps for: ", end="")
381 pprint.pprint(unsatisfied)
382 if match:
383 missing = set()
384 for lib in unsatisfied:
Allen Webbe8c1da02023-09-08 18:25:22 +0000385 missing.update(resolver.lib_to_package(lib))
Allen Webb3e498aa2023-09-05 14:40:49 +0000386 if missing:
387 print(f"'{cpvr}' needs: ", end="")
388 pprint.pprint(missing)
389 return False
390 return True
391
392
393def main(argv: Optional[List[str]]):
394 """Main."""
395 opts = parse_arguments(argv)
396 opts.Freeze()
397
398 board = opts.board
399 root = build_target_lib.get_default_sysroot_path(board)
400 if board:
401 os.environ["PORTAGE_CONFIGROOT"] = root
402 os.environ["SYSROOT"] = root
403 os.environ["ROOT"] = root
404
405 failed = False
406 resolver = DotSoResolver(board, root)
Allen Webb3e498aa2023-09-05 14:40:49 +0000407
408 if not opts.package:
Allen Webbe8c1da02023-09-08 18:25:22 +0000409 if opts.build_info:
Allen Webb8fc0bba2023-09-11 14:37:25 +0000410 pkg = portage_util.InstalledPackage(resolver.db, opts.build_info)
411 image_path = opts.image or os.environ.get("D")
412 if image_path:
413 resolver.cache_libs_from_build(pkg, Path(image_path))
414 packages = [pkg]
Allen Webbe8c1da02023-09-08 18:25:22 +0000415 else:
416 packages = resolver.db.InstalledPackages()
Allen Webb3e498aa2023-09-05 14:40:49 +0000417 else:
418 packages = [resolver.get_package(p) for p in opts.package]
419
420 implicit = resolver.get_implicit_libs()
421 if opts.debug:
422 print("implicit")
423 pprint.pprint(implicit)
424
425 if opts.jobs == 1:
426 for package in packages:
427 if not check_package(
428 package,
Allen Webb3e498aa2023-09-05 14:40:49 +0000429 implicit,
430 resolver,
431 opts.match,
432 opts.debug,
433 ):
434 failed = True
435 else:
Allen Webbe8c1da02023-09-08 18:25:22 +0000436 if opts.match:
437 # Pre initialize the map before starting jobs.
438 resolver.lib_to_package()
Allen Webb3e498aa2023-09-05 14:40:49 +0000439 for ret in parallel.RunTasksInProcessPool(
440 lambda p: check_package(
Allen Webbe8c1da02023-09-08 18:25:22 +0000441 p, implicit, resolver, opts.match, opts.debug
Allen Webb3e498aa2023-09-05 14:40:49 +0000442 ),
443 [[p] for p in packages],
444 opts.jobs,
445 ):
446 if not ret:
447 failed = True
448
449 if failed:
450 sys.exit(1)
451
452
453if __name__ == "__main__":
454 main(sys.argv[1:])