91e75e620b
CI: cua-driver distro-compat matrix / Resolve release version (push) Waiting to run
CI: cua-driver distro-compat matrix / debian:12 (glibc 2.36) (push) Blocked by required conditions
CI: cua-driver distro-compat matrix / fedora:41 (glibc 2.40) (push) Blocked by required conditions
CI: cua-driver distro-compat matrix / rockylinux:9 (glibc 2.34) (push) Blocked by required conditions
CI: cua-driver distro-compat matrix / ubuntu:22.04 (glibc 2.35) (push) Blocked by required conditions
CI: cua-driver distro-compat matrix / ubuntu:24.04 (glibc 2.39) (push) Blocked by required conditions
CI: cua-driver distro-compat matrix / Distro compat summary (push) Blocked by required conditions
CI: Nix Linux Rust source / Nix / compositor build (push) Waiting to run
CI: Nix Linux Rust source / Nix / driver package (push) Waiting to run
CI: Nix Linux Rust source / Nix / Rust unit tests (push) Waiting to run
CI: Rust Linux unit / Rust Linux unit and compile (push) Waiting to run
CI: Rust Windows unit / Rust Windows unit and compile (push) Waiting to run
CI: SPDX Headers / Check SPDX headers (warn-only) (push) Waiting to run
CD: Docs MCP Server / build (linux/amd64) (push) Waiting to run
CD: Docs MCP Server / build (linux/arm64) (push) Waiting to run
CD: Docs MCP Server / merge (push) Blocked by required conditions
125 lines
3.9 KiB
Python
Executable File
125 lines
3.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Add or verify SPDX + copyright headers on cua-driver source files.
|
|
|
|
Two modes:
|
|
--apply (default): insert the header on files that lack it
|
|
--check: exit 1 if any tracked file is missing the header (use in CI)
|
|
|
|
Header format (matches Linux-kernel / Rust ecosystem convention):
|
|
|
|
// SPDX-License-Identifier: MIT
|
|
// Copyright (c) 2026 Cua AI, Inc.
|
|
//
|
|
// <blank line>
|
|
// <original file content>
|
|
|
|
The script is idempotent: it skips any file that already contains an
|
|
SPDX-License-Identifier marker in its first 2 KiB.
|
|
|
|
Typical use:
|
|
scripts/spdx-headers.py # apply to libs/cua-driver
|
|
scripts/spdx-headers.py libs/cua-driver-rs # apply to a different tree
|
|
scripts/spdx-headers.py --check # CI gate
|
|
scripts/spdx-headers.py --dry-run # preview only
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import pathlib
|
|
import sys
|
|
from typing import Iterable
|
|
|
|
LICENSE_ID = "MIT"
|
|
COPYRIGHT_HOLDER = "Cua AI, Inc."
|
|
COPYRIGHT_YEAR = "2026"
|
|
|
|
HEADER_LINES = [
|
|
f"// SPDX-License-Identifier: {LICENSE_ID}",
|
|
f"// Copyright (c) {COPYRIGHT_YEAR} {COPYRIGHT_HOLDER}",
|
|
"",
|
|
]
|
|
MARKER = "SPDX-License-Identifier"
|
|
EXTENSIONS = {".swift", ".rs"}
|
|
SKIP_DIR_NAMES = {"target", ".build", "build", "node_modules", "DerivedData", ".git"}
|
|
DEFAULT_ROOT = "libs/cua-driver"
|
|
|
|
|
|
def file_has_header(path: pathlib.Path) -> bool:
|
|
try:
|
|
head = path.read_bytes()[:2048].decode("utf-8", errors="replace")
|
|
except OSError:
|
|
return True # don't try to rewrite unreadable files
|
|
return MARKER in head
|
|
|
|
|
|
def insert_header(path: pathlib.Path) -> None:
|
|
body = path.read_text(encoding="utf-8")
|
|
path.write_text("\n".join(HEADER_LINES) + "\n" + body, encoding="utf-8")
|
|
|
|
|
|
def iter_source_files(root: pathlib.Path) -> Iterable[pathlib.Path]:
|
|
for path in root.rglob("*"):
|
|
if not path.is_file():
|
|
continue
|
|
if path.suffix not in EXTENSIONS:
|
|
continue
|
|
if any(part in SKIP_DIR_NAMES for part in path.parts):
|
|
continue
|
|
yield path
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
parser.add_argument("root", nargs="?", default=DEFAULT_ROOT, help=f"directory to scan (default: {DEFAULT_ROOT})")
|
|
mode = parser.add_mutually_exclusive_group()
|
|
mode.add_argument("--apply", action="store_true", help="insert headers on files that lack them (default)")
|
|
mode.add_argument("--check", action="store_true", help="exit 1 if any file is missing a header")
|
|
mode.add_argument("--dry-run", action="store_true", help="report what would change without writing")
|
|
args = parser.parse_args()
|
|
|
|
root = pathlib.Path(args.root).resolve()
|
|
if not root.exists():
|
|
print(f"error: {root} does not exist", file=sys.stderr)
|
|
return 2
|
|
|
|
apply_mode = args.apply or not (args.check or args.dry_run)
|
|
missing: list[pathlib.Path] = []
|
|
touched = 0
|
|
skipped = 0
|
|
|
|
for path in iter_source_files(root):
|
|
if file_has_header(path):
|
|
skipped += 1
|
|
continue
|
|
missing.append(path)
|
|
rel = path.relative_to(root)
|
|
if apply_mode:
|
|
insert_header(path)
|
|
touched += 1
|
|
print(f"+ {rel}")
|
|
elif args.dry_run:
|
|
print(f"would add header: {rel}")
|
|
else: # --check
|
|
print(f"missing header: {rel}")
|
|
|
|
print()
|
|
if args.check:
|
|
if missing:
|
|
print(f"error: {len(missing)} file(s) missing SPDX header under {root}", file=sys.stderr)
|
|
return 1
|
|
print(f"ok: all {skipped} source files under {root} carry SPDX headers")
|
|
return 0
|
|
|
|
if args.dry_run:
|
|
print(f"would add headers to {len(missing)} file(s); {skipped} already have one")
|
|
return 0
|
|
|
|
print(f"added headers to {touched} file(s); {skipped} already had one")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|