0.1.0-0-pre.7

This commit is contained in:
2026-09-16 09:29:19 +02:00
parent fa4e2925bd
commit 98b830da6e
18 changed files with 434 additions and 20 deletions

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python3
# file: scripts/audit_rust_general_rules.py
# version: 1
# version: 2
"""Audit mechanically verifiable Rust normalization rules used by games.sasedev."""
@@ -513,9 +513,18 @@ def audit_file(root: pathlib.Path, path: pathlib.Path) -> list[Violation]:
violations.append(Violation("RUST-IMPORT-114", relative, current_idx, "module use block is not alphabetically ordered"))
# Crate façade constraints.
if path.name in {"lib.rs", "main.rs"}:
for attribute in ("#![warn(missing_docs)]", "#![deny(unreachable_pub)]", "#![forbid(unsafe_code)]"):
ffi_export_exception = relative == "crates/apps/game-android-entrypoint/src/lib.rs"
required_attributes = ["#![warn(missing_docs)]", "#![deny(unreachable_pub)]"]
required_attributes.append("#![allow(unsafe_code)]" if ffi_export_exception else "#![forbid(unsafe_code)]")
for attribute in required_attributes:
if attribute not in lines[:24]:
violations.append(Violation("RUST-BASE-103", relative, 1, f"missing `{attribute}`"))
if ffi_export_exception:
export_attribute = '#[unsafe(export_name = "SDL_main")]'
if text.count(export_attribute) != 1:
violations.append(Violation("RUST-FFI-101", relative, 1, "Android entrypoint must contain exactly one SDL_main export attribute"))
if "unsafe {" in text or re.search(r"\bunsafe\s+fn\b", text) is not None or re.search(r"\bunsafe\s+extern\b", text) is not None:
violations.append(Violation("RUST-FFI-102", relative, 1, "Android entrypoint exception permits an unsafe export attribute only; unsafe blocks/functions remain forbidden"))
exports: list[tuple[int, str]] = []
for idx, line in enumerate(lines, 1):
stripped = line.strip()

View File

@@ -0,0 +1,79 @@
#!/usr/bin/env python3
# file: scripts/build_android_rust.py
# version: 1
"""Build one Android Rust game entrypoint and stage it as Gradle jniLibs."""
from __future__ import annotations
import argparse
import os
import pathlib
import subprocess
import sys
import zipfile
ABI_TO_TRIPLE = {
"arm64-v8a": "aarch64-linux-android",
"armeabi-v7a": "armv7-linux-androideabi",
"x86_64": "x86_64-linux-android",
"x86": "i686-linux-android",
}
GAME_TO_MODULE = {"reflex": "game-reflex-poc", "snake": "game-snake-poc"}
def read_sdl3_aar_name(properties_path: pathlib.Path) -> str:
"""Read the configured SDL3 AAR filename."""
for raw_line in properties_path.read_text(encoding="utf-8").splitlines():
line = raw_line.strip()
if line.startswith("sdl3AarName="):
return line.split("=", 1)[1].strip()
raise RuntimeError("sdl3AarName is missing from Android/gradle.properties")
def extract_sdl3_link_library(aar_path: pathlib.Path, abi: str, output_dir: pathlib.Path) -> pathlib.Path:
"""Extract the SDL3 shared library used only for the Rust linker search path."""
expected = f"jni/{abi}/libSDL3.so"
output_dir.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(aar_path) as archive:
if expected not in archive.namelist():
raise RuntimeError(f"{expected} is missing from {aar_path}")
target = output_dir / "libSDL3.so"
target.write_bytes(archive.read(expected))
return target
def main() -> int:
"""Build the requested game entrypoint for one Android ABI."""
parser = argparse.ArgumentParser()
parser.add_argument("game", choices=sorted(GAME_TO_MODULE))
parser.add_argument("--abi", choices=sorted(ABI_TO_TRIPLE), default="arm64-v8a")
parser.add_argument("--release", action="store_true")
arguments = parser.parse_args()
root = pathlib.Path(__file__).resolve().parent.parent
aar_name = read_sdl3_aar_name(root / "Android" / "gradle.properties")
aar_path = root / "Android" / "libs" / aar_name
if not aar_path.is_file():
print(f"missing SDL3 AAR: {aar_path}", file=sys.stderr)
return 2
link_dir = root / "Android" / "build" / "rust-link" / arguments.abi
try:
extract_sdl3_link_library(aar_path, arguments.abi, link_dir)
except (OSError, RuntimeError, zipfile.BadZipFile) as error:
print(str(error), file=sys.stderr)
return 2
module = GAME_TO_MODULE[arguments.game]
output = root / "Android" / module / "src" / "main" / "jniLibs"
environment = os.environ.copy()
rustflags = environment.get("RUSTFLAGS", "").strip()
link_flag = f"-L native={link_dir}"
environment["RUSTFLAGS"] = f"{rustflags} {link_flag}".strip()
environment["CARGO_NDK_PLATFORM"] = "21"
command = [
"cargo", "ndk", "-t", arguments.abi, "-o", str(output), "build",
"-p", "game-android-entrypoint", "--no-default-features", "--features", arguments.game,
]
if arguments.release:
command.append("--release")
completed = subprocess.run(command, cwd=root, env=environment, check=False)
return completed.returncode
if __name__ == "__main__":
raise SystemExit(main())