PREFIX?=/usr/local BINDIR?=$(PREFIX)/bin LIBDIR?=$(PREFIX)/lib SHAREDIR?=$(PREFIX)/share ASSETS?=/usr/share/sourcehut SERVICE=dolt.sr.ht STATICDIR=$(SHAREDIR)/sourcehut/static/$(SERVICE) MIGRATIONDIR=$(SHAREDIR)/sourcehut/migrations/$(SERVICE) SASSC?=sassc SASSC_INCLUDE=-I$(ASSETS)/scss/ MINIFY?=minify # Pure-Go build (default): no cgo, no ICU/zstd C libraries, statically linkable. # Two things make it work: the `gms_pure_go` tag swaps go-mysql-server's ICU # regex for the stdlib regexp (this service never runs the SQL engine), and a # `replace github.com/dolthub/gozstd => ./third_party/gozstd-purego` directive # backs dolt's only other hard cgo dependency with a pure-Go klauspost shim. # To build the cgo variant instead: `make CGO_ENABLED=1 GO_TAGS=`. GO_TAGS?=gms_pure_go CGO_ENABLED?=0 export CGO_ENABLED GO=go # The tag flag is a variable of its own because `go build` is not the only # command that needs it: `go vet` and `go test` type-check the same tree, and a # vet or a test run without gms_pure_go pulls go-icu-regex in and wants the ICU # headers the pure-Go build exists to avoid. GO_TAGSFLAG=$(if $(GO_TAGS),-tags "$(GO_TAGS)",) GOBUILD=$(GO) build $(GO_TAGSFLAG) # The one stylesheet the service looks for at startup: web/router.go resolves # `main.min.*.css` in its static dir by glob. Named here because check-css and # clean-share both have to mean exactly the same set of files as that glob. CSS=static/main.min.*.css BINARIES=\ doltsrht \ doltsrht-migrate \ dolt-git-hook # Default target builds the Go binaries only. CSS (all-share) is deliberately # kept off the default path because sassc/minify are not always installed on # dev machines; run `make css` explicitly to build stylesheets. all: all-bin all-bin: $(BINARIES) all-share: static/main.min.css css: all-share # One line each, and no `if [ -d ./cmd/... ]` in front of it. The guard dated # from Phase 2, when cmd/ did not exist yet and a missing package had to be a # no-op rather than a hard failure; cmd/ has held all three since Phase 3. What # the guard does now is turn a deleted or renamed cmd/ directory into a GREEN # `make all-bin` that produced no binary at all — and, since abuild packages # whatever is on disk, into an apk quietly missing a program. doltsrht: $(GOBUILD) -o $@ ./cmd/doltsrht doltsrht-migrate: $(GOBUILD) -o $@ ./cmd/doltsrht-migrate dolt-git-hook: $(GOBUILD) -o $@ ./cmd/dolt-git-hook # Compile every buildable package; used as the CI build gate. build: $(GOBUILD) ./... # `go vet` and `go test` over the whole module, both with the pure-Go tag, so # that CI can name them without repeating CGO_ENABLED and GO_TAGS in the # manifest — a second copy of those two is a second copy to forget. CGO_ENABLED # is exported above, so both inherit it. # # Neither is gated on a DSN: db/'s suites skip themselves when DOLTSRHT_TEST_PG # is unset, which is what makes `make test` useful on a laptop. The refusal to # accept that in CI belongs to the manifest's test task, which knows it asked # for a Postgres. vet: $(GO) vet $(GO_TAGSFLAG) ./... test: $(GO) test $(GO_TAGSFLAG) ./... # 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 # needs; 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 $(GO_TAGSFLAG) -covermode=atomic -coverprofile="$(COVERPROFILE)" ./... $(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 executes the # suites charges their wall clock to the benchmark task and, in CI, would need # the Postgres the suites need. bench: $(GO) test $(GO_TAGSFLAG) -run='^$$' -bench=. -benchmem -count=$(BENCH_COUNT) ./... # `install` still means "build it, check it, then copy it", which is what a # person at a checkout wants. The build is a prerequisite and install-files is # invoked from the recipe rather than listed as a third prerequisite: # /usr/share/abuild/default.conf exports MAKEFLAGS=-j$(nproc), prerequisites of # one target run in parallel under -j, and `install: check-css all-bin # install-files` would let the copying start beside the build it is meant to # follow. A recipe line always runs after the prerequisites are done. install: check-css all-bin @$(MAKE) install-files # The copying half of `install`, with nothing to build in front of it. This is # the target a packaging run calls once it has already built and checked, so # that what it stages is the very bytes it checked. # # That distinction is the whole point of the split. The binary targets above are # .PHONY (Go decides staleness itself), and abuild runs package() in a FRESH # abuild process under fakeroot which re-sources the APKBUILD and never calls # build() — so nothing build() exported reaches it, the CI cache pins included. # An `install` there recompiled all three binaries from a cold module cache and # packaged that second compilation, which is not the one anything had inspected. install-files: install-bin install-share # No `if [ -x $$bin ]` around the copy, deliberately, and this is the half of # the split that makes it safe: now that this target no longer builds, the one # way to reach it with a missing binary is to call it before a build, and # `install -Dm755 doltsrht` on a missing file is already a fatal error naming # the file. The old guard would instead have staged whichever binaries happened # to be there and returned 0. # # `|| exit 1` because a `for` loop reports only its LAST command: without it a # missing doltsrht followed by two binaries that copied fine is a green # install-bin, which is the same silence spelled differently. install-bin: mkdir -p $(BINDIR) for bin in $(BINARIES); do \ install -Dm755 $$bin $(BINDIR)/ || exit 1; \ done install-share: mkdir -p $(STATICDIR) mkdir -p $(MIGRATIONDIR) install -Dm644 schema.sql $(SHAREDIR)/sourcehut/$(SERVICE).sql install -Dm644 migrations/*.sql $(MIGRATIONDIR) if [ -d static ]; then install -Dm644 static/*.css static/*.svg $(STATICDIR) 2>/dev/null || true; fi # The packaging gate: no stylesheet, no package. It exists because the failure # it catches is invisible at build time — install-share copies static/*.css # under `2>/dev/null || true`, so a `make css` that produced nothing stages a # service with no stylesheet and fails nothing; the first sign of trouble is an # unstyled page in production. # # It counts the matches rather than asking whether there are any, because TWO # stylesheets are as wrong as none and quieter: web/router.go resolves $(CSS) by # glob and takes the first match, so a second file makes the served stylesheet # depend on readdir order. `css` does not remove the previous build's hashed # file, which is exactly why this 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 below, out of a hex digest. check-css: @set -- $$(ls $(CSS) 2>/dev/null); \ if [ $$# -eq 0 ]; then \ echo "error: no $(CSS) — run 'make css' (sassc and minify required)"; \ exit 1; \ elif [ $$# -gt 1 ]; then \ echo "error: $$# files match $(CSS) — web/ takes the first, so the choice is arbitrary."; \ echo " run 'make clean-share && make css' to get back to one:"; \ for f in "$$@"; do echo " $$f"; done; \ exit 1; \ fi clean: clean-bin clean-share clean-bin: rm -f $(BINARIES) clean-share: rm -f static/main.min.css static/main.css $(CSS) .PHONY: all all-bin all-share css check-css build vet test cover bench .PHONY: install install-files install-bin install-share .PHONY: clean clean-bin clean-share $(BINARIES) static/main.css: scss/main.scss mkdir -p $(@D) $(SASSC) $(SASSC_INCLUDE) $< $@ static/main.min.css: static/main.css $(MINIFY) -o $@ $< cp $@ $(@D)/main.min.$$(sha256sum $@ | cut -c1-8).css