export GOPRIVATE := $(GOPRIVATE),amadan.net/oficina/estilo.git APP := calls # What `carlos ship -target` defaults to, and therefore what a release # must be built for. Building for your own machine and shipping that is a # silent architecture mismatch: the binary uploads fine and fails to exec # on the instance. Override for a one-off: # make release RELEASE_GOARCH=amd64 RELEASE_GOOS ?= linux RELEASE_GOARCH ?= arm64 RELEASE_BIN := releases/$(APP)-$(RELEASE_GOOS)-$(RELEASE_GOARCH) # What the running binary reports from GET /api/version. An unstamped # binary answers "dev" from every build, so the platform's deploy # verification can never disagree with itself. Stamping is what gives # that check the ability to fail. Outside a repository, or before the # first commit, name it yourself: make release VERSION=v0.1.0 VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null) .PHONY: build release version-check test vet fmt-check migration-check ci race browser # build is the compile check: with more than one package matched, go # build discards the output, so this catches a broken package without # producing an artifact. build: CGO_ENABLED=0 go build ./... # release is what ships. -s -w drops the symbol table and DWARF, which on # this family's apps is 23-31% off the binary and 45-53% off the # compressed artifact — and compressed size is what actually gets # transferred. carlos is built the same way (see platform's Makefile). # # What survives: panic traces keep their function names AND line numbers # (Go's pclntab is untouched by these flags), debug.ReadBuildInfo works, # and go version -m still reports module metadata. What you lose is # source-level debugging with delve or gdb — build without the flags for # that. rastrillo dev never strips, for the same reason. release: version-check @mkdir -p releases CGO_ENABLED=0 GOOS=$(RELEASE_GOOS) GOARCH=$(RELEASE_GOARCH) \ go build -ldflags="-s -w -X 'amadan.net/rastrillo/rastrillo.BuildVersion=$(VERSION)'" \ -o $(RELEASE_BIN) ./cmd/$(APP) @echo @echo "built $(RELEASE_BIN) reporting version $(VERSION)" @echo "ship it with:" @echo " carlos ship -app $(APP) -target $(RELEASE_GOOS)-$(RELEASE_GOARCH) $(RELEASE_BIN)" version-check: @if [ -z "$(VERSION)" ]; then \ echo "make release: no version to stamp."; \ echo " git describe found nothing here: no repository, or no commit yet."; \ echo " Commit, or name it: make release VERSION=v0.1.0"; \ exit 1; \ fi @case "$(VERSION)" in *-dirty) \ echo "make release: the tree is dirty, so $(VERSION) does not name what you are about to ship."; \ echo " Commit first, or name it: make release VERSION=v0.1.0-wip"; \ exit 1;; \ esac test: go test ./... vet: go vet ./... # The race gate, and the one place in this repo that sets CGO_ENABLED=1. # `-race` refuses to build without cgo, and AGENTS.md's cgo-free rule is # about what ships: this builds a test binary and never a release, so the # exception costs the deployed artifact nothing. It is not folded into # `ci` for that reason — `ci` must stay cgo-free — and runs as its own # CI step (.amadan/ci.d/50-race) where the runner has a C compiler. # # Slice 2 is the first slice with real concurrency: a hub behind a mutex, # a tick goroutine, one goroutine per stream and three atomics. Run this # before every push that touches internal/calls/hub or the stream. race: CGO_ENABLED=1 go test -race ./internal/... fmt-check: @out=$$(gofmt -l .); if [ -n "$$out" ]; then echo "gofmt needed:"; echo "$$out"; exit 1; fi # A step of its own, not folded into another target's recipe: ci.d/ # steps each exec one make target, so migration-check needs its own # name to get its own reported step on a runner with step support. # # go run, not a bare "rastrillo": go.mod already requires rastrillo, so # this needs nothing on PATH but the Go toolchain, and works offline # after go mod download. A bare "rastrillo migration check" failed a # fresh clone's very first CI run with "make: rastrillo: Command not # found", and nothing in the scaffold said what to install. migration-check: go run amadan.net/rastrillo/rastrillo/cmd/rastrillo migration check # ci is the one gate: what a runner executes and what you run before # pushing are the same definition. amadan's runner falls back to this # target when .amadan/ci is absent; ci.d/ carries the same targets, # reported one by one, for a runner with step support. migration-check # fails the build the moment models.go and migrations/ disagree, # instead of at boot on whatever machine notices next. If the app # declares manifest resources, also add: rastrillo generate --check ci: vet fmt-check test migration-check # The browser gate: a real call between two real Chromium contexts, with # fake camera and microphone. The scenarios, and the order they run in, # are the `scenarios` slice in internal/callstest/browser_test.go — that # list is the one that has to be right, so it is not copied here. # # Deliberately NOT part of `ci`. It needs node and `npm install`, and the # one gate the team runs on every push must not depend on a toolchain the # app itself does not use. Run it before every review that touches a page # or the client. # # The vet runs first because the whole gate is behind a build tag, so # `make ci` never compiles browser_test.go: without this line a typo in # it is found by nobody until the day someone runs this target. # # `npm install` fetches the driver, not the browser — playwright 1.62.1 # has no postinstall step — so `playwright install` is a separate line, # and without it this target passes only on a machine whose Playwright # cache someone has already warmed. `--with-deps` is deliberately not # used: it installs system packages and needs root, which is the # operator's call and not a test target's. # # It also needs UDP. Inside the Claude Code bash sandbox Chromium gathers # zero ICE candidates and every call fails with frames stuck at 0 — run # this target with the sandbox off. # # -count=1 because call.mjs is not an input to Go's test cache: node # opens it, not the test, so an edited browser script would otherwise be # answered with a cached pass from before the edit. # The .test.mjs files run first because they need no browser at # all: they lift functions out of room.js and chat.js by name — pure # ones for their answers, whole loops for what they do — and check # §5.1's glare decision against the W3C truth table; §6's DOM safety, # the log cap and §4.4's byte counter; §5.4's camera-or-screen judgement # with §4.3's fan-out wire shape; and the two sequencing contracts, # every sender on the broadcast lane answered exactly once and one # gesture taking one capture; and the gate's own observation helper, # which is the one piece of call.mjs that can be wrong in a way that # makes the gate quieter rather than louder. Under a second between # them. Named explicitly rather than by directory — node's test runner treats # everything under test/ as a test file, and call.mjs is not one. browser: go vet -tags browser ./internal/callstest/ node --test test/browser/negotiation.test.mjs node --test test/browser/chat.test.mjs node --test test/browser/room-share.test.mjs node --test test/browser/room-mute.test.mjs node --test test/browser/room-space.test.mjs node --test test/browser/room-hearing.test.mjs node --test test/browser/room-screen.test.mjs node --test test/browser/room-reconnect.test.mjs node --test test/browser/room-connecting.test.mjs node --test test/browser/room-glow.test.mjs node --test test/browser/room-layout.test.mjs node --test test/browser/room-window.test.mjs node --test test/browser/room-lane.test.mjs node --test test/browser/room-quality.test.mjs node --test test/browser/media.test.mjs node --test test/browser/audio.test.mjs node --test test/browser/devices.test.mjs npm install --no-save playwright@1.62.1 npx playwright install chromium go test -tags browser -count=1 -timeout 20m -run TestCallInARealBrowser -v ./internal/callstest/ # Native builds isolate gomobile's cgo bridge from the cgo-free server. .PHONY: native-mac native-test native-browser native-pair native-mac: ./native/scripts/build-mac.sh native-test: CGO_ENABLED=0 go test ./callscore ./protocol cd native && CGO_ENABLED=0 go test ./bridge CGO_ENABLED=1 go test -race ./callscore native-browser: native-mac # The gate runs CallsInterop, which native-mac does not build; without # this it runs whatever binary a previous build left behind. swift build --package-path native/apple --product CallsInterop CALLS_DEEPFILTER_MODEL=$(CURDIR)/native/build/DeepFilterNet3_onnx.tar.gz CGO_ENABLED=0 go test -tags browser -count=1 -timeout 25m -run 'TestNative(MacBarLayout|MacCallsBrowser|MacChat|MacScreenMotion|MacScreenRecovery|BrowserAuthentication|BrowserAppLink|MacAudioSelfTest|MacAudioStartStall)' -v ./internal/callstest/ # The Mac app in a call with Playwright's WebKit (not iOS Safari; see # test/browser/native-webkit.mjs): codecs, Opus FEC, a thin path, and the # tab getting its camera and microphone back. Apart from native-browser # because it needs WebKit installed. .PHONY: native-webkit native-webkit: native-mac swift build --package-path native/apple --product CallsInterop npm install --no-save playwright@1.62.1 npx playwright install webkit CALLS_DEEPFILTER_MODEL=$(CURDIR)/native/build/DeepFilterNet3_onnx.tar.gz CGO_ENABLED=0 go test -tags browser -count=1 -timeout 10m -run 'TestNativeMacWebKit' -v ./internal/callstest/ # Two apps in one call, sharing screens to each other. Separate from # native-browser because two unsigned apps on one Mac do not reliably # connect, which looks like the macOS firewall: see TestNativeMacScreenShare. # It runs CallsInterop too, so it builds it for the same reason. native-pair: native-mac swift build --package-path native/apple --product CallsInterop CALLS_DEEPFILTER_MODEL=$(CURDIR)/native/build/DeepFilterNet3_onnx.tar.gz CGO_ENABLED=0 go test -tags browser -count=1 -timeout 20m -run 'TestNativeMacScreen(Share|PairRecovery)' -v ./internal/callstest/ .PHONY: iphone iphone-device iphone-browser iphone: ./native/scripts/build-iphone.sh simulator iphone-device: ./native/scripts/build-iphone.sh device iphone-browser: xcodegen generate --spec native/ios/project.yml CGO_ENABLED=0 go test -tags browser -count=1 -timeout 10m -run 'TestIPhone(SafariAuthentication|NativeMedia)' -v ./internal/callstest/ .PHONY: credits credits: @set -eu; notices_tmp=$$(mktemp -d); trap 'rm -rf "$$notices_tmp"' EXIT; \ GOOS=$$(go env GOHOSTOS) GOARCH=$$(go env GOHOSTARCH) CGO_ENABLED=0 \ go build -o "$$notices_tmp/notices" amadan.net/oficina/estilo.git/cmd/oficina-notices; \ GOOS=linux GOARCH=arm64 CGO_ENABLED=0 "$$notices_tmp/notices" \ -package ./cmd/calls -lucide -onest \ -exclude amadan.net/oficina/estilo.git,amadan.net/oficina/home/connect,amadan.net/oficina/home/handoff \ > "$$notices_tmp/notices.json"; \ mv "$$notices_tmp/notices.json" internal/calls/credits/notices.json