#!/bin/sh
# The cross-compile gate, one target per shard.
#
# Why it exists at all is worth keeping next to it: readDiskFreeBytes in
# internal/host/probes.go multiplied an int64 by syscall.Statfs_t.Bsize,
# which is int64 on Linux and uint32 on darwin. That is a compile error
# on macOS for every GOARCH, internal/host is linked into the carlos CLI
# people run on Macs, and it sat in main undetected because everyone who
# built it was on Linux. Cross-compiling catches it in seconds and needs
# no Mac.
#
# Windows is a CLIENT target: `carlos deploy` from a Windows workstation
# against a Linux fleet. The box-side packages still have to COMPILE
# there, because the CLI is the same binary as the agent, so unix-only
# syscalls live behind per-OS files (internal/edge/purge_listen_*.go,
# internal/host/{statfs,flock}_*.go). These two shards are what stops
# the next syscall.Foo from silently un-shipping the Windows build.
#
# The @5 is amadan's whole sharding interface: it runs this script five
# times concurrently and tells each copy which one it is. One target per
# shard rather than five builds in a row, because they are independent
# and this box has the cores — and because a failing target then names
# itself in the shard that failed, instead of being the first line of a
# combined log.
set -eu

shard=${AMADAN_SHARD:?this step must be run by amadan CI, which sets AMADAN_SHARD}
shards=${AMADAN_SHARD_COUNT:?this step must be run by amadan CI, which sets AMADAN_SHARD_COUNT}

# The fence. The targets below are a fixed list, and the shard count in
# this file's name is what deals them out — so the two MUST agree. Rename
# this to @4 and, without this check, one platform silently stops being
# compiled while CI stays green. That is the exact shape of the bug the
# job exists to catch, arriving through the job that catches it.
if [ "$shards" -ne 5 ]; then
	echo "this step deals 5 fixed targets but was run with $shards shards —"
	echo "rename the file back to 20-cross@5, or add a target below"
	exit 1
fi

case "$shard" in
0) GOOS=darwin GOARCH=arm64 ;;
1) GOOS=darwin GOARCH=amd64 ;;
2) GOOS=linux GOARCH=arm64 ;;
3) GOOS=windows GOARCH=amd64 ;;
4) GOOS=windows GOARCH=arm64 ;;
*) echo "no target for shard $shard"; exit 1 ;;
esac

echo "cross-compiling $GOOS/$GOARCH"

# CGO_ENABLED=0 is already the default when cross-compiling; explicit
# here so that adding a cgo dependency fails loudly rather than quietly
# changing what this covers. The tree is pure Go today.
GOOS="$GOOS" GOARCH="$GOARCH" CGO_ENABLED=0 go build ./...

# The size ceiling, on the one shard that builds the shipping target.
#
# The platform binary is what every box downloads and what the update gate
# waits on, so its size is a cost to the fleet. On 2026-10-13 it doubled from
# about 45 MB to 77.7 MB linux/arm64 with no one deciding that (discussion
# #275). Cause: html/template, which the edge and console pages use, puts the
# Go linker in a conservative mode that keeps EVERY exported method of any
# concrete type stored in an interface value. internal/awsinv's factoryFor put
# *ec2.Client (~2,400 methods, 14 MB) and six more SDK clients into narrow
# interfaces, and the whole lot came along. The fix is to store METHOD VALUES
# (a struct or func type bound to the few calls actually made) and not the
# client; wrapping the client in a struct does not help.
#
# The ceiling is the measured size after that fix (45,678,754 bytes, 45.7 MB)
# plus 6 MB of headroom for ordinary growth, rounded up to a whole MB:
# 52 MB (decimal, 52000000 bytes). It is a ceiling and not an equality on
# purpose. If you are here because it fired, look for an SDK client stored in
# an interface, which html/template's method retention then drags in whole:
#
#   go build -ldflags=-dumpdep -o /dev/null . 2>&1 |
#       grep 'go:itab.*aws-sdk-go-v2/service'
#
# names the conversions. Two are inherent and are not the cause: s3.New and
# config.assumeWebIdentity convert their own clients inside the SDK, so S3
# and STS stay whole whatever this repo does. Raise the number only for
# growth you meant, and say why in the commit.
if [ "$GOOS/$GOARCH" = "linux/arm64" ]; then
	max_bytes=52000000
	size_dir=$(mktemp -d)
	trap 'rm -rf "$size_dir"' EXIT
	GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags "-s -w" -o "$size_dir/carlos" .
	# No pipeline here: `wc | tr` would report tr's exit status, an unreadable
	# binary would leave size empty, and the comparison below would error
	# inside `if` and let the step pass. Fail on the read, then on anything
	# that is not a number (BSD wc pads with spaces, which $(( )) accepts).
	size=$(wc -c <"$size_dir/carlos") || { echo "cannot read the built binary $size_dir/carlos"; exit 1; }
	case $size in
	'' | *[!0-9' ']*) echo "could not measure the linux/arm64 binary (wc said '$size')"; exit 1 ;;
	esac
	size=$((size))
	echo "linux/arm64 carlos binary: $size bytes (ceiling $max_bytes)"
	if [ "$size" -gt "$max_bytes" ]; then
		echo "the linux/arm64 binary is $size bytes, over the $max_bytes ceiling."
		echo "look for an SDK client stored in an interface under html/template's"
		echo "method retention (every exported method of the client is kept):"
		echo "store method values instead. See discussion #275 and the comment in"
		echo ".amadan/ci.d/20-cross@5."
		exit 1
	fi
fi
