# 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 '(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)--/ @$(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 beside krino mkdir -p $(BINDIR) rm -f $(BINDIR)/krino-gui install -m 755 gui/krino-gui $(BINDIR)/krino-gui clean: ## remove build output rm -f $(BIN) gui/krino-gui