# Configuration (File extensions, tools to be used, etc)

OPTLIBEXT=-O
DEBUGLIBEXT=-g
LIBEXTS=$(OPTLIBEXT) $(DEBUGLIBEXT)

ifeq "$(MAKECMDGOALS)" "opt"
   LIBEXT=$(OPTLIBEXT)
   OPTFLAGS=$(OPTFLAGS_OPT)
else
   LIBEXT=$(DEBUGLIBEXT)
   OPTFLAGS=$(OPTFLAGS_DEBUG)
endif

CXX=clang++
CC=clang
LN=ln
RM=rm
MKDIR=mkdir
TOUCH=touch
DIRNAME=dirname

OPTFLAGS_OPT=-O3
OPTFLAGS_DEBUG=-O0 -g
# flags used for linking and compilation
FLAGS=#
# These flags are for compilation (not linking) only
# -MMD auto-genenerates .d files in Make format when .o files are created
COMPILE_FLAGS=-MMD -Werror -Wall -Wextra -pedantic -pipe $(OPTFLAGS)
# Flags for linking only. 
LINK_FLAGS=-fuse-ld=gold
# C / C++ specific compilation flags
CXX_STD_FLAGS=-std=c++20
CXX_COMPILE_FLAGS=$(CXX_STD_FLAGS)
C_STD_FLAGS=-std=c99
C_COMPILE_FLAGS=$(C_STD_FLAGS)

CXX_SRC_EXTS=cpp C CPP
C_SRC_EXTS=c

SRC_EXTS=$(CXX_SRC_EXTS) $(C_SRC_EXTS)

BUILDDIR=build
DIRECTORIES=$(BUILDDIR)
EXECUTABLE_REL=main
LINK_NAME=$(BUILDDIR)/$(EXECUTABLE_REL)
EXECUTABLE=$(LINK_NAME)$(LIBEXT)

# Collect files to be compiled

CXX_SRCS=$(foreach ext, $(CXX_SRC_EXTS), $(shell find src -name "*.$(ext)"))
C_SRCS=$(foreach ext, $(C_SRC_EXTS), $(shell find src -name "*.$(ext)"))
SRCS=$(CXX_SRCS) $(C_SRCS)
OBJS=$(foreach ext, $(SRC_EXTS), $(patsubst %.$(ext), $(BUILDDIR)/%$(LIBEXT).o, $(filter %.$(ext), $(SRCS))))

# If there is at least one C++ source use the c++ compiler CXX to link.
LINKER=$(CC) $(FLAGS) $(LINK_FLAGS)
ifneq "$(strip $(CXX_SRCS))" ""
LINKER=$(CXX) $(FLAGS) $(LINK_FLAGS)
endif

MAKECONFIG=Make.config
# The set of Make-files influencing the build, hence everything depends on them
MAKE_DEPENDENCIES=Makefile $(MAKECONFIG)

DEFAULT_TARGET=debug

# Include Make.config to modify variables above
-include $(MAKECONFIG)

# The supported meta targets, dependencies
.PHONY: clean debug opt default check
default: $(DEFAULT_TARGET)
clean debug opt: check

opt debug: $(EXECUTABLE)

$(OBJS) : $(MAKE_DEPENDENCIES) | $(BUILDDIR)

# include autogenerated .d files
-include $(OBJS:.o=.d)

# Rules

define CXX_TO_OBJ
	$(CXX) $(FLAGS) $(CXX_COMPILE_FLAGS) $(COMPILE_FLAGS) $< -c -o $@
endef

define C_TO_OBJ
	$(CC) $(FLAGS) $(C_COMPILE_FLAGS) $(COMPILE_FLAGS) $< -c -o $@
endef

define MK_TARGETDIR
	@$(MKDIR) -p $$($(DIRNAME) $@)
endef

$(BUILDDIR)/%$(LIBEXT).o: %.cpp
	$(MK_TARGETDIR)
	$(CXX_TO_OBJ)

$(BUILDDIR)/%$(LIBEXT).o: %.C
	$(MK_TARGETDIR)
	$(CXX_TO_OBJ)

$(BUILDDIR)/%$(LIBEXT).o: %.CPP
	$(MK_TARGETDIR)
	$(CXX_TO_OBJ)

$(BUILDDIR)/%$(LIBEXT).o: %.c
	$(MK_TARGETDIR)
	$(C_TO_OBJ)

$(EXECUTABLE):  $(OBJS) | $(BUILDDIR)
	$(LINKER) $(OBJS) -o $@

# Create link in opt/debug target
opt debug:
	$(LN) -sf $(EXECUTABLE_REL)$(LIBEXT) $(LINK_NAME)

check: $(BUILDDIR)
	@forbidden=$$(find $(BUILDDIR) -not -type d $(foreach filext, .o .d, -not -name "*$(filext)") $(foreach libext, $(LIBEXTS) "", -not -name "$(EXECUTABLE_REL)$(libext)")); \
	if [ "$${forbidden}x" != "x" ]; then echo "Error: $(BUILDDIR) contains non-autogenerated files $${forbidden}"; exit 1; fi

$(DIRECTORIES):
	@$(MKDIR) $@

$(MAKECONFIG):
	@$(TOUCH) $@

clean:
	@$(RM) -rf $(BUILDDIR)