#!/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.
#
# Restored without a channel and built with one, which is safe and worth one line: the two channel
# PropertyGroups change the application id, the version and some assembly metadata, and none of those
# is in the restore graph. If a channel ever gains a PackageReference of its own, this splits and the
# restore has to move under the same -p: as the build.
#
# Into /root/.nuget/packages, which the job mounts as a named volume — see the workflow. That is the
# whole of this job's package caching and it is what took a run from eight minutes to two and a half.
dotnet restore "$project" --locked-mode

echo "==> package the nightly"
# ◆ ONE BUILD, WHERE THERE WERE TWO, AND THE FIRST WAS NOT A CHECK.
#
# There used to be a plain `dotnet build` above this. It looked like a cheap compile before the
# expensive packaging and it was neither: SignAndroidPackage depends on Build, so this line compiles
# everything anyway, and the build above ran with no -p:DodoChannel — which means it ran as the
# *release* channel. Different ApplicationId, different ApplicationVersion, different assembly
# metadata; MSBuild treats a different set of global properties as a different project instance, so
# not one output was reused. It was a complete second compile of the whole reference closure,
# producing an APK for a channel this job must never build, thrown away unread.
#
# Removing it does not lose a check. Anything that fails in a compile fails in this one, and the
# link-time failures below are the ones only packaging finds.
#
# Measured in the toolchain image with a warm package volume, same commit, same APK out of both:
#
#   two builds   2m52 + 2m26   5m21 total
#   one build            3m01   3m04 total
#
# versionCode 203, versionName 0.0.0-alpha.0.136, dev.dodotech.dodossh.nightly, either way.
#
# 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"
