mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-08-06 10:49:33 +00:00
ci: audit release flags and dependency pins
This commit is contained in:
@@ -28,6 +28,10 @@ jobs:
|
||||
- name: Build ${{ matrix.environment }}
|
||||
run: pio run -e ${{ matrix.environment }}
|
||||
|
||||
- name: Audit release artifact
|
||||
if: matrix.environment == 'tdeck-release'
|
||||
run: python tools/audit_release_build.py
|
||||
|
||||
- name: Upload firmware artifact
|
||||
if: matrix.environment == 'tdeck-release'
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
@@ -33,6 +33,9 @@ jobs:
|
||||
- name: Build release-qualified T-Deck firmware
|
||||
run: pio run -e tdeck-release
|
||||
|
||||
- name: Audit release artifact
|
||||
run: python tools/audit_release_build.py
|
||||
|
||||
- name: Determine version string
|
||||
id: version
|
||||
run: |
|
||||
|
||||
@@ -47,8 +47,10 @@ def test_ci_and_deployment_build_the_release_environment():
|
||||
assert "environment: [tdeck, tdeck-release]" in build_check
|
||||
assert "matrix.environment == 'tdeck-release'" in build_check
|
||||
assert ".pio/build/tdeck-release/firmware.bin" in build_check
|
||||
assert "python tools/audit_release_build.py" in build_check
|
||||
|
||||
assert "pio run -e tdeck-release" in release
|
||||
assert "python tools/audit_release_build.py" in release
|
||||
assert ".pio/build/tdeck-release/bootloader.bin" in release
|
||||
assert ".pio/build/tdeck-release/partitions.bin" in release
|
||||
assert ".pio/build/tdeck-release/firmware.bin" in release
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fail closed when a tdeck-release build is not release-qualified."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
ENVIRONMENT = "tdeck-release"
|
||||
BUILD = ROOT / ".pio" / "build" / ENVIRONMENT
|
||||
EXCLUDED_DEFINITIONS = (
|
||||
"PYXIS_TEST_HOOKS",
|
||||
"PYXIS_TEST_TCP_HOST",
|
||||
"PYXIS_TEST_TCP_PORT",
|
||||
"MEMORY_INSTRUMENTATION_ENABLED",
|
||||
"BOOT_PROFILING_ENABLED",
|
||||
)
|
||||
EXCLUDED_STRINGS = (
|
||||
b"T:CALL_PROFILE",
|
||||
b"T:CALL_INJECT",
|
||||
b"T:DEST",
|
||||
b"PYXIS_TEST_HOOKS",
|
||||
b"Boot Profile Summary",
|
||||
b"Memory monitor started",
|
||||
)
|
||||
EXCLUDED_SYMBOLS = (
|
||||
"test_call_",
|
||||
"process_test_serial_command",
|
||||
"MemoryMonitor::",
|
||||
"BootProfiler::",
|
||||
)
|
||||
PINNED_DEPENDENCIES = {
|
||||
"microReticulum": "6054f6ba82367628a85cd07fcb668b95e947f046",
|
||||
"microLXMF": "d9bbc04cf69bfa9b555c3f293b89b440b4820518",
|
||||
"microStore": "c5fb69d68229e684c7fbd17692a67ae8193b84e2",
|
||||
}
|
||||
|
||||
|
||||
def run(*args: str, cwd: Path = ROOT) -> str:
|
||||
return subprocess.run(
|
||||
args,
|
||||
cwd=cwd,
|
||||
check=True,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
).stdout.strip()
|
||||
|
||||
|
||||
def require(condition: bool, message: str) -> None:
|
||||
if not condition:
|
||||
raise SystemExit(f"release audit failed: {message}")
|
||||
|
||||
|
||||
def package_commit(name: str) -> str:
|
||||
package = ROOT / ".pio" / "libdeps" / ENVIRONMENT / name
|
||||
require(package.is_dir(), f"missing dependency package {name}")
|
||||
top = Path(run("git", "rev-parse", "--show-toplevel", cwd=package)).resolve()
|
||||
require(top == package.resolve(), f"{name} is not an independently verifiable VCS package")
|
||||
return run("git", "rev-parse", "HEAD", cwd=package)
|
||||
|
||||
|
||||
def locate_nm() -> str:
|
||||
direct = shutil.which("xtensa-esp32s3-elf-nm")
|
||||
if direct:
|
||||
return direct
|
||||
candidate = (
|
||||
Path.home()
|
||||
/ ".platformio/packages/toolchain-xtensa-esp32s3/bin/xtensa-esp32s3-elf-nm"
|
||||
)
|
||||
require(candidate.is_file(), "xtensa-esp32s3-elf-nm not found")
|
||||
return str(candidate)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
run("pio", "run", "-e", ENVIRONMENT, "-t", "compiledb")
|
||||
|
||||
compile_commands = ROOT / "compile_commands.json"
|
||||
commands = json.loads(compile_commands.read_text())
|
||||
main_compile = next(
|
||||
entry for entry in commands if Path(entry["file"]).as_posix().endswith("src/main.cpp")
|
||||
)
|
||||
command = main_compile.get("command") or " ".join(main_compile["arguments"])
|
||||
present = [flag for flag in EXCLUDED_DEFINITIONS if flag in command]
|
||||
require(not present, f"excluded compiler definitions present: {', '.join(present)}")
|
||||
|
||||
firmware = BUILD / "firmware.bin"
|
||||
elf = BUILD / "firmware.elf"
|
||||
require(firmware.is_file(), "firmware.bin missing")
|
||||
require(elf.is_file(), "firmware.elf missing")
|
||||
|
||||
firmware_bytes = firmware.read_bytes()
|
||||
strings_present = [value.decode() for value in EXCLUDED_STRINGS if value in firmware_bytes]
|
||||
require(not strings_present, f"excluded firmware strings present: {', '.join(strings_present)}")
|
||||
|
||||
symbols = run(locate_nm(), "-C", str(elf))
|
||||
symbols_present = [value for value in EXCLUDED_SYMBOLS if value in symbols]
|
||||
require(not symbols_present, f"excluded ELF symbols present: {', '.join(symbols_present)}")
|
||||
|
||||
for name, expected in PINNED_DEPENDENCIES.items():
|
||||
actual = package_commit(name)
|
||||
require(actual == expected, f"{name} resolved to {actual}, expected {expected}")
|
||||
|
||||
compile_commands.unlink()
|
||||
print("release audit passed")
|
||||
print(f"firmware_size={len(firmware_bytes)}")
|
||||
for name, expected in PINNED_DEPENDENCIES.items():
|
||||
print(f"{name}={expected}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user