# SPDX-License-Identifier: GPL-3.0-or-later
# Works with GNU make and BSD make.

DESTDIR ?=
PREFIX  ?= $(HOME)/.local
BINDIR   = $(DESTDIR)$(PREFIX)/bin
SHAREDIR = $(DESTDIR)$(PREFIX)/share
MANDIR   = $(SHAREDIR)/man
DOCDIR   = $(SHAREDIR)/doc/krino
BIN      = krino
# The tag's "v" is dropped, so dist directories read krino-0.0.8-..., as
# `make release VERSION=0.0.8` names them.
VERSION != (git describe --tags --always --dirty 2>/dev/null || echo dev) | sed 's/^v//'
LDFLAGS  = -s -w -X main.version=$(VERSION)
# VERSION reaches -ldflags and directory names, so every target using it
# refuses anything but a plain version string.
CHECK_VERSION = case '$(VERSION)' in ''|*[!A-Za-z0-9._+-]*) echo "refusing: VERSION must contain only letters, digits, and the characters . - _ +" >&2; exit 1 ;; esac
CROSS_PLATFORMS = linux/amd64 linux/arm64 freebsd/amd64 openbsd/amd64

.PHONY: all help build install uninstall test vet fmt lint ci bench fuzz race vulncheck cross dist release deps install-hooks clean gui gui-ci install-gui

all: build

help: ## show this help
	@grep -E '^[a-z-]+:.*## ' Makefile | \
	  awk 'BEGIN {FS = ":.*## "}; {printf "  %-10s %s\n", $$1, $$2}'
	@echo "  version: $(VERSION)   prefix: $(PREFIX)"

build: ## build ./krino; Go fetches module dependencies itself; reports missing optional extractors
	@$(CHECK_VERSION)
	CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o $(BIN) ./cmd/krino
	@missing=""; \
	for t in pdftotext antiword catdoc xls2csv catppt; do \
	  command -v "$$t" >/dev/null 2>&1 || missing="$$missing $$t"; \
	done; \
	if [ -n "$$missing" ]; then \
	  echo "optional extractors not found:$$missing (needed for some content rules; run 'make deps' or install by hand)"; \
	else \
	  echo "all optional extractors found: pdftotext antiword catdoc xls2csv catppt"; \
	fi

install: build ## install binary, man pages and examples under $(PREFIX)
	mkdir -p $(BINDIR) $(MANDIR)/man1 $(MANDIR)/man5 $(DOCDIR)/examples
	rm -f $(BINDIR)/$(BIN)
	install -m 755 $(BIN) $(BINDIR)/$(BIN)
	install -m 644 man/krino.1 $(MANDIR)/man1/krino.1
	install -m 644 man/krino.conf.5 $(MANDIR)/man5/krino.conf.5
	install -m 644 examples/*.conf $(DOCDIR)/examples/
	install -m 644 docs/sexp-primer.md $(DOCDIR)/sexp-primer.md

uninstall: ## remove everything install placed, and nothing else
	rm -f $(BINDIR)/$(BIN)
	rm -f $(MANDIR)/man1/krino.1
	rm -f $(MANDIR)/man5/krino.conf.5
	rm -f $(DOCDIR)/sexp-primer.md
	for f in examples/*.conf; do \
	  rm -f $(DOCDIR)/examples/$$(basename "$$f"); \
	done
	-rmdir $(DOCDIR)/examples $(DOCDIR) 2>/dev/null

test: ## run the tests
	go test ./...

vet: ## go vet
	go vet ./...

fmt: ## gofmt the tree
	gofmt -w .

lint: ## lint the man pages
	scripts/man-lint

ci: ## the gate: gofmt, vet, tests, dependencies, no personal data staged, man page lint
	@test -z "$$(gofmt -l .)" || { echo "gofmt needed:"; gofmt -l .; exit 1; }
	go vet ./...
	GOOS=freebsd CGO_ENABLED=0 go vet ./...
	GOOS=openbsd CGO_ENABLED=0 go vet ./...
	go test ./...
	@deps=$$(for os in linux freebsd openbsd; do GOOS=$$os CGO_ENABLED=0 go list -deps -test -f '{{with .Module}}{{.Path}}{{end}}' ./... || echo "go list failed for $$os"; done | sort -u | grep -vxE '(git\.labunix\.xyz/krino|golang\.org/x/(term|text|sys))?' || true); \
	  test -z "$$deps" || { echo "unexpected dependencies:"; echo "$$deps"; exit 1; }
	@scripts/leak-check
	@scripts/man-lint
	@echo "ci ok"

bench: ## run the Go benchmarks on generated trees
	go test -run '^$$' -bench . -benchmem ./...

# FUZZ_TARGETS lists every fuzz target as package-directory:FuzzName.
FUZZTIME ?= 20s
FUZZ_TARGETS = \
	internal/sexp:FuzzParse \
	internal/journal:FuzzEscapeRoundTrip \
	internal/journal:FuzzParseLine \
	internal/journal:FuzzEntryRoundTrip \
	internal/trash:FuzzPercentRoundTrip \
	internal/trash:FuzzParsePath \
	internal/sexp:FuzzQuoteRoundTrip \
	internal/config:FuzzParseSize \
	internal/config:FuzzParseDuration \
	internal/config:FuzzPrintRule \
	internal/norm:FuzzTextIdempotent \
	internal/norm:FuzzFoldMapped \
	internal/plan:FuzzExpand \
	internal/ignore:FuzzMatch \
	internal/kwcache:FuzzLoad \
	internal/extract:FuzzStripMarkup \
	internal/extract:FuzzZipText \
	cmd/krino:FuzzDisplay

fuzz: ## run every fuzz target for FUZZTIME each (default 20s); a crasher is saved under testdata/fuzz
	@for t in $(FUZZ_TARGETS); do \
	  pkg=$${t%%:*}; name=$${t##*:}; \
	  echo "fuzz $$pkg $$name ($(FUZZTIME))"; \
	  go test -run '^$$' -fuzz "^$$name$$" -fuzztime $(FUZZTIME) ./$$pkg || exit 1; \
	done

race: ## run the tests under the race detector (needs cgo and a C compiler)
	CGO_ENABLED=1 go test -race ./...

# govulncheck is pinned; it runs on the toolchain go.mod names, so the
# standard library it checks is the one release builds use.
vulncheck: ## check the standard library and dependencies against the Go vulnerability database (network)
	go run golang.org/x/vuln/cmd/govulncheck@v1.8.0 ./...

cross: ## cross-compile linux/amd64, linux/arm64, freebsd/amd64, openbsd/amd64 into dist/krino-$(VERSION)-<os>-<arch>/
	@$(CHECK_VERSION)
	@for t in $(CROSS_PLATFORMS); do \
	  os=$${t%/*}; arch=$${t#*/}; \
	  dir=dist/krino-$(VERSION)-$$os-$$arch; \
	  case "$$dir" in \
	    dist/krino-*) rm -rf "$$dir" ;; \
	    *) echo "refusing to remove unexpected path: $$dir" >&2; exit 1 ;; \
	  esac; \
	  mkdir -p "$$dir"; \
	  echo "cross: $$os/$$arch -> $$dir/$(BIN)"; \
	  CGO_ENABLED=0 GOOS=$$os GOARCH=$$arch go build -trimpath -ldflags "$(LDFLAGS)" -o "$$dir/$(BIN)" ./cmd/krino || exit 1; \
	  for f in README.md LICENSE CHANGELOG.md man/krino.1 man/krino.conf.5 docs/sexp-primer.md; do \
	    test -f "$$f" && cp "$$f" "$$dir/$$(basename "$$f")"; \
	  done; \
	  test -d examples && cp -r examples "$$dir/examples"; \
	done

# dist: cross plus tarballs and SHA256SUMS, no tag. Undocumented plumbing so
# `make release` is verifiable without tagging; deliberately absent from
# `make help` (no `## ` comment).
dist: cross
	@command -v sha256sum >/dev/null 2>&1 && SUM=sha256sum || SUM="sha256 -r"; \
	  tarballs=""; \
	  cd dist && \
	  for t in $(CROSS_PLATFORMS); do \
	    os=$${t%/*}; arch=$${t#*/}; \
	    name=krino-$(VERSION)-$$os-$$arch; \
	    tar czf "$$name.tar.gz" "$$name" || exit 1; \
	    tarballs="$$tarballs $$name.tar.gz"; \
	  done && \
	  $$SUM $$tarballs > SHA256SUMS

release: ## make cross + tarballs + SHA256SUMS in dist/, then tag v$(VERSION) (annotated; never pushes) - usage: make release VERSION=0.0.1
	@$(CHECK_VERSION)
	@default_version=$$( (git describe --tags --always --dirty 2>/dev/null || echo dev) | sed 's/^v//'); \
	  test -n "$(VERSION)" && test "$(VERSION)" != "$$default_version" || \
	  { echo "refusing: pass an explicit VERSION, e.g. make release VERSION=0.0.1"; exit 1; }
	@test -z "$$(git status --porcelain)" || \
	  { echo "refusing: working tree is not clean (git status --porcelain)"; exit 1; }
	@git rev-parse -q --verify refs/tags/v$(VERSION) >/dev/null 2>&1 && \
	  { echo "refusing: tag v$(VERSION) already exists"; exit 1; } || true
	@test -f README.md || \
	  { echo "refusing: README.md is missing"; exit 1; }
	$(MAKE) ci
	$(MAKE) dist VERSION=$(VERSION)
	git tag -a "v$(VERSION)" -m "krino v$(VERSION)"
	@echo "release v$(VERSION) built in dist/; tag v$(VERSION) created, not pushed"

deps: ## install the optional content extractors; may ask for privileges
	scripts/deps

install-hooks: ## install the pre-commit hook that runs the leak check
	install -m 755 scripts/hooks/pre-commit "$$(git rev-parse --git-path hooks)/pre-commit"

# The GUI is its own module in gui/, with its own dependencies (gotk4), so
# krino's dependency gate above is unaffected. It needs cgo, a C compiler,
# GTK 4 and gobject-introspection development files.
gui: ## build ./gui/krino-gui (needs GTK 4 development files)
	@$(CHECK_VERSION)
	cd gui && CGO_ENABLED=1 go build -trimpath -ldflags "$(LDFLAGS)" -o krino-gui ./cmd/krino-gui

gui-ci: ## the GUI module's gate: gofmt, vet, tests
	@test -z "$$(cd gui && gofmt -l .)" || { echo "gofmt needed in gui:"; (cd gui && gofmt -l .); exit 1; }
	cd gui && CGO_ENABLED=1 go vet ./...
	cd gui && CGO_ENABLED=1 go test ./...

install-gui: gui ## install krino-gui and its man page beside krino
	mkdir -p $(BINDIR) $(MANDIR)/man1
	rm -f $(BINDIR)/krino-gui
	install -m 755 gui/krino-gui $(BINDIR)/krino-gui
	install -m 644 man/krino-gui.1 $(MANDIR)/man1/krino-gui.1

clean: ## remove build output
	rm -f $(BIN) gui/krino-gui
