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.
128 lines
5.9 KiB
Python
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()
|