# Makefile template, generic for libraries (static + shared)
PROJECT=exopt
DEFAULT_NAMESPACE=exopt
VERSION=0.0.0

SRC_C   = $(wildcard src/*.c)
SRC_CXX = $(wildcard src/*.cpp)

INCLUDE=include

ifeq ($(PREFIX),)
	PREFIX := /usr/local
endif

# Default archivers
AR?=ar
RANLIB?=ranlib

# Use gcc-{ar,ranlib} when using gcc
ifeq ($(CXX),g++)
	AR=gcc-ar
	RANLIB=gcc-ranlib
endif

# Link to these libraries dynamicalls
SHARED_LIBS= 
# Link to these libraries statically
STATIC_LIBS=

override __VERSION_SPLIT:= $(subst ., ,$(VERSION))
override __VERSION_REVISION:=$(word 3,$(__VERSION_SPLIT)) 0

VERSION_MAJOR:= $(word 1,$(__VERSION_SPLIT))
VERSION_MINOR:= $(word 2,$(__VERSION_SPLIT))
VERSION_BUGFIX:= $(word 3,$(__VERSION_SPLIT))
VERSION_REVISION:= $(word 2,$(subst r, ,$(__VERSION_REVISION)))

override __VERSION_SPLIT:= MAJOR:$(word 1,$(__VERSION_SPLIT)) MINOR:$(word 2,$(__VERSION_SPLIT)) BUGFIX:$(word 1,$(subst r, ,$(__VERSION_REVISION))) REVISION:$(word 2,$(subst r, ,$(__VERSION_REVISION))) REVISION_STRING:$(word 3,$(__VERSION_SPLIT))

COMMON_FLAGS?= -W -Wall 
COMMON_FLAGS+= -pipe -Wstrict-aliasing -fno-strict-aliasing $(addprefix -I,$(INCLUDE))
COMMON_FLAGS+= $(addprefix -D_VERSION_,$(subst :,=,$(__VERSION_SPLIT))) '-D_VERSION="$(VERSION)"'

ifneq ($(DEFAULT_NAMESPACE),)
	COMMON_FLAGS+= '-D_DEFAULT_NS=$(DEFAULT_NAMESPACE)'
endif

## CPU feature flags
CPU_FLAGS+=

DEFINITIONS?=_IMPL

COMMON_FLAGS+=$(addprefix -D,$(DEFINITIONS)) 
COMMON_FLAGS+=$(addprefix -m,$(CPU_FLAGS))

## For LTO and linking over all TUs in general
BINFLAGS+=

DEBUG_BINFLAGS+=
RELEASE_BINFLAGS+= -fuse-linker-plugin

# Target arch. Set to blank for generic
ARCH?=native
# Enable OpenMP and loop parallelisation? (dyn-links to openmp)
PARALLEL?=yes

OPT_FLAGS?= -fgraphite \
	    -floop-interchange -ftree-loop-distribution -floop-strip-mine -floop-block \
	    -fno-stack-check

SHARED_FLAGS+=-fPIC
SHARED_RELEASE_FLAGS+=
SHARED_DEBUG_FLAGS+=

STATIC_FLAGS+=
STATIC_RELEASE_FLAGS+=-ffat-lto-objects
STATIC_DEBUG_FLAGS+=

ifneq ($(ARCH),)
	OPT_FLAGS+= $(addprefix -march=,$(ARCH))
endif

ifeq ($(PARALLEL),yes)
	SHARED_FLAGS+= -fopenmp 
	SHARED_RELEASE_FLAGS+= -floop-parallelize-all -ftree-parallelize-loops=4
endif

CXX_OPT_FLAGS?= $(OPT_FLAGS) -felide-constructors

CSTD?=gnu2x
CXXSTD?=gnu++23

CFLAGS   += $(COMMON_FLAGS) --std=$(CSTD)
CXXFLAGS += $(COMMON_FLAGS) --std=$(CXXSTD)
LDFLAGS  += $(addsuffix .a,$(addprefix -l:lib,$(STATIC_LIBS))) $(addprefix -l,$(SHARED_LIBS))

STRIP=strip

# TODO: XXX: Benchmark to see if `-fno-plt` actually helps... 
RELEASE_COMMON_FLAGS+= -fno-plt -fno-bounds-check

DEBUG_COMMON_FLAGS+= -ggdb -gz -ftrapv -fbounds-check
# -fanalyzer

ifneq ($(TARGET_SPEC_FLAGS),no)
	RELEASE_CFLAGS?=  -O3 -flto $(OPT_FLAGS)
	RELEASE_CXXFLAGS?= -O3 -flto $(CXX_OPT_FLAGS)
	RELEASE_LDFLAGS?=  -Wl,-O3 -Wl,-flto

	#SHARED_FLAGS+=$(SHARED_RELEASE_FLAGS)

	DEBUG_CFLAGS?= -Og
	DEBUG_CXXFLAGS?= -Og

	DEBUG_LDFLAGS?=
endif

DEBUG_CFLAGS+=-DDEBUG $(DEBUG_COMMON_FLAGS)
DEBUG_CXXFLAGS+=-DDEBUG $(DEBUG_COMMON_FLAGS) -fasynchronous-unwind-tables

RELEASE_CFLAGS+=-DRELEASE $(RELEASE_COMMON_FLAGS)
RELEASE_CXXFLAGS+=-DRELEASE $(RELEASE_COMMON_FLAGS)

# Objects

OBJ_C   = $(addprefix obj/c/,$(SRC_C:.c=.o))
OBJ_CXX = $(addprefix obj/cxx/,$(SRC_CXX:.cpp=.o))
OBJ = $(OBJ_C) $(OBJ_CXX)

# Phonies

.PHONY: release
release: | dirs
	$(MAKE) lib$(PROJECT).a
	@$(MAKE) clean-rebuild >> /dev/null
	@$(MAKE) dirs >> /dev/null
	$(MAKE) lib$(PROJECT).so

.PHONY: debug
debug: | dirs
	$(MAKE) lib$(PROJECT)-debug.a
	@$(MAKE) clean-rebuild >> /dev/null
	@$(MAKE) dirs >> /dev/null
	$(MAKE) lib$(PROJECT)-debug.so

# Rebuild both release and debug targets from scratch
.PHONY: all
all: | clean
	@$(MAKE) release
	@$(MAKE) clean-rebuild
	@$(MAKE) debug

.PHONY: install
.PHONY: uninstall

.PHONY: test
test:
	@rm -f $(PROJECT)-test
	@$(MAKE) $(PROJECT)-test

# Targets

dirs:
	@mkdir -p obj/c{,xx}/src{,/rng}

obj/c/%.o: %.c
	$(CC) -c $< $(CFLAGS) -o $@ $(LDFLAGS)

obj/cxx/%.o: %.cpp
	$(CXX) -c $< $(CXXFLAGS) -o $@ $(LDFLAGS)

lib$(PROJECT)-release.a: CFLAGS+= $(RELEASE_CFLAGS) $(STATIC_FLAGS) $(STATIC_RELEASE_FLAGS)
lib$(PROJECT)-release.a: CXXFLAGS += $(RELEASE_CXXFLAGS) $(STATIC_FLAGS) $(STATIC_RELEASE_FLAGS)
lib$(PROJECT)-release.a: LDFLAGS += $(RELEASE_LDFLAGS)
lib$(PROJECT)-release.a: $(OBJ)
	$(AR) rcs $@ $^
	$(RANLIB) $@

lib$(PROJECT)-debug.a: CFLAGS+= $(DEBUG_CFLAGS) $(STATIC_FLAGS) $(STATIC_DEBUG_FLAGS)
lib$(PROJECT)-debug.a: CXXFLAGS += $(DEBUG_CXXFLAGS) $(STATIC_FLAGS) $(STATIC_DEBUG_FLAGS)
lib$(PROJECT)-debug.a: LDFLAGS += $(DEBUG_LDFLAGS)
lib$(PROJECT)-debug.a: $(OBJ)
	$(AR) rcs $@ $^ 
	$(RANLIB) $@

lib$(PROJECT)-release.so: CFLAGS+= $(RELEASE_CFLAGS) $(SHARED_FLAGS) $(SHARED_RELEASE_FLAGS)
lib$(PROJECT)-release.so: CXXFLAGS += $(RELEASE_CXXFLAGS) $(SHARED_FLAGS) $(SHARED_RELEASE_FLAGS)
lib$(PROJECT)-release.so: LDFLAGS += $(RELEASE_LDFLAGS)
lib$(PROJECT)-release.so: BINFLAGS += $(RELEASE_BINFLAGS)
lib$(PROJECT)-release.so: $(OBJ)
	$(CXX) -shared $^ $(BINFLAGS) $(CXXFLAGS) -o $@ $(LDFLAGS)
	$(STRIP) $@

lib$(PROJECT)-debug.so: CFLAGS+= $(DEBUG_CFLAGS) $(SHARED_FLAGS) $(SHARED_DEBUG_FLAGS)
lib$(PROJECT)-debug.so: CXXFLAGS += $(DEBUG_CXXFLAGS) $(SHARED_FLAGS) $(SHARED_DEBUG_FLAGS)
lib$(PROJECT)-debug.so: LDFLAGS += $(DEBUG_LDFLAGS)
lib$(PROJECT)-debug.so: BINFLAGS += $(DEBUG_BINFLAGS)
lib$(PROJECT)-debug.so: $(OBJ)
	$(CXX) -shared $^ $(BINFLAGS) $(CXXFLAGS) -o $@ $(LDFLAGS)

lib$(PROJECT).a: lib$(PROJECT)-release.a
	ln -f $< $@

lib$(PROJECT).so: LDFLAGS+= -Wl,-soname,lib$(PROJECT).so.$(VERSION_MAJOR)
lib$(PROJECT).so: lib$(PROJECT)-release.so
	ln -f $< $@.$(VERSION)
	ln -sf $@.$(VERSION) $@.$(VERSION_MAJOR)
	ln -sf $@.$(VERSION_MAJOR) $@

clean-rebuild:
	rm -rf obj

clean: clean-rebuild
	rm -f lib$(PROJECT){,-{release,debug,pgo}}.{a,so{,.*}}
	rm -f $(PROJECT)-test

install:
	install -d $(DESTDIR)$(PREFIX)/lib/
	install -m 644 lib$(PROJECT).a $(DESTDIR)$(PREFIX)/lib/
	install -s -m 755 lib$(PROJECT).so.$(VERSION) $(DESTDIR)$(PREFIX)/lib/
	ln -sf lib$(PROJECT).so.$(VERSION) $(DESTDIR)$(PREFIX)/lib/lib$(PROJECT).so.$(VERSION_MAJOR)
	ln -sf lib$(PROJECT).so.$(VERSION_MAJOR) $(DESTDIR)$(PREFIX)/lib/lib$(PROJECT).so
	install -d $(DESTDIR)$(PREFIX)/include/$(PROJECT)/
	install -m 644 $(wildcard $(INCLUDE)/*.*) $(DESTDIR)$(PREFIX)/include/$(PROJECT)/
uninstall:
	-rm $(DESTDIR)$(PREFIX)/lib/lib$(PROJECT).{a,so{,.*}}
	cd $(INCLUDE) && find . -type f | xargs -I {} rm "$(DESTDIR)$(PREFIX)/include/$(PROJECT)/{}"
	-rmdir $(DESTDIR)$(PREFIX)/include/$(PROJECT)

$(PROJECT)-test: LDFLAGS+= -lfmt -lstdc++
$(PROJECT)-test: CFLAGS+= -Og -g
$(PROJECT)-test: lib$(PROJECT)-debug.a
	$(CC) $(CFLAGS) src/test/*.c -o $@ -l:$< $(LDFLAGS)
	-valgrind ./$@