SurfSense/surfsense_local/backend/scripts/write_python_notices.py
Cedric Karungu 6f5eff039b feat(local): generate third-party notices at build time
Collect licence notices for the npm trees, the Python sidecars, the staged
native runtimes and the bundled model packs, merge them into
THIRD_PARTY_NOTICES.json and .txt, and ship them as resources/notices.
The merge fails the build when a dependency has no licence text and the
reviewed allowlist gives no reason.

Refs #1941
2026-10-06 18:10:43 +02:00

56 lines
1.8 KiB
Python

"""Write the Python third-party notices fragment for the desktop installer.
uv run scripts/write_python_notices.py <fragments-dir>
Names come from `uv export --frozen --no-dev` for this platform; versions, ids
and texts from the installed distributions. `build:notices` in electron/ runs it.
"""
import argparse
import json
import os
import shutil
import subprocess
from importlib.metadata import distribution
from pathlib import Path
from notices.distribution_notice import python_notices
from notices.frozen_runtime import PYINSTALLER, PYINSTALLER_NOTE, interpreter_notice
from notices.shipped_requirements import shipped_names
BACKEND = Path(__file__).resolve().parents[1]
def exported_requirements() -> str:
# `uv run` sets UV to its own path; PATH is the fallback.
uv = os.environ.get("UV") or shutil.which("uv") or "uv"
command = [uv, "export", "--frozen", "--no-dev", "--no-hashes", "--no-emit-project"]
return subprocess.run(
command, cwd=BACKEND, check=True, capture_output=True, text=True
).stdout
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument("fragments", type=Path)
args = parser.parse_args()
names = [*shipped_names(exported_requirements()), PYINSTALLER]
entries = [
interpreter_notice(),
*python_notices(names, distribution, {PYINSTALLER: PYINSTALLER_NOTE}),
]
args.fragments.mkdir(parents=True, exist_ok=True)
out = args.fragments / "python.json"
out.write_text(
json.dumps({"entries": entries}, indent=2) + "\n",
encoding="utf-8",
newline="\n",
)
bare = [e["name"] for e in entries if not e["text"]]
print(f"{len(entries)} Python distributions in {out}")
if bare:
print(f"no licence text: {', '.join(bare)}")
if __name__ == "__main__":
main()