export GOTOOLCHAIN := go1.26.5 export GOPRIVATE := $(GOPRIVATE),amadan.net/oficina/estilo.git APP := calendar # 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. The platform's # deploy verification polls that endpoint on every instance socket and # compares what it finds against the release it believes it adopted; an # unstamped binary answers "dev" from every build you have ever made, so # those two facts can never disagree and a process that was never # recycled onto the new release verifies green. Stamping this is what # gives that check the ability to fail. # # git describe, because the tag is what you ship against. 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 deps test vet fmt-check migration-check ci 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 refuses rather than letting release ship a binary whose # version is untrue. Both refusals are about the same thing: /api/version # is only evidence if the answer identifies one artifact. # # An empty stamp is worse than the "dev" default, because "dev" is # saying something true about itself and "" looks like a value that # arrived from somewhere. # # A dirty tree is refused rather than suffixed. A -dirty stamp names a # commit plus changes nobody can name, so two different binaries can # carry the identical string — which does not fix the check, it makes # it look reliable while it is not, and that is the more expensive # failure. Refusing costs one `git stash`; a wrong stamp ships. If you # mean to ship uncommitted work, say so out loud: # make release VERSION=v0.1.0-wip 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 or stash first, or name it: make release VERSION=v0.1.0-wip"; \ exit 1;; \ esac test: go test ./... # deps makes sure the module cache holds what go.sum says, and repairs it # when it does not. A CI runner's cache once held an extracted # amadan.net/rastrillo/rastrillo whose embedded files (locales/*.toml, # migrations/*.sql, ui/calendar.js) were missing, so vet failed with # "pattern ...: no matching files found" on every run, while the same # commit passed here. `go mod verify` names such a directory as "dir has # been modified"; deleting it lets `go mod download` extract it again # from the checksummed zip. deps: @go mod download @out=$$(go mod verify 2>&1) || { \ echo "$$out"; \ echo "$$out" | sed -n 's/.*: dir has been modified (\(.*\))$$/\1/p' | while read -r dir; do \ echo "deps: re-extracting $$dir"; chmod -R u+w "$$dir" && rm -rf "$$dir"; \ done; \ go mod download && go mod verify; \ } # Also vets the browser-tagged tests: they are outside `make ci`, and a # test behind a tag that nothing compiles rots silently. vet: deps go vet ./... go vet -tags browser ./internal/calendartest/ 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 tool, not a bare "rastrillo": go.mod pins the CLI as a tool at the # module's own version, so this needs nothing on PATH but the Go # toolchain, works offline after go mod download, and always runs the # migrate the app is built against. 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 tool 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: the quick-add box driven in a real Chromium, with # script and then with JavaScript disabled. # # 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 shipping a # change to the box. # # TestNoZoomOnTap (the iOS zoom-on-tap check), # TestFormFootOnANarrowScreen and TestPlacedChipsSurviveTheProductionCSP # drive Chromium through chromedp instead, and find it through # RASTRILLO_CHROME; on a Mac the Playwright cache's Chromium is used when # that is unset. The CSP check is the one served through rastrillo's # production stack, so it is the one that sees the real policy. browser: npm install --no-save playwright RASTRILLO_CHROME="$(RASTRILLO_CHROME)" go test -tags browser -run 'InARealBrowser|OnTap|OnANarrowScreen|SurviveTheProductionCSP' ./internal/calendartest/ RASTRILLO_CHROME ?= $(firstword $(wildcard $(HOME)/Library/Caches/ms-playwright/chromium-*/chrome-mac*/Chromium.app/Contents/MacOS/Chromium)) .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/calendar -lucide -onest \ -exclude amadan.net/oficina/estilo.git,amadan.net/oficina/home/connect,amadan.net/oficina/home/handoff,amadan.net/rastrillo/native/server \ > "$$notices_tmp/notices.json"; \ mv "$$notices_tmp/notices.json" internal/calendar/credits/notices.json