| 1 | #!/bin/sh |
| 2 | # The cross-compile gate, one target per shard. |
| 3 | # |
| 4 | # Why it exists at all is worth keeping next to it: readDiskFreeBytes in |
| 5 | # internal/host/probes.go multiplied an int64 by syscall.Statfs_t.Bsize, |
| 6 | # which is int64 on Linux and uint32 on darwin. That is a compile error |
| 7 | # on macOS for every GOARCH, internal/host is linked into the carlos CLI |
| 8 | # people run on Macs, and it sat in main undetected because everyone who |
| 9 | # built it was on Linux. Cross-compiling catches it in seconds and needs |
| 10 | # no Mac. |
| 11 | # |
| 12 | # Windows is a CLIENT target: `carlos deploy` from a Windows workstation |
| 13 | # against a Linux fleet. The box-side packages still have to COMPILE |
| 14 | # there, because the CLI is the same binary as the agent, so unix-only |
| 15 | # syscalls live behind per-OS files (internal/edge/purge_listen_*.go, |
| 16 | # internal/host/{statfs,flock}_*.go). These two shards are what stops |
| 17 | # the next syscall.Foo from silently un-shipping the Windows build. |
| 18 | # |
| 19 | # The @5 is amadan's whole sharding interface: it runs this script five |
| 20 | # times concurrently and tells each copy which one it is. One target per |
| 21 | # shard rather than five builds in a row, because they are independent |
| 22 | # and this box has the cores — and because a failing target then names |
| 23 | # itself in the shard that failed, instead of being the first line of a |
| 24 | # combined log. |
| 25 | set -eu |
| 26 | |
| 27 | shard=${AMADAN_SHARD:?this step must be run by amadan CI, which sets AMADAN_SHARD} |
| 28 | shards=${AMADAN_SHARD_COUNT:?this step must be run by amadan CI, which sets AMADAN_SHARD_COUNT} |
| 29 | |
| 30 | # The fence. The targets below are a fixed list, and the shard count in |
| 31 | # this file's name is what deals them out — so the two MUST agree. Rename |
| 32 | # this to @4 and, without this check, one platform silently stops being |
| 33 | # compiled while CI stays green. That is the exact shape of the bug the |
| 34 | # job exists to catch, arriving through the job that catches it. |
| 35 | if [ "$shards" -ne 5 ]; then |
| 36 | echo "this step deals 5 fixed targets but was run with $shards shards —" |
| 37 | echo "rename the file back to 20-cross@5, or add a target below" |
| 38 | exit 1 |
| 39 | fi |
| 40 | |
| 41 | case "$shard" in |
| 42 | 0) GOOS=darwin GOARCH=arm64 ;; |
| 43 | 1) GOOS=darwin GOARCH=amd64 ;; |
| 44 | 2) GOOS=linux GOARCH=arm64 ;; |
| 45 | 3) GOOS=windows GOARCH=amd64 ;; |
| 46 | 4) GOOS=windows GOARCH=arm64 ;; |
| 47 | *) echo "no target for shard $shard"; exit 1 ;; |
| 48 | esac |
| 49 | |
| 50 | echo "cross-compiling $GOOS/$GOARCH" |
| 51 | |
| 52 | # CGO_ENABLED=0 is already the default when cross-compiling; explicit |
| 53 | # here so that adding a cgo dependency fails loudly rather than quietly |
| 54 | # changing what this covers. The tree is pure Go today. |
| 55 | GOOS="$GOOS" GOARCH="$GOARCH" CGO_ENABLED=0 go build ./... |
| 56 | |
| 57 | # The size ceiling, on the one shard that builds the shipping target. |
| 58 | # |
| 59 | # The platform binary is what every box downloads and what the update gate |
| 60 | # waits on, so its size is a cost to the fleet. On 2026-10-13 it doubled from |
| 61 | # about 45 MB to 77.7 MB linux/arm64 with no one deciding that (discussion |
| 62 | # #275). Cause: html/template, which the edge and console pages use, puts the |
| 63 | # Go linker in a conservative mode that keeps EVERY exported method of any |
| 64 | # concrete type stored in an interface value. internal/awsinv's factoryFor put |
| 65 | # *ec2.Client (~2,400 methods, 14 MB) and six more SDK clients into narrow |
| 66 | # interfaces, and the whole lot came along. The fix is to store METHOD VALUES |
| 67 | # (a struct or func type bound to the few calls actually made) and not the |
| 68 | # client; wrapping the client in a struct does not help. |
| 69 | # |
| 70 | # The ceiling is the measured size after that fix (45,678,754 bytes, 45.7 MB) |
| 71 | # plus 6 MB of headroom for ordinary growth, rounded up to a whole MB: |
| 72 | # 52 MB (decimal, 52000000 bytes). It is a ceiling and not an equality on |
| 73 | # purpose. If you are here because it fired, look for an SDK client stored in |
| 74 | # an interface, which html/template's method retention then drags in whole: |
| 75 | # |
| 76 | # go build -ldflags=-dumpdep -o /dev/null . 2>&1 | |
| 77 | # grep 'go:itab.*aws-sdk-go-v2/service' |
| 78 | # |
| 79 | # names the conversions. Two are inherent and are not the cause: s3.New and |
| 80 | # config.assumeWebIdentity convert their own clients inside the SDK, so S3 |
| 81 | # and STS stay whole whatever this repo does. Raise the number only for |
| 82 | # growth you meant, and say why in the commit. |
| 83 | if [ "$GOOS/$GOARCH" = "linux/arm64" ]; then |
| 84 | max_bytes=52000000 |
| 85 | size_dir=$(mktemp -d) |
| 86 | trap 'rm -rf "$size_dir"' EXIT |
| 87 | GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags "-s -w" -o "$size_dir/carlos" . |
| 88 | # No pipeline here: `wc | tr` would report tr's exit status, an unreadable |
| 89 | # binary would leave size empty, and the comparison below would error |
| 90 | # inside `if` and let the step pass. Fail on the read, then on anything |
| 91 | # that is not a number (BSD wc pads with spaces, which $(( )) accepts). |
| 92 | size=$(wc -c <"$size_dir/carlos") || { echo "cannot read the built binary $size_dir/carlos"; exit 1; } |
| 93 | case $size in |
| 94 | '' | *[!0-9' ']*) echo "could not measure the linux/arm64 binary (wc said '$size')"; exit 1 ;; |
| 95 | esac |
| 96 | size=$((size)) |
| 97 | echo "linux/arm64 carlos binary: $size bytes (ceiling $max_bytes)" |
| 98 | if [ "$size" -gt "$max_bytes" ]; then |
| 99 | echo "the linux/arm64 binary is $size bytes, over the $max_bytes ceiling." |
| 100 | echo "look for an SDK client stored in an interface under html/template's" |
| 101 | echo "method retention (every exported method of the client is kept):" |
| 102 | echo "store method values instead. See discussion #275 and the comment in" |
| 103 | echo ".amadan/ci.d/20-cross@5." |
| 104 | exit 1 |
| 105 | fi |
| 106 | fi |
| 107 | |