Files
proton-stream/scripts/build-libmpv-android.sh
T
nvrlandClaude Opus 5.5 cf065fc5fe ci(android): install libdbus, cache cargo and the staged libmpv
Every Android run since August failed in :app:buildRustHost: the host
build of pstr-android reaches libdbus-sys through keyring's Secret
Service store, and the runner had no libdbus-1-dev. With no successful
run, nothing was ever cached, so each run rebuilt from nothing.

- libdbus-1-dev in both jobs
- one shared, save-on-failure Rust cache for both jobs
- the staged libmpv tree cached on the build script's hash; the script
  stamps its output with the revision and its own hash and skips the
  libmpv build when the stamp matches

PLAN.md records the Android parity and UI work that follows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 03:06: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=20a3fa526fac6d3fe267aee0d4c349893fee65a3
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/29.0.14206865"
else
echo "error: set ANDROID_NDK_HOME or ANDROID_HOME to an Android NDK r29 installation" >&2
exit 1
fi
if [[ ! -f "$ndk_dir/source.properties" ]] ||
! grep -q '^Pkg.Revision = 29\.0\.14206865$' "$ndk_dir/source.properties"; then
echo "error: libmpv requires Android NDK 29.0.14206865: $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-r29"
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"