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
