| 1 | export GOPRIVATE := $(GOPRIVATE),amadan.net/oficina/estilo.git |
| 2 | |
| 3 | APP := calls |
| 4 | |
| 5 | # What `carlos ship -target` defaults to, and therefore what a release |
| 6 | # must be built for. Building for your own machine and shipping that is a |
| 7 | # silent architecture mismatch: the binary uploads fine and fails to exec |
| 8 | # on the instance. Override for a one-off: |
| 9 | # make release RELEASE_GOARCH=amd64 |
| 10 | RELEASE_GOOS ?= linux |
| 11 | RELEASE_GOARCH ?= arm64 |
| 12 | RELEASE_BIN := releases/$(APP)-$(RELEASE_GOOS)-$(RELEASE_GOARCH) |
| 13 | |
| 14 | # What the running binary reports from GET /api/version. An unstamped |
| 15 | # binary answers "dev" from every build, so the platform's deploy |
| 16 | # verification can never disagree with itself. Stamping is what gives |
| 17 | # that check the ability to fail. Outside a repository, or before the |
| 18 | # first commit, name it yourself: make release VERSION=v0.1.0 |
| 19 | VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null) |
| 20 | |
| 21 | .PHONY: build release version-check test vet fmt-check migration-check ci race browser |
| 22 | |
| 23 | # build is the compile check: with more than one package matched, go |
| 24 | # build discards the output, so this catches a broken package without |
| 25 | # producing an artifact. |
| 26 | build: |
| 27 | CGO_ENABLED=0 go build ./... |
| 28 | |
| 29 | # release is what ships. -s -w drops the symbol table and DWARF, which on |
| 30 | # this family's apps is 23-31% off the binary and 45-53% off the |
| 31 | # compressed artifact — and compressed size is what actually gets |
| 32 | # transferred. carlos is built the same way (see platform's Makefile). |
| 33 | # |
| 34 | # What survives: panic traces keep their function names AND line numbers |
| 35 | # (Go's pclntab is untouched by these flags), debug.ReadBuildInfo works, |
| 36 | # and go version -m still reports module metadata. What you lose is |
| 37 | # source-level debugging with delve or gdb — build without the flags for |
| 38 | # that. rastrillo dev never strips, for the same reason. |
| 39 | release: version-check |
| 40 | @mkdir -p releases |
| 41 | CGO_ENABLED=0 GOOS=$(RELEASE_GOOS) GOARCH=$(RELEASE_GOARCH) \ |
| 42 | go build -ldflags="-s -w -X 'amadan.net/rastrillo/rastrillo.BuildVersion=$(VERSION)'" \ |
| 43 | -o $(RELEASE_BIN) ./cmd/$(APP) |
| 44 | @echo |
| 45 | @echo "built $(RELEASE_BIN) reporting version $(VERSION)" |
| 46 | @echo "ship it with:" |
| 47 | @echo " carlos ship -app $(APP) -target $(RELEASE_GOOS)-$(RELEASE_GOARCH) $(RELEASE_BIN)" |
| 48 | |
| 49 | version-check: |
| 50 | @if [ -z "$(VERSION)" ]; then \ |
| 51 | echo "make release: no version to stamp."; \ |
| 52 | echo " git describe found nothing here: no repository, or no commit yet."; \ |
| 53 | echo " Commit, or name it: make release VERSION=v0.1.0"; \ |
| 54 | exit 1; \ |
| 55 | fi |
| 56 | @case "$(VERSION)" in *-dirty) \ |
| 57 | echo "make release: the tree is dirty, so $(VERSION) does not name what you are about to ship."; \ |
| 58 | echo " Commit first, or name it: make release VERSION=v0.1.0-wip"; \ |
| 59 | exit 1;; \ |
| 60 | esac |
| 61 | |
| 62 | test: |
| 63 | go test ./... |
| 64 | |
| 65 | vet: |
| 66 | go vet ./... |
| 67 | |
| 68 | # The race gate, and the one place in this repo that sets CGO_ENABLED=1. |
| 69 | # `-race` refuses to build without cgo, and AGENTS.md's cgo-free rule is |
| 70 | # about what ships: this builds a test binary and never a release, so the |
| 71 | # exception costs the deployed artifact nothing. It is not folded into |
| 72 | # `ci` for that reason — `ci` must stay cgo-free — and runs as its own |
| 73 | # CI step (.amadan/ci.d/50-race) where the runner has a C compiler. |
| 74 | # |
| 75 | # Slice 2 is the first slice with real concurrency: a hub behind a mutex, |
| 76 | # a tick goroutine, one goroutine per stream and three atomics. Run this |
| 77 | # before every push that touches internal/calls/hub or the stream. |
| 78 | race: |
| 79 | CGO_ENABLED=1 go test -race ./internal/... |
| 80 | |
| 81 | fmt-check: |
| 82 | @out=$$(gofmt -l .); if [ -n "$$out" ]; then echo "gofmt needed:"; echo "$$out"; exit 1; fi |
| 83 | |
| 84 | # A step of its own, not folded into another target's recipe: ci.d/ |
| 85 | # steps each exec one make target, so migration-check needs its own |
| 86 | # name to get its own reported step on a runner with step support. |
| 87 | # |
| 88 | # go run, not a bare "rastrillo": go.mod already requires rastrillo, so |
| 89 | # this needs nothing on PATH but the Go toolchain, and works offline |
| 90 | # after go mod download. A bare "rastrillo migration check" failed a |
| 91 | # fresh clone's very first CI run with "make: rastrillo: Command not |
| 92 | # found", and nothing in the scaffold said what to install. |
| 93 | migration-check: |
| 94 | go run amadan.net/rastrillo/rastrillo/cmd/rastrillo migration check |
| 95 | |
| 96 | # ci is the one gate: what a runner executes and what you run before |
| 97 | # pushing are the same definition. amadan's runner falls back to this |
| 98 | # target when .amadan/ci is absent; ci.d/ carries the same targets, |
| 99 | # reported one by one, for a runner with step support. migration-check |
| 100 | # fails the build the moment models.go and migrations/ disagree, |
| 101 | # instead of at boot on whatever machine notices next. If the app |
| 102 | # declares manifest resources, also add: rastrillo generate --check |
| 103 | ci: vet fmt-check test migration-check |
| 104 | |
| 105 | # The browser gate: a real call between two real Chromium contexts, with |
| 106 | # fake camera and microphone. The scenarios, and the order they run in, |
| 107 | # are the `scenarios` slice in internal/callstest/browser_test.go — that |
| 108 | # list is the one that has to be right, so it is not copied here. |
| 109 | # |
| 110 | # Deliberately NOT part of `ci`. It needs node and `npm install`, and the |
| 111 | # one gate the team runs on every push must not depend on a toolchain the |
| 112 | # app itself does not use. Run it before every review that touches a page |
| 113 | # or the client. |
| 114 | # |
| 115 | # The vet runs first because the whole gate is behind a build tag, so |
| 116 | # `make ci` never compiles browser_test.go: without this line a typo in |
| 117 | # it is found by nobody until the day someone runs this target. |
| 118 | # |
| 119 | # `npm install` fetches the driver, not the browser — playwright 1.62.1 |
| 120 | # has no postinstall step — so `playwright install` is a separate line, |
| 121 | # and without it this target passes only on a machine whose Playwright |
| 122 | # cache someone has already warmed. `--with-deps` is deliberately not |
| 123 | # used: it installs system packages and needs root, which is the |
| 124 | # operator's call and not a test target's. |
| 125 | # |
| 126 | # It also needs UDP. Inside the Claude Code bash sandbox Chromium gathers |
| 127 | # zero ICE candidates and every call fails with frames stuck at 0 — run |
| 128 | # this target with the sandbox off. |
| 129 | # |
| 130 | # -count=1 because call.mjs is not an input to Go's test cache: node |
| 131 | # opens it, not the test, so an edited browser script would otherwise be |
| 132 | # answered with a cached pass from before the edit. |
| 133 | # The .test.mjs files run first because they need no browser at |
| 134 | # all: they lift functions out of room.js and chat.js by name — pure |
| 135 | # ones for their answers, whole loops for what they do — and check |
| 136 | # §5.1's glare decision against the W3C truth table; §6's DOM safety, |
| 137 | # the log cap and §4.4's byte counter; §5.4's camera-or-screen judgement |
| 138 | # with §4.3's fan-out wire shape; and the two sequencing contracts, |
| 139 | # every sender on the broadcast lane answered exactly once and one |
| 140 | # gesture taking one capture; and the gate's own observation helper, |
| 141 | # which is the one piece of call.mjs that can be wrong in a way that |
| 142 | # makes the gate quieter rather than louder. Under a second between |
| 143 | # them. Named explicitly rather than by directory — node's test runner treats |
| 144 | # everything under test/ as a test file, and call.mjs is not one. |
| 145 | browser: |
| 146 | go vet -tags browser ./internal/callstest/ |
| 147 | node --test test/browser/negotiation.test.mjs |
| 148 | node --test test/browser/chat.test.mjs |
| 149 | node --test test/browser/room-share.test.mjs |
| 150 | node --test test/browser/room-mute.test.mjs |
| 151 | node --test test/browser/room-space.test.mjs |
| 152 | node --test test/browser/room-hearing.test.mjs |
| 153 | node --test test/browser/room-screen.test.mjs |
| 154 | node --test test/browser/room-reconnect.test.mjs |
| 155 | node --test test/browser/room-connecting.test.mjs |
| 156 | node --test test/browser/room-glow.test.mjs |
| 157 | node --test test/browser/room-layout.test.mjs |
| 158 | node --test test/browser/room-window.test.mjs |
| 159 | node --test test/browser/room-lane.test.mjs |
| 160 | node --test test/browser/room-quality.test.mjs |
| 161 | node --test test/browser/media.test.mjs |
| 162 | node --test test/browser/audio.test.mjs |
| 163 | node --test test/browser/devices.test.mjs |
| 164 | npm install --no-save playwright@1.62.1 |
| 165 | npx playwright install chromium |
| 166 | go test -tags browser -count=1 -timeout 20m -run TestCallInARealBrowser -v ./internal/callstest/ |
| 167 | |
| 168 | # Native builds isolate gomobile's cgo bridge from the cgo-free server. |
| 169 | .PHONY: native-mac native-test native-browser native-pair |
| 170 | native-mac: |
| 171 | ./native/scripts/build-mac.sh |
| 172 | |
| 173 | native-test: |
| 174 | CGO_ENABLED=0 go test ./callscore ./protocol |
| 175 | cd native && CGO_ENABLED=0 go test ./bridge |
| 176 | CGO_ENABLED=1 go test -race ./callscore |
| 177 | |
| 178 | native-browser: native-mac |
| 179 | # The gate runs CallsInterop, which native-mac does not build; without |
| 180 | # this it runs whatever binary a previous build left behind. |
| 181 | swift build --package-path native/apple --product CallsInterop |
| 182 | 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/ |
| 183 | |
| 184 | # The Mac app in a call with Playwright's WebKit (not iOS Safari; see |
| 185 | # test/browser/native-webkit.mjs): codecs, Opus FEC, a thin path, and the |
| 186 | # tab getting its camera and microphone back. Apart from native-browser |
| 187 | # because it needs WebKit installed. |
| 188 | .PHONY: native-webkit |
| 189 | native-webkit: native-mac |
| 190 | swift build --package-path native/apple --product CallsInterop |
| 191 | npm install --no-save playwright@1.62.1 |
| 192 | npx playwright install webkit |
| 193 | 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/ |
| 194 | |
| 195 | # Two apps in one call, sharing screens to each other. Separate from |
| 196 | # native-browser because two unsigned apps on one Mac do not reliably |
| 197 | # connect, which looks like the macOS firewall: see TestNativeMacScreenShare. |
| 198 | # It runs CallsInterop too, so it builds it for the same reason. |
| 199 | native-pair: native-mac |
| 200 | swift build --package-path native/apple --product CallsInterop |
| 201 | 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/ |
| 202 | |
| 203 | .PHONY: iphone iphone-device iphone-browser |
| 204 | iphone: |
| 205 | ./native/scripts/build-iphone.sh simulator |
| 206 | iphone-device: |
| 207 | ./native/scripts/build-iphone.sh device |
| 208 | iphone-browser: |
| 209 | xcodegen generate --spec native/ios/project.yml |
| 210 | CGO_ENABLED=0 go test -tags browser -count=1 -timeout 10m -run 'TestIPhone(SafariAuthentication|NativeMedia)' -v ./internal/callstest/ |
| 211 | |
| 212 | .PHONY: credits |
| 213 | credits: |
| 214 | @set -eu; notices_tmp=$$(mktemp -d); trap 'rm -rf "$$notices_tmp"' EXIT; \ |
| 215 | GOOS=$$(go env GOHOSTOS) GOARCH=$$(go env GOHOSTARCH) CGO_ENABLED=0 \ |
| 216 | go build -o "$$notices_tmp/notices" amadan.net/oficina/estilo.git/cmd/oficina-notices; \ |
| 217 | GOOS=linux GOARCH=arm64 CGO_ENABLED=0 "$$notices_tmp/notices" \ |
| 218 | -package ./cmd/calls -lucide -onest \ |
| 219 | -exclude amadan.net/oficina/estilo.git,amadan.net/oficina/home/connect,amadan.net/oficina/home/handoff \ |
| 220 | > "$$notices_tmp/notices.json"; \ |
| 221 | mv "$$notices_tmp/notices.json" internal/calls/credits/notices.json |
| 222 | |