rpgmaker / Dockerfile
Cnass's picture
Claude Opus 5.5
Fix missing game files on Android and unsigned release APKs
a859d16
Raw History Blame Contribute Delete
7.41 kB
# Riaslink Port Builder — Hugging Face Space
#
# Builds a debug or release APK from a RPG Maker MV/MZ `www/` export, with the
# RIA_TavernMenu + RIA_FPSCounter plugins (see /android-template/app/src/main/
# assets/www/js/plugins/) auto-injected into the game's own plugin list.
#
# Base image choice: eclipse-temurin's JDK image (not a generic openjdk image,
# which Docker Hub deprecated) gives a known-good JDK 17 without extra apt
# juggling; everything Android-specific (cmdline-tools, licenses, platform)
# is installed explicitly below so the exact SDK contents are visible here
# rather than hidden in a third-party Android base image.
FROM eclipse-temurin:17-jdk-jammy
ENV DEBIAN_FRONTEND=noninteractive
# UTF-8 locale for every JVM in the image (Gradle included): with an ASCII
# locale Java cannot open files whose names contain Japanese or Turkish
# characters, so those game assets silently went missing from the APK.
ENV LANG=C.UTF-8 LC_ALL=C.UTF-8
# --- System packages ---------------------------------------------------
# python3-venv: Spaces' base images vary in whether pip is pre-wired for
# the system python, a venv sidesteps that entirely.
# unzip: the Android cmdline-tools ship as a zip; also used by the pipeline
# to unpack the user's uploaded www.zip.
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 \
python3-pip \
python3-venv \
unzip \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
# --- Bootstrap Gradle itself --------------------------------------------
# The template ships gradlew + gradlew.bat (the standard, version-independent
# wrapper scripts) but deliberately NOT gradle/wrapper/gradle-wrapper.jar,
# since that's a compiled binary this environment cannot author by hand.
# A real, versioned Gradle install here regenerates that jar via
# `gradle wrapper` right below, which is the officially supported way to
# produce it — after which every actual build in this image runs through
# ./gradlew as normal, matching what a contributor's machine would do.
ARG GRADLE_VERSION=8.10.2
RUN curl -fsSL -o /tmp/gradle.zip "https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip" && \
unzip -q /tmp/gradle.zip -d /opt && \
ln -s /opt/gradle-${GRADLE_VERSION}/bin/gradle /usr/local/bin/gradle && \
rm /tmp/gradle.zip
# --- Android SDK ---------------------------------------------------------
# Only the pieces actually needed to run `gradlew assembleDebug/Release`
# against the template's compileSdk=35/minSdk=26 (see android-template/app/
# build.gradle.kts) are installed — no emulator, no system images, no NDK.
ENV ANDROID_SDK_ROOT=/opt/android-sdk
ENV ANDROID_HOME=$ANDROID_SDK_ROOT
ENV PATH=$PATH:$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$ANDROID_SDK_ROOT/platform-tools
# Version pin: commandlinetools revision current at the time this Space was
# built. If Google retires this exact URL, replace it with the current
# "Command line tools only" link from https://developer.android.com/studio
# — the rest of this Dockerfile does not need to change.
ARG CMDLINE_TOOLS_URL=https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip
RUN mkdir -p $ANDROID_SDK_ROOT/cmdline-tools && \
curl -fsSL -o /tmp/cmdline-tools.zip "$CMDLINE_TOOLS_URL" && \
unzip -q /tmp/cmdline-tools.zip -d $ANDROID_SDK_ROOT/cmdline-tools && \
mv $ANDROID_SDK_ROOT/cmdline-tools/cmdline-tools $ANDROID_SDK_ROOT/cmdline-tools/latest && \
rm /tmp/cmdline-tools.zip
# Accepting licenses non-interactively is required before sdkmanager will
# install anything in a headless container.
RUN yes | sdkmanager --licenses > /dev/null || true
RUN sdkmanager \
"platform-tools" \
"platforms;android-35" \
"build-tools;35.0.0" \
> /dev/null
# --- Gradle cache warm-up --------------------------------------------------
# Copies just the wrapper + build files first (not the whole template) so
# Docker's layer cache can skip re-downloading Gradle/dependencies on
# rebuilds that only change app source, www content, or app.py.
WORKDIR /android-template
COPY android-template/gradle ./gradle
COPY android-template/gradlew ./gradlew
COPY android-template/gradlew.bat ./gradlew.bat
COPY android-template/settings.gradle.kts ./settings.gradle.kts
COPY android-template/build.gradle.kts ./build.gradle.kts
COPY android-template/gradle.properties ./gradle.properties
COPY android-template/app/build.gradle.kts ./app/build.gradle.kts
RUN chmod +x ./gradlew
# `help` alone is enough to make Gradle resolve the app module's dependency
# graph against real build files, without needing actual app sources yet.
# Uses the system `gradle` binary directly here (not yet ./gradlew — see
# below for why) purely to warm Gradle's own dependency-download cache.
RUN mkdir -p app/src/main/java app/src/main/res app/src/main/assets/www && \
gradle help --no-daemon || true
# Now bring in the rest of the template (Kotlin sources, manifest, res,
# default www/ harness) that COULD NOT be copied above without invalidating
# the dependency-resolution cache layer on every source edit.
#
# IMPORTANT ORDERING NOTE: this COPY brings in the template's own
# gradle/wrapper/ folder too — which in source control only has
# gradle-wrapper.properties, never gradle-wrapper.jar (a compiled binary
# this environment cannot author by hand; see the git history/README for
# that constraint). COPYing a directory replaces the destination's
# contents wholesale, so doing this COPY before generating the wrapper jar
# would silently overwrite it with the source tree's jar-less version —
# that was the actual bug behind "Could not find or load main class
# org.gradle.wrapper.GradleWrapperMain" in production. Generating the
# wrapper jar (below) must run AFTER this COPY, as the last mutation to
# gradle/wrapper/ in this image, so nothing overwrites it afterward.
COPY android-template ./
# Regenerates gradle/wrapper/gradle-wrapper.jar and pins it to the same
# version as the system Gradle used to generate it, so every subsequent
# ./gradlew call in this image (including the actual per-request builds in
# build_pipeline.py) is self-contained and reproducible. Must be the LAST
# thing that touches gradle/wrapper/ in this image — see the comment on
# the COPY above.
RUN gradle wrapper --gradle-version=${GRADLE_VERSION} --distribution-type=bin
# --- Python / FastAPI -------------------------------------------------------
WORKDIR /app
COPY requirements.txt .
RUN python3 -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
RUN pip install --no-cache-dir -r requirements.txt
# Fails the build here, at build time, rather than letting the container
# start, get marked healthy by Spaces, and only then crash on the first
# real request — the same reasoning that caught the earlier Gradio
# `requests` omission before it reached production.
RUN python3 -c "import fastapi, uvicorn, multipart; print('fastapi import OK:', fastapi.__version__)"
COPY app.py build_pipeline.py ./
COPY static ./static
# Spaces expects the app to listen on 7860 by default.
EXPOSE 7860
# Builds happen in a fresh copy of /android-template per request (see
# build_pipeline.py) so concurrent builds never share mutable state;
# ANDROID_TEMPLATE_DIR tells the pipeline where that read-only source lives.
ENV ANDROID_TEMPLATE_DIR=/android-template
CMD ["python3", "app.py"]