Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/Makefile
1613 views
# SPDX-FileCopyrightText: Copyright The Lima Authors
# SPDX-License-Identifier: Apache-2.0
# Files are installed under $(DESTDIR)/$(PREFIX)
PREFIX ?= /usr/local
DEST := $(shell echo "$(DESTDIR)/$(PREFIX)" | sed 's:///*:/:g; s://*$$::')

GO ?= go
TAR ?= tar
ZIP ?= zip
PLANTUML ?= plantuml # may also be "java -jar plantuml.jar" if installed elsewhere

GOARCH ?= $(shell $(GO) env GOARCH)
GOHOSTARCH := $(shell $(GO) env GOHOSTARCH)
GOHOSTOS := $(shell $(GO) env GOHOSTOS)
GOOS ?= $(shell $(GO) env GOOS)
ifeq ($(GOOS),windows)
bat = .bat
exe = .exe
endif

GO_BUILDTAGS ?=
ifeq ($(GOOS),darwin)
MACOS_SDK_VERSION = $(shell xcrun --show-sdk-version | cut -d . -f 1)
ifeq ($(shell test $(MACOS_SDK_VERSION) -lt 13; echo $$?),0)
# The "vz" mode needs macOS 13 SDK or later
GO_BUILDTAGS += no_vz
endif
endif

ifeq ($(GOHOSTOS),windows)
WINVER_MAJOR=$(shell powershell.exe "[System.Environment]::OSVersion.Version.Major")
ifeq ($(WINVER_MAJOR),10)
WINVER_BUILD=$(shell powershell.exe "[System.Environment]::OSVersion.Version.Build")
WINVER_BUILD_HIGH_ENOUGH=$(shell powershell.exe $(WINVER_BUILD) -ge 19041)
ifeq ($(WINVER_BUILD_HIGH_ENOUGH),False)
GO_BUILDTAGS += no_wsl
endif
endif
endif

PACKAGE := github.com/lima-vm/lima/v2

VERSION := $(shell git describe --match 'v[0-9]*' --dirty='.m' --always --tags)
VERSION_TRIMMED := $(VERSION:v%=%)

KEEP_SYMBOLS ?=
GO_BUILD_LDFLAGS_S := true
ifeq ($(KEEP_SYMBOLS),1)
	GO_BUILD_LDFLAGS_S = false
endif
GO_BUILD_LDFLAGS := -ldflags="-s=$(GO_BUILD_LDFLAGS_S) -w -X $(PACKAGE)/pkg/version.Version=$(VERSION)"
# `go -version -m` returns -tags with comma-separated list, because space-separated list is deprecated in go1.13.
# converting to comma-separated list is useful for comparing with the output of `go version -m`.
GO_BUILD_FLAG_TAGS := $(addprefix -tags=,$(shell echo "$(GO_BUILDTAGS)"|tr " " "\n"|paste -sd "," -))
GO_BUILD := $(GO) build $(GO_BUILD_LDFLAGS) $(GO_BUILD_FLAG_TAGS)

################################################################################
# Features
.NOTPARALLEL:
.SECONDEXPANSION:

################################################################################
.PHONY: all
all: binaries manpages

################################################################################
# Help
.PHONY: help
help:
	@echo  '  binaries        - Build all binaries'
	@echo  '  manpages        - Build manual pages'
	@echo
	@echo  '  help-variables  - Show Makefile variables'
	@echo  '  help-targets    - Show additional Makefile targets'

.PHONY: help-variables
help-variables:
	@echo  '# Variables that can be overridden.'
	@echo
	@echo  '- PREFIX       (directory)  : Installation prefix (default: /usr/local)'
	@echo  '- KEEP_SYMBOLS (1 or 0)     : Whether to keep symbols (default: 0)'

.PHONY: help-targets
help-targets:
	@echo  '# Targets can be categorized by their location.'
	@echo
	@echo  'Targets for files in _output/bin/:'
	@echo  '- limactl                   : Build limactl, and lima'
	@echo  '- lima                      : Copy lima, and lima.bat'
	@echo  '- helpers                   : Copy nerdctl.lima, apptainer.lima, docker.lima, podman.lima, and kubectl.lima'
	@echo
	@echo  'Targets for files in _output/share/lima/:'
	@echo  '- guestagents               : Build guestagents'
	@echo  '- native-guestagent         : Build guestagent for native arch'
	@echo  '- additional-guestagents    : Build guestagents for archs other than native arch'
	@echo  '- <arch>-guestagent         : Build guestagent for <arch>: $(sort $(LINUX_GUESTAGENT_ARCHS))'
	@echo
	@echo  'Targets for files in _output/share/lima/templates/:'
	@echo  '- templates                 : Copy templates'
	@echo  '- template_experimentals    : Copy experimental templates to experimental/'
	@echo  '- default_template          : Copy default.yaml template'
	@echo  '- update-templates          : Update templates'
	@echo
	@echo  'Targets for files in _output/share/doc/lima:'
	@echo  '- documentation             : Copy documentation to _output/share/doc/lima'
	@echo  '- create-links-in-doc-dir   : Create some symlinks pointing ../../lima/templates'
	@echo
	@echo  '# e.g. to install limactl, helpers, native guestagent, and templates:'
	@echo  '#   make native install'

.PHONY: help-artifact
help-artifact:
	@echo  '# Targets for building artifacts to _artifacts/'
	@echo
	@echo  'Targets to building multiple archs artifacts for GOOS:'
	@echo  '- artifacts                 : Build artifacts for current OS and supported archs'
	@echo  '- artifacts-<GOOS>          : Build artifacts for supported archs and <GOOS>: darwin, linux, or windows'
	@echo
	@echo  'Targets to building GOOS and ARCH (GOARCH, or uname -m) specific artifacts:'
	@echo  '- artifact                  : Build artifacts for current GOOS and GOARCH'
	@echo  '- artifact-<GOOS>           : Build artifacts for current GOARCH and <GOOS>: darwin, linux, or windows'
	@echo  '- artifact-<ARCH>           : Build artifacts for current GOOS with <ARCH>: amd64, arm64, x86_64, or aarch64'
	@echo  '- artifact-<GOOS>-<ARCH>    : Build artifacts for <GOOS> and <ARCH>'
	@echo
	@echo  '# GOOS and GOARCH can be specified with make parameters or environment variables.'
	@echo  '# e.g. to build artifact for linux and arm64:'
	@echo  '#   make GOOS=linux GOARCH=arm64 artifact'
	@echo
	@echo  'Targets for miscellaneous artifacts:'
	@echo  '- artifacts-misc            : Build artifacts for go.mod, go.sum, and vendor'

################################################################################
# convenience targets
exe: _output/bin/limactl$(exe)

.PHONY: minimal native
minimal: clean limactl native-guestagent default_template
native: clean limactl helpers native-guestagent templates template_experimentals

################################################################################
# These configs were once customizable but should no longer be changed.
CONFIG_GUESTAGENT_OS_LINUX=y
CONFIG_GUESTAGENT_ARCH_X8664=y
CONFIG_GUESTAGENT_ARCH_AARCH64=y
CONFIG_GUESTAGENT_ARCH_ARMV7L=y
CONFIG_GUESTAGENT_ARCH_PPC64LE=y
CONFIG_GUESTAGENT_ARCH_RISCV64=y
CONFIG_GUESTAGENT_ARCH_S390X=y
CONFIG_GUESTAGENT_COMPRESS=y

################################################################################
.PHONY: binaries
binaries: limactl helpers guestagents \
	templates template_experimentals \
	documentation create-links-in-doc-dir

################################################################################
# _output/bin
.PHONY: limactl lima helpers
limactl: _output/bin/limactl$(exe) lima

### Listing Dependencies

# returns a list of files expanded from $(1) excluding directories and files ending with '_test.go'.
find_files_excluding_dir_and_test = $(shell find $(1) ! -type d ! -name '*_test.go')
FILES_IN_PKG = $(call find_files_excluding_dir_and_test, ./pkg)

# returns a list of files which are dependencies for the command $(1).
dependencies_for_cmd = go.mod $(call find_files_excluding_dir_and_test, ./cmd/$(1)) $(FILES_IN_PKG)

### Force Building Targets

# returns GOVERSION, CGO*, GO*, -ldflags, and -tags build variables from the output of `go version -m $(1)`.
# When CGO_* variables are not set, they are not included in the output.
# Because the CGO_* variables are not set means that those values are default values,
# it can be assumed that those values are same if the GOVERSION is same.
# $(1): target binary
extract_build_vars = $(shell \
	($(GO) version -m $(1) 2>&- || echo $(1):) | \
	awk 'FNR==1{print "GOVERSION="$$2}$$2~/^(CGO|GO|-ldflags|-tags).*=.+$$/{sub("^.*"$$2,$$2); print $$0}' \
)

# a list of keys from the GO build variables to be used for calling `go env`.
# keys starting with '-' are excluded because `go env` does not support those keys.
# $(1): extracted build variables from the binary
keys_in_build_vars = $(filter-out -%,$(shell for i in $(1); do echo $${i%%=*}; done))

# a list of GO build variables to build the target binary.
# $(1): target binary. expecting ENVS_$(2) is set to use the environment variables for the target binary.
# $(2): key of the GO build variable to be used for calling `go env`.
go_build_vars = $(shell \
	$(ENVS_$(1)) $(GO) env $(2) | \
	awk '/ /{print "\""$$0"\""; next}{print}' | \
	for k in $(2); do read -r v && echo "$$k=$${v}"; done \
) $(GO_BUILD_LDFLAGS) $(GO_BUILD_FLAG_TAGS)

# returns the difference between $(1) and $(2).
diff = $(filter-out $(2),$(1))$(filter-out $(1),$(2))

# returns diff between the GO build variables in the binary $(1) and the building variables.
# $(1): target binary
# $(2): extracted GO build variables from the binary
compare_build_vars = $(call diff,$(call go_build_vars,$(1),$(call keys_in_build_vars,$(2))),$(2))

# returns "force" if the GO build variables in the binary $(1) is different from the building variables.
# $(1): target binary. expecting ENVS_$(1) is set to use the environment variables for the target binary.
force_build = $(if $(call compare_build_vars,$(1),$(call extract_build_vars,$(1))),force,)

# returns the file name without .gz extension. It also gunzips the file with .gz extension if exists.
# $(1): target file
gunzip_if_exists = $(shell f=$(1); f=$${f%.gz}; test -f "$${f}.gz" && (set -x; gunzip -f "$${f}.gz") ; echo "$${f}")

# call force_build with passing output of gunzip_if_exists as an argument.
# $(1): target file
force_build_with_gunzip = $(call force_build,$(call gunzip_if_exists,$(1)))

force: # placeholder for force build

################################################################################
# _output/bin/limactl$(exe)

# dependencies for limactl
LIMACTL_DEPS = $(call dependencies_for_cmd,limactl)
ifeq ($(GOOS),darwin)
LIMACTL_DEPS += vz.entitlements
endif

# environment variables for limactl. this variable is used for checking force build.
#
# The hostagent must be compiled with CGO_ENABLED=1 so that net.LookupIP() in the DNS server
# calls the native resolver library and not the simplistic version in the Go library.
ENVS__output/bin/limactl$(exe) = CGO_ENABLED=1 GOOS="$(GOOS)" GOARCH="$(GOARCH)" CC="$(CC)"

LIMACTL_DRIVER_TAGS :=
ifneq (,$(findstring vz,$(ADDITIONAL_DRIVERS)))
LIMACTL_DRIVER_TAGS += external_vz
endif
ifneq (,$(findstring qemu,$(ADDITIONAL_DRIVERS)))
LIMACTL_DRIVER_TAGS += external_qemu
endif
ifneq (,$(findstring wsl2,$(ADDITIONAL_DRIVERS)))
LIMACTL_DRIVER_TAGS += external_wsl2
endif

GO_BUILDTAGS ?=
GO_BUILDTAGS_LIMACTL := $(strip $(GO_BUILDTAGS) $(LIMACTL_DRIVER_TAGS))

_output/bin/limactl$(exe): $(LIMACTL_DEPS) $$(call force_build,$$@)
ifneq ($(GOOS),windows) #
	@rm -rf _output/bin/limactl.exe
else
	@rm -rf _output/bin/limactl
endif
	$(ENVS_$@) $(GO_BUILD) -tags '$(GO_BUILDTAGS_LIMACTL)' -o $@ ./cmd/limactl
ifeq ($(GOOS),darwin)
	codesign -f -v --entitlements vz.entitlements -s - $@
endif

DRIVER_INSTALL_DIR := _output/libexec/lima

.PHONY: additional-drivers
additional-drivers:
	@mkdir -p $(DRIVER_INSTALL_DIR)
	@for drv in $(ADDITIONAL_DRIVERS); do \
		echo "Building $$drv as external"; \
		if [ "$(GOOS)" = "windows" ]; then \
			$(GO_BUILD) -o $(DRIVER_INSTALL_DIR)/lima-driver-$$drv.exe ./cmd/lima-driver-$$drv; \
		else \
			$(GO_BUILD) -o $(DRIVER_INSTALL_DIR)/lima-driver-$$drv ./cmd/lima-driver-$$drv; \
			fi; \
		if [ "$$drv" = "vz" ] && [ "$(GOOS)" = "darwin" ]; then \
			codesign -f -v --entitlements vz.entitlements -s - $(DRIVER_INSTALL_DIR)/lima-driver-vz; \
		fi; \
	done

LIMA_CMDS = $(sort lima lima$(bat)) # $(sort ...) deduplicates the list
LIMA_DEPS = $(addprefix _output/bin/,$(LIMA_CMDS))
lima: $(LIMA_DEPS)

HELPER_CMDS = nerdctl.lima apptainer.lima docker.lima podman.lima kubectl.lima
HELPERS_DEPS = $(addprefix _output/bin/,$(HELPER_CMDS))
helpers: $(HELPERS_DEPS)

_output/bin/%: ./cmd/% | _output/bin
	cp -a $< $@

MKDIR_TARGETS += _output/bin

################################################################################
# _output/share/lima/lima-guestagent
LINUX_GUESTAGENT_PATH_COMMON = _output/share/lima/lima-guestagent.Linux-

# How to add architecture specific guestagent:
# 1. Add the architecture to GUESTAGENT_ARCHS
# 2. Add ENVS_$(*_GUESTAGENT_PATH_COMMON)<arch> to set GOOS, GOARCH, and other necessary environment variables
LINUX_GUESTAGENT_ARCHS = aarch64 armv7l ppc64le riscv64 s390x x86_64

ifeq ($(CONFIG_GUESTAGENT_OS_LINUX),y)
ALL_GUESTAGENTS_NOT_COMPRESSED += $(addprefix $(LINUX_GUESTAGENT_PATH_COMMON),$(LINUX_GUESTAGENT_ARCHS))
endif
ifeq ($(CONFIG_GUESTAGENT_COMPRESS),y)
$(info Guestagents are unzipped each time to check the build configuration; they may be gunzipped afterward.)
gz=.gz
endif

ALL_GUESTAGENTS = $(addsuffix $(gz),$(ALL_GUESTAGENTS_NOT_COMPRESSED))

# guestagent path for the given platform. it may has .gz extension if CONFIG_GUESTAGENT_COMPRESS is enabled.
# $(1): operating system (os)
# $(2): list of architectures
guestagent_path = $(foreach arch,$(2),$($(1)_GUESTAGENT_PATH_COMMON)$(arch)$(gz))

ifeq ($(CONFIG_GUESTAGENT_OS_LINUX),y)
NATIVE_GUESTAGENT_ARCH = $(shell echo $(GOARCH) | sed -e s/arm64/aarch64/ -e s/arm/armv7l/ -e s/amd64/x86_64/)
NATIVE_GUESTAGENT = $(call guestagent_path,LINUX,$(NATIVE_GUESTAGENT_ARCH))
ADDITIONAL_GUESTAGENT_ARCHS = $(filter-out $(NATIVE_GUESTAGENT_ARCH),$(LINUX_GUESTAGENT_ARCHS))
ADDITIONAL_GUESTAGENTS = $(call guestagent_path,LINUX,$(ADDITIONAL_GUESTAGENT_ARCHS))
endif

# config_guestagent_arch returns expanded value of CONFIG_GUESTAGENT_ARCH_<arch>
# $(1): architecture
# CONFIG_GUESTAGENT_ARCH_<arch> naming convention: uppercase, remove '_'
config_guestagent_arch = $(filter y,$(CONFIG_GUESTAGENT_ARCH_$(shell echo $(1)|tr -d _|tr a-z A-Z)))

# guestagent_path_enabled_by_config returns the path to the guestagent binary for the given architecture,
# or an empty string if the CONFIG_GUESTAGENT_ARCH_<arch> is not set.
guestagent_path_enabled_by_config = $(if $(call config_guestagent_arch,$(2)),$(call guestagent_path,$(1),$(2)))

ifeq ($(CONFIG_GUESTAGENT_OS_LINUX),y)
# apply CONFIG_GUESTAGENT_ARCH_*
GUESTAGENTS += $(foreach arch,$(LINUX_GUESTAGENT_ARCHS),$(call guestagent_path_enabled_by_config,LINUX,$(arch)))
endif

.PHONY: guestagents native-guestagent additional-guestagents
guestagents: $(GUESTAGENTS)
native-guestagent: $(NATIVE_GUESTAGENT)
additional-guestagents: $(ADDITIONAL_GUESTAGENTS)
%-guestagent:
	@[ "$(findstring $(*),$(LINUX_GUESTAGENT_ARCHS))" == "$(*)" ] && make $(call guestagent_path,LINUX,$*)

# environment variables for linux-guestagent. these variable are used for checking force build.
ENVS_$(LINUX_GUESTAGENT_PATH_COMMON)aarch64 = CGO_ENABLED=0 GOOS=linux GOARCH=arm64
ENVS_$(LINUX_GUESTAGENT_PATH_COMMON)armv7l = CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7
ENVS_$(LINUX_GUESTAGENT_PATH_COMMON)ppc64le = CGO_ENABLED=0 GOOS=linux GOARCH=ppc64le
ENVS_$(LINUX_GUESTAGENT_PATH_COMMON)riscv64 = CGO_ENABLED=0 GOOS=linux GOARCH=riscv64
ENVS_$(LINUX_GUESTAGENT_PATH_COMMON)s390x = CGO_ENABLED=0 GOOS=linux GOARCH=s390x
ENVS_$(LINUX_GUESTAGENT_PATH_COMMON)x86_64 = CGO_ENABLED=0 GOOS=linux GOARCH=amd64
$(ALL_GUESTAGENTS_NOT_COMPRESSED): $(call dependencies_for_cmd,lima-guestagent) $$(call force_build_with_gunzip,$$@) | _output/share/lima
	$(ENVS_$@) $(GO_BUILD) -o $@ ./cmd/lima-guestagent
	chmod 644 $@
$(LINUX_GUESTAGENT_PATH_COMMON)%.gz: $(LINUX_GUESTAGENT_PATH_COMMON)% $$(call force_build_with_gunzip,$$@)
	@set -x; gzip $<

MKDIR_TARGETS += _output/share/lima

################################################################################
# _output/share/lima/templates
TEMPLATES = $(addprefix _output/share/lima/templates/,$(filter-out experimental,$(notdir $(wildcard templates/*))))
TEMPLATE_EXPERIMENTALS = $(addprefix _output/share/lima/templates/experimental/,$(notdir $(wildcard templates/experimental/*)))

.PHONY: default_template templates template_experimentals
default_template: _output/share/lima/templates/default.yaml
templates: $(TEMPLATES)
template_experimentals: $(TEMPLATE_EXPERIMENTALS)

$(TEMPLATES): | _output/share/lima/templates
$(TEMPLATE_EXPERIMENTALS): | _output/share/lima/templates/experimental
MKDIR_TARGETS += _output/share/lima/templates _output/share/lima/templates/experimental

_output/share/lima/templates/%: templates/%
	cp -aL $< $@

# returns "force" if GOOS==windows, or GOOS!=windows and the file $(1) is not a symlink.
# $(1): target file
# On Windows, always copy to ensure the target has the same file as the source.
force_link = $(if $(filter windows,$(GOOS)),force,$(shell test ! -L $(1) && echo force))

################################################################################
# templates/_images

# fedora-N.yaml should not be updated to refer to Fedora N+1 images
TEMPLATES_TO_BE_UPDATED = $(filter-out $(wildcard templates/_images/fedora*.yaml),$(wildcard templates/_images/*.yaml))

.PHONY: update-templates
update-templates: $(TEMPLATES_TO_BE_UPDATED)
	./hack/update-template.sh $^

################################################################################
# _output/share/doc/lima
DOCUMENTATION = $(addprefix _output/share/doc/lima/,$(wildcard *.md) LICENSE SECURITY.md VERSION)

.PHONY: documentation
documentation: $(DOCUMENTATION)

_output/share/doc/lima/SECURITY.md: | _output/share/doc/lima
	echo "Moved to https://github.com/lima-vm/.github/blob/main/SECURITY.md" > $@

_output/share/doc/lima/VERSION: | _output/share/doc/lima
	echo $(VERSION) > $@

_output/share/doc/lima/%: % | _output/share/doc/lima
	cp -aL $< $@

MKDIR_TARGETS += _output/share/doc/lima

.PHONY: create-links-in-doc-dir
create-links-in-doc-dir: _output/share/doc/lima/templates
_output/share/doc/lima/templates: _output/share/lima/templates $$(call force_link,$$@)
# remove the existing directory or symlink
	rm -rf $@
ifneq ($(GOOS),windows)
	ln -sf ../../lima/templates $@
else
# copy from templates built in build process
	cp -aL $< $@
endif

################################################################################
# returns difference between GOOS GOARCH and GOHOSTOS GOHOSTARCH.
cross_compiling = $(call diff,$(GOOS) $(GOARCH),$(GOHOSTOS) $(GOHOSTARCH))
# returns true if cross_compiling is empty.
native_compiling = $(if $(cross_compiling),,true)

################################################################################
# _output/share/man/man1
.PHONY: manpages
# Set limactl.1 as explicit dependency.
# It's uncertain how many manpages will be generated by `make`,
# because `limactl` generates them without corresponding source code for the manpages.
manpages: _output/share/man/man1/limactl.1
_output/share/man/man1/limactl.1: _output/bin/limactl$(exe)
	@mkdir -p _output/share/man/man1
ifeq ($(native_compiling),true)
# The manpages are generated by limactl, so the limactl binary must be native.
	$< generate-doc _output/share/man/man1 \
		--output _output --prefix $(PREFIX)
endif

################################################################################
.PHONY: docsy
# Set limactl.md as explicit dependency.
# It's uncertain how many docsy pages will be generated by `make`,
# because `limactl` generates them without corresponding source code for the docsy pages.
docsy: website/_output/docsy/limactl.md
website/_output/docsy/limactl.md: _output/bin/limactl$(exe)
	@mkdir -p website/_output/docsy
ifeq ($(native_compiling),true)
# The docs are generated by limactl, so the limactl binary must be native.
	$< generate-doc --type docsy website/_output/docsy \
		--output _output --prefix $(PREFIX)
endif

################################################################################
default-template.yaml: _output/bin/limactl$(exe)
ifeq ($(native_compiling),true)
	$< tmpl copy --embed-all templates/default.yaml $@
endif

schema-limayaml.json: _output/bin/limactl$(exe) templates/default.yaml default-template.yaml
ifeq ($(native_compiling),true)
	# validate both the original template (with the "base" etc), and the embedded template
	$< generate-jsonschema --schemafile $@ templates/default.yaml default-template.yaml
endif

.PHONY: check-jsonschema
check-jsonschema: schema-limayaml.json templates/default.yaml default-template.yaml
	check-jsonschema --schemafile schema-limayaml.json templates/default.yaml default-template.yaml

################################################################################
.PHONY: diagrams
diagrams: docs/lima-sequence-diagram.png
docs/lima-sequence-diagram.png: docs/images/lima-sequence-diagram.puml
	$(PLANTUML) ./docs/images/lima-sequence-diagram.puml

################################################################################
.PHONY: install
install: uninstall
	mkdir -p "$(DEST)"
	# Use tar rather than cp, for better symlink handling
	( cd _output && $(TAR) c * | $(TAR) -xv --no-same-owner -C "$(DEST)" )
	if [ "$(shell uname -s )" != "Linux" -a ! -e "$(DEST)/bin/nerdctl" ]; then ln -sf nerdctl.lima "$(DEST)/bin/nerdctl"; fi
	if [ "$(shell uname -s )" != "Linux" -a ! -e "$(DEST)/bin/apptainer" ]; then ln -sf apptainer.lima "$(DEST)/bin/apptainer"; fi

.PHONY: uninstall
uninstall:
	@test -f "$(DEST)/bin/lima" || echo "lima not found in $(DEST) prefix"
	rm -rf \
		"$(DEST)/bin/lima" \
		"$(DEST)/bin/lima$(bat)" \
		"$(DEST)/bin/limactl$(exe)" \
		"$(DEST)/bin/nerdctl.lima" \
		"$(DEST)/bin/apptainer.lima" \
		"$(DEST)/bin/docker.lima" \
		"$(DEST)/bin/podman.lima" \
		"$(DEST)/bin/kubectl.lima" \
		"$(DEST)/share/man/man1/lima.1" \
		"$(DEST)/share/man/man1/limactl"*".1" \
		"$(DEST)/share/lima" "$(DEST)/share/doc/lima"
	if [ "$$(readlink "$(DEST)/bin/nerdctl")" = "nerdctl.lima" ]; then rm "$(DEST)/bin/nerdctl"; fi
	if [ "$$(readlink "$(DEST)/bin/apptainer")" = "apptainer.lima" ]; then rm "$(DEST)/bin/apptainer"; fi

.PHONY: check-generated
check-generated:
	@test -z "$$(git status --short | grep ".pb.desc" | tee /dev/stderr)" || \
		((git diff $$(find . -name '*.pb.desc') | cat) && \
		(echo "Please run 'make generate' when making changes to proto files and check-in the generated file changes" && false))

.PHONY: lint
lint: check-generated
	golangci-lint run ./...
	yamllint .
	ls-lint
	find . -name '*.sh' ! -path "./.git/*" | xargs shellcheck
	find . -name '*.sh' ! -path "./.git/*" | xargs shfmt -s -d
	go-licenses check --include_tests ./... --allowed_licenses=$$(cat ./hack/allowed-licenses.txt)
	ltag -t ./hack/ltag --check -v
	protolint .

.PHONY: clean
clean:
	rm -rf _output vendor

.PHONY: install-protoc-tools
install-protoc-tools:
	go install -modfile=./hack/tools/go.mod google.golang.org/protobuf/cmd/protoc-gen-go
	go install -modfile=./hack/tools/go.mod google.golang.org/grpc/cmd/protoc-gen-go-grpc

.PHONY: generate
generate:
	go generate ./...

################################################################################
# _artifacts/lima-$(VERSION_TRIMMED)-$(ARTIFACT_OS)-$(ARTIFACT_UNAME_M)
# _artifacts/lima-additional-guestagents-$(VERSION_TRIMMED)-$(ARTIFACT_OS)-$(ARTIFACT_UNAME_M)
.PHONY: artifact

# returns the capitalized string of $(1).
capitalize = $(shell echo "$(1)"|awk '{print toupper(substr($$0,1,1)) tolower(substr($$0,2))}')

# returns the architecture name converted from GOARCH to GNU coreutils uname -m.
to_uname_m = $(foreach arch,$(1),$(shell echo $(arch) | sed 's/amd64/x86_64/' | sed 's/arm64/aarch64/'))

ARTIFACT_FILE_EXTENSIONS := .tar.gz

ifeq ($(GOOS),darwin)
# returns the architecture name converted from GOARCH to macOS's uname -m.
to_uname_m = $(foreach arch,$(1),$(shell echo $(arch) | sed 's/amd64/x86_64/'))
else ifeq ($(GOOS),linux)
# CC is required for cross-compiling on Linux.
CC = $(call to_uname_m,$(GOARCH))-linux-gnu-gcc
else ifeq ($(GOOS),windows)
# artifact in zip format also provided for Windows.
ARTIFACT_FILE_EXTENSIONS += .zip
endif

# artifacts: artifacts-$(GOOS)
ARTIFACT_OS = $(call capitalize,$(GOOS))
ARTIFACT_UNAME_M = $(call to_uname_m,$(GOARCH))
ARTIFACT_PATH_COMMON = _artifacts/lima-$(VERSION_TRIMMED)-$(ARTIFACT_OS)-$(ARTIFACT_UNAME_M)
ARTIFACT_ADDITIONAL_GUESTAGENTS_PATH_COMMON = _artifacts/lima-additional-guestagents-$(VERSION_TRIMMED)-$(ARTIFACT_OS)-$(ARTIFACT_UNAME_M)

artifact: $(addprefix $(ARTIFACT_PATH_COMMON),$(ARTIFACT_FILE_EXTENSIONS)) \
	$(addprefix $(ARTIFACT_ADDITIONAL_GUESTAGENTS_PATH_COMMON),$(ARTIFACT_FILE_EXTENSIONS))

ARTIFACT_DES =  _output/bin/limactl$(exe) $(LIMA_DEPS) $(HELPERS_DEPS) \
	$(NATIVE_GUESTAGENT) \
	$(TEMPLATES) $(TEMPLATE_EXPERIMENTALS) \
	$(DOCUMENTATION) _output/share/doc/lima/templates \
	_output/share/man/man1/limactl.1

# file targets
$(ARTIFACT_PATH_COMMON).tar.gz: $(ARTIFACT_DES) | _artifacts
	$(TAR) -C _output/ --no-xattrs -czvf $@ ./

$(ARTIFACT_ADDITIONAL_GUESTAGENTS_PATH_COMMON).tar.gz:
	# FIXME: do not exec make from make
	make clean additional-guestagents
	$(TAR) -C _output/ --no-xattrs -czvf $@ ./

$(ARTIFACT_PATH_COMMON).zip: $(ARTIFACT_DES) | _artifacts
	cd _output && $(ZIP) -r ../$@ *

$(ARTIFACT_ADDITIONAL_GUESTAGENTS_PATH_COMMON).zip:
	make clean additional-guestagents
	cd _output && $(ZIP) -r ../$@ *

# generate manpages using native limactl.
manpages-using-native-limactl: GOOS = $(GOHOSTOS)
manpages-using-native-limactl: GOARCH = $(GOHOSTARCH)
manpages-using-native-limactl: manpages

# returns "manpages-using-native-limactl" if $(1) is not equal to $(GOHOSTOS).
# $(1): GOOS
generate_manpages_if_needed = $(if $(filter $(if $(1),$(1),$(GOOS)),$(GOHOSTOS)),,manpages-using-native-limactl)

# build native arch first, then build other archs.
artifact_goarchs = arm64 amd64
goarchs_native_and_others = $(GOHOSTARCH) $(filter-out $(GOHOSTARCH),$(artifact_goarchs))

# artifacts is artifact bundles for each OS.
# if target GOOS is native, build native arch first, generate manpages, then build other archs.
# if target GOOS is not native, build native limactl, generate manpages, then build the target GOOS with archs.
.PHONY: artifacts artifacts-darwin artifacts-linux artifacts-windows
artifacts: $$(addprefix artifact-$$(GOOS)-,$$(goarchs_native_and_others))
artifacts-darwin: $$(call generate_manpages_if_needed,darwin) $$(addprefix artifact-darwin-,$$(goarchs_native_and_others))
artifacts-linux: $$(call generate_manpages_if_needed,linux) $$(addprefix artifact-linux-,$$(goarchs_native_and_others))
artifacts-windows: $$(call generate_manpages_if_needed,windows) $$(addprefix artifact-windows-,$$(goarchs_native_and_others))

# set variables for artifact variant targets.
artifact-darwin% artifact-darwin: GOOS = darwin
artifact-linux% artifact-linux: GOOS = linux
artifact-windows% artifact-windows: GOOS = windows
artifact-%-amd64 artifact-%-x86_64 artifact-amd64 artifact-x86_64: GOARCH = amd64
artifact-%-arm64 artifact-%-aarch64 artifact-arm64 artifact-aarch64: GOARCH = arm64

# build cross arch binaries.
artifact-%: $$(call generate_manpages_if_needed)
	make clean artifact GOOS=$(GOOS) GOARCH=$(GOARCH)

.PHONY: artifacts-misc
artifacts-misc: | _artifacts
	go mod vendor
	$(TAR) --no-xattrs -czf _artifacts/lima-$(VERSION_TRIMMED)-go-mod-vendor.tar.gz go.mod go.sum vendor

MKDIR_TARGETS += _artifacts

################################################################################
# This target must be placed after any changes to the `MKDIR_TARGETS` variable.
# It seems that variable expansion in Makefile targets is not done recursively.
$(MKDIR_TARGETS):
	mkdir -p $@