Patch Bluepad32 in build-local copy

This commit is contained in:
Joey Yakimowich-Payne 2026-09-03 14:35:29 -06:00
commit 3f6e2c9e12
5 changed files with 249 additions and 335 deletions

View file

@ -1,8 +1,9 @@
#!/usr/bin/env python3
"""Apply the project Bluepad32 patch exactly once."""
"""Create a patched build-local copy of the pinned Bluepad32 source."""
from __future__ import annotations
import argparse
import shutil
import subprocess
import sys
from pathlib import Path
@ -12,69 +13,125 @@ class PatchError(RuntimeError):
pass
def resolve_paths(repo_root: Path | None = None) -> tuple[Path, Path]:
def resolve_paths(repo_root: Path | None = None) -> tuple[Path, Path, Path]:
root = Path.cwd() if repo_root is None else Path(repo_root)
return (
root / "external" / "bluepad32",
root / "patches" / "bluepad32-sdl3-imu.patch",
root / "build" / "_deps" / "bluepad32-src",
)
def check_paths(bluepad32_path: Path, patch_path: Path) -> None:
if not bluepad32_path.is_dir():
raise PatchError(f"bluepad32 directory does not exist: {bluepad32_path}")
if not (bluepad32_path / ".git").exists():
raise PatchError(f"Bluepad32 is not a git repository: {bluepad32_path}")
if not patch_path.is_file():
raise PatchError(f"patch file does not exist: {patch_path}")
def git_apply(bluepad32_path: Path, patch_path: Path, *args: str) -> subprocess.CompletedProcess[str]:
def _run(command: list[str], *, cwd: Path | None = None) -> subprocess.CompletedProcess[str]:
return subprocess.run(
["git", "-C", str(bluepad32_path), "apply", *args, str(patch_path)],
command,
cwd=cwd,
capture_output=True,
text=True,
check=False,
)
def is_patch_applied(bluepad32_path: Path, patch_path: Path) -> bool:
return git_apply(bluepad32_path, patch_path, "--reverse", "--check").returncode == 0
def check_paths(source_path: Path, patch_path: Path, output_path: Path) -> None:
if not source_path.is_dir():
raise PatchError(f"bluepad32 directory does not exist: {source_path}")
if not patch_path.is_file():
raise PatchError(f"patch file does not exist: {patch_path}")
repository_check = _run(
["git", "-C", str(source_path), "rev-parse", "--is-inside-work-tree"]
)
if repository_check.returncode != 0:
raise PatchError(f"Bluepad32 is not a git repository: {source_path}")
source = source_path.resolve()
output = output_path.resolve()
if output == source or source in output.parents:
raise PatchError("patched output must be outside the Bluepad32 source tree")
def apply_patch(bluepad32_path: Path, patch_path: Path) -> None:
if is_patch_applied(bluepad32_path, patch_path):
return
def validate_pristine_source(source_path: Path, patch_path: Path) -> None:
status = _run(
[
"git",
"-C",
str(source_path),
"status",
"--porcelain",
"--untracked-files=all",
]
)
if status.returncode != 0:
detail = status.stderr.strip() or "git status failed"
raise PatchError(f"Could not inspect Bluepad32 source: {detail}")
if status.stdout.strip():
raise PatchError(
"Bluepad32 source must be pristine; patches are applied only to the build-local copy"
)
check = git_apply(bluepad32_path, patch_path, "--check")
check = _run(
["git", "-C", str(source_path), "apply", "--check", str(patch_path)]
)
if check.returncode != 0:
detail = check.stderr.strip() or "patch does not apply"
raise PatchError(f"Patch validation failed (repository may be diverged):\n{detail}")
raise PatchError(f"Patch validation failed (source revision may have diverged):\n{detail}")
result = git_apply(bluepad32_path, patch_path)
if result.returncode != 0:
detail = result.stderr.strip() or "git apply failed"
raise PatchError(f"Could not patch Bluepad32: {detail}")
def _copy_and_patch(source_path: Path, patch_path: Path, output_path: Path) -> None:
output_path.parent.mkdir(parents=True, exist_ok=True)
staging_path = output_path.with_name(f".{output_path.name}.tmp")
if staging_path.exists():
shutil.rmtree(staging_path)
try:
shutil.copytree(
source_path,
staging_path,
symlinks=True,
ignore=shutil.ignore_patterns(".git"),
)
result = _run(
["git", "apply", "--no-index", str(patch_path.resolve())],
cwd=staging_path,
)
if result.returncode != 0:
detail = result.stderr.strip() or "git apply failed"
raise PatchError(f"Could not patch build-local Bluepad32 copy: {detail}")
if output_path.exists():
shutil.rmtree(output_path)
staging_path.replace(output_path)
except Exception:
if staging_path.exists():
shutil.rmtree(staging_path)
raise
def prepare_bluepad32(
bluepad32_path: Path | None = None,
source_path: Path | None = None,
patch_path: Path | None = None,
) -> None:
default_bluepad32, default_patch = resolve_paths()
dependency = Path(bluepad32_path or default_bluepad32)
output_path: Path | None = None,
) -> Path:
default_source, default_patch, default_output = resolve_paths()
source = Path(source_path or default_source)
patch = Path(patch_path or default_patch)
check_paths(dependency, patch)
apply_patch(dependency, patch)
output = Path(output_path or default_output)
check_paths(source, patch, output)
validate_pristine_source(source, patch)
_copy_and_patch(source, patch, output)
return output
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--bluepad32", type=Path)
parser.add_argument("--patch", type=Path)
parser.add_argument("--output", type=Path)
args = parser.parse_args()
try:
prepare_bluepad32(args.bluepad32, args.patch)
prepare_bluepad32(args.bluepad32, args.patch, args.output)
except PatchError as exc:
print(f"error: {exc}", file=sys.stderr)
return 1