carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

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.
25set -eu
26
27shard=${AMADAN_SHARD:?this step must be run by amadan CI, which sets AMADAN_SHARD}
28shards=${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.
35if [ "$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
39fi
40
41case "$shard" in
420) GOOS=darwin GOARCH=arm64 ;;
431) GOOS=darwin GOARCH=amd64 ;;
442) GOOS=linux GOARCH=arm64 ;;
453) GOOS=windows GOARCH=amd64 ;;
464) GOOS=windows GOARCH=arm64 ;;
47*) echo "no target for shard $shard"; exit 1 ;;
48esac
49
50echo "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.
55GOOS="$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.
83if [ "$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
106fi
107