PREFIX := /usr/local
PKG := gitlab.com/gitlab-org/gitaly
BUILD_DIR := $(CURDIR)
TARGET_DIR := $(BUILD_DIR)/_build
TARGET_SETUP := $(TARGET_DIR)/.ok
BIN_BUILD_DIR := $(TARGET_DIR)/bin
PKG_BUILD_DIR := $(TARGET_DIR)/src/$(PKG)
export TEST_REPO_STORAGE_PATH := $(BUILD_DIR)/internal/testhelper/testdata/data
TEST_REPO := $(TEST_REPO_STORAGE_PATH)/gitlab-test.git
INSTALL_DEST_DIR := $(DESTDIR)$(PREFIX)/bin/
COVERAGE_DIR := $(TARGET_DIR)/cover
ASSEMBLY_ROOT := $(TARGET_DIR)/assembly
export GITALY_TEST_RUBY_DIR := $(BUILD_DIR)/ruby
BUNDLE_FLAGS ?= --deployment

BUILDTIME = $(shell date -u +%Y%m%d.%H%M%S)
VERSION_PREFIXED = $(shell git describe)
VERSION = $(VERSION_PREFIXED:v%=%)
GO_LDFLAGS = -ldflags '-X $(PKG)/internal/version.version=$(VERSION) -X $(PKG)/internal/version.buildtime=$(BUILDTIME)'

unexport GOROOT
unexport GOBIN

export GOPATH := $(TARGET_DIR)
export PATH := $(GOPATH)/bin:$(PATH)

# Returns a list of all non-vendored (local packages)
LOCAL_PACKAGES = $(shell cd "$(PKG_BUILD_DIR)" && GOPATH=$(GOPATH) go list ./... | grep -v '^$(PKG)/vendor/' | grep -v '^$(PKG)/ruby/')
LOCAL_GO_FILES = $(shell find -L $(PKG_BUILD_DIR)  -name "*.go" -not -path "$(PKG_BUILD_DIR)/vendor/*" -not -path "$(PKG_BUILD_DIR)/_build/*")
CHANGED_LOCAL_GO_FILES = $(shell git status  --porcelain --short | awk '{ print $$2 }' | grep -v '^$(PKG)/vendor/' | grep .go$)
CHANGED_LOCAL_GO_PACKAGES = $(foreach file,$(CHANGED_LOCAL_GO_FILES),./$(dir $(file))/...)
COMMAND_PACKAGES = $(shell cd "$(PKG_BUILD_DIR)" && GOPATH=$(GOPATH) go list ./cmd/...)
COMMANDS = $(subst $(PKG)/cmd/,,$(COMMAND_PACKAGES))

# Developer Tools
GOVENDOR = $(BIN_BUILD_DIR)/govendor
GOLINT = $(BIN_BUILD_DIR)/golint
GOCOVMERGE = $(BIN_BUILD_DIR)/gocovmerge
GOIMPORTS = $(BIN_BUILD_DIR)/goimports
MEGACHECK = $(BIN_BUILD_DIR)/megacheck

.NOTPARALLEL:

.PHONY: all
all: build

$(TARGET_SETUP):
	rm -rf $(TARGET_DIR)
	mkdir -p "$(dir $(PKG_BUILD_DIR))"
	ln -sf ../../../.. "$(PKG_BUILD_DIR)"
	mkdir -p "$(BIN_BUILD_DIR)"
	touch "$(TARGET_SETUP)"

build:	.ruby-bundle $(TARGET_SETUP)
	go install $(GO_LDFLAGS) $(COMMAND_PACKAGES)
	cp $(foreach cmd,$(COMMANDS),$(BIN_BUILD_DIR)/$(cmd)) $(BUILD_DIR)/

.ruby-bundle:	ruby/Gemfile.lock ruby/Gemfile
	cd ruby && bundle config # for debugging
	cd ruby && bundle install $(BUNDLE_FLAGS)
	cd ruby && bundle show gitaly-proto # sanity check
	touch $@

# TODO: confirm what references this target? Omnibus? Source installs?
.PHONY: install
install: build
	mkdir -p $(INSTALL_DEST_DIR)
	cd $(BIN_BUILD_DIR) && install $(COMMANDS) $(INSTALL_DEST_DIR)

.PHONY: force-ruby-bundle
force-ruby-bundle:
	rm -f .ruby-bundle

# Assembles all runtime components into a directory
# Used by the GDK: run `make assemble ASSEMBLY_ROOT=.../gitaly`
.PHONY: assemble
assemble: force-ruby-bundle build assemble-internal

# assemble-internal does not force 'bundle install' to run again
.PHONY: assemble-internal
assemble-internal: build
	rm -rf $(ASSEMBLY_ROOT)/bin $(ASSEMBLY_ROOT)/ruby
	mkdir -p $(ASSEMBLY_ROOT)/bin
	rm -rf ruby/tmp
	cp -r ruby $(ASSEMBLY_ROOT)/ruby
	rm -rf $(ASSEMBLY_ROOT)/ruby/spec
	install $(foreach cmd,$(COMMANDS),$(BIN_BUILD_DIR)/$(cmd)) $(ASSEMBLY_ROOT)/bin

binaries: assemble
	@if [ $$(uname -m) != 'x86_64' ]; then echo Incorrect architecture for build: $(uname -m); exit 1; fi
	@cd $(ASSEMBLY_ROOT) && shasum -a 256 bin/* | tee checksums.sha256.txt

docker: $(TARGET_SETUP)
	rm -rf $(TARGET_DIR)/docker/
	mkdir -p $(TARGET_DIR)/docker/bin/
	cp -r ruby $(TARGET_DIR)/docker/ruby/
	rm -rf $(TARGET_DIR)/docker/ruby/vendor/bundle

	for cmd in $(COMMAND_PACKAGES); do \
		GOOS=linux GOARCH=amd64 go build $(GO_LDFLAGS) -o "$(TARGET_DIR)/docker/bin/$$(basename $$cmd)" $$cmd; \
	done

	cp Dockerfile $(TARGET_DIR)/docker/
	docker build -t gitlab/gitaly:$(VERSION_PREFIXED) -t gitlab/gitaly:latest $(TARGET_DIR)/docker/

.PHONY: verify
verify: lint check-formatting megacheck govendor-status notice-up-to-date govendor-tagged

.PHONY: govendor-status
govendor-status: $(TARGET_SETUP) $(GOVENDOR)
	cd $(PKG_BUILD_DIR) && govendor status

.PHONY: govendor-tagged
govendor-tagged: $(TARGET_SETUP) $(GOVENDOR)
	cd $(PKG_BUILD_DIR) && _support/gitaly-proto-tagged

$(TEST_REPO):
	git clone --bare https://gitlab.com/gitlab-org/gitlab-test.git $@
	# Git notes aren't fetched by default with git clone
	git -C $@ fetch origin refs/notes/*:refs/notes/*

.PHONY: prepare-tests
prepare-tests: $(TARGET_SETUP) $(TEST_REPO) .ruby-bundle

.PHONY: test
test: test-go rspec

.PHONY: test-go
test-go: prepare-tests
	@go test -count=1 $(LOCAL_PACKAGES) # count=1 bypasses go 1.10 test caching

.PHONY: race-go
race-go: prepare-tests
	@go test -race $(LOCAL_PACKAGES)

.PHONY: rspec
rspec: assemble-internal prepare-tests
	cd ruby && bundle exec rspec

.PHONY: test-changes
test-changes: prepare-tests
	cd $(PKG_BUILD_DIR) && go test $(CHANGED_LOCAL_GO_PACKAGES)

.PHONY: lint
lint: $(GOLINT)
	go run _support/lint.go

.PHONY: megacheck
megacheck: $(MEGACHECK)
	@$(MEGACHECK) $(LOCAL_PACKAGES)

.PHONY: check-formatting
check-formatting: $(TARGET_SETUP) $(GOIMPORTS)
	@test -z "$$($(GOIMPORTS) -e -l $(LOCAL_GO_FILES))" || (echo >&2 "Formatting or imports need fixing: 'make format'" && $(GOIMPORTS) -e -l $(LOCAL_GO_FILES) && false)

.PHONY: format
format: $(TARGET_SETUP) $(GOIMPORTS)
    # In addition to fixing imports, goimports also formats your code in the same style as gofmt
	# so it can be used as a replacement.
	@$(GOIMPORTS) -w -l $(LOCAL_GO_FILES)

.PHONY: package
package: build
	./_support/package/package $(COMMANDS)

.PHONY: notice
notice: $(TARGET_SETUP) $(GOVENDOR)
	cd $(PKG_BUILD_DIR) && govendor license -template _support/notice.template -o $(BUILD_DIR)/NOTICE

.PHONY: notice-up-to-date
notice-up-to-date: $(TARGET_SETUP) $(GOVENDOR)
	# TODO re-enable this! https://gitlab.com/gitlab-org/gitaly/issues/1358
	@true || (cd $(PKG_BUILD_DIR) && govendor license -template _support/notice.template | cmp - NOTICE) || (echo >&2 "NOTICE requires update: 'make notice'" && false)

.PHONY: codeclimate-report
codeclimate-report:
	docker run --env CODECLIMATE_CODE="$(BUILD_DIR)" --volume "$(BUILD_DIR)":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f text

.PHONY: clean
clean:
	rm -rf $(TARGET_DIR) $(TEST_REPO) $(TEST_REPO_STORAGE_PATH) ./internal/service/ssh/gitaly-*-pack .ruby-bundle

.PHONY: cover
cover: prepare-tests $(GOCOVMERGE)
	@echo "NOTE: make cover does not exit 1 on failure, don't use it to check for tests success!"
	mkdir -p "$(COVERAGE_DIR)"
	rm -f $(COVERAGE_DIR)/*.out "$(COVERAGE_DIR)/all.merged" "$(COVERAGE_DIR)/all.html"
	go run _support/test-cover-parallel.go $(COVERAGE_DIR) $(LOCAL_PACKAGES)
	$(GOCOVMERGE) $(COVERAGE_DIR)/*.out > "$(COVERAGE_DIR)/all.merged"
	go tool cover -html  "$(COVERAGE_DIR)/all.merged" -o "$(COVERAGE_DIR)/all.html"
	@echo ""
	@echo "=====> Total test coverage: <====="
	@echo ""
	@go tool cover -func "$(COVERAGE_DIR)/all.merged"

# Install govendor
$(GOVENDOR): $(TARGET_SETUP)
	go get -v github.com/kardianos/govendor

# Install golint
$(GOLINT): $(TARGET_SETUP)
	go get -v golang.org/x/lint/golint

# Install gocovmerge
$(GOCOVMERGE): $(TARGET_SETUP)
	go get -v github.com/wadey/gocovmerge

# Install goimports
$(GOIMPORTS): $(TARGET_SETUP)
	go get -v golang.org/x/tools/cmd/goimports

# Install megacheck
$(MEGACHECK): $(TARGET_SETUP)
	go get -v honnef.co/go/tools/cmd/megacheck
