# Copyright 2019 The Volcano Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

OUTPUT_DIR ?= _output
BIN_DIR=${OUTPUT_DIR}/bin
RELEASE_DIR=${OUTPUT_DIR}/release
IMAGES_DIR=${OUTPUT_DIR}/images
REPO_PATH=volcano.sh/volcano
IMAGE_PREFIX=volcanosh
CRD_OPTIONS ?= "crd:crdVersions=v1,generateEmbeddedObjectMeta=true"
CRD_OPTIONS_EXCLUDE_DESCRIPTION=${CRD_OPTIONS}",maxDescLen=0"
CC ?= "gcc"
MUSL_CC ?= "/usr/local/musl/bin/musl-gcc"
SUPPORT_PLUGINS ?= "no"
CRD_VERSION ?= v1
BUILDX_OUTPUT_TYPE ?= "docker"
FORCE_REBUILD ?= true

# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN))
GOBIN=$(shell go env GOPATH)/bin
else
GOBIN=$(shell go env GOBIN)
endif

OS=$(shell uname -s | tr '[:upper:]' '[:lower:]')

# Get OS architecture
OSARCH=$(shell uname -m)
ifeq ($(OSARCH),x86_64)
GOARCH?=amd64
else ifeq ($(OSARCH),x64)
GOARCH?=amd64
else ifeq ($(OSARCH),aarch64)
GOARCH?=arm64
else ifeq ($(OSARCH),aarch64_be)
GOARCH?=arm64
else ifeq ($(OSARCH),armv8b)
GOARCH?=arm64
else ifeq ($(OSARCH),armv8l)
GOARCH?=arm64
else ifeq ($(OSARCH),i386)
GOARCH?=x86
else ifeq ($(OSARCH),i686)
GOARCH?=x86
else ifeq ($(OSARCH),arm)
GOARCH?=arm
else
GOARCH?=$(OSARCH)
endif

# Run `make images DOCKER_PLATFORMS="linux/amd64,linux/arm64" BUILDX_OUTPUT_TYPE=registry IMAGE_PREFIX=[yourregistry]` to push multi-platform
DOCKER_PLATFORMS ?= "linux/${GOARCH}"

GOOS ?= linux

include Makefile.def

.EXPORT_ALL_VARIABLES:

all: vc-scheduler vc-agent-scheduler vc-controller-manager vc-webhook-manager vc-agent vcctl command-lines

init:
	mkdir -p ${BIN_DIR}
	mkdir -p ${RELEASE_DIR}

vc-scheduler: init
	if [ ${SUPPORT_PLUGINS} = "yes" ];then\
		CC=${MUSL_CC} CGO_ENABLED=1 go build -ldflags ${LD_FLAGS_CGO} -o ${BIN_DIR}/vc-scheduler ./cmd/scheduler;\
	else\
		CC=${CC} CGO_ENABLED=0 go build -ldflags ${LD_FLAGS} -o ${BIN_DIR}/vc-scheduler ./cmd/scheduler;\
	fi;

vc-agent-scheduler: init
	if [ ${SUPPORT_PLUGINS} = "yes" ];then\
		CC=${MUSL_CC} CGO_ENABLED=1 go build -ldflags ${LD_FLAGS_CGO} -o ${BIN_DIR}/vc-agent-scheduler ./cmd/agent-scheduler;\
	else\
		CC=${CC} CGO_ENABLED=0 go build -ldflags ${LD_FLAGS} -o ${BIN_DIR}/vc-agent-scheduler ./cmd/agent-scheduler;\
	fi;

vc-controller-manager: init
	CC=${CC} CGO_ENABLED=0 go build -ldflags ${LD_FLAGS} -o ${BIN_DIR}/vc-controller-manager ./cmd/controller-manager

vc-webhook-manager: init
	CC=${CC} CGO_ENABLED=0 go build -ldflags ${LD_FLAGS} -o ${BIN_DIR}/vc-webhook-manager ./cmd/webhook-manager

vc-agent: init
	CC=${CC} CGO_ENABLED=0 go build -ldflags ${LD_FLAGS} -o ${BIN_DIR}/vc-agent ./cmd/agent
	CC=${CC} CGO_ENABLED=0 go build -ldflags ${LD_FLAGS} -o ${BIN_DIR}/network-qos ./cmd/network-qos

vcctl: init
	CC=${CC} CGO_ENABLED=0 GOOS=$(if $(filter file,$(origin GOOS)),$(OS),$(GOOS)) GOARCH=$(GOARCH) go build -ldflags ${LD_FLAGS} -o ${BIN_DIR}/vcctl ./cmd/cli

image_bins: vc-scheduler vc-agent-scheduler vc-controller-manager vc-webhook-manager vc-agent

images: vc-scheduler-image vc-agent-scheduler-image vc-controller-manager-image vc-webhook-manager-image vc-agent-image

# Define a reusable build function for individual component images
define build_component_image
	@if [ "$(FORCE_REBUILD)" = "true" ] || [ "$(BUILDX_OUTPUT_TYPE)" = "registry" ] || ! docker image inspect "${IMAGE_PREFIX}/vc-$(1):$(TAG)" >/dev/null 2>&1; then \
		echo "Building image ${IMAGE_PREFIX}/vc-$(1):$(TAG)..."; \
		docker buildx build -t "${IMAGE_PREFIX}/vc-$(1):$(TAG)" . \
			-f ./installer/dockerfile/$(1)/Dockerfile \
			--output=type=${BUILDX_OUTPUT_TYPE} \
			--platform ${DOCKER_PLATFORMS} \
			--build-arg APK_MIRROR=${APK_MIRROR} \
			--build-arg OPEN_EULER_IMAGE_TAG=${OPEN_EULER_IMAGE_TAG}; \
	else \
		echo "Image ${IMAGE_PREFIX}/vc-$(1):$(TAG) already exists, skipping (use FORCE_REBUILD=true to rebuild)"; \
	fi
endef

vc-controller-manager-image:
	$(call build_component_image,controller-manager)

vc-scheduler-image:
	$(call build_component_image,scheduler)

vc-agent-scheduler-image:
	$(call build_component_image,agent-scheduler)

vc-webhook-manager-image:
	$(call build_component_image,webhook-manager)

vc-agent-image:
	$(call build_component_image,agent)

save-images:
	@mkdir -p ${IMAGES_DIR}
	@echo "Saving images with gzip compression..."
	bash -o pipefail -c 'docker save ${IMAGE_PREFIX}/vc-controller-manager:$(TAG) | gzip > ${IMAGES_DIR}/vc-controller-manager-$(TAG).tar.gz'
	bash -o pipefail -c 'docker save ${IMAGE_PREFIX}/vc-scheduler:$(TAG) | gzip > ${IMAGES_DIR}/vc-scheduler-$(TAG).tar.gz'
	bash -o pipefail -c 'docker save ${IMAGE_PREFIX}/vc-agent-scheduler:$(TAG) | gzip > ${IMAGES_DIR}/vc-agent-scheduler-$(TAG).tar.gz'
	bash -o pipefail -c 'docker save ${IMAGE_PREFIX}/vc-webhook-manager:$(TAG) | gzip > ${IMAGES_DIR}/vc-webhook-manager-$(TAG).tar.gz'
	bash -o pipefail -c 'docker save ${IMAGE_PREFIX}/vc-agent:$(TAG) | gzip > ${IMAGES_DIR}/vc-agent-$(TAG).tar.gz'
	@echo "Images saved to ${IMAGES_DIR}"

load-images:
	@echo "Loading images from ${IMAGES_DIR}..."
	@set -- ${IMAGES_DIR}/*.tar.gz; \
	if [ ! -e "$$1" ]; then \
		echo "No image archives (*.tar.gz) found in ${IMAGES_DIR}"; \
		exit 1; \
	fi; \
	for image in "$$@"; do \
		echo "Loading $$image..."; \
		gunzip -c "$$image" | docker load; \
	done
	@echo "All images loaded successfully"

generate-code:
	./hack/update-gencode.sh

# Generate manifests e.g. CRD, RBAC etc.
manifests: controller-gen
	# volcano crd base
	$(CONTROLLER_GEN) $(CRD_OPTIONS) \
		paths="./staging/src/volcano.sh/apis/pkg/apis/scheduling/v1beta1; \
		./staging/src/volcano.sh/apis/pkg/apis/batch/v1alpha1; \
		./staging/src/volcano.sh/apis/pkg/apis/bus/v1alpha1; \
		./staging/src/volcano.sh/apis/pkg/apis/nodeinfo/v1alpha1; \
		./staging/src/volcano.sh/apis/pkg/apis/topology/v1alpha1; \
		./staging/src/volcano.sh/apis/pkg/apis/shard/v1alpha1; \
		./staging/src/volcano.sh/apis/pkg/apis/config/v1alpha1" \
		output:crd:artifacts:config=config/crd/volcano/bases
	# generate volcano job crd yaml without description to avoid yaml size limit when using `kubectl apply`
	$(CONTROLLER_GEN) $(CRD_OPTIONS_EXCLUDE_DESCRIPTION) \
		paths="./staging/src/volcano.sh/apis/pkg/apis/batch/v1alpha1" \
		output:crd:artifacts:config=config/crd/volcano/bases
	# jobflow crd base
	$(CONTROLLER_GEN) $(CRD_OPTIONS) \
		paths="./staging/src/volcano.sh/apis/pkg/apis/flow/v1alpha1" \
		output:crd:artifacts:config=config/crd/jobflow/bases
	# generate volcano jobflow crd yaml without description to avoid yaml size limit when using `kubectl apply`
	$(CONTROLLER_GEN) $(CRD_OPTIONS_EXCLUDE_DESCRIPTION) \
		paths="./staging/src/volcano.sh/apis/pkg/apis/flow/v1alpha1" \
		output:crd:artifacts:config=config/crd/jobflow/bases

unit-test:
	go clean -testcache
	if [ ${OS} = 'darwin' ];then\
		go list ./... | grep -v "/e2e" | GOOS=${OS} xargs go test;\
	else\
		go test -p 8 -race $$(find pkg cmd -type f -name '*_test.go' | sed -r 's|/[^/]+$$||' | sort | uniq | sed "s|^|volcano.sh/volcano/|");\
	fi;

e2e: images
	./hack/run-e2e-kind.sh

e2e-test-schedulingbase: images
	# volume binding e2e testing has some cases using "kubernetes.io/no-provisioner" as the storage provisioner, so we need to specify an ignored provisioner here.
	E2E_TYPE=SCHEDULINGBASE IGNORED_PROVISIONERS="kubernetes.io/no-provisioner" ./hack/run-e2e-kind.sh

e2e-test-schedulingaction: images
	E2E_TYPE=SCHEDULINGACTION ./hack/run-e2e-kind.sh

e2e-test-schedulinggates: images
	E2E_TYPE=SCHEDULINGGATES FEATURE_GATES="SchedulingGatesQueueAdmission=true" ./hack/run-e2e-kind.sh

e2e-test-jobp: images
	E2E_TYPE=JOBP ./hack/run-e2e-kind.sh

e2e-test-jobseq: images
	E2E_TYPE=JOBSEQ ./hack/run-e2e-kind.sh

e2e-test-vcctl: vcctl images
	E2E_TYPE=VCCTL ./hack/run-e2e-kind.sh

e2e-test-stress: images
	E2E_TYPE=STRESS ./hack/run-e2e-kind.sh

e2e-test-cronjob: images  
	E2E_TYPE=CRONJOB ./hack/run-e2e-kind.sh

e2e-test-dra: images
	E2E_TYPE=DRA FEATURE_GATES="DynamicResourceAllocation=true,DRAConsumableCapacity=true,DRAPrioritizedList=true" ./hack/run-e2e-kind.sh

e2e-test-hypernode: images
	E2E_TYPE=HYPERNODE ./hack/run-e2e-kind.sh

e2e-test-admission-webhook: images
	E2E_TYPE=ADMISSION_WEBHOOK ./hack/run-e2e-kind.sh

e2e-test-admission-policy: images
	E2E_TYPE=ADMISSION_POLICY ./hack/run-e2e-kind.sh

e2e-test-agentscheduler: images
	E2E_TYPE=AGENTSCHEDULER$(if $(SHARDING_MODE),_$(shell echo $(SHARDING_MODE) | tr '[:lower:]' '[:upper:]'),) ./hack/run-e2e-kind.sh

e2e-test-shardingcontroller: images
	E2E_TYPE=SHARDINGCONTROLLER ./hack/run-e2e-kind.sh

e2e-test-gangevict: images
	E2E_TYPE=GANGEVICT ./hack/run-e2e-kind.sh

e2e-test-schedulersharding: images
	E2E_TYPE=SCHEDULERSHARDING$(if $(SHARDING_MODE),_$(shell echo $(SHARDING_MODE) | tr '[:lower:]' '[:upper:]'),) ./hack/run-e2e-kind.sh

generate-yaml: init manifests
	./hack/generate-yaml.sh CRD_VERSION=${CRD_VERSION}

generate-charts: init manifests
	./hack/generate-charts.sh

release-env:
	./hack/build-env.sh release

dev-env:
	./hack/build-env.sh dev

release: images generate-yaml
	./hack/publish.sh

clean:
	rm -rf _output/
	rm -f *.log

verify:
	hack/verify-gofmt.sh
	hack/verify-gencode.sh
    # this verify is deprecated and use make lint-licenses instead.
	#hack/verify-vendor-licenses.sh

lint: ## Lint the files
	hack/verify-golangci-lint.sh

verify-generated-yaml:
	./hack/check-generated-yaml.sh

command-lines:
	go build -ldflags ${LD_FLAGS} -o=${BIN_DIR}/vcancel ./cmd/cli/vcancel
	go build -ldflags ${LD_FLAGS} -o=${BIN_DIR}/vresume ./cmd/cli/vresume
	go build -ldflags ${LD_FLAGS} -o=${BIN_DIR}/vsuspend ./cmd/cli/vsuspend
	go build -ldflags ${LD_FLAGS} -o=${BIN_DIR}/vjobs ./cmd/cli/vjobs
	go build -ldflags ${LD_FLAGS} -o=${BIN_DIR}/vqueues ./cmd/cli/vqueues
	go build -ldflags ${LD_FLAGS} -o=${BIN_DIR}/vsub ./cmd/cli/vsub

# find or download controller-gen
# download controller-gen if necessary
controller-gen:
ifeq (, $(shell which controller-gen))
	@{ \
	set -e ;\
	CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\
	cd $$CONTROLLER_GEN_TMP_DIR ;\
	go mod init tmp ;\
	GOOS=${OS} go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.20.0 ;\
	rm -rf $$CONTROLLER_GEN_TMP_DIR ;\
	}
CONTROLLER_GEN=$(GOBIN)/controller-gen
else
CONTROLLER_GEN=$(shell which controller-gen)
endif

update-development-yaml:
	make generate-yaml RELEASE_DIR=installer
	mv installer/volcano-${TAG}.yaml installer/volcano-development.yaml
	mv installer/volcano-agent-scheduler-${TAG}.yaml installer/volcano-agent-scheduler-development.yaml
	mv installer/volcano-agent-${TAG}.yaml installer/volcano-agent-development.yaml
	mv installer/volcano-monitoring-${TAG}.yaml installer/volcano-monitoring.yaml

	ENABLE_VAP=true make generate-yaml RELEASE_DIR=installer
	mv installer/volcano-${TAG}.yaml installer/volcano-development-vap.yaml
	rm installer/volcano-agent-scheduler-${TAG}.yaml
	rm installer/volcano-agent-${TAG}.yaml
	rm installer/volcano-monitoring-${TAG}.yaml

	ENABLE_VAP=true ENABLE_MAP=true make generate-yaml RELEASE_DIR=installer
	mv installer/volcano-${TAG}.yaml installer/volcano-development-vap-map.yaml
	rm installer/volcano-agent-scheduler-${TAG}.yaml
	rm installer/volcano-agent-${TAG}.yaml
	rm installer/volcano-monitoring-${TAG}.yaml

mod-download-go:
	@-GOFLAGS="-mod=readonly" find -name go.mod -execdir go mod download \;
# go mod tidy is needed with Golang 1.16+ as go mod download affects go.sum
# https://github.com/golang/go/issues/43994
# exclude docs folder
	@find . -path ./docs -prune -o -name go.mod -execdir go mod tidy \;

.PHONY: mirror-licenses
mirror-licenses: mod-download-go; \
	GOOS=${OS} go install istio.io/tools/cmd/license-lint@1.25.0; \
	cd licenses; \
	rm -rf `ls ./ | grep -v LICENSE`; \
	cd -; \
	license-lint --mirror

.PHONY: lint-licenses
lint-licenses:
	@if test -d licenses; then license-lint --config config/license-lint.yaml; fi

.PHONY: licenses-check
licenses-check: mirror-licenses; \
    hack/licenses-check.sh
