Public Access
Build the phone in a container, because this runner cannot build it at all
The runner is Alpine, and .NET for Android does not work on musl. Not "needs setting up" — the SDK's own MSBuild tasks pull glibc shared objects out of the workload pack into the build process, and a musl-linked dotnet will not load one: error XARLP7000: Error relocating .../libZipSharpNative-3-3.so: __snprintf_chk: symbol not found That is a glibc fortify symbol musl does not implement, reached through a DllImport rather than an exec, so gcompat is no help: it gets a glibc *executable* started, which is a different problem. There is no musl variant of the pack. Everything this job did on the host to make Android work was therefore treatment of symptoms, mine included. The missing aapt2 was present. The "unsupported version" was of a binary that had never run. Both were this one sentence in a different accent, and the loader was the accent, not the sentence. So the toolchain moves into build/android-build.Dockerfile — Microsoft's own sdk:10.0-noble plus a JDK, the Android SDK and the workload — and the job keeps on the host only what the host is good at: checkout, git, publishing. The daemon needed no arranging, since the image job already builds with it and every Testcontainers suite reaches it over the socket. The image is tagged by the digest of the Dockerfile that made it, so on a persistent runner every run after the first is a cache hit, and a change to the toolchain is the only thing that buys a new one. Built rather than pulled: a community image with the Android SDK already in it would put a stranger in the path of a package this project signs and publishes. Eleven lines of apt and sdkmanager is the cheaper trade. Verified end to end in that image against a real clone rather than reasoned about, which after three rounds of reasoning seemed the least I could do. Restore under locked mode, Release build, then SignAndroidPackage: package: name='dev.dodotech.dodossh.nightly' versionCode='195' versionName='0.0.0-alpha.0.128' Signer #1 certificate SHA-256 digest: a9f067877724ddb0fdc04b637fbd5bfb97df753976616f100b48b522e132ba22 which is the keystore in build/. The versionName carries MinVer's height, so the csproj's target fires in the container too, and the manifest the feed publishes parses back on the host. Staging moves from RUNNER_TEMP to artifacts/, which is forced rather than preferred: the package is made inside a container and read outside one, so it has to land under the bind-mounted checkout.
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
# 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/<channel>/ — 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
|
||||
Reference in New Issue
Block a user