BINARIES     := collector aggregator frontend cli
MODULE       := git.ipng.ch/ipng/nginx-logtail
PROTO_DIR    := proto
PROTO_FILE   := $(PROTO_DIR)/logtail.proto
GEN_FILES    := proto/logtailpb/logtail.pb.go proto/logtailpb/logtail_grpc.pb.go

NATIVE_ARCH  := $(shell go env GOARCH)
VERSION      := 1.0.1
COMMIT_HASH  := $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
DATE         := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
LDFLAGS      := -s -w \
                -X '$(MODULE)/internal/version.Version=$(VERSION)' \
                -X '$(MODULE)/internal/version.Commit=$(COMMIT_HASH)' \
                -X '$(MODULE)/internal/version.Date=$(DATE)'

# CGO_ENABLED=0 produces fully static binaries: no libc dependency, so the
# .deb runs on any Linux-amd64/arm64 host regardless of glibc version.
export CGO_ENABLED := 0

IMAGE        := git.ipng.ch/ipng/nginx-logtail

# GO_VERSION is the floor install-deps-go enforces. go.mod currently requires
# 1.24; override on the command line to pull a specific patch release:
#   make install-deps GO_VERSION=1.25.0
GO_VERSION ?= 1.24.6

# GOLANGCI_LINT_VERSION is the minimum golangci-lint version install-deps-go-tools
# accepts. Older releases can't parse recent Go syntax.
GOLANGCI_LINT_VERSION ?= 1.64.0

.PHONY: help all build build-amd64 build-arm64 test lint fixstyle proto pkg-deb docker docker-push clean \
        install-deps install-deps-apt install-deps-go install-deps-go-tools

# help is the default target. Keep the list aligned with the .PHONY block above.
help:
	@echo "nginx-logtail — make targets (version $(VERSION))"
	@echo ""
	@echo "  build                build all four binaries for the native arch into build/$(NATIVE_ARCH)/"
	@echo "  build-amd64          build all four binaries for linux/amd64 into build/amd64/"
	@echo "  build-arm64          build all four binaries for linux/arm64 into build/arm64/"
	@echo "  test                 run 'go test ./...'"
	@echo "  lint                 run 'golangci-lint run ./...'"
	@echo "  fixstyle             rewrite all .go files in place with 'gofmt -w'"
	@echo "  proto                regenerate proto/logtailpb/*.pb.go from proto/logtail.proto"
	@echo "  pkg-deb              build amd64 + arm64 .deb packages (requires dpkg-deb)"
	@echo "  docker               buildx --load two tags ($(IMAGE):v$(VERSION), $(IMAGE):latest) for the native arch"
	@echo "  docker-push          buildx multi-arch push (amd64+arm64) to $(IMAGE)"
	@echo "  clean                remove build/ and generated proto files"
	@echo ""
	@echo "  install-deps         apt + Go toolchain + Go tools (runs the three sub-targets)"
	@echo "  install-deps-apt     apt-install protobuf-compiler, git, make, dpkg-dev, curl, tar"
	@echo "  install-deps-go      download Go $(GO_VERSION)+ to /usr/local/go if missing"
	@echo "  install-deps-go-tools go install protoc-gen-go, protoc-gen-go-grpc, golangci-lint"
	@echo ""
	@echo "Overridable variables:"
	@echo "  VERSION=$(VERSION)   GO_VERSION=$(GO_VERSION)   GOLANGCI_LINT_VERSION=$(GOLANGCI_LINT_VERSION)"

all: build

build: $(GEN_FILES)
	mkdir -p build/$(NATIVE_ARCH)
	$(foreach b,$(BINARIES),go build -trimpath -ldflags "$(LDFLAGS)" -o build/$(NATIVE_ARCH)/$(b) ./cmd/$(b) &&) true

build-amd64: $(GEN_FILES)
	mkdir -p build/amd64
	$(foreach b,$(BINARIES),GOOS=linux GOARCH=amd64 go build -trimpath -ldflags "$(LDFLAGS)" -o build/amd64/$(b) ./cmd/$(b) &&) true

build-arm64: $(GEN_FILES)
	mkdir -p build/arm64
	$(foreach b,$(BINARIES),GOOS=linux GOARCH=arm64 go build -trimpath -ldflags "$(LDFLAGS)" -o build/arm64/$(b) ./cmd/$(b) &&) true

test: $(GEN_FILES)
	go test ./...

lint:
	golangci-lint run ./...

# fixstyle rewrites all tracked .go files with gofmt. Generated proto files are
# excluded — they are regenerated from .proto via `make proto`.
fixstyle:
	gofmt -w $(shell find . -name '*.go' -not -path './proto/logtailpb/*')

proto: $(GEN_FILES)

# protoc's go_package option places output at the go_package path (not source-relative).
# We invoke protoc from the repo root so the resulting proto/logtailpb/*.pb.go tree
# lands alongside the .proto file.
$(GEN_FILES): $(PROTO_FILE)
	protoc \
		--go_out=. --go_opt=module=$(MODULE) \
		--go-grpc_out=. --go-grpc_opt=module=$(MODULE) \
		$(PROTO_FILE)

# pkg-deb builds one package per arch; both contain all four static binaries under
# /usr/local/bin/ with the nginx-logtail- prefix. Each build-<arch> target is a hard
# prerequisite — the packaging script refuses to run without build/<arch>/ populated.
pkg-deb: build-amd64 build-arm64
	debian/build-deb.sh amd64 $(VERSION)
	debian/build-deb.sh arm64 $(VERSION)

# docker — build one image for the native host arch and --load it into the local
# daemon. Tagged both :v$(VERSION) and :latest in a single build, so bumping
# VERSION is the only change needed to cut a release.
docker:
	docker buildx build --load \
		--build-arg VERSION=$(VERSION) \
		--build-arg COMMIT=$(COMMIT_HASH) \
		--build-arg DATE=$(DATE) \
		-t $(IMAGE):v$(VERSION) -t $(IMAGE):latest .

# docker-push — build a multi-arch (amd64+arm64) manifest and push it. Buildx
# won't --load a multi-platform result, so this is the only path that produces
# the combined manifest. Assumes the caller is already logged in to the registry.
docker-push:
	docker buildx build --platform linux/amd64,linux/arm64 --push \
		--build-arg VERSION=$(VERSION) \
		--build-arg COMMIT=$(COMMIT_HASH) \
		--build-arg DATE=$(DATE) \
		-t $(IMAGE):v$(VERSION) -t $(IMAGE):latest .

clean:
	rm -rf build/
	rm -f $(GEN_FILES)

# install-deps is an opt-in "set up a fresh developer box" target. Tested on
# Debian Trixie; the apt half should also work on Bookworm and recent Ubuntu LTS.
install-deps: install-deps-apt install-deps-go install-deps-go-tools
	@echo ""
	@echo "==> All build dependencies installed."
	@echo "    Make sure these are on PATH:"
	@echo "      /usr/local/go/bin       (Go toolchain)"
	@echo "      \$$(go env GOPATH)/bin  (protoc-gen-go, golangci-lint, ...)"

install-deps-apt:
	@set -eu; \
	if [ "$$(id -u)" = 0 ]; then SUDO=""; else SUDO="sudo"; fi; \
	echo "==> Installing apt packages (protoc, git, make, dpkg-dev, curl, tar)"; \
	$$SUDO apt-get update; \
	$$SUDO apt-get install -y --no-install-recommends \
		protobuf-compiler git make dpkg-dev ca-certificates curl tar

# install-deps-go short-circuits when go env GOVERSION already reports a version
# >= GO_VERSION. Otherwise it downloads the official upstream tarball and extracts
# it to /usr/local/go.
install-deps-go:
	@set -eu; \
	if [ "$$(id -u)" = 0 ]; then SUDO=""; else SUDO="sudo"; fi; \
	echo "==> Checking Go toolchain (required: $(GO_VERSION)+)"; \
	if command -v go >/dev/null 2>&1; then \
		CURRENT=$$(go env GOVERSION 2>/dev/null | sed 's/^go//'); \
		OLDEST=$$(printf '%s\n%s\n' "$(GO_VERSION)" "$$CURRENT" | sort -V | head -n1); \
		if [ "$$OLDEST" = "$(GO_VERSION)" ] && [ -n "$$CURRENT" ]; then \
			echo "    go$$CURRENT already installed (>= $(GO_VERSION)), skipping."; \
			exit 0; \
		fi; \
		echo "    go$$CURRENT is older than $(GO_VERSION), upgrading."; \
	else \
		echo "    no Go toolchain on PATH, installing."; \
	fi; \
	DEB_ARCH=$$(dpkg --print-architecture); \
	case "$$DEB_ARCH" in \
		amd64) GOARCH=amd64 ;; \
		arm64) GOARCH=arm64 ;; \
		armhf) GOARCH=armv6l ;; \
		*) echo "    unsupported architecture: $$DEB_ARCH" >&2; exit 1 ;; \
	esac; \
	TARBALL="go$(GO_VERSION).linux-$$GOARCH.tar.gz"; \
	URL="https://go.dev/dl/$$TARBALL"; \
	echo "    downloading $$URL"; \
	curl -fsSL -o "/tmp/$$TARBALL" "$$URL"; \
	echo "    installing to /usr/local/go"; \
	$$SUDO rm -rf /usr/local/go; \
	$$SUDO tar -C /usr/local -xzf "/tmp/$$TARBALL"; \
	rm -f "/tmp/$$TARBALL"; \
	echo "    installed $$(/usr/local/go/bin/go version)"

install-deps-go-tools:
	@set -eu; \
	if ! command -v go >/dev/null 2>&1; then \
		export PATH="/usr/local/go/bin:$$PATH"; \
	fi; \
	echo "==> Installing Go tools via 'go install'"; \
	echo "    google.golang.org/protobuf/cmd/protoc-gen-go"; \
	go install google.golang.org/protobuf/cmd/protoc-gen-go@latest; \
	echo "    google.golang.org/grpc/cmd/protoc-gen-go-grpc"; \
	go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest; \
	echo "    github.com/golangci/golangci-lint/v2/cmd/golangci-lint"; \
	go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest; \
	GOBIN="$$(go env GOBIN)"; \
	if [ -z "$$GOBIN" ]; then GOBIN="$$(go env GOPATH)/bin"; fi; \
	echo "==> Asserting golangci-lint version >= $(GOLANGCI_LINT_VERSION)"; \
	if ! "$$GOBIN/golangci-lint" version >/dev/null 2>&1; then \
		echo "    ERROR: $$GOBIN/golangci-lint is not executable" >&2; \
		exit 1; \
	fi; \
	INSTALLED=$$("$$GOBIN/golangci-lint" version 2>&1 | sed -En 's/.*has version v?([0-9][0-9.]*).*/\1/p' | head -n1); \
	if [ -z "$$INSTALLED" ]; then \
		echo "    ERROR: could not parse golangci-lint version output" >&2; \
		"$$GOBIN/golangci-lint" version >&2; \
		exit 1; \
	fi; \
	OLDEST=$$(printf '%s\n%s\n' "$(GOLANGCI_LINT_VERSION)" "$$INSTALLED" | sort -V | head -n1); \
	if [ "$$OLDEST" != "$(GOLANGCI_LINT_VERSION)" ]; then \
		echo "    ERROR: golangci-lint $$INSTALLED is older than the required $(GOLANGCI_LINT_VERSION)" >&2; \
		exit 1; \
	fi; \
	echo "    golangci-lint $$INSTALLED (>= $(GOLANGCI_LINT_VERSION)) OK"
