repo-buildinfo: add option to filter based on the DLL imports

For example:

	--imports-dll libstdc++-6.dll --built-with-package "mingw-w64-ucrt-x86_64-gcc<16.1.0"

to get all packages linking against an older libstdc++
This commit is contained in:
Christoph Reiter committed 2026-05-01 20:45:27 +02:00
1 parent b1890a8103
commit 403c277e4b
3 files changed
+70 -8

No files matched your search

+57 -7
View File
@@ -4,11 +4,13 @@ import argparse
import fnmatch
import os
import sys
from contextlib import contextmanager, closing
from msys2_devtools.exttarfile import tarfile
from msys2_devtools.utils import vercmp
from fastprogress.fastprogress import progress_bar
from concurrent.futures import ThreadPoolExecutor
import pefile
def find_dbs(target_dir: str) -> list[str]:
@@ -53,20 +55,57 @@ def parse_buildinfo(buildinfo: str) -> dict[str, list[str]]:
return res
def get_buildinfo(path: str) -> dict:
@contextmanager
def pefile_set_max_import_symbols(max_symbols: int):
old = pefile.MAX_IMPORT_SYMBOLS
pefile.MAX_IMPORT_SYMBOLS = max_symbols
try:
yield
finally:
pefile.MAX_IMPORT_SYMBOLS = old
# python uses .pyd for extensions, ruby uses .so, and octave uses .oct
PE_FILE_EXTENSIONS = (".dll", ".exe", ".pyd", ".so", ".oct")
def get_imported_dlls(data: bytes, name: str) -> set[str]:
try:
with pefile_set_max_import_symbols(0x10000), \
closing(pefile.PE(data=data, fast_load=True)) as pe:
pe.parse_data_directories(directories=[
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_IMPORT']
])
if len(pe.get_warnings()) > 0:
print(f"Warnings for PE ({name}):")
pe.show_warnings()
if not hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
return set()
return {entry.dll.decode().lower() for entry in pe.DIRECTORY_ENTRY_IMPORT}
except pefile.PEFormatError:
print(f"PEFormatError while parsing PE file ({name}), skipping imports")
return set()
def get_buildinfo(path: str, needs_files: bool, needs_imports: bool) -> tuple[dict, set[str], set[str]]:
buildinfo = None
files = set()
imports = set()
with tarfile.TarFile.open(path, mode="r") as tar:
for info in tar.getmembers():
if info.name == ".BUILDINFO":
buildinfo = tar.extractfile(info).read().decode()
if info.isfile() and not info.name.startswith("."):
if needs_files and info.isfile() and not info.name.startswith("."):
files.add(info.name)
if needs_imports and info.isfile() and info.name.lower().endswith(PE_FILE_EXTENSIONS):
data = tar.extractfile(info).read()
imports.update(get_imported_dlls(data, info.name))
if buildinfo is None:
raise RuntimeError(f"Cannot find .BUILDINFO in {path}")
return parse_buildinfo(buildinfo), files
return parse_buildinfo(buildinfo), files, imports
def parse_vercmp(comparison_expression) -> tuple[str, str | None, str | None]:
@@ -126,21 +165,28 @@ def main(argv):
"(optionally with a version constraint, e.g. 'foo>=1.0')")
parser.add_argument("--contains-file",
help="filter packages that contain files matching this glob pattern (e.g. '*.a')")
parser.add_argument("--imports-dll",
action="append",
default=[],
metavar="DLL",
help="filter packages that import this DLL (e.g. 'libfoo.dll'); "
"can be given multiple times")
args = parser.parse_args(argv[1:])
installed_filter = args.built_with_package
file_pattern = args.contains_file
dll_filters = args.imports_dll
found = set()
paths = get_package_paths(args.root)
def process_path(p):
buildinfo, files = get_buildinfo(p)
return buildinfo, files
buildinfo, files, imports = get_buildinfo(p, needs_files=file_pattern is not None, needs_imports=bool(dll_filters))
return buildinfo, files, imports
with ThreadPoolExecutor(4) as executor:
for buildinfo, files in progress_bar(executor.map(process_path, paths), total=len(paths), leave=False):
for buildinfo, files, imports in progress_bar(executor.map(process_path, paths), total=len(paths), leave=False):
if installed_filter is not None:
for installed_full_name in buildinfo["installed"]:
if package_matches(installed_full_name, installed_filter):
@@ -155,6 +201,10 @@ def main(argv):
else:
continue
if dll_filters:
if not any(dll in imports for dll in dll_filters):
continue
found.update(buildinfo["pkgbase"])
for f in sorted(found):
+1
View File
@@ -34,6 +34,7 @@ all = [
"pgpdump>=1.5,<2",
"fastprogress>=1.0.3,<1.1",
"requests-cache>=1.2.1,<2",
"pefile>=2024.8.26",
]
[project.scripts]
Generated
+12 -1
View File
@@ -3,7 +3,7 @@ revision = 3
requires-python = ">=3.11.0, <4.0"
[options]
exclude-newer = "2026-04-24T11:04:34.845645838Z"
exclude-newer = "2026-04-24T18:16:55.102883823Z"
exclude-newer-span = "P7D"
[[package]]
@@ -316,6 +316,7 @@ all = [
{ name = "fastprogress" },
{ name = "netaddr" },
{ name = "packageurl-python" },
{ name = "pefile" },
{ name = "pgpdump" },
{ name = "requests-cache" },
]
@@ -354,6 +355,7 @@ requires-dist = [
{ name = "packageurl-python", marker = "extra == 'all'", specifier = ">=0.17.0,<0.18" },
{ name = "packageurl-python", marker = "extra == 'pypi-cache'", specifier = ">=0.17.0,<0.18" },
{ name = "packageurl-python", marker = "extra == 'sbom'", specifier = ">=0.17.0,<0.18" },
{ name = "pefile", marker = "extra == 'all'", specifier = ">=2024.8.26" },
{ name = "pgpdump", marker = "extra == 'all'", specifier = ">=1.5,<2" },
{ name = "pgpdump", marker = "extra == 'sigstats'", specifier = ">=1.5,<2" },
{ name = "pydantic", specifier = ">=2.0,<3" },
@@ -398,6 +400,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7a/c2/920ef838e2f0028c8262f16101ec09ebd5969864e5a64c4c05fad0617c56/packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f", size = 95831, upload-time = "2026-04-14T21:12:47.56Z" },
]
[[package]]
name = "pefile"
version = "2024.8.26"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/03/4f/2750f7f6f025a1507cd3b7218691671eecfd0bbebebe8b39aa0fe1d360b8/pefile-2024.8.26.tar.gz", hash = "sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632", size = 76008, upload-time = "2024-08-26T20:58:38.155Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/54/16/12b82f791c7f50ddec566873d5bdd245baa1491bac11d15ffb98aecc8f8b/pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f", size = 74766, upload-time = "2024-08-26T21:01:02.632Z" },
]
[[package]]
name = "pgpdump"
version = "1.5"