| 1 | export GOTOOLCHAIN := go1.26.5 |
| 2 | export GOPRIVATE := $(GOPRIVATE),amadan.net/oficina/estilo.git |
| 3 | |
| 4 | APP := calendar |
| 5 | |
| 6 | # What `carlos ship -target` defaults to, and therefore what a release |
| 7 | # must be built for. Building for your own machine and shipping that is a |
| 8 | # silent architecture mismatch: the binary uploads fine and fails to exec |
| 9 | # on the instance. Override for a one-off: |
| 10 | # make release RELEASE_GOARCH=amd64 |
| 11 | RELEASE_GOOS ?= linux |
| 12 | RELEASE_GOARCH ?= arm64 |
| 13 | RELEASE_BIN := releases/$(APP)-$(RELEASE_GOOS)-$(RELEASE_GOARCH) |
| 14 | |
| 15 | # What the running binary reports from GET /api/version. The platform's |
| 16 | # deploy verification polls that endpoint on every instance socket and |
| 17 | # compares what it finds against the release it believes it adopted; an |
| 18 | # unstamped binary answers "dev" from every build you have ever made, so |
| 19 | # those two facts can never disagree and a process that was never |
| 20 | # recycled onto the new release verifies green. Stamping this is what |
| 21 | # gives that check the ability to fail. |
| 22 | # |
| 23 | # git describe, because the tag is what you ship against. Outside a |
| 24 | # repository, or before the first commit, name it yourself: |
| 25 | # make release VERSION=v0.1.0 |
| 26 | VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null) |
| 27 | |
| 28 | .PHONY: build release version-check deps test vet fmt-check migration-check ci browser |
| 29 | |
| 30 | # build is the compile check: with more than one package matched, go |
| 31 | # build discards the output, so this catches a broken package without |
| 32 | # producing an artifact. |
| 33 | build: |
| 34 | CGO_ENABLED=0 go build ./... |
| 35 | |
| 36 | # release is what ships. -s -w drops the symbol table and DWARF, which on |
| 37 | # this family's apps is 23-31% off the binary and 45-53% off the |
| 38 | # compressed artifact — and compressed size is what actually gets |
| 39 | # transferred. carlos is built the same way (see platform's Makefile). |
| 40 | # |
| 41 | # What survives: panic traces keep their function names AND line numbers |
| 42 | # (Go's pclntab is untouched by these flags), debug.ReadBuildInfo works, |
| 43 | # and go version -m still reports module metadata. What you lose is |
| 44 | # source-level debugging with delve or gdb — build without the flags for |
| 45 | # that. rastrillo dev never strips, for the same reason. |
| 46 | release: version-check |
| 47 | @mkdir -p releases |
| 48 | CGO_ENABLED=0 GOOS=$(RELEASE_GOOS) GOARCH=$(RELEASE_GOARCH) \ |
| 49 | go build -ldflags="-s -w -X 'amadan.net/rastrillo/rastrillo.BuildVersion=$(VERSION)'" \ |
| 50 | -o $(RELEASE_BIN) ./cmd/$(APP) |
| 51 | @echo |
| 52 | @echo "built $(RELEASE_BIN) reporting version $(VERSION)" |
| 53 | @echo "ship it with:" |
| 54 | @echo " carlos ship -app $(APP) -target $(RELEASE_GOOS)-$(RELEASE_GOARCH) $(RELEASE_BIN)" |
| 55 | |
| 56 | # version-check refuses rather than letting release ship a binary whose |
| 57 | # version is untrue. Both refusals are about the same thing: /api/version |
| 58 | # is only evidence if the answer identifies one artifact. |
| 59 | # |
| 60 | # An empty stamp is worse than the "dev" default, because "dev" is |
| 61 | # saying something true about itself and "" looks like a value that |
| 62 | # arrived from somewhere. |
| 63 | # |
| 64 | # A dirty tree is refused rather than suffixed. A -dirty stamp names a |
| 65 | # commit plus changes nobody can name, so two different binaries can |
| 66 | # carry the identical string — which does not fix the check, it makes |
| 67 | # it look reliable while it is not, and that is the more expensive |
| 68 | # failure. Refusing costs one `git stash`; a wrong stamp ships. If you |
| 69 | # mean to ship uncommitted work, say so out loud: |
| 70 | # make release VERSION=v0.1.0-wip |
| 71 | version-check: |
| 72 | @if [ -z "$(VERSION)" ]; then \ |
| 73 | echo "make release: no version to stamp."; \ |
| 74 | echo " git describe found nothing here: no repository, or no commit yet."; \ |
| 75 | echo " Commit, or name it: make release VERSION=v0.1.0"; \ |
| 76 | exit 1; \ |
| 77 | fi |
| 78 | @case "$(VERSION)" in *-dirty) \ |
| 79 | echo "make release: the tree is dirty, so $(VERSION) does not name what you are about to ship."; \ |
| 80 | echo " Commit or stash first, or name it: make release VERSION=v0.1.0-wip"; \ |
| 81 | exit 1;; \ |
| 82 | esac |
| 83 | |
| 84 | test: |
| 85 | go test ./... |
| 86 | |
| 87 | # deps makes sure the module cache holds what go.sum says, and repairs it |
| 88 | # when it does not. A CI runner's cache once held an extracted |
| 89 | # amadan.net/rastrillo/rastrillo whose embedded files (locales/*.toml, |
| 90 | # migrations/*.sql, ui/calendar.js) were missing, so vet failed with |
| 91 | # "pattern ...: no matching files found" on every run, while the same |
| 92 | # commit passed here. `go mod verify` names such a directory as "dir has |
| 93 | # been modified"; deleting it lets `go mod download` extract it again |
| 94 | # from the checksummed zip. |
| 95 | deps: |
| 96 | @go mod download |
| 97 | @out=$$(go mod verify 2>&1) || { \ |
| 98 | echo "$$out"; \ |
| 99 | echo "$$out" | sed -n 's/.*: dir has been modified (\(.*\))$$/\1/p' | while read -r dir; do \ |
| 100 | echo "deps: re-extracting $$dir"; chmod -R u+w "$$dir" && rm -rf "$$dir"; \ |
| 101 | done; \ |
| 102 | go mod download && go mod verify; \ |
| 103 | } |
| 104 | |
| 105 | # Also vets the browser-tagged tests: they are outside `make ci`, and a |
| 106 | # test behind a tag that nothing compiles rots silently. |
| 107 | vet: deps |
| 108 | go vet ./... |
| 109 | go vet -tags browser ./internal/calendartest/ |
| 110 | |
| 111 | fmt-check: |
| 112 | @out=$$(gofmt -l .); if [ -n "$$out" ]; then echo "gofmt needed:"; echo "$$out"; exit 1; fi |
| 113 | |
| 114 | # A step of its own, not folded into another target's recipe: ci.d/ |
| 115 | # steps each exec one make target, so migration-check needs its own |
| 116 | # name to get its own reported step on a runner with step support. |
| 117 | # |
| 118 | # go tool, not a bare "rastrillo": go.mod pins the CLI as a tool at the |
| 119 | # module's own version, so this needs nothing on PATH but the Go |
| 120 | # toolchain, works offline after go mod download, and always runs the |
| 121 | # migrate the app is built against. A bare "rastrillo migration check" |
| 122 | # failed a fresh clone's very first CI run with "make: rastrillo: |
| 123 | # Command not found", and nothing in the scaffold said what to install. |
| 124 | migration-check: |
| 125 | go tool rastrillo migration check |
| 126 | |
| 127 | # ci is the one gate: what a runner executes and what you run before |
| 128 | # pushing are the same definition. amadan's runner falls back to this |
| 129 | # target when .amadan/ci is absent; ci.d/ carries the same targets, |
| 130 | # reported one by one, for a runner with step support. migration-check |
| 131 | # fails the build the moment models.go and migrations/ disagree, |
| 132 | # instead of at boot on whatever machine notices next. If the app |
| 133 | # declares manifest resources, also add: rastrillo generate --check |
| 134 | ci: vet fmt-check test migration-check |
| 135 | |
| 136 | # The browser gate: the quick-add box driven in a real Chromium, with |
| 137 | # script and then with JavaScript disabled. |
| 138 | # |
| 139 | # Deliberately NOT part of `ci`. It needs node and `npm install`, and |
| 140 | # the one gate the team runs on every push must not depend on a |
| 141 | # toolchain the app itself does not use. Run it before shipping a |
| 142 | # change to the box. |
| 143 | # |
| 144 | # TestNoZoomOnTap (the iOS zoom-on-tap check), |
| 145 | # TestFormFootOnANarrowScreen and TestPlacedChipsSurviveTheProductionCSP |
| 146 | # drive Chromium through chromedp instead, and find it through |
| 147 | # RASTRILLO_CHROME; on a Mac the Playwright cache's Chromium is used when |
| 148 | # that is unset. The CSP check is the one served through rastrillo's |
| 149 | # production stack, so it is the one that sees the real policy. |
| 150 | browser: |
| 151 | npm install --no-save playwright |
| 152 | RASTRILLO_CHROME="$(RASTRILLO_CHROME)" go test -tags browser -run 'InARealBrowser|OnTap|OnANarrowScreen|SurviveTheProductionCSP' ./internal/calendartest/ |
| 153 | |
| 154 | RASTRILLO_CHROME ?= $(firstword $(wildcard $(HOME)/Library/Caches/ms-playwright/chromium-*/chrome-mac*/Chromium.app/Contents/MacOS/Chromium)) |
| 155 | |
| 156 | .PHONY: credits |
| 157 | credits: |
| 158 | @set -eu; notices_tmp=$$(mktemp -d); trap 'rm -rf "$$notices_tmp"' EXIT; \ |
| 159 | GOOS=$$(go env GOHOSTOS) GOARCH=$$(go env GOHOSTARCH) CGO_ENABLED=0 \ |
| 160 | go build -o "$$notices_tmp/notices" amadan.net/oficina/estilo.git/cmd/oficina-notices; \ |
| 161 | GOOS=linux GOARCH=arm64 CGO_ENABLED=0 "$$notices_tmp/notices" \ |
| 162 | -package ./cmd/calendar -lucide -onest \ |
| 163 | -exclude amadan.net/oficina/estilo.git,amadan.net/oficina/home/connect,amadan.net/oficina/home/handoff,amadan.net/rastrillo/native/server \ |
| 164 | > "$$notices_tmp/notices.json"; \ |
| 165 | mv "$$notices_tmp/notices.json" internal/calendar/credits/notices.json |
| 166 | |