Files
volumen/tools/license-audit.py
T

89 lines
2.6 KiB
Python
Raw Permalink Normal View History

2026-07-26 18:15:29 +02:00
#!/usr/bin/env python3
"""Audit ``uv.lock`` for license compliance.
Parses the lockfile (TOML) and reports each package's ``license`` field.
The tool is intentionally read-only — it never modifies ``uv.lock``.
Permissive licenses (MIT, BSD-2/3-Clause, Apache-2.0, ISC, MPL-2.0,
PSF-2.0, etc.) are flagged as OK; everything else is reported as a
warning. Unknown or missing licenses are also reported as warnings.
Usage::
uv run python tools/license-audit.py
"""
from __future__ import annotations
import sys
import tomllib
from pathlib import Path
from typing import Any
LOCK_PATH = Path(__file__).resolve().parent.parent / "uv.lock"
PERMISSIVE_LICENSES: frozenset[str] = frozenset(
{
# SPDX expressions (case-insensitive)
"mit",
"mit-0",
"apache-2.0",
"apache-2.0-with-clauses",
"bsd-2-clause",
"bsd-3-clause",
"isc",
"mpl-2.0",
"python-2.0",
"psf-2.0",
"zlib",
"cc0-1.0",
"cc-by-4.0",
"unlicense",
"0bsd",
"blueoak-1.0.0",
"lgpl-2.1",
"lgpl-3.0",
}
)
def main() -> int:
if not LOCK_PATH.exists():
print(f"error: {LOCK_PATH} not found", file=sys.stderr)
return 2
with LOCK_PATH.open("rb") as fh:
data: dict[str, Any] = tomllib.load(fh)
packages: list[dict[str, Any]] = list(data.get("package", []))
if not packages:
print("error: no [[package]] entries in uv.lock", file=sys.stderr)
return 2
warnings_count = 0
for pkg in packages:
name = pkg.get("name", "?")
version = pkg.get("version", "?")
license_field = pkg.get("license")
if license_field is None:
# uv stores a separate ``[[package.license]]`` table only for
# SPDX-refs; for many packages the field is absent.
print(f"WARN {name}=={version}: license field missing")
warnings_count += 1
continue
text = str(license_field).strip().lower()
# Compound expressions like ``MIT OR Apache-2.0`` are accepted when
# any single token is permissive.
tokens = {
tok.strip(" ()")
for tok in text.replace(" and ", " ").replace(" or ", " ").split()
}
if tokens & PERMISSIVE_LICENSES:
print(f"OK {name}=={version}: {text}")
else:
print(f"WARN {name}=={version}: {text}")
warnings_count += 1
print(f"\nAudited {len(packages)} packages; {warnings_count} warning(s).")
return 0
if __name__ == "__main__":
raise SystemExit(main())