# 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: go test ./... # 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..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 css run-dev install install-files check-css clean \ $(BIN) $(MIGRATE_BIN)