Files
OpenPrestige/scripts/build_release_imgui.py
bbldrizzy 2d6b2a3cd1 Build offline OpenPrestige release bundles
Rename the injector and package it with an embedded payload for Windows, Linux, and macOS x64/ARM64. Add a core-only ImGui native build, multi-platform CI and release packaging, and shared per-user storage with legacy config migration.
2026-11-03 18:45:13 +00:00

128 lines
5.9 KiB
Python

"""Fetch and build every core ImGui native required by build-release.bat."""
import argparse
import importlib.util
import importlib.metadata
import os
from pathlib import Path
import shutil
import struct
import subprocess
import sys
import sysconfig
ROOT = Path(__file__).resolve().parents[1]
TARGETS = (
("windows-x86_64", "x86_64-windows-gnu", "imgui-java64.dll"),
("windows-aarch64", "aarch64-windows-gnu", "imgui-java64.dll"),
("linux-x86_64", "x86_64-linux-gnu.2.17", "libimgui-java64.so"),
("linux-aarch64", "aarch64-linux-gnu.2.17", "libimgui-java64.so"),
("macos-x86_64", "x86_64-macos.11.0", "libimgui-java64.dylib"),
("macos-aarch64", "aarch64-macos.11.0", "libimgui-java64.dylib"),
)
def ensure_tools():
scripts = Path(sys.executable).parent / "Scripts"
user_scheme = "nt_user" if os.name == "nt" else "posix_user"
user_scripts = Path(sysconfig.get_path("scripts", scheme=user_scheme))
os.environ["PATH"] = os.pathsep.join((str(scripts), str(user_scripts), os.environ["PATH"]))
missing = [tool for tool in ("cmake", "ninja") if not shutil.which(tool)]
if importlib.util.find_spec("ziglang") is None or importlib.metadata.version("ziglang") != "0.16.0":
missing.append("ziglang==0.16.0")
if missing:
subprocess.run([sys.executable, "-m", "pip", "install", "--user", *missing], check=True)
for tool in ("cmake", "ninja"):
if not shutil.which(tool):
raise RuntimeError(f"{tool} is unavailable after installation. Add the Python Scripts directory to PATH.")
def universal(slices, destination):
images = [path.read_bytes() for path in slices]
records = []
offset = 16384
for image in images:
magic, cpu, subtype = struct.unpack_from("<III", image)
if magic != 0xFEEDFACF or cpu not in (0x01000007, 0x0100000C):
raise ValueError("The macOS build produced an unexpected Mach-O architecture.")
records.append((cpu, subtype, offset, len(image), 14))
offset = (offset + len(image) + 16383) & ~16383
if len({record[0] for record in records}) != 2:
raise ValueError("The universal library needs one Intel slice and one ARM64 slice.")
temporary = destination.with_suffix(".dylib.tmp")
with temporary.open("wb") as output:
output.write(struct.pack(">II", 0xCAFEBABE, len(images)))
for record in records:
output.write(struct.pack(">IIIII", *record))
for record, image in zip(records, images):
output.seek(record[2])
output.write(image)
temporary.replace(destination)
def validate_native(path, platform):
with path.open("rb") as library:
header = library.read(64)
if platform.startswith("windows-") and header[:2] == b"MZ":
library.seek(struct.unpack_from("<I", header, 60)[0])
signature, machine = struct.unpack("<4sH", library.read(6))
expected = 0xAA64 if platform.endswith("aarch64") else 0x8664
if signature == b"PE\0\0" and machine == expected:
return
elif platform.startswith("linux-") and header[:6] == b"\x7fELF\x02\x01":
expected = 183 if platform.endswith("aarch64") else 62
if struct.unpack_from("<H", header, 18)[0] == expected:
return
elif platform.startswith("macos-") and header[:4] == b"\xcf\xfa\xed\xfe":
expected = 0x0100000C if platform.endswith("aarch64") else 0x01000007
if struct.unpack_from("<I", header, 4)[0] == expected:
return
raise ValueError(f"The {platform} build produced a library with an unexpected architecture.")
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--platform", choices=[item[0] for item in TARGETS],
help="Build one slice for troubleshooting. Default builds the full release set.")
args = parser.parse_args()
ensure_tools()
cache = ROOT / "build/imgui-core-cache"
for platform, target, filename in TARGETS:
if args.platform and args.platform != platform:
continue
print(f"Building small ImGui JNI for {platform}", flush=True)
output = ROOT / "build/core-natives" / platform
if platform.startswith("macos-"):
output = ROOT / "build/imgui-core-slices" / platform
work = ROOT / "build/imgui-core-release" / platform
work.mkdir(parents=True, exist_ok=True)
log_path = work / "build.log"
command = [
sys.executable, str(ROOT / "scripts/build_imgui_core.py"),
"--build-dir", str(work),
"--cache-dir", str(cache), "--output", str(output), "--zig-target", target,
]
with log_path.open("w", encoding="utf-8") as log:
result = subprocess.run(command, stdout=log, stderr=subprocess.STDOUT)
if result.returncode:
print("\n".join(log_path.read_text(encoding="utf-8", errors="replace").splitlines()[-40:]), flush=True)
raise RuntimeError(f"The {platform} build failed. Full log: {log_path}")
if not (output / filename).is_file():
raise RuntimeError(f"The {platform} build did not produce {filename}.")
validate_native(output / filename, platform)
print(f"Built {output / filename} ({(output / filename).stat().st_size:,} bytes)", flush=True)
if not args.platform:
destination = ROOT / "build/core-natives/macos-universal"
destination.mkdir(parents=True, exist_ok=True)
slices = ROOT / "build/imgui-core-slices"
universal([
slices / "macos-x86_64/libimgui-java64.dylib",
slices / "macos-aarch64/libimgui-java64.dylib",
], destination / "libimgui-java64.dylib")
for license_file in (slices / "macos-x86_64").glob("LICENSE-*.txt"):
shutil.copyfile(license_file, destination / license_file.name)
print("All five release ImGui native libraries are ready.", flush=True)
if __name__ == "__main__":
main()