mirror of
https://github.com/Nexoniarz/Fry3DX.git
synced 2026-11-03 12:34:27 +00:00
The Galaxy S22 (Xclipse 920) says it has ray queries but can't build the ray-traced light and fog shaders; Fry3DX crashed on start there. Android builds use a current glslc (not the NDK's 2022 one). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NMSrd5oe1GpUD7E6d825KP
120 lines
6.5 KiB
Python
120 lines
6.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Builds Fry3DX's Android APK (arm64, Vulkan) with no Gradle: the NDK build (cmake --preset
|
|
android), the content as plain files in assets/, SDL's Java layer, the icon, then aapt2, d8,
|
|
zipalign and apksigner.
|
|
|
|
nix-shell android/shell.nix --run "python3 android/build_apk.py [--install]"
|
|
|
|
Out: dist/Fry3DX-<version>-android-arm64.apk. --install puts it on a phone over adb (USB
|
|
debugging on) and starts it. The first start copies the content out (a moment).
|
|
Signed with ~/.local/share/Nexoniarz/Fry3DX-signing/release.keystore (made the first time),
|
|
or the key in $FRY_KEYSTORE / $FRY_KEYSTORE_PASS. Keep it safe and keep using it: Android only
|
|
updates an app signed with the key it was installed with.
|
|
"""
|
|
import os
|
|
import secrets
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
import zipfile
|
|
|
|
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
BUILD = os.path.join(ROOT, "build", "android")
|
|
APP = "Fry3DX"
|
|
PACKAGE = "io.github.nexoniarz.fry3dx"
|
|
|
|
|
|
def run(*cmd, **kw):
|
|
subprocess.run(list(cmd), check=True, **kw)
|
|
|
|
|
|
def app_version():
|
|
with open(os.path.join(ROOT, "CMakeLists.txt")) as f:
|
|
for line in f:
|
|
if line.startswith("project(Fry3DX VERSION "):
|
|
return line.split()[2]
|
|
return "0"
|
|
|
|
|
|
def main():
|
|
sdk, ndk = os.environ["ANDROID_HOME"], os.environ["ANDROID_NDK_ROOT"]
|
|
tools = os.path.join(sdk, "build-tools", "35.0.0")
|
|
android_jar = os.path.join(sdk, "platforms", "android-35", "android.jar")
|
|
llvm = os.path.join(ndk, "toolchains", "llvm", "prebuilt", "linux-x86_64", "bin")
|
|
|
|
if not os.path.isdir(BUILD):
|
|
# A current glslc: $GLSLC, or the one on PATH. (Not the NDK's: its 2022 shaderc makes compute
|
|
# shaders some phone drivers can't build, e.g. the Galaxy S22's.)
|
|
glslc = os.environ.get("GLSLC") or shutil.which("glslc")
|
|
if not glslc:
|
|
sys.exit("No glslc: install shaderc (or the Vulkan SDK), or set GLSLC")
|
|
run("cmake", "--preset", "android", f"-DGLSLC={glslc}", cwd=ROOT)
|
|
run("cmake", "--build", "--preset", "android", cwd=ROOT)
|
|
|
|
stage = os.path.join(BUILD, "apk")
|
|
shutil.rmtree(stage, ignore_errors=True)
|
|
assets, libs = os.path.join(stage, "assets"), os.path.join(stage, "lib", "arm64-v8a")
|
|
os.makedirs(libs)
|
|
bin_dir = os.path.join(BUILD, "bin")
|
|
sdl = os.path.join(BUILD, "_deps", "sdl3-build", "libSDL3.so")
|
|
for src in (os.path.join(bin_dir, "libmain.so"), sdl):
|
|
run(os.path.join(llvm, "llvm-strip"), "--strip-debug", "-o", os.path.join(libs, os.path.basename(src)), src)
|
|
|
|
# Content: the folders next to the program on a desktop, and the list it unpacks from (a
|
|
# version on its first line, so an update unpacks again).
|
|
for d in ("engine", APP):
|
|
shutil.copytree(os.path.join(bin_dir, d), os.path.join(assets, d))
|
|
commit = subprocess.run(["git", "rev-parse", "--short", "HEAD"], cwd=ROOT, capture_output=True, text=True).stdout.strip() or "local"
|
|
version = f"{app_version()}-{commit}"
|
|
files = [os.path.relpath(os.path.join(r, n), assets).replace(os.sep, "/") for r, _, ns in os.walk(assets) for n in ns]
|
|
with open(os.path.join(assets, "content.txt"), "w") as f:
|
|
f.write(f"{version}-{int(time.time())}\n" + "\n".join(sorted(files)) + "\n")
|
|
|
|
# Java: SDL's activity and helpers, and ours on top; then dex.
|
|
java_src = [os.path.join(ROOT, "android", "java"), os.path.join(BUILD, "_deps", "sdl3-src", "android-project", "app", "src", "main", "java")]
|
|
sources = [os.path.join(r, n) for d in java_src for r, _, ns in os.walk(d) for n in ns if n.endswith(".java")]
|
|
classes = os.path.join(BUILD, "java-classes")
|
|
shutil.rmtree(classes, ignore_errors=True)
|
|
os.makedirs(classes)
|
|
print(f"Compiling {len(sources)} Java files")
|
|
run("javac", "-nowarn", "-source", "8", "-target", "8", "-bootclasspath", android_jar, "-d", classes, *sources,
|
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
class_files = [os.path.join(r, n) for r, _, ns in os.walk(classes) for n in ns if n.endswith(".class")]
|
|
run(os.path.join(tools, "d8"), "--release", "--min-api", "28", "--lib", android_jar, "--output", stage, *class_files)
|
|
|
|
# Resources (the icon), the manifest and the assets; then dex and libraries; aligned, signed.
|
|
res_zip = os.path.join(BUILD, "res.zip")
|
|
run(os.path.join(tools, "aapt2"), "compile", "--dir", os.path.join(ROOT, "android", "res"), "-o", res_zip)
|
|
unsigned = os.path.join(BUILD, "unsigned.apk")
|
|
vcode = str(int(time.time()) // 60 % 2000000000)
|
|
run(os.path.join(tools, "aapt2"), "link", "-o", unsigned, "-I", android_jar, "--manifest", os.path.join(ROOT, "android", "AndroidManifest.xml"),
|
|
"-A", assets, "-R", res_zip, "--auto-add-overlay", "--min-sdk-version", "28", "--target-sdk-version", "35",
|
|
"--version-code", vcode, "--version-name", version)
|
|
with zipfile.ZipFile(unsigned, "a") as z:
|
|
z.write(os.path.join(stage, "classes.dex"), "classes.dex")
|
|
for n in os.listdir(libs):
|
|
z.write(os.path.join(libs, n), f"lib/arm64-v8a/{n}")
|
|
aligned = os.path.join(BUILD, "aligned.apk")
|
|
run(os.path.join(tools, "zipalign"), "-p", "-f", "4", unsigned, aligned)
|
|
keystore = os.environ.get("FRY_KEYSTORE") or os.path.expanduser("~/.local/share/Nexoniarz/Fry3DX-signing/release.keystore")
|
|
pass_file = keystore + ".pass" # (its password, beside it, when made here)
|
|
password = os.environ.get("FRY_KEYSTORE_PASS") or (open(pass_file).read().strip() if os.path.exists(pass_file) else "")
|
|
if not os.path.exists(keystore):
|
|
os.makedirs(os.path.dirname(keystore), exist_ok=True)
|
|
password = password or secrets.token_urlsafe(24)
|
|
with open(os.open(pass_file, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600), "w") as f:
|
|
f.write(password + "\n")
|
|
run("keytool", "-genkeypair", "-keystore", keystore, "-storepass", password, "-keypass", password, "-alias", "fry3dx",
|
|
"-keyalg", "RSA", "-keysize", "2048", "-validity", "10000", "-dname", "CN=Fry3DX", stdout=subprocess.DEVNULL)
|
|
os.makedirs(os.path.join(ROOT, "dist"), exist_ok=True)
|
|
out = os.path.join(ROOT, "dist", f"{APP}-{app_version()}-android-arm64.apk")
|
|
run(os.path.join(tools, "apksigner"), "sign", "--v4-signing-enabled", "false", "--ks", keystore, "--ks-pass", f"pass:{password}", "--out", out, aligned)
|
|
print(f"{os.path.relpath(out, ROOT)}: {os.path.getsize(out) // (1024 * 1024)} MB")
|
|
if "--install" in sys.argv:
|
|
run("adb", "install", "-r", out)
|
|
run("adb", "shell", "am", "start", "-n", f"{PACKAGE}/.FryActivity")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|