ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
check_no_unsafe_python_install.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# SPDX-License-Identifier: MIT
3# Copyright (c) 2026 Brighton Sikarskie
4"""Reject first-party attempts to override PEP 668 package ownership.
5
6Python-managed repository tools belong in a virtual environment. A system-pip
7override can mutate apt-owned files, while a user-site fallback makes the
8interpreter and PATH depend on whichever account happened to provision a host.
9Scan every authored tracked or untracked file so the unsafe option cannot
10return in workflows, images, provisioning, documentation, or error hints.
11"""
12
13from __future__ import annotations
14
15import shutil
16import subprocess
17import sys
18from pathlib import Path
19
20REPO_ROOT = Path(__file__).resolve().parents[2]
21SELF = "scripts/checks/check_no_unsafe_python_install.py"
22FORBIDDEN = "--break-" + "system-packages"
23EXCLUDED_PREFIXES = (
24 "docs/sbom/upstream/",
25 "libs/third_party/",
26 "apps/shared_libs/third_party/",
27 "port/netxduo/",
28 "port/nimble/",
29 "port/threadx/",
30 "port/usbx/",
31 "tests/fixtures/",
32)
33MIN_SCOPED_FILES = 4000
34
35
36def scoped_files() -> list[str]:
37 """Return all first-party files known to Git, including new files."""
38 git_bin = shutil.which("git") or "git"
39 proc = subprocess.run( # noqa: S603 -- resolved Git executable; fixed arguments
40 [git_bin, "ls-files", "--cached", "--others", "--exclude-standard", "-z"],
41 cwd=REPO_ROOT,
42 check=True,
43 capture_output=True,
44 )
45 rels = proc.stdout.decode("utf-8", errors="strict").split("\0")
46 selected = {
47 rel
48 for rel in rels
49 if rel and not rel.startswith(EXCLUDED_PREFIXES) and (REPO_ROOT / rel).is_file()
50 }
51 if (REPO_ROOT / SELF).is_file():
52 selected.add(SELF)
53 return sorted(selected)
54
55
56def scan_text(text: str) -> list[int]:
57 """Return one-based line numbers containing the unsafe pip option."""
58 return [number for number, line in enumerate(text.splitlines(), start=1) if FORBIDDEN in line]
59
60
61def scan(rels: list[str]) -> list[str]:
62 """Return path/line findings, skipping non-text tracked assets."""
63 findings: list[str] = []
64 for rel in rels:
65 try:
66 text = (REPO_ROOT / rel).read_text(encoding="utf-8")
67 except UnicodeDecodeError:
68 continue
69 findings.extend(f"{rel}:{line}" for line in scan_text(text))
70 return findings
71
72
73def selftest() -> int:
74 """Prove the detector fires and accepts isolated installation guidance."""
75 unsafe = "python3 -m pip install " + FORBIDDEN + " libclang"
76 cases = (
77 (unsafe, [1], "an unsafe active install fires"),
78 ("hint: " + unsafe, [1], "an unsafe documentation hint fires"),
79 ("python3 -m venv .venv\n.venv/bin/pip install libclang", [], "a venv passes"),
80 ("python3 -m pip --version", [], "a non-mutating pip probe passes"),
81 )
82 failures = [label for text, expected, label in cases if scan_text(text) != expected]
83 if failures:
84 for failure in failures:
85 print(f"check_no_unsafe_python_install.py --selftest: FAIL: {failure}", file=sys.stderr)
86 return 1
87 print(f"check_no_unsafe_python_install.py --selftest: PASS ({len(cases)} cases)")
88 return 0
89
90
91def main() -> int:
92 """Run detector self-tests or scan the live first-party tree."""
93 if sys.argv[1:] == ["--selftest"]:
94 return selftest()
95 if sys.argv[1:]:
96 print("usage: check_no_unsafe_python_install.py [--selftest]", file=sys.stderr)
97 return 2
98 try:
99 rels = scoped_files()
100 except (OSError, subprocess.CalledProcessError, UnicodeError) as exc:
101 print(f"cannot enumerate first-party files: {exc}", file=sys.stderr)
102 return 2
103 if len(rels) < MIN_SCOPED_FILES or SELF not in rels:
104 print(
105 f"scope collapsed to {len(rels)} files; expected at least "
106 f"{MIN_SCOPED_FILES} including {SELF}",
107 file=sys.stderr,
108 )
109 return 2
110 findings = scan(rels)
111 if findings:
112 print("unsafe system-Python package override found:", file=sys.stderr)
113 for finding in findings:
114 print(f" {finding}", file=sys.stderr)
115 print("Create a venv and wire its interpreter/PATH explicitly.", file=sys.stderr)
116 return 1
117 print(f"check_no_unsafe_python_install.py: clean ({len(rels)} first-party files)")
118 return 0
119
120
121if __name__ == "__main__":
122 raise SystemExit(main())
void main(void)
The application entry point Reset_Handler hands control to.
Definition main.c:298