DEBUG := false
OUT_DIR := out
KEY_DIR := keys
SRC_DIR := src
TARGET := local
CACHE_DIR := cache
CONFIG_DIR := config
SRC_DIR := src
USER := $(shell id -g):$(shell id -g)
CPUS := $(shell nproc)
ARCH := x86_64

include $(PWD)/config/global.env

.DEFAULT_GOAL := default
.PHONY: default
default: fetch $(OUT_DIR)/bzImage

# Clean repo back to initial clone state
.PHONY: clean
clean:
	rm -rf cache out
	docker image rm -f local/$(NAME)-build

# Launch a shell inside the toolchain container
.PHONY: toolchain-shell
toolchain-shell: $(OUT_DIR)/toolchain.tar
	$(call toolchain,root,bash)

# Pin all packages in toolchain container to latest versions
.PHONY: toolchain-update
toolchain-update:
	$(call toolchain,root,packages-update )

# Source anything required from the internet to build
.PHONY: fetch
fetch: \
	keys \
	$(OUT_DIR) \
	$(OUT_DIR)/toolchain.tar \
	$(CACHE_DIR) \
	$(CACHE_DIR)/linux-$(LINUX_VERSION).tar.xz \
	$(CACHE_DIR)/linux-$(LINUX_VERSION).tar.sign \
	$(CACHE_DIR)/busybox-$(BUSYBOX_VERSION).tar.bz2 \
	$(CACHE_DIR)/busybox-$(BUSYBOX_VERSION).tar.bz2.sig \
	$(CACHE_DIR)/aws-nitro-enclaves-image-format/.git/HEAD

# Build latest image and run in terminal via Qemu
.PHONY: run
run: default
	qemu-system-x86_64 \
		-m 512M \
		-nographic \
		-kernel $(OUT_DIR)/bzImage

# Run ncurses busybox config menu and save output
.PHONY: busybox-config
busybox-config:
	rm $(CONFIG_DIR)/debug/busybox.config
	make $(CONFIG_DIR)/debug/busybox.config

# Run linux config menu and save output
.PHONY: linux-config
linux-config:
	rm $(CONFIG_DIR)/$(TARGET)/linux.config
	make $(CONFIG_DIR)/$(TARGET)/linux.config

.PHONY: keys
keys: \
	$(KEY_DIR)/$(LINUX_KEY).asc \
	$(KEY_DIR)/$(BUSYBOX_KEY).asc

$(KEY_DIR)/$(LINUX_KEY).asc:
	$(call fetch_pgp_key,$(LINUX_KEY))

$(KEY_DIR)/$(BUSYBOX_KEY).asc:
	$(call fetch_pgp_key,,$(BUSYBOX_KEY))

define fetch_pgp_key
	mkdir -p $(KEY_DIR) && \
	$(call toolchain,$(USER), " \
		for server in \
    	    ha.pool.sks-keyservers.net \
    	    hkp://keyserver.ubuntu.com:80 \
    	    hkp://p80.pool.sks-keyservers.net:80 \
    	    pgp.mit.edu \
    	; do \
			echo "Trying: $${server}"; \
    	   	gpg \
    	   		--recv-key \
    	   		--keyserver "$${server}" \
    	   		--keyserver-options timeout=10 \
    	   		--recv-keys "$(1)" \
    	   	&& break; \
    	done; \
		gpg --export -a $(1) > $(KEY_DIR)/$(1).asc; \
	")
endef

$(OUT_DIR):
	mkdir -p $(OUT_DIR)

$(CACHE_DIR):
	mkdir -p $(CACHE_DIR)

$(CACHE_DIR)/aws-nitro-enclaves-image-format/.git/HEAD:
	$(call toolchain,$(USER), " \
		cd /cache; \
		git clone https://github.com/aws/aws-nitro-enclaves-image-format.git; \
		cd aws-nitro-enclaves-image-format; \
		git checkout $(AWS_EIF_REF); \
		git rev-parse --verify HEAD | grep -q $(AWS_EIF_REF) || { \
			echo 'Error: Git ref/branch collision.'; exit 1; \
		}; \
	")

$(CACHE_DIR)/busybox-$(BUSYBOX_VERSION).tar.bz2.sig:
	curl \
		--url $(BUSYBOX_SERVER)/busybox-$(BUSYBOX_VERSION).tar.bz2.sig \
		--output $(CACHE_DIR)/busybox-$(BUSYBOX_VERSION).tar.bz2.sig

$(CACHE_DIR)/busybox-$(BUSYBOX_VERSION).tar.bz2:
	curl \
		--url $(BUSYBOX_SERVER)/busybox-$(BUSYBOX_VERSION).tar.bz2 \
		--output $(CACHE_DIR)/busybox-$(BUSYBOX_VERSION).tar.bz2

$(CACHE_DIR)/linux-$(LINUX_VERSION).tar.sign:
	curl \
		--url $(LINUX_SERVER)/linux-$(LINUX_VERSION).tar.sign \
		--output $(CACHE_DIR)/linux-$(LINUX_VERSION).tar.sign

$(CACHE_DIR)/linux-$(LINUX_VERSION).tar.xz:
	curl \
		--url $(LINUX_SERVER)/linux-$(LINUX_VERSION).tar.xz \
		--output $(CACHE_DIR)/linux-$(LINUX_VERSION).tar.xz

$(CACHE_DIR)/linux-$(LINUX_VERSION).tar:
	xz -d $(CACHE_DIR)/linux-$(LINUX_VERSION).tar.xz

$(CACHE_DIR)/linux-$(LINUX_VERSION): $(CACHE_DIR)/linux-$(LINUX_VERSION).tar
	$(call toolchain,$(USER), " \
		cd /cache && \
		gpg --import /keys/$(LINUX_KEY).asc && \
		gpg --verify linux-$(LINUX_VERSION).tar.sign && \
		tar xf linux-$(LINUX_VERSION).tar; \
	")

$(CACHE_DIR)/busybox-$(BUSYBOX_VERSION):
	$(call toolchain,$(USER), " \
		cd /cache && \
		gpg --import /keys/$(BUSYBOX_KEY).asc && \
		gpg --verify busybox-$(BUSYBOX_VERSION).tar.bz2.sig && \
		tar -xf busybox-$(BUSYBOX_VERSION).tar.bz2 \
	")

$(OUT_DIR)/toolchain.tar:
	DOCKER_BUILDKIT=1 \
	docker build \
		--tag local/$(NAME)-build \
		--build-arg DEBIAN_HASH=$(DEBIAN_HASH) \
		--build-arg RUST_REF=$(RUST_REF) \
		--build-arg CARGO_REF=$(CARGO_REF) \
		--build-arg CONFIG_DIR=$(CONFIG_DIR) \
		--build-arg SCRIPTS_DIR=$(SRC_DIR)/toolchain/scripts \
		-f $(SRC_DIR)/toolchain/Dockerfile \
		.
	docker save "local/$(NAME)-build" -o "$@"

define toolchain
	docker load -i $(OUT_DIR)/toolchain.tar
	docker run \
		--rm \
		--tty \
		--interactive \
		--user=$(1) \
		--platform=linux/$(ARCH) \
		--volume $(PWD)/$(CONFIG_DIR):/config \
		--volume $(PWD)/$(CACHE_DIR):/cache \
		--volume $(PWD)/$(KEY_DIR):/keys \
		--volume $(PWD)/$(OUT_DIR):/out \
		--volume $(PWD)/$(SRC_DIR):/src \
		--env GNUPGHOME=/cache/.gnupg \
		--env ARCH=$(ARCH) \
		--env KBUILD_BUILD_USER=$(KBUILD_BUILD_USER) \
		--env KBUILD_BUILD_HOST=$(KBUILD_BUILD_HOST) \
		--env KBUILD_BUILD_VERSION=$(KBUILD_BUILD_VERSION) \
		--env KBUILD_BUILD_TIMESTAMP=$(KBUILD_BUILD_TIMESTAMP) \
		--env KCONFIG_NOTIMESTAMP=$(KCONFIG_NOTIMESTAMP) \
		--env SOURCE_DATE_EPOCH=$(SOURCE_DATE_EPOCH) \
		local/$(NAME)-build \
		bash -c $(2)
endef


$(CONFIG_DIR)/debug/busybox.config:
	$(call toolchain,$(USER), " \
		cd /cache/busybox-$(BUSYBOX_VERSION) && \
		KCONFIG_NOTIMESTAMP=1 make menuconfig && \
		cp .config /config/debug/busybox.config; \
	")

$(CONFIG_DIR)/$(TARGET)/linux.config:
	$(call toolchain,$(USER)," \
		cd /cache/linux-$(LINUX_VERSION) && \
		make menuconfig && \
		cp .config /config/$(TARGET)/linux.config; \
	")

$(OUT_DIR)/busybox: \
	$(CACHE_DIR)/busybox-$(BUSYBOX_VERSION) \
	$(CACHE_DIR)/busybox-$(BUSYBOX_VERSION).tar.bz2 \
	$(CACHE_DIR)/busybox-$(BUSYBOX_VERSION).tar.bz2.sig
	$(call toolchain,$(USER)," \
		cd /cache/busybox-$(BUSYBOX_VERSION) && \
		cp /config/debug/busybox.config .config && \
		make -j$(CPUS) busybox && \
		cp busybox /out/; \
	")

$(OUT_DIR)/init:
	$(call toolchain,$(USER)," \
		gcc \
			-static \
			-static-libgcc /src/init/init.c \
			-o /out/init; \
	")

$(CACHE_DIR)/linux-$(LINUX_VERSION)/usr/gen_init_cpio: \
	$(CACHE_DIR)/linux-$(LINUX_VERSION) \
	$(CACHE_DIR)/linux-$(LINUX_VERSION) \
	$(CACHE_DIR)/linux-$(LINUX_VERSION).tar.xz \
	$(CACHE_DIR)/linux-$(LINUX_VERSION).tar.sign
	$(call toolchain,$(USER)," \
		cd /cache/linux-$(LINUX_VERSION) && \
		gcc usr/gen_init_cpio.c -o usr/gen_init_cpio \
	")

$(OUT_DIR)/eif_build:
	$(call toolchain,$(USER)," \
		cd /cache/aws-nitro-enclaves-image-format \
		&& CARGO_HOME=/cache/cargo cargo build --example eif_build \
		&& cp target/debug/examples/eif_build /out; \
	")

$(OUT_DIR)/rootfs.cpio: \
	$(OUT_DIR)/busybox \
	$(OUT_DIR)/init \
	$(CACHE_DIR)/linux-$(LINUX_VERSION)/usr/gen_init_cpio
	mkdir -p $(CACHE_DIR)/$(TARGET)/rootfs/bin
	cp $(CONFIG_DIR)/$(TARGET)/rootfs.list $(CACHE_DIR)/$(TARGET)/rootfs.list
ifeq ($(DEBUG), true)
	cp $(OUT_DIR)/init $(CACHE_DIR)/$(TARGET)/rootfs/real_init
	cp $(SRC_DIR)/scripts/busybox_init $(CACHE_DIR)/$(TARGET)/rootfs/init
	cp $(OUT_DIR)/busybox $(CACHE_DIR)/$(TARGET)/rootfs/bin/
	echo "file /bin/busybox /cache/rootfs/bin/busybox 0755 0 0" \
		> $(CACHE_DIR)/$(TARGET)/rootfs.list
else
	cp $(OUT_DIR)/init $(CACHE_DIR)/$(TARGET)/rootfs/init
endif
	$(call toolchain,$(USER)," \
		cd /cache/$(TARGET)/rootfs && \
		find . -mindepth 1 -execdir touch -hcd "@0" "{}" + && \
		find . -mindepth 1 -printf '%P\0' && \
		cd /cache/linux-$(LINUX_VERSION) && \
		usr/gen_initramfs.sh \
			-o /out/rootfs.cpio \
			/cache/$(TARGET)/rootfs.list && \
		cpio -itv < /out/rootfs.cpio && \
		sha256sum /out/rootfs.cpio; \
	")

$(OUT_DIR)/bzImage: \
	$(OUT_DIR)/rootfs.cpio
	$(call toolchain,$(USER)," \
		cd /cache/linux-$(LINUX_VERSION) && \
		cp /config/$(TARGET)/linux.config .config && \
		make olddefconfig && \
		make -j$(CPUS) ARCH=$(ARCH) bzImage && \
		cp arch/x86_64/boot/bzImage /out/ && \
		sha256sum /out/bzImage; \
	")

$(OUT_DIR)/nitro.eif: \
	$(OUT_DIR)/eif_build \
	$(OUT_DIR)/bzImage \
	$(OUT_DIR)/rootfs.cpio
	$(call toolchain,$(USER)," \
		/out/eif_build \
			--kernel /out/bzImage \
			--kernel_config /config/$(TARGET)/linux.config \
			--cmdline "init=/init" \
			--ramdisk /out/rootfs.cpio \
			--output /out/nitro.eif \
	")