#!/usr/bin/env bash # # Everything the android CI job does inside the toolchain image, which is everything that needs a # glibc host. See build/android-build.Dockerfile for why there is an image at all, and # docs/platform-flags.md for the three rounds of CI that established it. # # It runs against a copy of the checkout at /build, put there with `docker cp` rather than a bind # mount — see the job for why — so it may read .git and it may write anywhere. What it leaves in # artifacts/android-nightly is what the job copies back out and publishes. # # Nothing here holds a secret. The key this signs with is committed to this repository in the open and # is meant to be; the token that publishes the result never enters this container. set -euo pipefail project=src/DodoSSH.Client.Android/DodoSSH.Client.Android.csproj staged=artifacts/android-nightly # Aapt2ToolPath takes the directory and aapt2 itself is inside it. Both come from the Android SDK in # this image rather than from the copy inside the workload pack: both work here, and using the SDK's # makes the build and the versionName read below the same binary, so the manifest the feed publishes # is read by the thing that wrote it. build_tools="$ANDROID_HOME/build-tools/$ANDROID_BUILD_TOOLS" echo "==> restore" # Locked mode, the same gate the solution restore gives every other project. This head is not in # DodoSSH.slnx — it needs a workload the other jobs have no reason to install — so this is the only # place its lock file is ever checked. It has silently gone stale before. dotnet restore "$project" --locked-mode echo "==> build" dotnet build "$project" \ --no-restore --configuration Release \ "-p:Aapt2ToolPath=$build_tools" echo "==> package the nightly" # Packaging rather than only compiling, because the two failures this head is most exposed to are both # link-time: a native library with no android ABI, and a managed assembly that resolves for net10.0 but # has nothing to dex. Neither shows up in a compile. # # versionCode is the commit count: monotonic by construction, and nobody has to remember anything. It # is not a version and is never displayed. versionName carries MinVer's full answer including the # prerelease height, which is what tells two nightlies apart. # # No RuntimeIdentifier, where this once pinned android-arm64. That produced the smallest possible build # check and the least installable artefact — an arm64-only APK will not run on an x86_64 emulator, # which is what most people testing a nightly actually have. code="$(git rev-list --count HEAD)" dotnet build "$project" \ --no-restore --configuration Release \ -t:SignAndroidPackage \ -p:DodoChannel=nightly \ -p:DodoNightlyVersionCode="$code" \ "-p:Aapt2ToolPath=$build_tools" apk="$(find src/DodoSSH.Client.Android/bin/Release -name '*-Signed.apk' | head -1)" if [ -z "$apk" ]; then echo "The package step produced no signed APK." >&2 exit 1 fi # Read back out of the APK rather than recomputed, so what the feed advertises is what the bytes say. # A versionName derived a second time in shell is a second implementation of the csproj's target, and # the two would drift on the first change to either. badging="$("$build_tools/aapt2" dump badging "$apk")" name="$(printf '%s' "$badging" | sed -n "s/.*versionName='\([^']*\)'.*/\1/p" | head -1)" if [ -z "$name" ]; then echo "aapt2 reported no versionName for $apk." >&2 printf '%s\n' "$badging" | head -3 >&2 exit 1 fi mkdir -p "$staged" cp "$apk" "$staged/DodoSSH-nightly-$name.apk" # The channel manifest, which is what the client reads and the whole reason the feed is machine- # readable at all. versionCode is the comparison — it is the number Android itself uses to accept or # refuse an install, so comparing anything else would let the client offer an update the platform then # rejects. versionName is for the person reading the banner. printf '{"versionCode":%s,"versionName":"%s","apk":"DodoSSH-nightly-%s.apk"}' \ "$code" "$name" "$name" > "$staged/android-nightly.json" printf '%s\n' "$badging" | head -1 ls -la "$staged"