flybrain/infra/build/build-flysim.sh
acamilo 3d9a08d0be infra: flyedge.service, off by default, and the feed bus through build, deploy and watchdog
flyedge.service runs /opt/fly/current/fly-edge After= and Requires=
flysim.service, with its metrics on loopback :9102 and a
ConditionPathExists so a release without the binary leaves it inactive. It is
in no target and 07-enable.sh does not enable it; the header has the switch
and the way back.

build-flysim.sh also builds fly-edge beside the flysim binary and
package-release.sh ships it when present. 05-deploy.sh writes
FLY_FEED_VIA (default direct) into fly.env, flysim.service names
FLY_BUS_DIR=/run/fly/bus and tmpfiles creates it. Watchdog check 2 reads the
feed counters from whoever serves the feed: flyedge when fly.env says bus.
lint.sh holds all of that, and drives check 2's choice against a fixture.
2026-09-23 11:41:28 +00:00

121 lines
6.3 KiB
Bash
Executable file

#!/usr/bin/env bash
# infra/build/build-flysim.sh [OUT_PATH]
#
# Run INSIDE the throwaway build container (the throwaway build container `fly-build`, Debian 13,
# 16 cores, onboot=0 — docs/design/infra.md section 2), never on the WSL
# box and never on the host directly. Building against a newer glibc than
# Debian 13 trixie's and running on trixie fails at load; the build box
# being the same template on the same host removes that class of problem
# entirely.
#
# The sharp edge this script exists to get right every time:
# RUSTFLAGS="-C target-cpu=haswell" correct — the host's E5-2660 v3 is Haswell
# RUSTFLAGS="-C target-cpu=native" WRONG off-host — a different build
# machine's native ISA (e.g. Zen3) can
# emit instructions Haswell does not
# have; illegal instruction at run time.
#
# Also rejects musl static linking (x86_64-unknown-linux-musl): musl's
# allocator is materially slower under the per-millisecond neuron sweep's
# allocation pattern, and real-time factor is the whole ballgame. Ships a
# dynamically linked glibc binary built on the matching template instead.
#
# FLY_CARGO_FEATURES — opt-in cargo features for the flysim bin, comma or
# space separated. DEFAULT EMPTY, which is the plain CPU-only release build
# every tagged release so far was made with. The only feature today is
# `cuda`, the bit-exact LIF tick on the GPU:
#
# FLY_CARGO_FEATURES=cuda infra/build/build-flysim.sh ./flysim
#
# Three things make that safe to compile into a release binary, all measured
# (infra/docs/lif-cuda-spike.md, infra/docs/cuda-on-dev.md):
#
# * it needs no CUDA toolkit to build — the PTX is committed and embedded
# with include_str!, and the driver JITs it at module load;
# * `cudarc` is built with `dynamic-loading`, so `libcuda` is dlopen'd on
# first use. `ldd` on the binary lists no CUDA library, and a run
# without FLY_LIF_CUDA=1 never opens the driver — the same binary is
# therefore deployable to a GPU-less container;
# * the GPU tick is bit-exact with the CPU one, so `--print-compatibility`
# is byte-identical to a CPU-only build of the same tree and a live
# checkpoint restores onto either.
#
# Compiling the feature in does NOT turn it on. The backend attaches only
# when `FLY_LIF_CUDA=1` reaches the process, which on a container means
# `FLY_LIF_CUDA=1` in the env file so 05-deploy.sh writes it into
# /etc/fly/fly.env (off by default there too).
set -euo pipefail
log() { echo "build-flysim: $*" >&2; }
die() { echo "build-flysim: FATAL: $*" >&2; exit 1; }
: "${CARGO_TARGET:=x86_64-unknown-linux-gnu}"
: "${FLY_CARGO_FEATURES:=}"
OUT_PATH="${1:-./flysim}"
command -v cargo >/dev/null 2>&1 || die "cargo not found — this must run inside fly-build (the build container), which has the Rust toolchain, not on the host or the WSL box"
if [ "$CARGO_TARGET" != "x86_64-unknown-linux-gnu" ]; then
die "refusing target '$CARGO_TARGET': must be x86_64-unknown-linux-gnu (glibc), never a *-musl target"
fi
crate_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../services/flysim" 2>/dev/null && pwd || true)"
if [ -z "$crate_dir" ]; then
die "services/flysim does not exist yet in this checkout (it is built by workstream W1, docs/stream-mvp-plan.md) — nothing to build"
fi
# Empty FLY_CARGO_FEATURES must produce the plain command, not `--features ''`
# (cargo accepts that, but it would put an empty-string feature in the build
# plan's fingerprint and make a default build look different from every
# release built before this knob existed). An array, so the flag and its
# value survive as two argv entries whatever the feature list contains.
features_args=()
if [ -n "$FLY_CARGO_FEATURES" ]; then
features_args=(--features "$FLY_CARGO_FEATURES")
log "opt-in cargo features: $FLY_CARGO_FEATURES (default is none — see the FLY_CARGO_FEATURES note in this script's header)"
fi
log "building in $crate_dir for target-cpu=haswell (the host is E5-2660 v3, Haswell, AVX2, no AVX-512)"
(
cd "$crate_dir"
RUSTFLAGS="-C target-cpu=haswell" cargo build --release --target "$CARGO_TARGET" --bin flysim "${features_args[@]}"
# fly-edge (FLY_FEED_VIA=bus, docs/design/flybus.md): the feed WebSocket
# served from flysim's feed bus. Small, and no cargo features of its own;
# built every time so a release can switch a container onto the bus
# without a rebuild. It lands next to OUT_PATH, where package-release.sh
# looks for it.
RUSTFLAGS="-C target-cpu=haswell" cargo build --release --target "$CARGO_TARGET" --bin fly-edge
)
built="${crate_dir}/target/${CARGO_TARGET}/release/flysim"
[ -x "$built" ] || die "expected binary not found after build: $built"
# Sanity: confirm it is dynamically linked glibc, not a static/musl binary.
if command -v file >/dev/null 2>&1; then
file "$built" | grep -q 'dynamically linked' || die "$built does not look dynamically linked — check for an accidental musl/static target"
fi
# Sanity: no CUDA library may appear in the link. The `cuda` feature is only
# deployable because `cudarc`'s `dynamic-loading` dlopen's libcuda on first
# use — a DT_NEEDED on libcuda.so.1 instead would make the binary refuse to
# start on any container without the userspace driver, including the release container
# before gate 2 of the v0.2 plan and the fly-build container itself. Checked
# unconditionally, because the way this breaks is someone turning
# `dynamic-loading` off in Cargo.toml, not someone setting the env var.
if command -v ldd >/dev/null 2>&1; then
if ldd "$built" 2>/dev/null | grep -qi 'libcuda\|libnvrtc\|libcudart'; then
die "$built links a CUDA library dynamically — cudarc must keep its 'dynamic-loading' feature so libcuda is dlopen'd, or this binary cannot start on a GPU-less container:
$(ldd "$built" | grep -i 'libcuda\|libnvrtc\|libcudart')"
fi
fi
cp "$built" "$OUT_PATH"
chmod 0755 "$OUT_PATH"
log "built $OUT_PATH ($(du -h "$OUT_PATH" | cut -f1))"
edge_built="${crate_dir}/target/${CARGO_TARGET}/release/fly-edge"
[ -x "$edge_built" ] || die "expected binary not found after build: $edge_built"
edge_out="$(dirname "$OUT_PATH")/fly-edge"
cp "$edge_built" "$edge_out"
chmod 0755 "$edge_out"
log "built $edge_out ($(du -h "$edge_out" | cut -f1))"
log "next: infra/build/package-release.sh VERSION $OUT_PATH <stage-dir> <bridge-dir> <out-dir>"