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.
This commit is contained in:
1 parent
5152e6e0a6
commit
2d6b2a3cd1
37 files changed
+1284
-451
No files matched your search
@@ -0,0 +1,182 @@
|
||||
"""Build the core-only JNI library matching imgui-java 1.92.7.1.
|
||||
|
||||
Requires Python 3, a JDK, CMake, and a C++17 compiler. Select a cross compiler
|
||||
with --toolchain or a Visual Studio target with --architecture ARM64.
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import urllib.request
|
||||
import zipfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
BINDING_COMMIT = "b164a88602af3e28483efbb4d85aea372010c7c5"
|
||||
IMGUI_COMMIT = "b1bcb12a624af7509894c8e77dd47416997777fa"
|
||||
NATIVE_FILES = (
|
||||
"_common.h", "_internal.h", "imconfig.h",
|
||||
"jni_jvm", "jni_common", "jni_binding_struct", "jni_callbacks", "jni_assertion", "jni_internal",
|
||||
)
|
||||
|
||||
|
||||
def write_if_changed(path, data):
|
||||
if not path.is_file() or path.read_bytes() != data:
|
||||
path.write_bytes(data)
|
||||
|
||||
def download(url, destination):
|
||||
if not destination.is_file():
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
with tempfile.NamedTemporaryFile(dir=destination.parent, suffix=".download", delete=False) as output:
|
||||
temporary = Path(output.name)
|
||||
try:
|
||||
with urllib.request.urlopen(url, timeout=60) as response:
|
||||
shutil.copyfileobj(response, output)
|
||||
except BaseException:
|
||||
output.close()
|
||||
temporary.unlink(missing_ok=True)
|
||||
raise
|
||||
try:
|
||||
if not destination.is_file():
|
||||
try:
|
||||
temporary.replace(destination)
|
||||
except PermissionError:
|
||||
if not destination.is_file():
|
||||
raise
|
||||
finally:
|
||||
temporary.unlink(missing_ok=True)
|
||||
return destination
|
||||
|
||||
|
||||
def archive(repository, commit, cache):
|
||||
path = download(f"https://codeload.github.com/{repository}/zip/{commit}",
|
||||
cache / "downloads" / f"{repository.split('/')[-1]}-{commit}.zip")
|
||||
return zipfile.ZipFile(path)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--build-dir", type=Path, default=ROOT / "build/imgui-core")
|
||||
parser.add_argument("--output", type=Path, required=True)
|
||||
parser.add_argument("--toolchain", type=Path)
|
||||
parser.add_argument("--architecture", choices=("x64", "ARM64"))
|
||||
parser.add_argument("--cache-dir", type=Path)
|
||||
parser.add_argument("--zig-target", choices=(
|
||||
"x86_64-windows-gnu", "aarch64-windows-gnu",
|
||||
"x86_64-linux-gnu.2.17", "aarch64-linux-gnu.2.17",
|
||||
"x86_64-macos.11.0", "aarch64-macos.11.0",
|
||||
))
|
||||
args = parser.parse_args()
|
||||
# cmd.exe cannot use extended-length (\\?\) working directory paths.
|
||||
build = Path(os.path.abspath(args.build_dir))
|
||||
cache = Path(os.path.abspath(args.cache_dir)) if args.cache_dir else build
|
||||
output = Path(os.path.abspath(args.output))
|
||||
generated = build / "jni"
|
||||
sources = build / "sources"
|
||||
for directory in (build, output, generated, sources):
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
dependencies = []
|
||||
for group, artifact, version in (
|
||||
("com.badlogicgames.gdx", "gdx-jnigen", "2.5.2"),
|
||||
("com.badlogicgames.gdx", "gdx-jnigen-loader", "2.5.2"),
|
||||
("com.github.javaparser", "javaparser-core", "3.14.14"),
|
||||
("io.github.spair", "imgui-java-binding", "1.92.7.1"),
|
||||
):
|
||||
name = f"{artifact}-{version}.jar"
|
||||
dependencies.append(download(
|
||||
f"https://repo.maven.apache.org/maven2/{group.replace('.', '/')}/{artifact}/{version}/{name}",
|
||||
cache / "deps" / name,
|
||||
))
|
||||
binding = dependencies[-1]
|
||||
with zipfile.ZipFile(dependencies[0]) as generator:
|
||||
header_prefix = "com/badlogic/gdx/jnigen/resources/headers/"
|
||||
for entry in generator.infolist():
|
||||
if entry.filename.startswith(header_prefix) and not entry.is_dir():
|
||||
destination = generated / "jni-headers" / entry.filename[len(header_prefix):]
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
write_if_changed(destination, generator.read(entry))
|
||||
with archive("SpaiR/imgui-java", BINDING_COMMIT, cache) as upstream:
|
||||
prefix = upstream.namelist()[0]
|
||||
java_prefix = prefix + "imgui-binding/src/generated/java/"
|
||||
native_prefix = prefix + "imgui-binding/src/main/native/"
|
||||
for entry in upstream.infolist():
|
||||
if entry.filename.startswith(java_prefix) and entry.filename.endswith(".java"):
|
||||
relative = entry.filename[len(java_prefix):]
|
||||
if relative.startswith(("imgui/extension/", "imgui/lwjgl3/")):
|
||||
continue
|
||||
target = sources / relative
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
write_if_changed(target, upstream.read(entry))
|
||||
if entry.filename.startswith(native_prefix):
|
||||
name = entry.filename[len(native_prefix):]
|
||||
if name in NATIVE_FILES or name.rsplit(".", 1)[0] in NATIVE_FILES:
|
||||
write_if_changed(generated / name, upstream.read(entry))
|
||||
write_if_changed(output / "LICENSE-imgui-java.txt", upstream.read(prefix + "LICENSE"))
|
||||
with archive("ocornut/imgui", IMGUI_COMMIT, cache) as upstream:
|
||||
prefix = upstream.namelist()[0]
|
||||
for entry in upstream.infolist():
|
||||
name = entry.filename[len(prefix):]
|
||||
if "/" not in name and name.endswith((".h", ".cpp")) and name != "imconfig.h":
|
||||
write_if_changed(generated / name, upstream.read(entry))
|
||||
write_if_changed(output / "LICENSE-imgui.txt", upstream.read(prefix + "LICENSE.txt"))
|
||||
|
||||
java_home = os.environ.get("JAVA_HOME")
|
||||
java = str(Path(java_home) / "bin/java") if java_home else shutil.which("java")
|
||||
if not java:
|
||||
raise RuntimeError("Set JAVA_HOME to a JDK.")
|
||||
env = os.environ.copy()
|
||||
if java_home:
|
||||
env["PATH"] = str(Path(java_home) / "bin") + os.pathsep + env["PATH"]
|
||||
# jnigen constructs its javac command as a string. Relative paths avoid its
|
||||
# whitespace bug without depending on Windows short filename support.
|
||||
write_if_changed(build / "binding.jar", binding.read_bytes())
|
||||
subprocess.run([
|
||||
java, "-cp", os.pathsep.join(map(str, dependencies)),
|
||||
str(ROOT / "scripts/imgui-core/GenerateCore.java"),
|
||||
"sources", "binding.jar", "jni",
|
||||
], check=True, env=env, cwd=build)
|
||||
# Upstream's GNU builds accept variable-length arrays. Use standard C++
|
||||
# storage in generated JNI code so the identical methods also build in MSVC.
|
||||
declaration = re.compile(r"\b(ImVec2|const char\*|jlong)\s+(\w+)\[(\w+)\];")
|
||||
for source in generated.glob("imgui_*.cpp"):
|
||||
text = source.read_text(encoding="utf-8")
|
||||
text, count = declaration.subn(
|
||||
lambda match: f"std::vector<{match[1]}> {match[2]}Storage({match[3]});\n"
|
||||
f" auto* {match[2]} = {match[2]}Storage.data();",
|
||||
text,
|
||||
)
|
||||
if count:
|
||||
source.write_text("#include <vector>\n" + text, encoding="utf-8")
|
||||
configuration = [
|
||||
"cmake", "-S", str(ROOT / "scripts/imgui-core"), "-B", str(build / "cmake"),
|
||||
f"-DCORE_JNI_DIR={generated.as_posix()}", f"-DCORE_OUTPUT_DIR={output.as_posix()}", "-DCMAKE_BUILD_TYPE=Release",
|
||||
]
|
||||
if args.zig_target:
|
||||
if args.toolchain or args.architecture:
|
||||
parser.error("--zig-target cannot be combined with --toolchain or --architecture")
|
||||
import ziglang
|
||||
executable = Path(ziglang.__file__).parent / ("zig.exe" if os.name == "nt" else "zig")
|
||||
family = "Windows" if "windows" in args.zig_target else "Darwin" if "macos" in args.zig_target else "Linux"
|
||||
toolchain = build / "zig-toolchain.cmake"
|
||||
toolchain.write_text(
|
||||
f'set(CMAKE_SYSTEM_NAME {family})\n'
|
||||
f'set(CMAKE_SYSTEM_PROCESSOR {args.zig_target.split("-")[0]})\n'
|
||||
f'set(CMAKE_CXX_COMPILER "{executable.as_posix()}")\n'
|
||||
f'set(CMAKE_CXX_COMPILER_ARG1 "c++ -target {args.zig_target} -Wno-nullability-completeness")\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
configuration.extend(("-G", "Ninja", f"-DCMAKE_TOOLCHAIN_FILE={toolchain.as_posix()}"))
|
||||
if args.toolchain:
|
||||
configuration.append(f"-DCMAKE_TOOLCHAIN_FILE={args.toolchain.resolve().as_posix()}")
|
||||
if args.architecture:
|
||||
configuration.extend(("-A", args.architecture))
|
||||
subprocess.run(configuration, check=True, env=env)
|
||||
subprocess.run(["cmake", "--build", str(build / "cmake"), "--config", "Release", "--parallel", "4"],
|
||||
check=True, env=env)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user