# 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 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. 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 \ && 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 \ && 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 \ && 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}" \ && 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 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"]