# Metadata about this makefile and position
MKFILE_PATH := $(lastword $(MAKEFILE_LIST))
CURRENT_DIR := $(patsubst %/,%,$(dir $(realpath $(MKFILE_PATH))))

# Ensure GOPATH
GOPATH ?= $(shell go env GOPATH)
# assume last entry in GOPATH is home to project
GOPATH := $(lastword $(subst :, ,${GOPATH}))

# Tags specific for building
GOTAGS ?=

# Number of procs to use
GOMAXPROCS ?= 4

# Get the project metadata
GO_DOCKER_VERSION ?= 1.13
PROJECT := $(shell go list -m -mod=vendor)
OWNER := "hashicorp"
NAME := $(notdir $(PROJECT))
GIT_COMMIT ?= $(shell git rev-parse --short HEAD || echo release)
VERSION := $(shell awk -F\" '/Version/ { print $$2; exit }' "${CURRENT_DIR}/version/version.go")

# Current system information
GOOS ?= $(shell go env GOOS)
GOARCH ?= $(shell go env GOARCH)

# Default os-arch combination to build
XC_OS ?= darwin freebsd linux netbsd openbsd solaris windows
XC_ARCH ?= 386 amd64 arm arm64
# XC_EXCLUDE "arm64" entries excludes both arm and arm64
XC_EXCLUDE ?= darwin/arm64 freebsd/arm64 netbsd/arm64 openbsd/arm64 solaris/arm64 windows/arm64 solaris/386

# GPG Signing key (blank by default, means no GPG signing)
GPG_KEY ?=

# List of ldflags
LD_FLAGS ?= \
	-s \
	-w \
	-X ${PROJECT}/version.Name=${NAME} \
	-X ${PROJECT}/version.GitCommit=${GIT_COMMIT}

# List of Docker targets to build
DOCKER_TARGETS ?= alpine light scratch

# Create a cross-compile target for every os-arch pairing. This will generate
# a make target for each os/arch like "make linux/amd64" as well as generate a
# meta target (build) for compiling everything.
define make-xc-target
  $1/$2:
  ifneq (,$(findstring ${1}/${2},$(XC_EXCLUDE)))
		@printf "%s%20s %s\n" "-->" "${1}/${2}:" "${PROJECT} (excluded)"
  else
		@printf "%s%20s %s\n" "-->" "${1}/${2}:" "${PROJECT}"
		case "$2" in \
			arm) export CGO_ENABLED="1" ; \
				  export GOARM=6 \
				  export CC="arm-linux-gnueabihf-gcc" ;; \
			arm64) export CGO_ENABLED="1" ; \
				  export CC="aarch64-linux-gnu-gcc" ;; \
			*) export CGO_ENABLED="0" ;; \
		esac ; \
		env \
			GOOS="${1}" \
			GOARCH="${2}" \
			go build \
				-a \
				-o="pkg/${1}_${2}/${NAME}${3}" \
				-ldflags "${LD_FLAGS}" \
				-tags "${GOTAGS}"
  endif
  .PHONY: $1/$2

  $1:: $1/$2
  .PHONY: $1

  build:: $1/$2
  .PHONY: build
endef
$(foreach goarch,$(XC_ARCH),$(foreach goos,$(XC_OS),$(eval $(call make-xc-target,$(goos),$(goarch),$(if $(findstring windows,$(goos)),.exe,)))))

# Use docker to create pristine builds for release
# First build image w/ arm build requirements, then build all binaries
pristine:
	@docker build \
		--rm \
		--force-rm \
		--no-cache \
		--compress \
		--file="docker/pristine/Dockerfile" \
		--build-arg="GOVERSION=${GO_DOCKER_VERSION}" \
		--tag="pristine-builder" .
	@docker run \
		--interactive \
		--user $$(id -u):$$(id -g) \
		--rm \
		--dns="8.8.8.8" \
		--volume="${CURRENT_DIR}:/go/src/${PROJECT}" \
		--volume="${GOPATH}/pkg/mod:/go/pkg/mod" \
		--workdir="/go/src/${PROJECT}" \
		--env=GO111MODULE=on \
		"pristine-builder" \
		env GOCACHE=/tmp make -j4 build

# dev builds and installs the project locally.
dev:
	@echo "==> Installing ${NAME} for ${GOOS}/${GOARCH}"
	@rm -f "${GOPATH}/pkg/${GOOS}_${GOARCH}/${PROJECT}/version.a" # ldflags change and go doesn't detect
	@env \
		CGO_ENABLED="0" \
		go install \
			-ldflags "${LD_FLAGS}" \
			-tags "${GOTAGS}"
.PHONY: dev

# dist builds the binaries and then signs and packages them for distribution
dist:
	@$(MAKE) -f "${MKFILE_PATH}" _cleanup
	@$(MAKE) -f "${MKFILE_PATH}" pristine
	@$(MAKE) -f "${MKFILE_PATH}" _compress _checksum
.PHONY: dist

release: dist
ifndef GPG_KEY
	@echo "==> ERROR: No GPG key specified! Without a GPG key, this release cannot"
	@echo "           be signed. Set the environment variable GPG_KEY to the ID of"
	@echo "           the GPG key to continue."
	@exit 127
else
	@$(MAKE) -f "${MKFILE_PATH}" _sign
endif
.PHONY: release

# Create a docker compile and push target for each container. This will create
# docker-build/scratch, docker-push/scratch, etc. It will also create two meta
# targets: docker-build and docker-push, which will build and push all
# configured Docker containers. Each container must have a folder in docker/
# named after itself with a Dockerfile (docker/alpine/Dockerfile).
define make-docker-target
  docker-build/$1:
		@echo "==> Building ${1} Docker container for ${PROJECT}"
		@go mod vendor
		@docker build \
			--rm \
			--force-rm \
			--no-cache \
			--compress \
			--file="docker/${1}/Dockerfile" \
			--build-arg="LD_FLAGS=${LD_FLAGS}" \
			--build-arg="GOTAGS=${GOTAGS}" \
			--build-arg="GOVERSION=${GO_DOCKER_VERSION}" \
			$(if $(filter $1,scratch),--tag="${OWNER}/${NAME}",) \
			--tag="${OWNER}/${NAME}:${1}" \
			--tag="${OWNER}/${NAME}:${VERSION}-${1}" \
			"${CURRENT_DIR}"
		@rm -rf "${CURRENT_DIR}/vendor/"
  .PHONY: docker-build/$1

  docker-build:: docker-build/$1
  .PHONY: docker-build

  docker-push/$1:
		@echo "==> Pushing ${1} to Docker registry"
		$(if $(filter $1,scratch),@docker push "${OWNER}/${NAME}",)
		@docker push "${OWNER}/${NAME}:${1}"
		@docker push "${OWNER}/${NAME}:${VERSION}-${1}"
  .PHONY: docker-push/$1

  docker-push:: docker-push/$1
  .PHONY: docker-push

  docker-clean/$1:
		@echo "==> Removing docker image ${1}"
		@docker rmi "${OWNER}/${NAME}:${1}" || true
		@docker rmi "${OWNER}/${NAME}:${VERSION}-${1}" || true

  docker-clean:: docker-clean/$1
  .PHONY: docker-clean

endef
$(foreach target,$(DOCKER_TARGETS),$(eval $(call make-docker-target,$(target))))

# test runs the test suite.
test:
	@echo "==> Testing ${NAME}"
	@go test -count=1 -timeout=30s -parallel=20 -failfast -tags="${GOTAGS}" ./... ${TESTARGS}
.PHONY: test

# test-race runs the test suite.
test-race:
	@echo "==> Testing ${NAME} (race)"
	@go test -timeout=60s -race -tags="${GOTAGS}" ./... ${TESTARGS}
.PHONY: test-race

# _cleanup removes any previous binaries
_cleanup:
	@rm -rf "${CURRENT_DIR}/pkg/"
	@rm -rf "${CURRENT_DIR}/bin/"
	@rm -f "consul-template"
.PHONY: _cleanup

clean: _cleanup docker-clean
.PHONY: clean

# _compress compresses all the binaries in pkg/* as tarball and zip.
_compress:
	@mkdir -p "${CURRENT_DIR}/pkg/dist"
	@for platform in $$(find ./pkg -mindepth 1 -maxdepth 1 -type d); do \
		osarch=$$(basename "$$platform"); \
		if [ "$$osarch" = "dist" ]; then \
			continue; \
		fi; \
		\
		ext=""; \
		if test -z "$${osarch##*windows*}"; then \
			ext=".exe"; \
		fi; \
		cd "$$platform"; \
		tar -czf "${CURRENT_DIR}/pkg/dist/${NAME}_${VERSION}_$${osarch}.tgz" "${NAME}$${ext}"; \
		zip -q "${CURRENT_DIR}/pkg/dist/${NAME}_${VERSION}_$${osarch}.zip" "${NAME}$${ext}"; \
		cd - >/dev/null; \
	done
.PHONY: _compress

# _checksum produces the checksums for the binaries in pkg/dist
_checksum:
	@cd "${CURRENT_DIR}/pkg/dist" && \
		shasum --algorithm 256 * > ${CURRENT_DIR}/pkg/dist/${NAME}_${VERSION}_SHA256SUMS && \
		cd - >/dev/null
.PHONY: _checksum

# _sign signs the binaries using the given GPG_KEY. This should not be called
# as a separate function.
_sign:
	@echo "==> Signing ${PROJECT} at v${VERSION}"
	@gpg \
		--default-key "${GPG_KEY}" \
		--detach-sig "${CURRENT_DIR}/pkg/dist/${NAME}_${VERSION}_SHA256SUMS"
	@git commit \
		--allow-empty \
		--gpg-sign="${GPG_KEY}" \
		--message "Release v${VERSION}" \
		--quiet \
		--signoff
	@git tag \
		--annotate \
		--create-reflog \
		--local-user "${GPG_KEY}" \
		--message "Version ${VERSION}" \
		--sign \
		"v${VERSION}" master
	@echo "--> Do not forget to run:"
	@echo ""
	@echo "    git push && git push --tags"
	@echo ""
	@echo "And then upload the binaries in dist/!"
.PHONY: _sign

# Add/Update the "Table Of Contents" in the README.md
toc:
	@./scripts/readme-toc.sh
.PHONY: toc
