mirror of
https://github.com/aphrody-labs/msys2-devtools.git
synced 2026-10-14 04:19:12 +00:00
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:
1 parent
b1890a8103
commit
403c277e4b
3 files changed
+70
-8
No files matched your search
+57
-7
@@ -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):
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user