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) ./...
# `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
.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