Files
OpenMaidEngine/tools/verify_windows_native.py
gamer147 02e9891baa
All checks were successful
Core validation / Linux core gate (push) Successful in 1m40s
Linux release build / Linux x64 artifact (push) Successful in 1m37s
Linux release build / Publish tagged Gitea release (push) Has been skipped
Cross-export Windows release package on Linux
2026-08-03 21:59:52 -04:00

148 lines
5.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""Verify the project-owned Windows x64 FFmpeg bundle without executing it."""
from __future__ import annotations
import argparse
import json
import re
import struct
import subprocess
import sys
from pathlib import Path
from typing import Any
SHIM = "age_movie_ffmpeg.dll"
RUNTIME_DLLS = (
"avformat-62.dll",
"avcodec-62.dll",
"avutil-60.dll",
"swscale-9.dll",
"swresample-6.dll",
)
REQUIRED_FILES = (SHIM, *RUNTIME_DLLS, "FFmpeg-LICENSE.txt")
REQUIRED_EXPORTS = frozenset({
"age_movie_abi_version",
"age_movie_open",
"age_movie_seek",
"age_movie_decode_video",
"age_movie_decode_audio",
"age_movie_last_error",
"age_movie_close",
})
FORBIDDEN_RUNTIME_IMPORTS = frozenset({"cygwin1.dll", "msys-2.0.dll"})
PE_MACHINE_AMD64 = 0x8664
def pe_machine(path: Path) -> int:
with path.open("rb") as stream:
header = stream.read(64)
if len(header) != 64 or header[:2] != b"MZ":
raise ValueError(f"file has no DOS/PE header: {path}")
pe_offset = struct.unpack_from("<I", header, 0x3C)[0]
stream.seek(pe_offset)
pe_header = stream.read(6)
if len(pe_header) != 6 or pe_header[:4] != b"PE\0\0":
raise ValueError(f"file has no PE signature: {path}")
return struct.unpack_from("<H", pe_header, 4)[0]
def parse_objdump(output: str) -> tuple[set[str], set[str]]:
imports = {
match.group(1).strip()
for match in re.finditer(r"^\s*DLL Name:\s*(\S+)\s*$", output, re.MULTILINE)
}
exports = {
name for name in REQUIRED_EXPORTS
if re.search(rf"(?<![A-Za-z0-9_]){re.escape(name)}(?![A-Za-z0-9_])", output)
}
return imports, exports
def verify_bundle(
bundle: Path,
objdump: str,
*,
exact_dlls: bool = True,
) -> dict[str, Any]:
bundle = bundle.resolve()
if not bundle.is_dir():
raise ValueError(f"Windows native bundle directory was not found: {bundle}")
missing = [name for name in REQUIRED_FILES if not (bundle / name).is_file()]
if missing:
raise ValueError("Windows native bundle is incomplete; missing: " + ", ".join(missing))
expected_dlls = {name.lower() for name in (SHIM, *RUNTIME_DLLS)}
unexpected_dlls = sorted(
path.name
for path in bundle.iterdir()
if path.is_file() and path.suffix.lower() == ".dll" and path.name.lower() not in expected_dlls
)
if exact_dlls and unexpected_dlls:
raise ValueError("Windows native bundle has unexpected DLLs: " + ", ".join(unexpected_dlls))
machines: dict[str, str] = {}
for name in (SHIM, *RUNTIME_DLLS):
machine = pe_machine(bundle / name)
if machine != PE_MACHINE_AMD64:
raise ValueError(f"Windows native file is not AMD64 PE (0x{machine:04x}): {name}")
machines[name] = "AMD64"
process = subprocess.run(
(objdump, "-p", str(bundle / SHIM)),
check=False,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
)
if process.returncode != 0:
raise RuntimeError(f"objdump failed for {SHIM}: {process.stderr.strip()}")
imports, exports = parse_objdump(process.stdout)
import_names = {name.lower() for name in imports}
missing_imports = sorted(name for name in RUNTIME_DLLS if name.lower() not in import_names)
if missing_imports:
raise ValueError("Windows shim is missing FFmpeg imports: " + ", ".join(missing_imports))
forbidden_imports = sorted(import_names & FORBIDDEN_RUNTIME_IMPORTS)
if forbidden_imports:
raise ValueError("Windows shim imports a non-native compatibility runtime: " + ", ".join(forbidden_imports))
missing_exports = sorted(REQUIRED_EXPORTS - exports)
if missing_exports:
raise ValueError("Windows shim is missing AGE ABI exports: " + ", ".join(missing_exports))
return {
"schema_version": 1,
"target": "win-x64",
"bundle": str(bundle),
"machines": machines,
"shim_imports": sorted(imports, key=str.lower),
"shim_exports": sorted(exports),
}
def main(arguments: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("bundle", type=Path)
parser.add_argument("--objdump", default="x86_64-w64-mingw32-objdump")
parser.add_argument("--report", type=Path)
args = parser.parse_args(arguments)
report = verify_bundle(args.bundle, args.objdump)
rendered = json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True) + "\n"
if args.report is not None:
args.report.parent.mkdir(parents=True, exist_ok=True)
with args.report.open("w", encoding="utf-8", newline="\n") as stream:
stream.write(rendered)
print(
f"WINDOWS NATIVE OK: dlls={len(report['machines'])} "
f"exports={len(report['shim_exports'])} ffmpeg-imports={len(RUNTIME_DLLS)}"
)
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except (OSError, RuntimeError, ValueError) as error:
print(f"Windows native verification failed: {error}", file=sys.stderr)
raise SystemExit(1)