# NOTE: only tested in GNU/Linux.

## Binary dependencies.

CC = gcc
AR = ar


## Paths.

PREFIX ?= /usr/local
bindir ::= $(PREFIX)/bin
libdir ::= $(PREFIX)/lib
includedir = $(PREFIX)/include/lsup
VALGRIND_DUMP = /tmp/lsup_valgrind.log
CALLGRIND_DUMP = /tmp/lsup_callgrind.out
MASSIF_DUMP = /tmp/lsup_massif.out

INCLUDE_BASE ::= . -Iinclude -Iext/tpl/src -Iext/hashmap -Iext/log/src
INCLUDE ::= -I$(INCLUDE_BASE)
_CFLAGS ::= -std=gnu11 -Wall -fPIC -MMD $(INCLUDE)
CFLAGS = $(_CFLAGS) -O3
DBG_CFLAGS = $(_CFLAGS) -Itest -O0 -g3 -DDEBUG
# NOTE: -luuid is a Linux system library. Other OS's might need a different
# link or a non-system library built.
LDFLAGS ::= -L. -L$(libdir) -llmdb -lxxhash -luuid

PARSER = bin/lemon
LEMON_SRC = ext/sqlite/tool/lemon.c
CODEC_DIR = src/codec

# External sources compiled in core object.
EXT_SRC ::= $(wildcard ext/log/src/*.c) \
	  	  $(wildcard ext/hashmap/*.c) \
	  	  $(wildcard ext/tpl/src/*.c)

# External headers of libraries compiled in core.
EXT_H ::= $(wildcard ext/log/src/*.h) \
	  	$(wildcard ext/tpl/src/*.h) \
	  	$(wildcard ext/hashmap/*.h)

LSUP_SRC = $(wildcard src/*.c)
SRC = $(EXT_SRC) $(LSUP_SRC)
TEST_SRC = $(wildcard test/*.c) test.c

EXT_OBJ ::= $(EXT_SRC:.c=.o)
# TODO This is extremely convoluted, simplify if possible.
CODEC_SRC ::= $(wildcard $(CODEC_DIR)/codec_*.c)
CODEC_REL_SRC ::= $(CODEC_SRC:$(CODEC_DIR)/%=%)
ALL_CODEC_REL_SRC ::= $(CODEC_REL_SRC) $(CODEC_REL_SRC:codec_%=parser_%) \
			$(CODEC_REL_SRC:codec_%=grammar_%)
CODEC_SRC = $(ALL_CODEC_REL_SRC:%=$(CODEC_DIR)/%)
CODEC_OBJ = $(CODEC_SRC:.c=.o)
CODEC_DBG_OBJ = $(CODEC_SRC:.c=_dbg.o)
OBJ = $(EXT_OBJ) $(LSUP_SRC:.c=.o)
DBG_OBJ = $(EXT_OBJ) $(LSUP_SRC:.c=_dbg.o)

LIBS = liblsuprdf.a liblsuprdf.so
DBG_LIBS = liblsuprdf_dbg.a liblsuprdf_dbg.so

# For visual dep graph.
DEPS := $(shell echo "${INCLUDE_BASE}" | sed -e 's/ -I/,/g'),include/codec
DOCS = docs


## Environment.

# Tests need the freshly compiled libs.
export LD_LIBRARY_PATH = .:$(libdir)


## Rules.

.DEFAULT_GOAL := lib

# Extract all rule comments into a help message.
.PHONY: help
help:
	@echo "Command overview:"; echo; \
		grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
		| sed -n 's/^\(.*\): \(.*\)##\(.*\)/\1|\3/p' \
		| column -t  -s '|'
	

lib: codec $(LIBS) ## Compile main library (static and dynamic linking).


debug: codec_dbg $(DBG_LIBS) ## Compile main library with debug symbols.


# Static library.
liblsuprdf.a: $(OBJ)
	$(AR) rs $@ $^ $(CODEC_OBJ)


# Dynamic library.
liblsuprdf.so: $(OBJ)
	$(CC) -shared $(LDFLAGS) -o $@ $^ $(CODEC_OBJ)


# Static debug library.
liblsuprdf_dbg.a: $(DBG_OBJ)
	$(AR) rs $@ $^ $(CODEC_DBG_OBJ)


# Dynamic debug library.
liblsuprdf_dbg.so: $(DBG_OBJ)
	$(CC) -shared $(LDFLAGS) -o $@ $^ $(CODEC_DBG_OBJ)


# Debug objects.
%_dbg.o: %.c
	$(CC) $(DBG_CFLAGS) -c $^ -o $@


# Codecs in a subfolder.

.PHONY: codec
codec: $(PARSER)
	$(MAKE) -C $(CODEC_DIR) codec

.PHONY: codec_dbg
codec_dbg: $(PARSER)
	$(MAKE) -C $(CODEC_DIR) debug

# Build the parser executable.
$(PARSER): $(LEMON_SRC)
	$(CC) $^ -o $@


install: lib ## Install library and dependencies to $PREFIX. May require sudo.
	@echo "Installing library files in $(PREFIX)."
	mkdir -p $(DESTDIR)$(libdir)
	mkdir -p $(DESTDIR)$(includedir)
	cp liblsuprdf.a liblsuprdf.so $(DESTDIR)$(libdir) && \
		cp -r include/*.h include/codec $(EXT_H) $(DESTDIR)$(includedir)


debug_install: install debug ## Install standard and debug libraries.
	@echo "Installing debug library files in $(PREFIX)."
	cp liblsuprdf_dbg.a liblsuprdf_dbg.so $(DESTDIR)$(libdir)


.PHONY: clean ## Clean up artifacts, including language parsers.
clean:
	rm -f src/*.[aod] ./*.[aod] src/codec/*.[aod] src/codec/*.out
	rm -rf build/ dist/ lsup_rdf.egg-info/
	rm -f *.so
	rm -f include/codec/grammar_*.h
	rm -f src/codec/grammar_*.c src/codec/parser_*.c


.PHONY: uninstall ## Uninstall library (not the dependencies).
uninstall:
	rm -f $(DESTDIR)$(libdir)/liblsuprdf*
	rm -rf $(DESTDIR)$(includedir)
	rm -f bin/test*


# For testing, use debug symbols.
bin/test: debug $(TEST_SRC)
	$(CC) $(DBG_CFLAGS) $(LDFLAGS) -llsuprdf_dbg \
		test.c -o bin/test


.PHONY: test
test: bin/test ## Run a test suite.
	@echo "Using libraries: "; ldd bin/test
	exec bin/test


.PHONY: gdb_test
gdb_test: bin/test ## Run a test suite within gdb.
	@echo "Using libraries: "; ldd bin/test
	exec gdb bin/test


lint:
	splint \
		$(INCLUDE) -Itest \
		-DUINT_MAX=0xFFFFFFFFUL \
		-nullpass \
		-posix-lib \
		test.c


.PHONY: memcheck
memcheck:
	valgrind \
	--leak-check=full --show-leak-kinds=all --track-origins=yes \
	--log-file=$(VALGRIND_DUMP) \
	./bin/test
	@echo "Memcheck complete. Valgrind log is at $(VALGRIND_DUMP)"


memtest: bin/test memcheck ## Run a test suite using Valgrind. Output to separate file.


# Performance test application. Essentially the profiling code without debug.
bin/profile: debug profile.c
	$(CC) $(CFLAGS) -g -DTESTING $(LDFLAGS) -llsuprdf_dbg \
		profile.c -o bin/profile


# Performance test application. Essentially the profiling code without debug.
bin/perftest: lib profile.c
	$(CC) $(CFLAGS) -g $(LDFLAGS) -llsuprdf profile.c -o bin/perftest


.PHONY: perftest
perftest: bin/perftest ## Run a performance test by creating, inserting and looking up triples.
	bin/perftest


.PHONY: profile
profile: bin/profile ## Run a profiling session. Output can be inspected with KCachegrind.
	LSUP_MDB_MAPSIZE=800000 valgrind --tool=callgrind \
		--callgrind-out-file="$(CALLGRIND_DUMP)" bin/perftest 1000
	@echo "Profile dump written at $(CALLGRIND_DUMP). Open it with "\
		"qcachegrind, kcachegrind, etc."


.PHONY: footprint
footprint: bin/perftest ## Measure memory footprint by generating and storing 100K triples.
	LSUP_MDB_MAPSIZE=80000000 valgrind --tool=massif \
		--massif-out-file=$(MASSIF_DUMP) bin/perftest 100000
	@echo "Memory stats file written at $(MASSIF_DUMP). Open it with "\
		"massif-visualizer or similar."


.PHONY: py
py: codec  ## Build and install python library.
	pip3 install build==0.8.0
	pip3 uninstall -y lsup_rdf
	python3 -m build
	pip3 install --no-index --find-links=dist/ lsup_rdf


.PHONY: py_dbg
py_dbg: codec_dbg  ## Build and install python library with debug symbols.
	pip3 install build==0.8.0
	pip3 uninstall -y lsup_rdf
	DEBUG=1 python3 -m build
	pip3 install --no-index --find-links=dist/ lsup_rdf


.PHONY: pytest
pytest: py_dbg ## Run a test suite for the Python package.
	python3 test/cpython_test.py


# Requires cinclude2dot (https://www.flourish.org/cinclude2dot) and Graphviz.
depgraph: $(LSUP_SRC) $(CODEC_SRC) include/* include/codec/* ## Build a visual dependency graph of the code.
	cinclude2dot --merge=module --include=$(DEPS) \
		--exclude='test|ext' >| $(DOCS)/dev/deps.dot
	dot $(DOCS)/dev/deps.dot -Tpdf >| $(DOCS)/dev/deps.pdf