# Makefile for Navy Applications and Libraries

### *Get a more readable version of this Makefile* by `make html` (requires python-markdown)
html:
	cat Makefile | sed 's/^\([^#]\)/    \1/g' | markdown_py > Makefile.html
.PHONY: html

## 1. Basic Setup and Checks

### Default to build an application
ifeq ($(MAKECMDGOALS),)
  MAKECMDGOALS  = app
  .DEFAULT_GOAL = app
endif

### Override checks when `make clean/clean-all/html/init`
ifeq ($(findstring $(MAKECMDGOALS),clean|clean-all|html|init),)

### Print build info message
$(info # Building $(NAME)-$(MAKECMDGOALS) [$(ISA)])

### Check: environment variable `$NAVY_HOME` looks sane
ifeq ($(wildcard $(NAVY_HOME)/libs/libos/src/syscall.h),)
  $(error $$NAVY_HOME must be a Navy-apps repo)
endif

### Check: environment variable `$ISA` must be in the supported list
ISAS = $(basename $(notdir $(shell ls $(NAVY_HOME)/scripts/*.mk)))
ifeq ($(filter $(ISAS), $(ISA)), )
  $(error Expected $$ISA in {$(ISAS)}, Got "$(ISA)")
endif

### Checks end here
endif

## 2. General Compilation Targets

### Create the destination directory (`build/$ISA`)
WORK_DIR  = $(shell pwd)
DST_DIR   = $(WORK_DIR)/build/$(ISA)
$(shell mkdir -p $(DST_DIR))

### Compilation targets (application or archive)
APP     ?= $(WORK_DIR)/build/$(NAME)-$(ISA)
ARCHIVE  = $(WORK_DIR)/build/$(NAME)-$(ISA).a


### Add default libraries for ISA != native
ifneq ($(ISA), native)
LIBS += libc libos
CFLAGS += -U_FORTIFY_SOURCE  # fix compile error in Newlib on ubuntu
else
WL = -Wl,
endif

### 32-bit ISA may need compiler-rt to support 64-bit mul/div
ifneq ($(findstring $(ISA), x86|mips32|riscv32|riscv32e|loongarch32r),)
LIBS += compiler-rt
endif

### All libraries to add to the include path
LIBS_INC := $(sort $(LIBS) $(LIB_DEP))

ifeq ($(MAKECMDGOALS),archive)
LIBS := $(LIB_DEP) # overwrite here to only build and archive the dependent libraries
endif

### Files to be linked: object files (`.o`) and libraries (`.a`)
OBJS      = $(addprefix $(DST_DIR)/, $(addsuffix .o, $(basename $(SRCS))))
LIBS     := $(sort $(LIBS)) # lazy evaluation ("=") causes infinite recursions
LINKAGE  += $(OBJS)  # library archives are added by LIB_TEMPLATE below

## 3. General Compilation Flags

### (Cross) compilers, e.g., mips-linux-gnu-g++
AS        = $(CROSS_COMPILE)gcc
CC        = $(CROSS_COMPILE)gcc
CXX       = $(CROSS_COMPILE)g++
LD        = $(CROSS_COMPILE)ld
AR        = $(CROSS_COMPILE)ar

### Compilation flags
INC_PATH += $(WORK_DIR)/include $(foreach l,$(LIBS_INC),$(NAVY_HOME)/libs/$(l)/include/)
INCFLAGS += $(addprefix -I, $(INC_PATH))

CFLAGS   += -O2 -MMD $(INCFLAGS) -static -D__NAVY__ \
            -D__ISA__=\"$(ISA)\" -D__ISA_$(shell echo $(ISA) | tr a-z A-Z)__ \
            -fno-asynchronous-unwind-tables -fno-builtin -fno-stack-protector
CXXFLAGS +=  $(CFLAGS) -fno-rtti -fno-exceptions
ASFLAGS  +=  $(CFLAGS)

## 4. ISA-Specific Configurations

### Paste in ISA-specific configurations (e.g., from `scripts/x86.mk`)
-include $(NAVY_HOME)/scripts/$(ISA).mk

## 5. Compilation Rules

### Rule (compile): a single `.c` -> `.o` (gcc)
$(DST_DIR)/%.o: %.c
	@mkdir -p $(dir $@) && echo + CC $<
	@$(CC) -std=gnu11 $(CFLAGS) -c -o $@ $(realpath $<)

### Rule (compile): a single `.cc` -> `.o` (g++)
$(DST_DIR)/%.o: %.cc
	@mkdir -p $(dir $@) && echo + CXX $<
	@$(CXX) -std=c++17 $(CXXFLAGS) -c -o $@ $(realpath $<)

### Rule (compile): a single `.cpp` -> `.o` (g++)
$(DST_DIR)/%.o: %.cpp
	@mkdir -p $(dir $@) && echo + CXX $<
	@$(CXX) -std=c++17 $(CXXFLAGS) -c -o $@ $(realpath $<)

### Rule (compile): a single `.S` -> `.o` (gcc, which calls as)
$(DST_DIR)/%.o: %.S
	@mkdir -p $(dir $@) && echo + AS $<
	@$(AS) $(ASFLAGS) -c -o $@ $(realpath $<)

# $(1): library name
define LIB_TEMPLATE =
$$(NAVY_HOME)/libs/$(1)/build/$(1)-$$(ISA).a: force
	@$$(MAKE) -s -C $$(NAVY_HOME)/libs/$(1) archive
LINKAGE += $$(NAVY_HOME)/libs/$(1)/build/$(1)-$$(ISA).a
endef

### Rule (recursive make): build dependent libraries (libc, libos, ...)
$(foreach lib, $(LIBS), $(eval $(call LIB_TEMPLATE,$(lib))))

### Rule (link): objects (`*.o`) and libraries (`*.a`) -> `$(APP)`, the final ELF binary to be packed into application (ld)
$(APP): $(LINKAGE)
	@echo + LD "->" $(shell realpath $@ --relative-to .)
	@$(LD) $(LDFLAGS) -o $@ $(WL)--start-group $^ $(WL)--end-group

### Rule (archive): objects (`*.o`) -> `ARCHIVE.a` (ar)
$(ARCHIVE): $(LINKAGE)
	@echo + AR "->" $(shell realpath $@ --relative-to .)
	@$(AR) rcs --thin $@ $^

### Rule (`#include` dependencies): paste in `.d` files generated by gcc on `-MMD`
-include $(addprefix $(DST_DIR)/, $(addsuffix .d, $(basename $(SRCS))))

## 6. Miscellaneous

### Pull newlib from github if it does not exist
ifeq ($(wildcard $(NAVY_HOME)/libs/libc/Makefile),)
  $(shell cd $(NAVY_HOME)/libs && git clone https://github.com/NJU-ProjectN/newlib-navy.git libc)
endif

### Build order control
app: $(APP)
archive: $(ARCHIVE)
.PHONY: app archive

### Force to rebuild a rule
force:
.PHONY: force

### Install an application to fsimg
install: app
	@echo + INSTALL "->" $(NAME)
	@mkdir -p $(NAVY_HOME)/fsimg/bin
	@cp $(APP) $(NAVY_HOME)/fsimg/bin/$(NAME)

.PHONY: install

### Clean a single project (remove `build/`)
clean:
	rm -rf Makefile.html $(WORK_DIR)/build/ build/
.PHONY: clean

### Clean all sub-projects within depth 2 (and ignore errors)
CLEAN_ALL = $(dir $(shell find . -mindepth 2 -maxdepth 3 -name Makefile))
clean-all: $(CLEAN_ALL) clean
	-@rm -f $(NAVY_HOME)/fsimg/bin/*
$(CLEAN_ALL):
	-@$(MAKE) -s -C $@ clean
.PHONY: clean-all $(CLEAN_ALL)

### Build fsimg and ramdisk for Nanos-lite
APPS =
TESTS = dummy hello file-test

fsimg: $(addprefix apps/, $(APPS)) $(addprefix tests/, $(TESTS))
	-for t in $^; do $(MAKE) -s -C $(NAVY_HOME)/$$t install; done

RAMDISK = build/ramdisk.img
RAMDISK_H = build/ramdisk.h
$(RAMDISK): fsimg
	$(eval FSIMG_FILES := $(shell find -L ./fsimg -type f))
	@mkdir -p $(@D)
	@cat $(FSIMG_FILES) > $@
	@truncate -s \%512 $@
	@echo "// file path, file size, offset in disk" > $(RAMDISK_H)
	@wc -c $(FSIMG_FILES) | grep -v 'total$$' | sed -e 's+ ./fsimg+ +' | awk -v sum=0 '{print "\x7b\x22" $$2 "\x22\x2c " $$1 "\x2c " sum "\x7d\x2c";sum += $$1}' >> $(RAMDISK_H)

ramdisk: $(RAMDISK)

.PHONY: fsimg ramdisk
