Files
DroidDeck/tools/build_local.sh
T
The412Banner d160f464b6 Rename the app SteamDeck → DroidDeck
App name, app ID and code package: DroidDeck, com.droiddeck.launcher
(sources moved to com/droiddeck/launcher, the 68 JNI functions renamed
with them). The new app ID installs beside SteamDeck rather than over it.

Everywhere the name was the app's own: the sidebar title, the session
report, share zips, the shell title, wake-lock and log tags, User-Agents,
the Compose theme, the Download folder and switch files (droiddeck-env,
-osc, -driver, -tu-debug, -no-pad, -pad-log, -no-hud, -wlr-renderer; the
old names are not read), and inside the runtime (droiddeck-desktop,
Firefox's droiddeck.js, the shortcuts tag, the rumble socket, debug and
cache paths). Build scripts, CI artifact (droiddeck-apk), CMake project,
README and the progress log follow.

Kept on purpose: Valve's Steam Deck (the -steamdeck flag,
steamdeck_publicbeta/stable, 'Steam Deck mode', the steamos.cloud
package URLs) and names inside the hosted desktop package (steamdeck.png,
steamdeck-steam, steamdeck-bigpicture). 'Desktop installed' now tests for
labwc instead of the launcher the app stages itself. A committed
__pycache__ .pyc is removed.
2026-09-23 18:28:04 -04:00

219 lines
9.0 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
repo_root=$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
sdk_dir=${ANDROID_HOME:-${ANDROID_SDK_ROOT:-"${HOME}/Library/Android/sdk"}}
java_dir=${JAVA_HOME:-"/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home"}
image_name=${DROIDDECK_BUILD_IMAGE:-droiddeck-local-cross:24.04-v2}
if [[ ! -x "${sdk_dir}/platform-tools/adb" ]]; then
echo "Android SDK not found at ${sdk_dir}; set ANDROID_HOME or ANDROID_SDK_ROOT." >&2
exit 1
fi
if [[ ! -x "${java_dir}/bin/java" ]]; then
echo "Java 17 not found at ${java_dir}; set JAVA_HOME." >&2
exit 1
fi
if ! command -v docker >/dev/null 2>&1; then
echo "Docker is required to cross-compile the glibc ARM64 preload libraries." >&2
exit 1
fi
if ! docker image inspect "${image_name}" >/dev/null 2>&1; then
docker build --platform linux/amd64 -t "${image_name}" \
-f "${repo_root}/tools/local-cross.Dockerfile" "${repo_root}"
fi
docker run --rm --platform linux/amd64 \
--user "$(id -u):$(id -g)" \
-v "${repo_root}:/src" -w /src "${image_name}" bash -lc '
set -euo pipefail
d=app/src/main/assets/linuxfs
mkdir -p "$d"
aarch64-linux-gnu-g++ -shared -fPIC -O2 -Wall -Wno-attributes -Wno-nonnull-compare \
-pthread -std=c++17 -static-libstdc++ -static-libgcc \
-o "$d/libfakeinput.so" app/src/main/cpp/fakeinput_steam.cpp -ldl
aarch64-linux-gnu-strip --strip-unneeded "$d/libfakeinput.so"
aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -pthread \
-o "$d/libblsession.so" tools/linuxfs/preload/*.c -ldl
aarch64-linux-gnu-strip --strip-unneeded "$d/libblsession.so"
for script in tools/linuxfs/overlay/usr/local/bin/bannerlator-*; do
install -Dm644 "$script" "$d/usr/local/bin/$(basename "$script")"
done
install -Dm644 tools/linuxfs/desktop/droiddeck-desktop "$d/usr/local/bin/droiddeck-desktop"
install -Dm644 tools/linuxfs/desktop/autostart "$d/etc/xdg/labwc/autostart"
install -Dm644 tools/linuxfs/desktop/rc.xml "$d/etc/xdg/labwc/rc.xml"
install -Dm644 tools/linuxfs/desktop/panel.conf "$d/etc/xdg/lxqt/panel.conf"
install -Dm644 tools/linuxfs/desktop/firefox-droiddeck.js \
"$d/usr/lib/firefox/defaults/pref/droiddeck.js"
need=$(aarch64-linux-gnu-readelf -d "$d/libfakeinput.so" | sed -n "s/.*NEEDED.*\\[\\(.*\\)\\]/\\1/p")
for bad in libstdc++.so.6 libgcc_s.so.1; do
if printf "%s\\n" "$need" | grep -qx "$bad"; then
echo "libfakeinput.so links $bad; the C++ runtime must stay static" >&2
exit 1
fi
done
syms() { aarch64-linux-gnu-readelf -Ws "$1" | awk '\''$4 == "FUNC" && $5 == "GLOBAL" {sub(/@.*/, "", $8); print $8}'\''; }
fake=$(syms "$d/libfakeinput.so")
for sym in open openat ioctl read close poll ppoll select stat fstat access scandir; do
printf "%s\\n" "$fake" | grep -qx "$sym" || {
echo "libfakeinput.so does not export $sym" >&2
exit 1
}
done
session=$(syms "$d/libblsession.so")
for sym in socket bind getsockname setsockopt statfs statvfs; do
printf "%s\\n" "$session" | grep -qx "$sym" || {
echo "libblsession.so does not export $sym" >&2
exit 1
}
done
test -f "$d/usr/local/bin/bannerlator-session"
test -f "$d/usr/local/bin/bannerlator-proton-extra"
'
export ANDROID_HOME="${sdk_dir}"
export ANDROID_SDK_ROOT="${sdk_dir}"
export JAVA_HOME="${java_dir}"
ndk_version=${DROIDDECK_NDK_VERSION:-}
if [[ -z "${ndk_version}" ]]; then
ndk_path=$(find "${sdk_dir}/ndk" -mindepth 1 -maxdepth 1 -type d -print | sort -V | tail -1)
ndk_version=${ndk_path##*/}
fi
if [[ -z "${ndk_version}" || ! -d "${sdk_dir}/ndk/${ndk_version}" ]]; then
echo "No Android NDK found under ${sdk_dir}/ndk; set DROIDDECK_NDK_VERSION." >&2
exit 1
fi
for tool in curl tar zstd; do
if ! command -v "${tool}" >/dev/null 2>&1; then
echo "${tool} is required to build the bundled audio modules." >&2
exit 1
fi
done
staging_dir=$(mktemp -d "${TMPDIR:-/tmp}/droiddeck-build.XXXXXX")
bundle_asset="${repo_root}/app/src/main/assets/pulseaudio.tzst"
bundle_backup="${staging_dir}/pulseaudio.original.tzst"
bundle_replaced=0
cleanup() {
local exit_code=$?
trap - EXIT
if [[ "${bundle_replaced}" == 1 ]]; then
cp -p "${bundle_backup}" "${bundle_asset}" || exit_code=1
fi
rm -rf -- "${staging_dir}" || exit_code=1
exit "${exit_code}"
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
pa_source=${DROIDDECK_PA13_SOURCE_DIR:-"${staging_dir}/pulseaudio-13.0"}
if [[ -z "${DROIDDECK_PA13_SOURCE_DIR:-}" ]]; then
curl -fsSL -o "${staging_dir}/pulseaudio-13.0.tar.gz" \
https://github.com/pulseaudio/pulseaudio/archive/refs/tags/v13.0.tar.gz
mkdir -p "${pa_source}"
tar -xzf "${staging_dir}/pulseaudio-13.0.tar.gz" \
-C "${pa_source}" --strip-components=1
fi
if [[ ! -f "${pa_source}/src/pulse/version.h.in" ]]; then
echo "PulseAudio 13.0 source not found at ${pa_source}; set DROIDDECK_PA13_SOURCE_DIR." >&2
exit 1
fi
export NDK="${sdk_dir}/ndk/${ndk_version}"
sink_output="${staging_dir}/sink-out"
"${repo_root}/tools/aaudio-sink/build.sh" "${pa_source}" "${sink_output}"
cp -p "${bundle_asset}" "${bundle_backup}"
bundle_dir="${staging_dir}/pulseaudio-bundle"
mkdir -p "${bundle_dir}"
zstd -dc "${bundle_asset}" | tar -xf - -C "${bundle_dir}"
if [[ -e "${bundle_dir}/modules/arm64/module-aaudio-sink.so" \
|| -e "${bundle_dir}/modules/arm64/module-directaudio-sink.so" ]]; then
echo "The committed audio bundle already contains a built ARM64 sink." >&2
exit 1
fi
install -m755 "${sink_output}/module-aaudio-sink.so" \
"${bundle_dir}/modules/arm64/module-aaudio-sink.so"
install -m755 "${sink_output}/module-directaudio-sink.so" \
"${bundle_dir}/modules/arm64/module-directaudio-sink.so"
tar -cf - -C "${bundle_dir}" . | zstd -19 -c > "${staging_dir}/pulseaudio.tzst"
bundle_replaced=1
mv "${staging_dir}/pulseaudio.tzst" "${bundle_asset}"
cd "${repo_root}"
./gradlew assembleRelease -PndkVersion="${ndk_version}"
cp -p "${bundle_backup}" "${bundle_asset}"
bundle_replaced=0
apk="${repo_root}/app/build/outputs/apk/release/app-release.apk"
audio_check="${staging_dir}/audio-check"
mkdir -p "${audio_check}"
unzip -p "${apk}" assets/pulseaudio.tzst | zstd -dc | tar -xf - -C "${audio_check}"
for audio_file in \
pactl \
modules/arm64/module-aaudio-sink.so \
modules/arm64/module-directaudio-sink.so; do
if [[ ! -f "${audio_check}/${audio_file}" ]]; then
echo "APK audio bundle is missing ${audio_file}." >&2
exit 1
fi
done
build_tools=$(find "${sdk_dir}/build-tools" -mindepth 1 -maxdepth 1 -type d -print | sort -V | tail -1)
if [[ ! -x "${build_tools}/zipalign" || ! -x "${build_tools}/apksigner" ]]; then
echo "Android build-tools with zipalign/apksigner are required under ${sdk_dir}/build-tools." >&2
exit 1
fi
"${build_tools}/zipalign" -p -f 4 "${apk}" "${staging_dir}/app-release.aligned.apk"
"${build_tools}/apksigner" sign \
--ks keystore/testkey.p12 --ks-type PKCS12 --ks-pass pass:android \
--ks-key-alias testkey --key-pass pass:android \
--v1-signing-enabled true --v2-signing-enabled true --v3-signing-enabled true \
--out "${apk}" "${staging_dir}/app-release.aligned.apk"
signature_output=$("${build_tools}/apksigner" verify --min-sdk-version 21 --verbose --print-certs "${apk}")
printf '%s\n' "${signature_output}"
for scheme in \
'Verified using v1 scheme (JAR signing): true' \
'Verified using v2 scheme (APK Signature Scheme v2): true' \
'Verified using v3 scheme (APK Signature Scheme v3): true'; do
grep -qF "${scheme}" <<<"${signature_output}" || {
echo "APK signature check failed: ${scheme}" >&2
exit 1
}
done
grep -qF 'Signer #1 certificate DN: EMAILADDRESS=android@android.com, CN=Android, OU=Android, O=Android' \
<<<"${signature_output}"
docker run --rm --platform linux/amd64 -v "${repo_root}:/src:ro" -w /src "${image_name}" \
bash -lc '
set -euo pipefail
apk=app/build/outputs/apk/release/app-release.apk
work=$(mktemp -d)
unzip -q "$apk" "lib/arm64-v8a/*" -d "$work"
cd "$work/lib/arm64-v8a"
system="libc.so libm.so libdl.so liblog.so libandroid.so libz.so libvulkan.so
libGLESv2.so libEGL.so libnativewindow.so libjnigraphics.so libaaudio.so
libOpenSLES.so libmediandk.so libcamera2ndk.so libsync.so libneuralnetworks.so"
fail=0
for so in *.so; do
for need in $(readelf -d "$so" | sed -n "s/.*NEEDED.*\\[\\(.*\\)\\]/\\1/p"); do
[ -f "$need" ] && continue
case " $(echo $system) " in *" $need "*) continue ;; esac
echo "missing: $so -> $need"
fail=1
done
done
[ "$fail" -eq 0 ]
echo "every NEEDED resolves"
'
printf 'APK: %s\n' "${apk}"
printf 'SHA-256: '
shasum -a 256 "${apk}" | awk '{print $1}'