#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-3.0-or-later # SPDX-FileCopyrightText: 2025 ai-ad4 and the FreePDFEditor contributors # # License gate (§8.2). Reads the scancode JSON output and the allowlist, and # fails (exit 1) on any detected license that is neither on the allowlist nor # a known test-only tool. Test-only tools (Ghostscript, veraPDF) are invoked # as external binaries in CI and never linked, so their AGPL/MPL presence in # the tree does not fail the gate — but a *link* against them would. # # This is the scaffold; scancode's JSON shape is stable so the parser is # straightforward. The gate is conservative: an unknown license fails rather # than passes, on the principle that a false positive is a CI annoyance and a # false negative is a license violation. from __future__ import annotations import json import sys import pathlib def load_allowlist(path: pathlib.Path) -> tuple[set[str], set[str]]: allowed: set[str] = set() test_only: set[str] = set() in_test_only = False for line in path.read_text(encoding="utf-8").splitlines(): line = line.strip() if not line or line.startswith("#"): continue if line == "TEST-ONLY:": in_test_only = True continue if line.startswith("- "): if in_test_only: test_only.add(line[2:].strip()) continue in_test_only = False allowed.add(line) return allowed, test_only def main(argv: list[str]) -> int: if len(argv) < 3: print("usage: check-licenses.py ", file=sys.stderr) return 2 scancode_path = pathlib.Path(argv[1]) allowlist_path = pathlib.Path(argv[2]) allowed, test_only = load_allowlist(allowlist_path) data = json.loads(scancode_path.read_text(encoding="utf-8")) failures: list[str] = [] for entry in data.get("files", []): path = entry.get("path", "?") for lic in entry.get("licenses", []): key = lic.get("key") or lic.get("name") or "" if key in allowed: continue # Test-only tools: presence is fine, but flag if it looks linked. if path in test_only: continue failures.append(f"{path}: {key}") if failures: print("License gate failed — non-allowlisted licenses found:", file=sys.stderr) for f in failures: print(f" {f}", file=sys.stderr) return 1 print("License gate passed.") return 0 if __name__ == "__main__": sys.exit(main(sys.argv[1:]))