# 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"]