# Copyright 2025 SUSE LLC
# SPDX-License-Identifier: Apache-2.0

# Makefile variables

# Directory to save the binaries to
BIN ?= ${CURDIR}/bin

# mcp-server-trento binary name
BIN_NAME = mcp-server-trento

IMAGE_REGISTRY ?= ghcr.io/trento-project
IMAGE_NAME ?= mcp-server-trento
IMAGE_TAG ?= latest
IMAGE ?= $(IMAGE_REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)

PORT ?= 5000

VERSION ?= $(shell ./hack/get_version_from_git.sh)
GOOS ?= $(shell go env GOOS)
GOARCH ?= $(shell go env GOARCH)

LDFLAGS = -X github.com/trento-project/mcp-server/cmd.version="$(VERSION)"
DEBUG ?= 0
BUILD_OUTPUT ?= $(BIN)/$(GOOS)-$(GOARCH)/$(BIN_NAME)
PLATFORMS ?= linux/amd64 linux/ppc64le linux/s390x darwin/amd64 windows/amd64
CGO_ENABLED ?= 0

##
# Build hardened Go binaries for Linux packaging:
# -buildmode=pie: Position Independent Executable (PIE), enables ASLR (Address Space Layout Randomization) for better security.
# -ldflags "-extldflags=-Wl,-z,now,-z,relro":
#     -z now: Immediate symbol resolution at startup, prevents lazy binding attacks.
#     -z relro: Read-only relocations, makes some binary sections read-only after startup to prevent memory corruption exploits.
# -s -w: Strip debug and symbol tables, reducing binary size and removing potentially sensitive info.
# -trimpath: Removes file system paths from the binary for reproducible builds and privacy.
LDFLAGS += -s -w
GO_BUILD ?= CGO_ENABLED=$(CGO_ENABLED) $(GO) build -buildmode=pie -ldflags "$(LDFLAGS) -extldflags=-Wl,-z,now,-z,relro" -trimpath

ifeq ($(DEBUG), 1)
	GO_BUILD = CGO_ENABLED=$(CGO_ENABLED) $(GO) build -ldflags "$(LDFLAGS)"
endif

GO := "$(shell which go)"
DOCKER := "$(shell which docker)"

##@ General

help: ## Display this help.
	@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

##@ Development

build: mcp-server-trento ## Build the entire project.

mcp-server-trento: ## Build the main project.
	$(GO_BUILD) -o $(BUILD_OUTPUT)

.PHONY: cross-compiled
cross-compiled: ## Cross compile the project for all PLATFORMS.
	@for platform in $(PLATFORMS); do \
		os=$${platform%/*}; arch=$${platform#*/}; \
		echo "Building for $$os/$$arch"; \
		GOOS=$$os GOARCH=$$arch $(GO_BUILD) -o  $(BIN)/$$os-$$arch/$(BIN_NAME); \
	done

.PHONY: clean ## Delete the files generated by make build.
clean:
	$(GO) clean
	rm -f "${BUILD_OUTPUT}" || true

.PHONY: run
run: build ## Compile and run the project.
	$(GO) run main.go --port ${PORT} --verbosity=info --enable-health-check --trento-url https://demo.trento-project.io

##@ Test

.PHONY: test
test: ## Test the project.
	$(GO) test ./... -cover

##@ Container

.PHONY: build-container
build-container: ## Build container image (eg. IMAGE=ghcr.io/trento-project/mcp-server-trento:dev make build-container).
	$(DOCKER) build --build-arg VERSION=${VERSION} --build-arg GOOS=${GOOS} --build-arg GOARCH=${GOARCH} --build-arg PORT=${PORT} -t ${IMAGE} -f Dockerfile .

.PHONY: push-container
push-container: ## Push container image (eg. IMAGE=ghcr.io/trento-project/mcp-server-trento:dev make push-container).
	$(DOCKER) push ${IMAGE}

.PHONY: run-container
run-container: ## Push container image (eg. IMAGE=ghcr.io/trento-project/mcp-server-trento:dev make run-container).
	$(DOCKER) run -p ${PORT}:${PORT} ${IMAGE}

##@ Linters

.PHONY: fmt
fmt: ## Format source code.
	$(GO) fmt ./...

.PHONY: vet
vet: ## Run go vet against code.
	$(GO) vet ./...

.PHONY: vendor
vendor: ## Run go mod vendor.
	$(GO) mod vendor

.PHONY: tidy
tidy: ## Run go mod tidy.
	$(GO) mod tidy

.PHONY: fix-ending
fix-ending: ## Fix the line endings, converting them back to unix.
	find . -path "./.git" -prune -o -type f -exec dos2unix {} \+;

.PHONY: lint
lint: linter-golangci linter-license linter-shellcheck linter-yamllint linter-asciidoc ## Run all the linters.

.PHONY: linter-golangci
linter-golangci: golangci-lint ## Run golangci-lint linter.
	$(GOLANGCI_LINT) run

.PHONY: linter-license
linter-license: ## Run license script.
	./hack/linters/license-linter.sh

.PHONY: linter-shellcheck
linter-shellcheck: ## Run shellcheck script.
	./hack/linters/shellcheck.sh

.PHONY: linter-yamllint
linter-yamllint: ## Run yamllint script.
	./hack/linters/yamllint.sh

.PHONY: linter-asciidoc
linter-asciidoc: ## Run asciidoc linter script.
	./hack/linters/asciidoc-linter.sh

##@ Dependencies

# Location to install dependencies to
LOCALBIN ?= "$(shell pwd)/bin"
$(LOCALBIN):
	mkdir -p $(LOCALBIN)

## Tool Binaries
GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint-$(GOLANGCI_LINT_VERSION)

## Tool Versions
TOOL_VERSIONS_FILE := ${CURDIR}/.tool-versions

GOLANGCI_LINT_VERSION ?= v$(shell grep '^golangci-lint' $(TOOL_VERSIONS_FILE) | cut -d' ' -f2) # See https://github.com/golangci/golangci-lint/releases

.PHONY: install-tools
install-tools: golangci-lint ## Download all the required tools.

.PHONY: golangci-lint
golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
$(GOLANGCI_LINT): $(LOCALBIN)
	$(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/cmd/golangci-lint,${GOLANGCI_LINT_VERSION})
	$(call check-gh-version,golangci/golangci-lint,${GOLANGCI_LINT_VERSION})

# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist
# $1 - target path with name of binary (ideally with version)
# $2 - package url which can be installed
# $3 - specific version of package
define go-install-tool
@[ -f $(1) ] || { \
set -e; \
package=$(2)@$(3) ;\
echo "Downloading $${package}" ;\
GOBIN=$(LOCALBIN) $(GO) install $${package} ;\
mv "$$(echo "$(1)" | sed "s/-$(3)$$//")" $(1) ;\
}
endef

# check-gh-version will 'go install' any package with custom target and name of binary, if it doesn't exist
# $1 - github "username/repository" pair
# $2 - version to check
define check-gh-version
@[ -f $(1) ] || { \
set -e; \
LATEST_VERSION=$$(curl --silent "https://api.github.com/repos/$(1)/releases/latest" | jq -r .tag_name) ;\
if [ $${LATEST_VERSION} != $(2) ]; then \
echo "[OUTDATED] Latest '$(1)' version is '$${LATEST_VERSION}', but using '$(2)'" ;\
fi ;\
}
endef
