1
0
Fork 0
mirror of https://github.com/xHyroM/void-packages.git synced 2024-12-27 22:41:06 +01:00
void-packages/common/scripts/parse-py-metadata.py

208 lines
6.7 KiB
Python

#!/usr/bin/python3
# vim: set ts=4 sw=4 et:
"""
Usage:
./parse-py-metadata.py -S "$DESTDIR/$py3_sitelib" provides -v "$version"
extract the names of top-level packages from:
- $DESTDIR/$py3_sitelib/*.dist-info/METADATA
- $DESTDIR/$py3_sitelib/*.egg-info/PKG-INFO
./parse-py-metadata.py -S "$DESTDIR/$py3_sitelib" [-s] [-C] depends -e "extra1 extra2 ..."
-D "$XBPS_STATEDIR/$pkgname-rdeps" -V <( xbps-query -R -p provides -s "py3:" )
check that the dependencies of a package match what's listed in the python
package metadata, using the virtual package provides entries generated by
`parse-py-metadata.py provides`.
This script requires python3-packaging-bootstrap to be installed in the chroot
to run (which should be taken care of by the python3-module and python3-pep517
build styles).
"""
import argparse
from pathlib import Path
from sys import stderr
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from packaging.metadata import Metadata
from packaging.requirements import Requirement
from packaging.utils import canonicalize_name
# packages to always ignore
global_ignore = ["tzdata"]
def msg_err(msg: str, *, nocolor: bool = False, strict: bool = False):
if nocolor:
print(msg, flush=True)
else:
color = "31" if strict else "33"
print(f"\033[1m\033[{color}m{msg}\033[m", file=stderr, flush=True)
def vpkgname(val: "str | Requirement", *, version: str | None = None) -> str:
sfx = ""
if version is not None:
sfx = f"-{version}"
if isinstance(val, Requirement):
name = val.name
else:
name = val
return f"py3:{canonicalize_name(name)}{sfx}"
def getpkgname(pkgver: str) -> str:
return pkgver.rpartition("-")[0]
def getpkgversion(pkgver: str) -> str:
return pkgver.rpartition("-")[2]
def getpkgdepname(pkgdep: str) -> str:
if "<" in pkgdep:
return pkgdep.partition("<")[0]
elif ">" in pkgdep:
return pkgdep.partition(">")[0]
else:
return pkgdep.rpartition("-")[0]
def match_markers(req: "Requirement", extras: set[str]) -> bool:
# unconditional requirement
if req.marker is None:
return True
# check the requirement for each extra we want and without any extras
if extras:
return req.marker.evaluate() or any(req.marker.evaluate({"extra": e}) for e in extras)
return req.marker.evaluate()
def find_metadata_files(sitepkgs: Path) -> list[Path]:
metafiles = list(sitepkgs.glob("*.dist-info/METADATA"))
metafiles.extend(sitepkgs.glob("*.egg-info/PKG-INFO"))
return metafiles
def parse_provides(args):
out = set()
for metafile in find_metadata_files(args.sitepkgs):
with metafile.open() as f:
raw = f.read()
meta = Metadata.from_email(raw, validate=False)
out.add(vpkgname(meta.name, version=getpkgversion(args.pkgver)))
if meta.provides_dist is not None:
out.update(map(lambda n: vpkgname(n, version=getpkgversion(args.pkgver)), meta.provides_dist))
# deprecated but may be used
if meta.provides is not None:
out.update(map(lambda n: vpkgname(n, version=getpkgversion(args.pkgver)), meta.provides))
print("\n".join(out), flush=True)
def parse_depends(args):
depends = dict()
vpkgs = dict()
extras = set(args.extras.split())
with args.vpkgs.open() as f:
for ln in f.readlines():
if not ln.strip():
continue
pkgver, _, rest = ln.partition(":")
vpkgvers, _, _ = rest.strip().partition("(")
pkg = getpkgname(pkgver)
vpkg = map(getpkgname, vpkgvers.split())
for v in vpkg:
vpkgs[v] = pkg
if args.rdeps.exists():
with args.rdeps.open() as f:
rdeps = list(map(getpkgdepname, f.read().split()))
else:
rdeps = []
for metafile in find_metadata_files(args.sitepkgs):
with metafile.open() as f:
raw = f.read()
meta = Metadata.from_email(raw, validate=False)
if meta.requires_dist is not None:
depends.update(map(lambda p: (vpkgname(p), None),
filter(lambda r: match_markers(r, extras), meta.requires_dist)))
# deprecated but may be used
if meta.requires is not None:
depends.update(map(lambda p: (vpkgname(p), None), meta.requires))
err = False
unknown = False
missing = []
for k in depends.keys():
if k in vpkgs.keys():
pkgname = vpkgs[k]
if pkgname in rdeps:
print(f" PYTHON: {k} <-> {pkgname}", flush=True)
elif pkgname in global_ignore:
print(f" PYTHON: {k} <-> {pkgname} (ignored)", flush=True)
else:
msg_err(f" PYTHON: {k} <-> {pkgname} NOT IN depends PLEASE FIX!",
nocolor=args.nocolor, strict=args.strict)
missing.append(pkgname)
err = True
else:
msg_err(f" PYTHON: {k} <-> UNKNOWN PKG PLEASE FIX!",
nocolor=args.nocolor, strict=args.strict)
unknown = True
err = True
if missing or unknown:
msg_err(f"=> {args.pkgver}: missing dependencies detected!",
nocolor=args.nocolor, strict=args.strict)
if missing:
msg_err(f"=> {args.pkgver}: please add these packages to depends: {' '.join(sorted(missing))}",
nocolor=args.nocolor, strict=args.strict)
if err and args.strict:
exit(1)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-S", dest="sitepkgs", type=Path)
parser.add_argument("-v", dest="pkgver")
parser.add_argument("-s", dest="strict", action="store_true")
parser.add_argument("-C", dest="nocolor", action="store_true")
subparsers = parser.add_subparsers()
prov_parser = subparsers.add_parser("provides")
prov_parser.set_defaults(func=parse_provides)
deps_parser = subparsers.add_parser("depends")
deps_parser.add_argument("-e", dest="extras", default="")
deps_parser.add_argument("-V", dest="vpkgs", type=Path)
deps_parser.add_argument("-D", dest="rdeps", type=Path)
deps_parser.set_defaults(func=parse_depends)
args = parser.parse_args()
try:
from packaging.metadata import Metadata
from packaging.requirements import Requirement
from packaging.utils import canonicalize_name
except ImportError:
msg_err(f"=> WARNING: {args.pkgver}: missing packaging module!\n"
f"=> WARNING: {args.pkgver}: please add python3-packaging-bootstrap to hostmakedepends to run this check",
nocolor=args.nocolor)
exit(0)
args.func(args)