Files
proton-stream/scripts/build-libmpv-android.sh
nvrl 99abc8f205 build(android): move to NDK r30 and mpv-android 687c7fd
Upstream mpv-android moved its build to NDK r30 (30.0.16248370), and each
mpv-android revision pins its own NDK, so the two move together. The new
revision also updates harfbuzz to 14.5.0, unibreak to 8.0, fribidi to
1.0.17, libxml2 to 2.15.4, fontconfig to 2.18.3 and curl to 8.22.0, and
builds libass with assembly on x86_64 (nasm, already installed in CI).

r30 brings an LLVM update with codegen and LTO fixes and sysroots up to
API 37. It is also the NDK current SDK managers install, so a fresh setup
no longer needs r29 installed beside it.

The script change invalidates the cached libmpv tree, so CI rebuilds it
once. The staged libraries are the same set as before, all 16 KiB aligned.
2026-10-04 02:42:53 +02:00

210 lines
8.1 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd -- "$script_dir/.." && pwd)"
source_dir="${PSTR_MPV_SOURCE_DIR:-$repo_root/.build/mpv-android}"
revision=687c7fd586d826fd0387809e14bec7a2b4fc45a1
destination="$repo_root/android/app/build/generated/mpv"
# The staged tree depends only on the pinned revision and on this script, which
# also pins the NDK. A tree stamped with both is reused as is: that is what lets
# CI restore it from a cache instead of rebuilding FFmpeg and every other
# dependency from source on each run.
stamp="$revision $(sha256sum "${BASH_SOURCE[0]}" | cut -d' ' -f1)"
if [[ "$(cat "$destination/STAMP" 2>/dev/null)" == "$stamp" &&
-f "$destination/jniLibs/arm64-v8a/libmpv.so" &&
-f "$destination/jniLibs/x86_64/libmpv.so" &&
-f "$destination/mpv-android-source-$revision.tar.gz" ]]; then
echo "libmpv $revision is already staged in $destination"
exit 0
fi
rm -f -- "$destination/STAMP"
if [[ ! -d "$source_dir/.git" ]]; then
git clone https://github.com/mpv-android/mpv-android.git "$source_dir"
fi
git -C "$source_dir" fetch --depth=1 origin "$revision"
git -C "$source_dir" checkout --detach "$revision"
git -C "$source_dir" submodule sync --recursive
git -C "$source_dir" submodule update --init --recursive
if [[ -n "${ANDROID_NDK_HOME:-}" ]]; then
ndk_dir="$ANDROID_NDK_HOME"
elif [[ -n "${ANDROID_HOME:-}" ]]; then
ndk_dir="$ANDROID_HOME/ndk/30.0.16248370"
else
echo "error: set ANDROID_NDK_HOME or ANDROID_HOME to an Android NDK r30 installation" >&2
exit 1
fi
if [[ ! -f "$ndk_dir/source.properties" ]] ||
! grep -q '^Pkg.Revision = 30\.0\.16248370$' "$ndk_dir/source.properties"; then
echo "error: libmpv requires Android NDK 30.0.16248370: $ndk_dir" >&2
exit 1
fi
ndk_dir="$(cd -- "$ndk_dir" && pwd -P)"
mpv_sdk_dir="$source_dir/buildscripts/sdk"
mpv_ndk_link="$mpv_sdk_dir/android-ndk-r30"
mkdir -p "$mpv_sdk_dir"
if [[ -e "$mpv_ndk_link" && ! -L "$mpv_ndk_link" ]]; then
echo "error: refusing to replace non-symlink NDK path: $mpv_ndk_link" >&2
exit 1
fi
ln -sfn "$ndk_dir" "$mpv_ndk_link"
ensure_lua_source() {
local lua_dir="$source_dir/buildscripts/deps/lua"
if [[ -f "$lua_dir/Makefile" ]]; then
return
fi
local lua_url="https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/lua5.2/5.2.4-3build2/lua5.2_5.2.4.orig.tar.gz"
local lua_sha256="86fb7e23cbbddfcd92684e5f8017ff41c9112251d1656dbece415a97fad171c0"
local lua_archive
local lua_staging
lua_archive="$(mktemp)"
lua_staging="$(mktemp -d)"
if ! curl --fail --location --retry 3 --output "$lua_archive" "$lua_url"; then
rm -f -- "$lua_archive"
rm -rf -- "$lua_staging"
echo "error: failed to download the pinned Lua 5.2.4 source" >&2
exit 1
fi
if ! printf '%s %s\n' "$lua_sha256" "$lua_archive" | sha256sum --check --status; then
rm -f -- "$lua_archive"
rm -rf -- "$lua_staging"
echo "error: Lua 5.2.4 source checksum mismatch" >&2
exit 1
fi
mkdir -p "$source_dir/buildscripts/deps"
tar -xzf "$lua_archive" -C "$lua_staging" --strip-components=1
rm -f -- "$lua_archive"
if [[ -e "$lua_dir" || -L "$lua_dir" ]]; then
# This path is fixed beneath the generated dependency directory, and the
# valid-source early return above prevents replacing a completed checkout.
rm -rf -- "$lua_dir"
fi
mv "$lua_staging" "$lua_dir"
}
# This pinned mpv-android revision stores its dependency recipes in-tree and
# materializes their exact source revisions under buildscripts/deps. Some of
# those dependencies contain submodules of their own. The downloader is
# idempotent and must run before buildall can resolve the `mpv` target.
ensure_lua_source
(cd "$source_dir/buildscripts" && ./include/download-deps.sh)
ensure_lua_source
for arch in arm64 x86_64; do
(cd "$source_dir/buildscripts" && ./buildall.sh --arch "$arch" mpv)
done
llvm_readelf="$(find "$ndk_dir/toolchains/llvm/prebuilt" -name llvm-readelf -print -quit)"
if [[ -z "$llvm_readelf" ]]; then
echo "error: llvm-readelf is missing from Android NDK: $ndk_dir" >&2
exit 1
fi
# These libraries are supplied by Android itself. Every other DT_NEEDED entry
# must be packaged in the APK for each ABI. Keep this list explicit: silently
# treating an unknown library as a platform library produces installable APKs
# that fail as soon as the dynamic linker loads pstr_mpv/libmpv.
is_android_system_library() {
case "$1" in
libc.so | libdl.so | liblog.so | libm.so | libz.so | libandroid.so | \
libmediandk.so | libnativewindow.so | libOpenSLES.so | libaaudio.so | \
libEGL.so | libGLESv1_CM.so | libGLESv2.so | libGLESv3.so | \
libjnigraphics.so | libvulkan.so)
return 0
;;
*)
return 1
;;
esac
}
stage_shared_dependency_closure() {
local arch="$1"
local abi="$2"
local ndk_triple="$3"
local prefix_lib="$source_dir/buildscripts/prefix/$arch/lib"
local ndk_lib="$ndk_dir/toolchains/llvm/prebuilt/$(basename "$(dirname "$(dirname "$llvm_readelf")")")/sysroot/usr/lib/$ndk_triple"
local output_dir="$destination/jniLibs/$abi"
local current needed source soname
local -a pending=(libmpv.so)
local -A staged=()
# Remove stale libraries from an earlier build before constructing the exact
# closure for this pinned revision.
mkdir -p "$output_dir"
find "$output_dir" -maxdepth 1 -type f -name '*.so' -delete
while ((${#pending[@]})); do
current="${pending[0]}"
pending=("${pending[@]:1}")
[[ -n "${staged[$current]:-}" ]] && continue
if [[ -f "$prefix_lib/$current" ]]; then
source="$prefix_lib/$current"
elif [[ "$current" == libc++_shared.so && -f "$ndk_lib/$current" ]]; then
source="$ndk_lib/$current"
else
echo "error: unresolved non-system dependency for $abi: $current" >&2
exit 1
fi
soname="$($llvm_readelf -d "$source" | sed -n 's/.*SONAME.*\[\(.*\)\]/\1/p')"
if [[ "$soname" != "$current" ]]; then
echo "error: $source has SONAME '$soname', expected '$current'" >&2
exit 1
fi
cp -- "$source" "$output_dir/$current"
staged[$current]=1
while IFS= read -r needed; do
if ! is_android_system_library "$needed" && [[ -z "${staged[$needed]:-}" ]]; then
pending+=("$needed")
fi
done < <("$llvm_readelf" -d "$source" | sed -n 's/.*Shared library: \[\(.*\)\]/\1/p')
done
# Re-read only the packaged copies, so verification also catches a bad or
# versioned staging filename rather than trusting the source tree.
for current in "${!staged[@]}"; do
while IFS= read -r needed; do
if ! is_android_system_library "$needed" && [[ ! -f "$output_dir/$needed" ]]; then
echo "error: $abi/$current needs unstaged library $needed" >&2
exit 1
fi
done < <("$llvm_readelf" -d "$output_dir/$current" | sed -n 's/.*Shared library: \[\(.*\)\]/\1/p')
done
printf 'staged %s shared libraries for %s:\n' "${#staged[@]}" "$abi"
printf ' %s\n' "${!staged[@]}" | LC_ALL=C sort
}
mkdir -p "$destination/include/mpv"
cp "$source_dir/buildscripts/prefix/arm64/include/mpv/"*.h "$destination/include/mpv/"
# FFmpeg's JNI entry points are not part of the mpv API, but registering the
# JavaVM MediaCodec needs is our job now that mpv-android's JNI layer is gone.
mkdir -p "$destination/include/libavcodec"
cp "$source_dir/buildscripts/prefix/arm64/include/libavcodec/jni.h" "$destination/include/libavcodec/"
stage_shared_dependency_closure arm64 arm64-v8a aarch64-linux-android
stage_shared_dependency_closure x86_64 x86_64 x86_64-linux-android
# `git archive` omits dependency working trees. Archive the pinned checkout and
# downloaded sources together, excluding only generated build/install output
# and Git administration data, so distributed binaries have corresponding
# source for mpv and every statically linked dependency.
tar \
--exclude='.git' \
--exclude='buildscripts/prefix' \
--exclude='buildscripts/sdk' \
--exclude='buildscripts/deps/*/_build_*' \
-C "$source_dir" \
-czf "$destination/mpv-android-source-$revision.tar.gz" \
.
printf '%s\n' "$revision" > "$destination/REVISION"
# Last, so an interrupted run never leaves a stamp over a partial tree.
printf '%s\n' "$stamp" > "$destination/STAMP"