• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pantsbuild / pants / 24637157883

19 Apr 2026 07:23PM UTC coverage: 52.377% (-40.5%) from 92.924%
24637157883

Pull #23274

github

web-flow
Merge b54f275c2 into 0283af69e
Pull Request #23274: rust: upgrade to v1.95.0

31658 of 60443 relevant lines covered (52.38%)

1.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/python/pants/backend/nfpm/native_libs/elfdeps/analyze.py
1
# Copyright 2025 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
"""A standalone script that uses elfdeps to analyze ELF binaries/libraries.
5

6
Rule code must treat this script as a `resource`, running it as a subprocess in a sandboxed venv.
7
Rule code must not import anything from this script or use it as a `python_source`.
8
"""
9

10
from __future__ import annotations
×
11

12
import argparse
×
13
import json
×
14
import sys
×
15
import zipfile
×
16
from collections.abc import Generator, Iterable
×
17
from dataclasses import asdict, dataclass
×
18
from pathlib import Path
×
19

20
# elfdeps 0.2.0 added analyze_zipfile
21
from elfdeps import ELFAnalyzeSettings, ELFInfo, SOInfo, analyze_dirtree, analyze_zipfile
×
22

23

24
@dataclass(frozen=True)
×
25
class ELFInfoAnalysis:
×
26
    provides: tuple[SOInfo, ...]
×
27
    requires: tuple[SOInfo, ...]
×
28

29
    def __init__(self, provides: Iterable[SOInfo], requires: Iterable[SOInfo]):
×
30
        object.__setattr__(self, "provides", tuple(sorted(provides)))
×
31
        object.__setattr__(self, "requires", tuple(sorted(requires)))
×
32

33
    def to_dict(self) -> dict[str, list[dict[str, str]]]:
×
34
        # so_info: SOInfo(soname: str, version: str, marker: str)
35
        # marker is one of "(64bit)" or ""
36
        # str(so_info) = f"{soname}({version}){marker}"
37

38
        def so_infos_to_dicts(so_infos: tuple[SOInfo, ...]) -> list[dict[str, str]]:
×
39
            return [asdict(so_info) | {"so_info": str(so_info)} for so_info in so_infos]
×
40

41
        return {
×
42
            "provides": so_infos_to_dicts(self.provides),
43
            "requires": so_infos_to_dicts(self.requires),
44
        }
45

46
    def to_json(self, indent=None, separators=(",", ":")) -> str:
×
47
        return json.dumps(self.to_dict(), indent=indent, separators=separators)
×
48

49
    @classmethod
×
50
    def from_elf_infos(cls, elf_infos: Iterable[ELFInfo]) -> ELFInfoAnalysis:
×
51
        provides: set[SOInfo] = set()
×
52
        requires: set[SOInfo] = set()
×
53
        for elf_info in elf_infos:
×
54
            provides.update(elf_info.provides)  # elf_info.provides: list[SOInfo]
×
55
            requires.update(elf_info.requires)  # elf_info.requires: list[SOInfo]
×
56

57
        return cls(tuple(provides), tuple(requires))
×
58

59

60
def analyze_wheel(wheel_path: Path, settings: ELFAnalyzeSettings) -> Generator[ELFInfo]:
×
61
    print(".", end="", file=sys.stderr)  # a progress indicator
×
62
    with zipfile.ZipFile(wheel_path, mode="r") as wheel:
×
63
        yield from analyze_zipfile(wheel, settings=settings)
×
64

65

66
def analyze_wheels_repo(wheel_repo: Path) -> ELFInfoAnalysis:
×
67
    settings = ELFAnalyzeSettings(unique=True)
×
68

69
    print(f"Analyzing wheels in {wheel_repo}", file=sys.stderr)
×
70
    elf_infos: list[ELFInfo] = [
×
71
        elf_info for wheel in wheel_repo.iterdir() for elf_info in analyze_wheel(wheel, settings)
72
    ]
73
    print(f"Done analyzing wheels in {wheel_repo}.", file=sys.stderr)  # end progress indicators
×
74

75
    return ELFInfoAnalysis.from_elf_infos(elf_infos)
×
76

77

78
def analyze_directory(directory: Path) -> ELFInfoAnalysis:
×
79
    settings = ELFAnalyzeSettings(unique=True)
×
80

81
    print(f"Analyzing files in {directory}", file=sys.stderr)
×
82
    elf_infos: list[ELFInfo] = list(analyze_dirtree(directory, settings=settings))
×
83
    print(f"Done analyzing files in {directory}.", file=sys.stderr)  # end progress indicators
×
84

85
    return ELFInfoAnalysis.from_elf_infos(elf_infos)
×
86

87

88
def main(args: list[str]) -> int:
×
89
    arg_parser = argparse.ArgumentParser()
×
90
    arg_parser.add_argument("--mode", required=True, choices=("wheels", "files"))
×
91
    arg_parser.add_argument("directory", nargs=1, type=Path)
×
92
    options = arg_parser.parse_args()
×
93

94
    directory = options.directory[0]
×
95
    if not directory.resolve().is_dir():
×
96
        raise NotADirectoryError(f"{directory} is not a directory (or a symlink to a directory)!")
×
97

98
    if options.mode == "wheels":
×
99
        elf_info_analysis = analyze_wheels_repo(wheel_repo=directory)
×
100
    elif options.mode == "files":
×
101
        elf_info_analysis = analyze_directory(directory=directory)
×
102

103
    print(elf_info_analysis.to_json())
×
104

105
    return 0
×
106

107

108
if __name__ == "__main__":
×
109
    sys.exit(main(sys.argv))
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc