mirror of
https://github.com/ARMSX2/ARMSX3.git
synced 2026-08-24 16:58:52 -07:00
Builds were reporting whichever commit cmake last configured against, not the one being built. rpcs3/git-version.h is generated at CONFIGURE time, and build-variants.sh deliberately skips reconfiguring an already-correct build dir because re-running cmake regenerates LLVM's headers and costs a full rebuild. So the stamp froze, and every build after that lied about itself. The gap was wide. HEAD is 20120-da26a455; the header on disk still said 19985-91952ae4, a commit from before 0.9.2. This is not cosmetic. A tester running 0.9.3 reported that older commit in their log, which sent an investigation hunting for a regression among upstream ISO changes their build did not contain, and very nearly had a fix built and sent for a version they were not running. A build that misreports itself makes every report from it ambiguous. stamp-git-version.sh writes the header from HEAD and is called by both build entry points before anything compiles. It only writes when the contents differ, so an unchanged HEAD does not force a rebuild, and ninja rebuilds just the translation units that include it.
204 lines
10 KiB
Bash
Executable File
204 lines
10 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Build the release variants of ARMSX3.
|
|
#
|
|
# ARMSX3 ships four APKs. The split is by platform level and ISA baseline -- the core is
|
|
# arm64-only in all four:
|
|
#
|
|
# legacy API 30 (Android 11), armv8.1-a the fallback, and the FLOOR the code
|
|
# compiles at: util/simd.hpp uses SQRDMLAH
|
|
# (v8.1 RDMA) and util/asm.hpp has inline
|
|
# LSE atomics, so armv8-a does not build.
|
|
# Its value is devices whose cores are 8.2
|
|
# WITHOUT the optional fp16/dotprod that the
|
|
# other three builds require.
|
|
# (Cortex-A53/A57/A73) have neither
|
|
# dotprod nor FP16, so this stays at the
|
|
# 8.1 baseline and keeps only LSE atomics.
|
|
# a11 API 30 (Android 11), armv8.2-a+dotprod+fp16
|
|
# a13 API 33 (Android 13), armv8.2-a+dotprod+fp16 the standard build; 8 Gen 1 and later
|
|
# a15 API 35 (Android 15), armv8.2-a+dotprod+fp16 current devices
|
|
#
|
|
# The three 8.2 builds differ ONLY in platform level, so they generate the same code and exist
|
|
# to widen the floor, not to go faster than each other. What a higher API buys is what the
|
|
# platform itself provides -- native ELF TLS from 29 up being the one that touches the emulator
|
|
# core -- so a device should install the highest variant it can run.
|
|
#
|
|
# +fp16 was briefly suspected of breaking Batman: Arkham City and is cleared. That failure was
|
|
# the Qualcomm proprietary driver running the device out of memory -- RAM climbed monotonically
|
|
# to 7.1GB and lowmemorykiller took the process, with no signal in any log. The same build is
|
|
# fine on Turnip. See the per-vkCmdEndRenderPass leak note in vkutils/device.cpp.
|
|
#
|
|
# All three build with NDK 29. The NDK is not a device-compatibility knob -- what gates devices
|
|
# is the API level and -march, and a device cannot tell which toolchain produced its binary. NDK
|
|
# 28 was tried for the older two and does not compile this tree at all: fmt::throw_exception is a
|
|
# class whose constructor and destructor are [[noreturn]], and clang 19 (NDK 28) does not
|
|
# propagate that through a temporary, so every function ending in a throw fails with "does not
|
|
# return a value in all control paths". Clang 21 (NDK 29) handles it. Satisfying the older
|
|
# compiler would mean adding unreachable returns across upstream code and re-doing it on every
|
|
# merge, for a toolchain that generates worse code anyway.
|
|
#
|
|
# On the ISA: LSE atomics come from the 8.1 baseline, so every variant has them. dotprod and
|
|
# FP16 are named explicitly on the 8.2 builds because 8.2 makes neither mandatory -- dotprod
|
|
# only becomes so at 8.4. That is a hard floor, not a preference: those instructions can appear
|
|
# anywhere in the C++ and a device without them faults rather than falling back, which is the
|
|
# whole reason the generic variant exists.
|
|
#
|
|
# Each core lives in its own build directory and shares no objects with the others. Every one
|
|
# is a full LLVM + RPCS3 build from cold, so all three together run for hours. They are built
|
|
# one after another on purpose: concurrent ninja jobs thrash each other and finish slower than
|
|
# in sequence.
|
|
#
|
|
# Usage:
|
|
# android/build-variants.sh # all three
|
|
# android/build-variants.sh a13 # one
|
|
# android/build-variants.sh a13 a15 # some
|
|
# VARIANTS="generic" android/build-variants.sh
|
|
#
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
: "${ANDROID_HOME:=$HOME/Library/Android/sdk}"
|
|
: "${JAVA_HOME:=/Applications/Android Studio.app/Contents/jbr/Contents/Home}"
|
|
: "${CMAKE_VERSION:=3.30.5}"
|
|
: "${OUT_DIR:=$HOME/Downloads}"
|
|
export ANDROID_HOME JAVA_HOME
|
|
|
|
CMAKE_BIN="$ANDROID_HOME/cmake/$CMAKE_VERSION/bin"
|
|
UI="$ROOT/android/armsx3-ui"
|
|
JNI_LIBS="$UI/app/src/main/jniLibs/arm64-v8a"
|
|
# Frame generation ships in the github flavor only, so its library lives in that source set.
|
|
# The play bundle must not contain it: excluding the file IS the exclusion, because the core
|
|
# dlopen's it by name and reports the feature unavailable when it is absent.
|
|
JNI_LIBS_GITHUB="$UI/app/src/github/jniLibs/arm64-v8a"
|
|
|
|
# variant : ndk : api : march : apk name suffix
|
|
#
|
|
# The suffix is spelled out rather than derived so the filename states the whole contract --
|
|
# platform level and ISA baseline both -- to someone picking a download with no release notes
|
|
# in front of them. It is also what the in-app updater matches on, so changing one of these
|
|
# strings changes which APK an existing install offers; keep them in step with pickApkAsset
|
|
# in UpdaterEntry.kt.
|
|
VARIANT_legacy="29.0.14206865:30:armv8.1-a:legacy-armv8.1-sdk30"
|
|
VARIANT_a11="29.0.14206865:30:armv8.2-a+dotprod+fp16:a11-armv8.2-sdk30"
|
|
VARIANT_a13="29.0.14206865:33:armv8.2-a+dotprod+fp16:a13-armv8.2-sdk33"
|
|
VARIANT_a15="29.0.14206865:35:armv8.2-a+dotprod+fp16:a15-armv8.2-sdk35"
|
|
|
|
VARIANTS="${VARIANTS:-${*:-legacy a11 a13 a15}}"
|
|
|
|
version_name() {
|
|
sed -n 's/.*versionName = "\([^"]*\)".*/\1/p' "$UI/app/build.gradle.kts" | head -1
|
|
}
|
|
|
|
build_variant() {
|
|
local name="$1"
|
|
local spec_var="VARIANT_${name}"
|
|
local spec="${!spec_var:-}"
|
|
|
|
if [[ -z "$spec" ]]; then
|
|
echo "unknown variant '$name' (expected legacy, a11, a13 or a15)" >&2
|
|
return 1
|
|
fi
|
|
|
|
local ndk api march suffix
|
|
IFS=: read -r ndk api march suffix <<<"$spec"
|
|
local build_dir="$ROOT/build-$name"
|
|
|
|
echo "==> $name: NDK $ndk, API $api, -march=$march"
|
|
|
|
# Does build.ninja already describe exactly the toolchain we want?
|
|
#
|
|
# The NDK bakes the target triple at first configure, so a warm build directory silently keeps
|
|
# the OLD api level -- which is exactly how you end up shipping an "API 35" build that is
|
|
# really API 33. Both of these are checked whether or not we configure.
|
|
configured_ok() {
|
|
[ -f "$build_dir/build.ninja" ] &&
|
|
grep -q "aarch64-none-linux-android$api" "$build_dir/build.ninja" &&
|
|
grep -q -- "-march=$march" "$build_dir/build.ninja"
|
|
}
|
|
|
|
# Skip configure when the build directory already matches.
|
|
#
|
|
# Re-running cmake regenerates LLVM's generated headers. Their CONTENT is identical, but ninja
|
|
# keys on mtime, so a one-line edit to our own code drags ~450 LLVM objects and a full relink
|
|
# along with it: 20 minutes instead of two. That turns any bisection into an overnight job.
|
|
#
|
|
# Skipping is safe precisely because the check above is the same one that used to run after
|
|
# configure -- if the directory does not already describe this exact API and -march we still
|
|
# configure, and we still verify afterwards. What is NOT covered either way is a change to
|
|
# configure.sh's own cmake arguments, since those do not appear in the two greps; delete the
|
|
# build directory when that file changes.
|
|
if configured_ok; then
|
|
echo "==> $name: build dir already configured for API $api / $march, skipping cmake"
|
|
else
|
|
# configure.sh forwards "$@" straight to cmake, so the march override rides along there.
|
|
NDK_VERSION="$ndk" ANDROID_API="$api" BUILD_DIR="$build_dir" \
|
|
bash "$ROOT/android/configure.sh" "-DARMSX3_ARM_MARCH=$march"
|
|
|
|
if ! configured_ok; then
|
|
echo "$name: configure did not produce API $api / -march=$march -- stale build dir?" \
|
|
"remove $build_dir" >&2
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
# Frame generation is a separate target on purpose.
|
|
#
|
|
# Nothing links libarmsx3_lsfg.so -- the core reaches it with dlopen, because framegen's volk
|
|
# defines the same 655 vk* globals our renderer uses and linking them together would let
|
|
# volkLoadDevice() repoint the whole renderer at framegen's device. The consequence for the
|
|
# build is that it is NOT a dependency of libarmsx3-core.so and will not be built by asking
|
|
# for it: name it here or ship an APK with frame generation silently missing.
|
|
PATH="$CMAKE_BIN:$PATH" ninja -C "$build_dir" android/libarmsx3-core.so armsx3_lsfg
|
|
|
|
local strip="$ANDROID_HOME/ndk/$ndk/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-strip"
|
|
|
|
"$strip" --strip-unneeded -o "$JNI_LIBS/libarmsx3-core.so" \
|
|
"$build_dir/android/libarmsx3-core.so"
|
|
|
|
local lsfg_so="$build_dir/3rdparty/lsfg/libarmsx3_lsfg.so"
|
|
|
|
if [[ -f "$lsfg_so" ]]; then
|
|
mkdir -p "$JNI_LIBS_GITHUB"
|
|
"$strip" --strip-unneeded -o "$JNI_LIBS_GITHUB/libarmsx3_lsfg.so" "$lsfg_so"
|
|
|
|
# The isolation is the whole design, so verify it every build rather than trusting it.
|
|
# Only the shim's own entry points may be dynamic: a single leaked vk* symbol means the
|
|
# dynamic linker can bind the renderer's Vulkan calls to framegen's copies.
|
|
local leaked
|
|
leaked=$("$ANDROID_HOME/ndk/$ndk/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-nm" \
|
|
-D --defined-only "$JNI_LIBS_GITHUB/libarmsx3_lsfg.so" 2>/dev/null | grep -cE "vk[A-Z]|LSFG" || true)
|
|
|
|
if [[ "$leaked" != "0" ]]; then
|
|
echo "$name: libarmsx3_lsfg.so exports $leaked Vulkan/LSFG symbols -- isolation broken," \
|
|
"refusing to package it" >&2
|
|
return 1
|
|
fi
|
|
else
|
|
echo "$name: libarmsx3_lsfg.so was not built, frame generation will be absent from this APK" >&2
|
|
rm -f "$JNI_LIBS_GITHUB/libarmsx3_lsfg.so"
|
|
fi
|
|
|
|
# assembleGithubRelease, not assembleRelease: the flavor split means there is no
|
|
# flavorless release variant any more. The play bundle is built by build-play-aab.sh.
|
|
( cd "$UI" && ./gradlew --quiet :app:assembleGithubRelease "-Parmsx3.minSdk=$api" )
|
|
|
|
local out="$OUT_DIR/ARMSX3-$(version_name)-$suffix.apk"
|
|
cp "$UI/app/build/outputs/apk/github/release/app-github-release.apk" "$out"
|
|
echo "==> $name: $out"
|
|
}
|
|
|
|
# Stamp the version before anything builds, or every APK reports whichever commit cmake
|
|
# last configured against rather than the one being built.
|
|
bash "$ROOT/android/stamp-git-version.sh"
|
|
|
|
for v in $VARIANTS; do
|
|
build_variant "$v"
|
|
done
|
|
|
|
echo
|
|
echo "Done. Both variants are the same source at the same versionName -- they are alternatives"
|
|
echo "for different devices, not an upgrade path, so publish them side by side and let the user"
|
|
echo "pick. If the in-app updater ever offers one to a user running the other, that is a release"
|
|
echo "metadata problem, not a build one."
|