# Build environment for the Shockolate (System Shock) engine snapshot in
# engine/. Everything the engine build needs from the network - SDL2,
# SDL2_mixer, the fluidsynth-lite MIDI synth, and a General MIDI soundfont -
# is fetched and built once, here, at image-build time. Rebuild with
# ../build-image.sh whenever a version below changes.
#
# The resulting image needs no network access to compile engine/: the
# in-container build script (build-image/build-engine.sh) copies the
# prebuilt pieces below straight into engine/build_ext/, matching the paths
# engine/CMakeLists.txt's BUNDLED dependency mode expects.
FROM ubuntu:22.04

ARG SDL2_VERSION=2.0.9
ARG SDL2_MIXER_VERSION=2.0.4
# EtherTyper/fluidsynth-lite has no releases/tags; pin by commit so the image
# is reproducible instead of silently picking up upstream changes.
ARG FLUIDSYNTH_LITE_REF=c539a8d9270ba5a3f7d6e460606483fc2ab1eb61
# Soundfont used for MIDI music, matching what engine/build_deps.sh itself
# fetches (a free substitute for the Windows default GM soundfont).
ARG SOUNDFONT_URL=http://rancid.kapsi.fi/windows.sf2
# Gitea/GitHub Actions' JS-based actions (actions/checkout,
# actions/upload-artifact, ...) need a node binary in the container job's
# PATH - this image is otherwise pure C toolchain, so it isn't pulled in
# by anything else.
ARG NODE_VERSION=20.18.1

# Android layer, for the Quest APK build (Quest 2, 3, 3S, Pro - all
# arm64-v8a, see build-apk.sh). Same SDL2 major version as the desktop
# build above, but SDL2/SDL2_mixer are much newer releases here - unlike
# the desktop autotools build, Android needs their real CMake+NDK build
# support, which only landed in later releases. targetSdkVersion 29 (not
# compileSdk, which can and does stay newer) is deliberate: paired with
# requestLegacyExternalStorage in the manifest, it keeps /sdcard a plain,
# unrestricted shared folder instead of hitting Android 11+ scoped
# storage - this is keyed off the *app's* targetSdkVersion, not the
# device's own Android version, so it keeps working on newer Quest
# hardware/OS updates too, not just whatever's current today.
ARG ANDROID_SDL2_VERSION=2.28.5
ARG ANDROID_SDL2_MIXER_VERSION=2.8.0
ARG ANDROID_CMDLINE_TOOLS_VERSION=11076708
ARG ANDROID_PLATFORM_VERSION=29
# compileSdk in android/app/build.gradle - separate from the app's own
# targetSdkVersion (29, above): compileSdk is just which android.jar the
# app compiles against, and androidx.core needs a newer one than 29.
ARG ANDROID_COMPILE_SDK_VERSION=34
ARG ANDROID_BUILD_TOOLS_VERSION=34.0.0
ARG ANDROID_NDK_VERSION=26.1.10909125
ARG ANDROID_CMAKE_VERSION=3.22.1
# NDK 26 doesn't yet default to 16 KB-aligned ELF LOAD segments (that only
# became automatic in NDK 28+) - Google requires this for Play Store
# submissions targeting Android 15+ as of Nov 2025, and it's cheap/harmless
# to do regardless of Play Store status. Passed to every Android shared-lib
# CMake configure below, and to the engine's own build via
# android/app/build.gradle's externalNativeBuild.cmake.arguments.
ARG ANDROID_16KB_LDFLAGS="-Wl,-z,max-page-size=16384"
# GL4ES (https://github.com/ptitSeb/gl4es) - translates the engine's
# desktop-style immediate-mode OpenGL calls into real GLES/EGL calls, so
# engine/src/MacSrc/OpenGL.cc needs no immediate-mode rewrite on Android.
ARG ANDROID_GL4ES_VERSION=1.1.6
# Khronos' own OpenXR loader (https://github.com/KhronosGroup/OpenXR-SDK) -
# the vendor-neutral loader, not Meta's redistributed copy, per this
# project's "favor OpenXR" rule (see README's Design principles). Only the
# loader is built (BUILD_LOADER=ON, everything else off) - the actual XR
# runtime implementation is resolved on-device by Horizon OS at load time,
# nothing else needs bundling. Verify this is still the latest release tag
# at https://github.com/KhronosGroup/OpenXR-SDK/releases when bumping.
ARG ANDROID_OPENXR_VERSION=1.1.42

ENV DEBIAN_FRONTEND=noninteractive

# build-essential/cmake/make: engine/ itself (CMake) and SDL2/SDL2_mixer
# (autotools).
# git: fetches fluidsynth-lite; also used by engine/CMakeLists.txt's own
# `git describe` version string (harmless no-op if engine/ isn't a repo).
# curl/ca-certificates: fetches SDL2/SDL2_mixer tarballs and the soundfont.
# pkg-config: SDL2_mixer's configure and engine/CMakeLists.txt's
# pkg_check_modules() calls.
# gosu: docker-entrypoint.sh drops from root to a HOST_UID/GID user so
# build output isn't left root-owned on the bind-mounted repo.
# libgl1-mesa-dev/libglx-dev/libxext-dev/libx11-dev/libxrandr-dev/libxi-dev/
# libxfixes-dev/libxss-dev/libxinerama-dev/libxcursor-dev: SDL2's video
# backend (X11 + GL).
# libogg-dev/libvorbis-dev: SDL2_mixer's OGG Vorbis music decoder.
# libasound2-dev: engine/CMakeLists.txt's optional native ALSA MIDI output.
# openssh-client: the CI workflow's `sftp` publish step.
# openjdk-17-jdk-headless: Gradle/AGP's own minimum JDK for the APK build.
# unzip: extracts the Android cmdline-tools zip below.
#
# All apt installs deliberately live in this one RUN, first, so editing
# anything below it (in particular the Android cross-compile steps, the
# newest and least settled part of this file) never invalidates Docker's
# build cache for this layer - and vice versa, adding a package here only
# ever costs a rebuild of the (slow) layers below it, not a re-download
# of packages that didn't change.
# patchelf: fixes up GL4ES's built-in ELF SONAME below (see the gl4es
# build step) - the Android APK packager (AGP) refuses to bundle a
# jniLibs file whose name doesn't literally end in ".so", but the
# dynamic linker resolves NEEDED entries by embedded SONAME, not
# filename - patchelf lets both agree on "libGL.so".
RUN apt-get update && apt-get install -y --no-install-recommends \
        build-essential cmake make git curl ca-certificates pkg-config gosu \
        libgl1-mesa-dev libglx-dev libxext-dev libx11-dev libxrandr-dev \
        libxi-dev libxfixes-dev libxss-dev libxinerama-dev libxcursor-dev \
        libogg-dev libvorbis-dev libasound2-dev openssh-client \
        openjdk-17-jdk-headless unzip patchelf \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /opt/prebuilt

# SDL2, built the same way engine/build_deps.sh builds it (BUNDLED mode),
# just at image-build time instead of every game build.
RUN curl -sSLO "https://www.libsdl.org/release/SDL2-${SDL2_VERSION}.tar.gz" \
    && tar xf "SDL2-${SDL2_VERSION}.tar.gz" \
    && cd "SDL2-${SDL2_VERSION}" \
    && ./configure --prefix=/opt/prebuilt/built_sdl \
    && make -j"$(nproc)" \
    && make install \
    && cd .. && rm -rf "SDL2-${SDL2_VERSION}" "SDL2-${SDL2_VERSION}.tar.gz"

# SDL2_mixer, linked against the SDL2 build above via SDL2_CONFIG - same as
# engine/build_deps.sh's build_sdl_mixer().
RUN curl -sSLO "https://www.libsdl.org/projects/SDL_mixer/release/SDL2_mixer-${SDL2_MIXER_VERSION}.tar.gz" \
    && tar xf "SDL2_mixer-${SDL2_MIXER_VERSION}.tar.gz" \
    && cd "SDL2_mixer-${SDL2_MIXER_VERSION}" \
    && SDL2_CONFIG=/opt/prebuilt/built_sdl/bin/sdl2-config \
        ./configure --prefix=/opt/prebuilt/built_sdl_mixer \
    && make -j"$(nproc)" \
    && make install \
    && cd .. && rm -rf "SDL2_mixer-${SDL2_MIXER_VERSION}" "SDL2_mixer-${SDL2_MIXER_VERSION}.tar.gz"

# fluidsynth-lite: the stripped-down FluidSynth fork engine/CMakeLists.txt's
# BUNDLED FluidSynth mode expects at build_ext/fluidsynth-lite. Built
# in-source (its own CMake setup expects that), forcing a shared library
# the same way engine/build_deps.sh's sed does.
RUN git clone https://github.com/EtherTyper/fluidsynth-lite.git \
    && cd fluidsynth-lite \
    && git checkout "${FLUIDSYNTH_LITE_REF}" \
    && sed -i 's/DLL"\ off/DLL"\ on/' CMakeLists.txt \
    && cmake . \
    && cmake --build . -j"$(nproc)" \
    && rm -rf .git

# General MIDI soundfont for fluidsynth playback - engine/build_deps.sh
# fetches the same file and drops it into engine/res/.
RUN mkdir -p soundfont \
    && curl -sSL -o soundfont/default.sf2 "${SOUNDFONT_URL}"

# Node.js: needed only so Gitea/GitHub Actions' JS-based actions can run
# when this image is used as a CI job's container - see NODE_VERSION above.
RUN curl -sSL -o /tmp/node.tar.xz \
        "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz" \
    && tar -xJf /tmp/node.tar.xz -C /usr/local --strip-components=1 \
    && rm /tmp/node.tar.xz

# Android SDK/NDK: the cmdline-tools' own sdkmanager installs exactly the
# platform/build-tools/NDK/CMake versions the app/build.gradle below pins.
# gradlew (invoked by build-apk.sh) resolves its own pinned Gradle version
# itself - no Gradle binary is baked into this image. Unlike the manually
# cross-compiled SDL2/SDL2_mixer/fluidsynth-lite below, `./gradlew
# assembleDebug` still needs network access at build time for Gradle/AGP's
# own dependency resolution - an accepted, deliberate exception to this
# image's otherwise-offline build philosophy (see build-apk.sh).
ENV ANDROID_HOME=/opt/android-sdk \
    ANDROID_SDK_ROOT=/opt/android-sdk
ENV PATH=${ANDROID_HOME}/cmdline-tools/latest/bin:${ANDROID_HOME}/platform-tools:${PATH}

RUN mkdir -p "${ANDROID_HOME}/cmdline-tools" \
    && curl -sSL -o /tmp/cmdline-tools.zip \
        "https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_CMDLINE_TOOLS_VERSION}_latest.zip" \
    && unzip -q /tmp/cmdline-tools.zip -d "${ANDROID_HOME}/cmdline-tools" \
    && mv "${ANDROID_HOME}/cmdline-tools/cmdline-tools" "${ANDROID_HOME}/cmdline-tools/latest" \
    && rm /tmp/cmdline-tools.zip \
    && yes | sdkmanager --licenses >/dev/null \
    && sdkmanager --install \
        "platform-tools" \
        "platforms;android-${ANDROID_PLATFORM_VERSION}" \
        "platforms;android-${ANDROID_COMPILE_SDK_VERSION}" \
        "build-tools;${ANDROID_BUILD_TOOLS_VERSION}" \
        "ndk;${ANDROID_NDK_VERSION}" \
        "cmake;${ANDROID_CMAKE_VERSION}"

ENV ANDROID_NDK_HOME=${ANDROID_HOME}/ndk/${ANDROID_NDK_VERSION}
ENV ANDROID_NDK_TOOLCHAIN=${ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake

# SDL2, SDL2_mixer, and fluidsynth-lite, cross-compiled for arm64-v8a (the
# Quest's only ABI, across Quest 2/3/3S/Pro) via the NDK's CMake toolchain file, each installed
# to its own prefix under /opt/prebuilt/android/ - mirrors the desktop
# build_ext/built_sdl / built_sdl_mixer layout, just for Android. Shipped
# in the APK as separate .so's (see build-apk.sh), loaded in that order by
# QuestShockActivity.getLibraries() before the game's own libmain.so.
ARG ANDROID_ABI=arm64-v8a

RUN curl -sSLO "https://www.libsdl.org/release/SDL2-${ANDROID_SDL2_VERSION}.tar.gz" \
    && tar xf "SDL2-${ANDROID_SDL2_VERSION}.tar.gz" \
    && cmake -S "SDL2-${ANDROID_SDL2_VERSION}" -B build-sdl2-android \
        -DCMAKE_TOOLCHAIN_FILE="${ANDROID_NDK_TOOLCHAIN}" \
        -DANDROID_ABI="${ANDROID_ABI}" -DANDROID_PLATFORM="android-${ANDROID_PLATFORM_VERSION}" \
        -DCMAKE_INSTALL_PREFIX=/opt/prebuilt/android/sdl2 -DBUILD_SHARED_LIBS=ON \
        -DSDL_STATIC=OFF \
        -DCMAKE_SHARED_LINKER_FLAGS="${ANDROID_16KB_LDFLAGS}" \
    && cmake --build build-sdl2-android -j"$(nproc)" \
    && cmake --install build-sdl2-android \
    && rm -rf "SDL2-${ANDROID_SDL2_VERSION}" "SDL2-${ANDROID_SDL2_VERSION}.tar.gz" build-sdl2-android

# All optional codecs disabled: Shockolate only ever calls
# Mix_LoadWAV_RW/Mix_HookMusic (confirmed by grep - it feeds fluidsynth's
# own PCM output through Mix_HookMusic, and never loads OGG/MOD/FLAC/MP3
# game data), so plain WAVE support (always built in, no extra
# dependency) is all that's needed - avoiding SDL2_mixer's vendored
# third-party codec libraries entirely.
RUN curl -sSLO "https://www.libsdl.org/projects/SDL_mixer/release/SDL2_mixer-${ANDROID_SDL2_MIXER_VERSION}.tar.gz" \
    && tar xf "SDL2_mixer-${ANDROID_SDL2_MIXER_VERSION}.tar.gz" \
    && cmake -S "SDL2_mixer-${ANDROID_SDL2_MIXER_VERSION}" -B build-sdl2mixer-android \
        -DCMAKE_TOOLCHAIN_FILE="${ANDROID_NDK_TOOLCHAIN}" \
        -DANDROID_ABI="${ANDROID_ABI}" -DANDROID_PLATFORM="android-${ANDROID_PLATFORM_VERSION}" \
        -DCMAKE_PREFIX_PATH=/opt/prebuilt/android/sdl2 \
        -DCMAKE_FIND_ROOT_PATH=/opt/prebuilt/android/sdl2 \
        -DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=BOTH \
        -DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=BOTH \
        -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=BOTH \
        -DCMAKE_INSTALL_PREFIX=/opt/prebuilt/android/sdl2_mixer -DBUILD_SHARED_LIBS=ON \
        -DSDL2MIXER_VENDORED=OFF -DSDL2MIXER_SAMPLES=OFF -DSDL2MIXER_CMD=OFF \
        -DSDL2MIXER_FLAC=OFF -DSDL2MIXER_GME=OFF -DSDL2MIXER_MOD=OFF \
        -DSDL2MIXER_MP3=OFF -DSDL2MIXER_MIDI=OFF -DSDL2MIXER_OPUS=OFF -DSDL2MIXER_VORBIS=OFF \
        -DSDL2MIXER_WAVPACK=OFF \
        -DCMAKE_SHARED_LINKER_FLAGS="${ANDROID_16KB_LDFLAGS}" \
    && cmake --build build-sdl2mixer-android -j"$(nproc)" \
    && cmake --install build-sdl2mixer-android \
    && rm -rf "SDL2_mixer-${ANDROID_SDL2_MIXER_VERSION}" "SDL2_mixer-${ANDROID_SDL2_MIXER_VERSION}.tar.gz" build-sdl2mixer-android

RUN git clone https://github.com/EtherTyper/fluidsynth-lite.git fluidsynth-lite-android \
    && cd fluidsynth-lite-android \
    && git checkout "${FLUIDSYNTH_LITE_REF}" \
    && sed -i 's/DLL"\ off/DLL"\ on/' CMakeLists.txt \
    && sed -i 's/-Wall -Werror -std=gnu11/-Wall -std=gnu11/' CMakeLists.txt \
    && sed -i 's/set ( LIBFLUID_LIBS m pthread )/set ( LIBFLUID_LIBS m )/' CMakeLists.txt \
    && rm -rf .git \
    && cd .. \
    && cmake -S fluidsynth-lite-android -B build-fluidsynth-android \
        -DCMAKE_TOOLCHAIN_FILE="${ANDROID_NDK_TOOLCHAIN}" \
        -DANDROID_ABI="${ANDROID_ABI}" -DANDROID_PLATFORM="android-${ANDROID_PLATFORM_VERSION}" \
        -DCMAKE_SHARED_LINKER_FLAGS="${ANDROID_16KB_LDFLAGS}" \
    && cmake --build build-fluidsynth-android -j"$(nproc)" \
    && mkdir -p /opt/prebuilt/android/fluidsynth-lite/lib /opt/prebuilt/android/fluidsynth-lite/include \
    && cp -a build-fluidsynth-android/src/libfluidsynth.so* /opt/prebuilt/android/fluidsynth-lite/lib/ \
    && cp -a fluidsynth-lite-android/include/. /opt/prebuilt/android/fluidsynth-lite/include/ \
    # version.h is generated from version.h.in at configure time - fine on
    # the desktop build (in-source, so it lands right back in the source
    # tree), but this is an out-of-source build, so it only exists under
    # build-fluidsynth-android/ and must be copied in separately.
    && cp -a build-fluidsynth-android/include/fluidsynth/version.h \
        /opt/prebuilt/android/fluidsynth-lite/include/fluidsynth/version.h \
    && rm -rf fluidsynth-lite-android build-fluidsynth-android

# gl4es's own CMakeLists.txt writes its output straight to
# ${CMAKE_SOURCE_DIR}/lib (the source tree, not the build dir) and gives
# the GL target a ".so.1" suffix, both in filename and in its embedded
# ELF SONAME - stage explicitly rather than `cmake --install` (which it
# doesn't support for this target anyway). Renaming the *file* to
# "libGL.so" isn't enough on its own: the dynamic linker resolves
# libmain.so's NEEDED entry by the *embedded* SONAME, not by whatever
# filename it was linked against, so a bare rename would link fine but
# fail to dlopen on-device. And keeping the real "libGL.so.1" filename
# isn't an option either - AGP's jniLibs packaging silently drops any
# native library whose filename doesn't literally end in ".so". So
# patchelf the embedded SONAME to "libGL.so" too, making the rename
# consistent both at link time and at runtime.
RUN git clone --branch "v${ANDROID_GL4ES_VERSION}" --depth 1 \
        https://github.com/ptitSeb/gl4es.git gl4es-android \
    && rm -rf gl4es-android/.git \
    && cmake -S gl4es-android -B build-gl4es-android \
        -DCMAKE_TOOLCHAIN_FILE="${ANDROID_NDK_TOOLCHAIN}" \
        -DANDROID_ABI="${ANDROID_ABI}" -DANDROID_PLATFORM="android-${ANDROID_PLATFORM_VERSION}" \
        -DANDROID=ON -DUSE_ANDROID_LOG=ON -DSTATICLIB=OFF \
        -DCMAKE_SHARED_LINKER_FLAGS="${ANDROID_16KB_LDFLAGS}" \
    && cmake --build build-gl4es-android -j"$(nproc)" \
    && mkdir -p /opt/prebuilt/android/gl4es/lib /opt/prebuilt/android/gl4es/include \
    && cp -a gl4es-android/lib/libGL.so.1 /opt/prebuilt/android/gl4es/lib/libGL.so \
    && patchelf --set-soname libGL.so /opt/prebuilt/android/gl4es/lib/libGL.so \
    && cp -a gl4es-android/include/. /opt/prebuilt/android/gl4es/include/ \
    && rm -rf gl4es-android build-gl4es-android

# OpenXR loader, for the immersive VR mode (android/app/src/main/cpp/
# xr_session.c and friends) - see android/engine-patches/
# 11-android-openxr-cmake.patch for how the engine links it.
RUN git clone --branch "release-${ANDROID_OPENXR_VERSION}" --depth 1 \
        https://github.com/KhronosGroup/OpenXR-SDK.git openxr-sdk-android \
    && rm -rf openxr-sdk-android/.git \
    && cmake -S openxr-sdk-android -B build-openxr-android \
        -DCMAKE_TOOLCHAIN_FILE="${ANDROID_NDK_TOOLCHAIN}" \
        -DANDROID_ABI="${ANDROID_ABI}" -DANDROID_PLATFORM="android-${ANDROID_PLATFORM_VERSION}" \
        -DCMAKE_INSTALL_PREFIX=/opt/prebuilt/android/openxr \
        -DBUILD_LOADER=ON -DBUILD_TESTS=OFF -DBUILD_API_LAYERS=OFF \
        -DBUILD_CONFORMANCE_TESTS=OFF -DBUILD_WITH_SYSTEM_JSONCPP=OFF \
        -DDYNAMIC_LOADER=ON \
        -DCMAKE_SHARED_LINKER_FLAGS="${ANDROID_16KB_LDFLAGS}" \
    && cmake --build build-openxr-android -j"$(nproc)" --target openxr_loader \
    && cmake --install build-openxr-android \
    && mkdir -p /opt/prebuilt/android/openxr/include \
    && cp -a openxr-sdk-android/include/. /opt/prebuilt/android/openxr/include/ \
    && rm -rf openxr-sdk-android build-openxr-android

COPY build-image/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
COPY build-image/build-engine.sh /usr/local/bin/build-engine.sh
RUN chmod +x /usr/local/bin/docker-entrypoint.sh /usr/local/bin/build-engine.sh

# Marks a shell as already running inside this image (with every engine
# build dependency prebuilt above) - lets the Makefile's `engine` target
# compile directly instead of shelling out to ./run-image.sh's `docker
# run`, which matters for CI jobs that already run inside this image
# (nested docker-in-docker isn't available there).
ENV QUESTSHOCK_BUILD_IMAGE=1

WORKDIR /workspace
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
CMD ["/usr/local/bin/build-engine.sh"]
