chore: prepare release v0.4.0
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user