~bigbes/sr-ht-spec

ref: bd5b615ab1fe6b50a20af8a9ebc2aa1180915823 sr-ht-spec/Makefile -rw-r--r-- 9.5 KiB
bd5b615a — Eugene Blikh docs: drop a stray closing tag from ci.md 2 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# spec.sr.ht — build scaffolding (compare.sr.ht / sourcehut-dolt style).
#
# One Go binary plus a brant migration wrapper, both under cmd/. The CSS is
# built from the shared sourcehut scss partials with sassc; there is no
# frontend bundle — the prose differ renders server-side.

SERVICE=spec.sr.ht
BIN=specsrht
MIGRATE_BIN=specsrht-migrate

PREFIX?=/usr/local
BINDIR?=$(PREFIX)/bin
# ASSETS is the RUNTIME lookup root: specsrht-migrate resolves migrations and
# the schema under [sr.ht]assets (default /usr/share/sourcehut), NOT under
# PREFIX. Deriving these from PREFIX instead would install to
# /usr/local/share/... on a default build while the binary kept looking in
# /usr/share/sourcehut — the two would only agree at PREFIX=/usr. Keep every
# installed data path anchored to ASSETS so a default `make install` works.
ASSETS?=/usr/share/sourcehut
MIGRATIONDIR?=$(ASSETS)/migrations/$(SERVICE)
SCHEMAFILE?=$(ASSETS)/$(SERVICE).sql

# INSTALL is a variable because the `install -D` of `install-files` below —
# create the leading directories, then copy — is GNU coreutils, which is what
# the Alpine builders of this family have and what the sibling Makefiles
# assume. BSD install has no -D and fails on the first missing directory, so on
# a machine with the GNU tools under their g-prefix,
# `make INSTALL=ginstall install-files` is the same command.
INSTALL?=install

SASSC?=sassc
SASSC_INCLUDE=-I$(ASSETS)/scss

# The one stylesheet the binary embeds. The hash in the name is the
# cache-busting version — web/ globs for it and serves it — which is why the
# glob has to match exactly one file, and why check-css counts rather than asks.
CSS=web/static/main.min.*.css

all: build

# Compile the binaries. Both targets are .PHONY: Go decides staleness itself,
# and a real file target would never rebuild after a source edit. The cost is
# that anything depending on them recompiles — which is why `install` is split
# in two below.
#
# The `[ -d ./cmd/... ]` guards these targets carried during the build-out are
# gone. They were written so a tree without cmd/ would not fail its default
# target; what they buy now is a `make build` that prints "skip" and exits 0
# with no binary, so a deleted or renamed cmd/ directory reads as a green build
# that packages nothing. A missing package is a `go build` error naming it.
build: $(BIN) $(MIGRATE_BIN)

$(BIN):
	go build -o $@ ./cmd/$(BIN)

$(MIGRATE_BIN):
	go build -o $@ ./cmd/$(MIGRATE_BIN)

# TEST_TIMEOUT is the per-package ceiling handed to `go test`, and it is named
# here rather than left to the toolchain because the toolchain's default is ten
# minutes, it is silent about being a default, and what it produces when a
# package walks into it is `panic: test timed out after 10m0s` over a few
# hundred lines of goroutine dump naming whichever test happened to be
# mid-flight — neither a hang nor a deadlock, and a Postgres schema left behind
# because a panic runs no t.Cleanup.
#
# Measured on this tree with go1.26.5 against a local Postgres, the whole suite
# is under two minutes and its slowest package (service/) is 20 s, so twenty
# minutes is not a bound anything is near. It is the family's number, and it is
# here so that the builder — slower, cold-cached, and running the database
# suites this laptop can also run — is nowhere near it either.
TEST_TIMEOUT?=20m

# PKG is what to run, a variable so `make test PKG=./db/` is the same command
# with the -timeout still on it.
PKG?=./...

test:
	go test -timeout $(TEST_TIMEOUT) $(PKG)

# The coverage profile cov.sr.ht is fed, and the one place `go test` grows the
# two flags that decide what that profile means. -covermode=atomic records real
# hit counts rather than a set/unset bit, which is what a trend across commits
# is read off; COVERPROFILE is a variable because CI writes it to $HOME (where
# `artifacts:` looks) while a checkout wants it in the checkout.
#
# It runs exactly the suites `test` runs, so CI runs one of the two and not
# both.
COVERPROFILE?=cover.out

cover:
	go test -timeout $(TEST_TIMEOUT) -covermode=atomic -coverprofile="$(COVERPROFILE)" $(PKG)
	go tool cover -func="$(COVERPROFILE)" | tail -1

# BENCH_COUNT is `go test -count` for the `bench` target. bench.sr.ht marks a
# point measured under six repetitions "low n" — a point's confidence interval
# only becomes finite at six — so ten is what an uploaded run carries. It is a
# variable so a checkout can say `make bench BENCH_COUNT=1` when it only wants
# to know that the benchmarks still run.
BENCH_COUNT?=10

# -run='^$$' so no test runs beside the benchmarks: a run that also executed the
# suites would charge their wall clock to the benchmark task and, in CI, would
# need the Postgres the suites need.
bench:
	go test -timeout $(TEST_TIMEOUT) -run='^$$' -bench=. -benchmem -count=$(BENCH_COUNT) $(PKG)

# CSS pipeline: sassc -> minify -> content-hashed filename. The running service
# globs web/static/main.min.*.css at startup, so the hash in the name is the
# cache-busting version. Requires the shared scss partials installed at
# $(ASSETS)/scss (core.sr.ht `make install`) and scss/main.scss to exist.
# Produces exactly ONE web/static/main.min.<sha256[:8]>.css; old ones and the
# intermediate main.css are removed.
css:
	mkdir -p web/static
	rm -f web/static/main.css web/static/main.min.*.css
	$(SASSC) $(SASSC_INCLUDE) scss/main.scss web/static/main.css
	minify -o web/static/main.min.css web/static/main.css
	mv web/static/main.min.css \
		web/static/main.min.$$(sha256sum web/static/main.min.css | cut -c1-8).css
	rm -f web/static/main.css

# Local development run. Requires a ./config.ini in the working directory (or
# ../config.ini, /etc/sr.ht/config.ini) carrying the instance's shared
# [sr.ht]/[webhooks] keys plus a [spec.sr.ht] section.
run-dev: build
	./$(BIN) -b localhost:5091

# schema.sql is installed as $(SCHEMAFILE) because `specsrht-migrate init`
# applies it wholesale on a fresh database; without it, init works from a
# checkout and fails on a packaged install.
#
# Static assets are not installed at all any more. web/templates.go go:embed-s
# the whole static directory, so those files are already inside the binary and a
# second copy under $(ASSETS)/$(SERVICE)/static is dead weight in the apk that
# nothing reads — the config has no static-dir key to point at it. It is also
# why check-css guards this target: an unstyled binary cannot be repaired by
# copying a stylesheet next to it afterwards.
install: check-css build
	@$(MAKE) install-files

# The copying half of `install`, with nothing to build in front of it — the
# target a packaging run calls once it has already built the binaries, so that
# the files it stages are the very bytes it built and not a second compilation
# of the same sources.
#
# That distinction is the whole point and it is not theoretical. $(BIN) above is
# .PHONY, so `install` recompiles; abuild runs package() in a FRESH abuild
# process under fakeroot, which re-sources the APKBUILD and never calls
# build(). Nothing build() exported reaches package(), the CI cache pins among
# it, so `make install` there relinked both binaries from a cold cache — a
# second binary, shipped, that nothing in the pipeline had tested.
#
# It is invoked through a sub-make rather than listed as a third prerequisite of
# `install` on purpose: /usr/share/abuild/default.conf exports
# MAKEFLAGS=-j$(nproc), prerequisites of one target run in parallel under -j,
# and `install: check-css build install-files` would let the copying start
# beside the build it is supposed to follow. A recipe line always runs after the
# prerequisites are done.
#
# There is no check in front of the copies: `install -Dm755 specsrht` on a
# missing file is already a fatal error naming it, which is the right report for
# the one way this target can be called too early.
install-files:
	$(INSTALL) -Dm755 $(BIN) $(DESTDIR)$(BINDIR)/$(BIN)
	$(INSTALL) -Dm755 $(MIGRATE_BIN) $(DESTDIR)$(BINDIR)/$(MIGRATE_BIN)
	mkdir -p $(DESTDIR)$(MIGRATIONDIR)
	$(INSTALL) -Dm644 -t $(DESTDIR)$(MIGRATIONDIR) migrations/*.sql
	$(INSTALL) -Dm644 schema.sql $(DESTDIR)$(SCHEMAFILE)

# check-css is the packaging gate: no stylesheet, no release. It exists because
# the failure it catches is invisible at build time — `go build` succeeds
# perfectly well with an unstyled static/, since //go:embed takes the directory
# and not the file, and the first sign of trouble is an unstyled page in
# production.
#
# It counts the matches rather than merely asking whether there are any, because
# TWO stylesheets are as wrong as none and quieter: web/ resolves this glob and
# takes the first match, so a second file makes the served stylesheet depend on
# readdir order. `css` removes the previous build for that reason, which is
# exactly why this gate must not assume it did.
#
# `set --` puts the matches in the positional parameters, so the count is $$#
# and no `wc` output has to be parsed. Splitting on whitespace is fine: every
# name it can see was produced by the recipe above, out of a hex digest.
check-css:
	@set -- $$(ls $(CSS) 2>/dev/null); \
	if [ $$# -eq 0 ]; then \
		echo "error: no $(CSS) — run 'make css' before 'go build' (the CSS is embedded)"; \
		exit 1; \
	elif [ $$# -gt 1 ]; then \
		echo "error: $$# files match $(CSS) — web/ takes the first, so the choice is arbitrary."; \
		echo "       run 'make clean && make css' to get back to one:"; \
		for f in "$$@"; do echo "         $$f"; done; \
		exit 1; \
	fi

clean:
	rm -f $(BIN) $(MIGRATE_BIN)
	rm -f web/static/main.css web/static/main.min.*.css

.PHONY: all build test cover bench css run-dev install install-files check-css \
	clean $(BIN) $(MIGRATE_BIN)