# The toolchain the Android head is built in, and the reason it is a container at all. # # docker build -f build/android-build.Dockerfile build # # ── WHY THIS EXISTS ───────────────────────────────────────────────────────────────────── # This project's runner is Alpine, and .NET for Android cannot build there. Not "is awkward # to install" — cannot. The SDK's own MSBuild tasks P/Invoke into glibc shared libraries # shipped inside the workload pack, and a musl-linked dotnet cannot load one: # # error XARLP7000: Error relocating …/libZipSharpNative-3-3.so: __snprintf_chk: symbol not found # # That is a glibc fortify symbol musl does not have, in a library loaded into the build # process rather than run beside it. gcompat supplies a loader for glibc *executables* and # is no help at all here; there is no musl variant of the pack. See docs/platform-flags.md. # # So the job stays on the host for everything that works there — checkout, git, publishing — # and hands the build to this image, which is glibc and carries the whole toolchain. The # runner already has a Docker daemon: the image job builds with it and every Testcontainers # suite reaches it over the socket. # # ── WHY IT IS BUILT RATHER THAN PULLED ────────────────────────────────────────────────── # There are community images with .NET and the Android SDK already in them, and using one # would put a stranger in the path of a package this project signs and publishes. This is # eleven lines of apt and sdkmanager over Microsoft's own base image, and the runner's # daemon is persistent, so every run after the first is a cache hit. FROM mcr.microsoft.com/dotnet/sdk:10.0-noble ENV DOTNET_NOLOGO=true \ DOTNET_CLI_TELEMETRY_OPTOUT=true \ DOTNET_SKIP_FIRST_TIME_EXPERIENCE=true # 17 is the floor rather than the preference: .NET for Android 36 refuses to start javac below # it, and says so in a message that names a path rather than a version. RUN apt-get update -qq \ && apt-get install -y --no-install-recommends openjdk-17-jdk-headless curl unzip \ && rm -rf /var/lib/apt/lists/* ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 \ ANDROID_HOME=/opt/android-sdk \ ANDROID_SDK_ROOT=/opt/android-sdk # Pinned, and the number is the commandline-tools release rather than an API level — they are # versioned separately. Floating it would make the toolchain a moving part of every build. ARG CMDLINE_TOOLS=commandlinetools-linux-11076708_latest.zip # The archive unpacks to a directory called cmdline-tools, and sdkmanager insists on living at # cmdline-tools// — unpacking it in place gives cmdline-tools/cmdline-tools and every # later call fails with "Could not determine SDK root". RUN curl -fsSL -o /tmp/tools.zip "https://dl.google.com/android/repository/$CMDLINE_TOOLS" \ && mkdir -p "$ANDROID_HOME/cmdline-tools" \ && unzip -q /tmp/tools.zip -d /tmp/tools \ && mv /tmp/tools/cmdline-tools "$ANDROID_HOME/cmdline-tools/latest" \ && rm -rf /tmp/tools /tmp/tools.zip # ANDROID_BUILD_TOOLS is read by the job as well as here, so the version is named once. API 36 # specifically, and it is not a preference: Avalonia.Controls.WebView ships only a # net10.0-android36.0 assembly, so anything lower cannot resolve it and the head loses its # terminal. See docs/android-port.md. # # yes rather than echo y: there are several licences and each wants its own answer, so a single # y accepts the first and leaves the rest pending — which surfaces later as a package that # "failed to install" with no reason given. ENV ANDROID_BUILD_TOOLS=36.0.0 RUN yes 2>/dev/null | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --licenses > /dev/null 2>&1 || true \ && "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" \ "platform-tools" "platforms;android-36" "build-tools;$ANDROID_BUILD_TOOLS" > /dev/null \ && "$ANDROID_HOME/build-tools/$ANDROID_BUILD_TOOLS/aapt2" version # --skip-sign-check is for the workload package feed and not for anything this project signs. RUN dotnet workload install android --skip-sign-check # The build runs as root over a bind-mounted checkout that git will otherwise refuse to read as # somebody else's — and MinVer answering 0.0.0-alpha.0 because it could not open the repository # is a warning, not an error, so the version would be wrong rather than absent. RUN git config --global --add safe.directory '*' WORKDIR /build