File size: 7,406 Bytes
b60b695
 
 
 
 
 
 
 
 
 
 
 
 
 
a859d16
 
 
 
b60b695
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
982e93a
 
b60b695
982e93a
b60b695
 
 
 
982e93a
 
 
 
 
 
 
 
 
 
 
 
b60b695
 
982e93a
 
 
 
 
 
 
 
3cbfe5b
b60b695
 
 
 
 
3cbfe5b
 
 
 
 
b60b695
 
3cbfe5b
b60b695
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# 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"]