PREFIX ?= /usr/local
DESTDIR ?=

BINDIR = $(PREFIX)/bin
LIBDIR = $(PREFIX)/lib/delta

OCAMLC ?= ocamlc
OCAMLOPT ?= ocamlopt
OCAMLDEP ?= ocamldep
OCAMLLEX ?= ocamllex
OCAMLYACC ?= ocamlyacc
INSTALL ?= install
RM = rm -f

SRC_DIR = src
RUNTIME_DIR = runtime
TEST_DIR = test
BENCH_DIR = bench
BUILD = _build

SRC_ML = $(wildcard $(SRC_DIR)/*.ml)
LIB_ML = $(filter-out $(SRC_DIR)/main.ml,$(SRC_ML))
RT_ML = $(wildcard $(RUNTIME_DIR)/*.ml)
TEST_ML = $(wildcard $(TEST_DIR)/*.ml)
BENCH_ML = $(wildcard $(BENCH_DIR)/*.ml)

LIB_CMX = $(patsubst $(SRC_DIR)/%.ml,$(BUILD)/%.cmx,$(LIB_ML)) $(patsubst %.ml,%.cmx,$(LIB_GEN_ML))
RT_CMX = $(patsubst $(RUNTIME_DIR)/%.ml,$(BUILD)/%.cmx,$(RT_ML))
TEST_CMX = $(patsubst $(TEST_DIR)/%.ml,$(BUILD)/%.cmx,$(TEST_ML))

LIB_ARCHIVE = $(BUILD)/delta.cmxa
RUNTIME_ARCHIVE = $(BUILD)/delta_runtime.cmxa
RUNTIME_BYTE = $(BUILD)/delta_runtime.cma
RUNTIME_NATIVE = $(BUILD)/delta_runtime.a
RUNTIME_ARTIFACTS = $(RUNTIME_ARCHIVE) $(RUNTIME_NATIVE) $(RUNTIME_BYTE) \
  $(patsubst $(RUNTIME_DIR)/%.ml,$(BUILD)/%.cmi,$(RT_ML)) $(patsubst $(RUNTIME_DIR)/%.ml,$(BUILD)/%.cmo,$(RT_ML)) $(RT_CMX)

ifeq ($(strip $(RT_ML)),)
RUNTIME_ARCHIVE =
RUNTIME_NATIVE =
RUNTIME_ARTIFACTS =
endif

TEST_EXE = $(BUILD)/test_main
BENCH_EXE = $(BUILD)/bench

FLAGS = -I $(BUILD) -I +unix
LIBS = unix.cmxa

.PHONY: all clean test bench install uninstall

all: deltac $(LIB_ARCHIVE) $(RUNTIME_ARCHIVE) $(TEST_EXE)

$(BUILD):
	mkdir -p $(BUILD)

$(BUILD)/%.cmi: $(SRC_DIR)/%.mli | $(BUILD)
	$(OCAMLC) $(FLAGS) -c $< -o $@

$(BUILD)/%.cmo: $(SRC_DIR)/%.ml | $(BUILD)
	$(OCAMLC) $(FLAGS) -c $< -o $@

$(BUILD)/%.cmx: $(SRC_DIR)/%.ml | $(BUILD)
	$(OCAMLOPT) $(FLAGS) -c $< -o $@

$(BUILD)/%.cmi: $(RUNTIME_DIR)/%.mli | $(BUILD)
	$(OCAMLC) $(FLAGS) -c $< -o $@

$(BUILD)/%.cmo: $(RUNTIME_DIR)/%.ml | $(BUILD)
	$(OCAMLC) $(FLAGS) -c $< -o $@

$(BUILD)/%.cmx: $(RUNTIME_DIR)/%.ml | $(BUILD)
	$(OCAMLOPT) $(FLAGS) -c $< -o $@

$(BUILD)/%.cmi: $(TEST_DIR)/%.mli | $(BUILD)
	$(OCAMLC) $(FLAGS) -c $< -o $@

$(BUILD)/%.cmx: $(TEST_DIR)/%.ml | $(BUILD)
	$(OCAMLOPT) $(FLAGS) -c $< -o $@

$(BUILD)/%.cmx: $(BENCH_DIR)/%.ml | $(BUILD)
	$(OCAMLOPT) $(FLAGS) -c $< -o $@

$(BUILD)/%.cmx: $(BUILD)/%.ml | $(BUILD)
	$(OCAMLOPT) $(FLAGS) -c $< -o $@

$(BUILD)/%.cmo: $(BUILD)/%.ml | $(BUILD)
	$(OCAMLC) $(FLAGS) -c $< -o $@

$(BUILD)/%.cmi: $(BUILD)/%.mli | $(BUILD)
	$(OCAMLC) $(FLAGS) -c $< -o $@

GEN =
LIB_GEN_ML =
ifneq ($(wildcard $(SRC_DIR)/lexer.mll),)
GEN += $(BUILD)/lexer.ml
LIB_GEN_ML += $(BUILD)/lexer.ml
endif
ifneq ($(wildcard $(SRC_DIR)/parser.mly),)
GEN += $(BUILD)/parser.ml $(BUILD)/parser.mli
LIB_GEN_ML += $(BUILD)/parser.ml
endif

$(BUILD)/lexer.ml: $(SRC_DIR)/lexer.mll | $(BUILD)
	$(OCAMLLEX) -o $@ $<

$(BUILD)/parser.ml: $(SRC_DIR)/parser.mly | $(BUILD)
	$(OCAMLYACC) -b $(BUILD)/parser $<

$(BUILD)/parser.mli: $(BUILD)/parser.ml ;

DEP_INCLUDES = -I $(SRC_DIR) $(if $(wildcard $(RUNTIME_DIR)),-I $(RUNTIME_DIR),) $(if $(wildcard $(TEST_DIR)),-I $(TEST_DIR),) -I $(BUILD)

INTERFACES = $(wildcard $(SRC_DIR)/*.mli) $(wildcard $(RUNTIME_DIR)/*.mli) $(wildcard $(TEST_DIR)/*.mli)

$(BUILD)/deps.d: $(MAKEFILE_LIST) $(SRC_ML) $(INTERFACES) $(RT_ML) $(TEST_ML) $(BENCH_ML) $(GEN) | $(BUILD)
	$(OCAMLDEP) $(DEP_INCLUDES) $(SRC_ML) $(INTERFACES) $(RT_ML) $(TEST_ML) $(BENCH_ML) $(GEN) > $(BUILD)/deps.tmp
	sed -e 's|$(SRC_DIR)/|$(BUILD)/|g' -e 's|$(RUNTIME_DIR)/|$(BUILD)/|g' \
	    -e 's|$(TEST_DIR)/|$(BUILD)/|g' -e 's|$(BENCH_DIR)/|$(BUILD)/|g' \
	    $(BUILD)/deps.tmp > $@
	$(RM) $(BUILD)/deps.tmp

-include $(BUILD)/deps.d

$(BUILD)/order.mk: $(MAKEFILE_LIST) $(SRC_ML) $(RT_ML) $(TEST_ML) $(GEN) | $(BUILD)
	{ echo -n "LIB_ORDER = " ; $(OCAMLDEP) -sort $(LIB_ML) $(LIB_GEN_ML) | sed -e 's|$(SRC_DIR)/\([^ ]*\)\.ml|$(BUILD)/\1.cmx|g' -e 's|$(BUILD)/\([^ ]*\)\.ml|$(BUILD)/\1.cmx|g' ; echo ; } > $@
	{ echo -n "RT_ORDER = " ; $(OCAMLDEP) -sort $(RT_ML) | sed -e 's|$(RUNTIME_DIR)/\([^ ]*\)\.ml|$(BUILD)/\1.cmx|g' ; echo ; } >> $@
	{ echo -n "TEST_ORDER = " ; $(OCAMLDEP) -sort $(TEST_ML) | sed -e 's|$(TEST_DIR)/\([^ ]*\)\.ml|$(BUILD)/\1.cmx|g' ; echo ; } >> $@

-include $(BUILD)/order.mk

$(LIB_ARCHIVE): $(LIB_CMX) $(BUILD)/order.mk
	$(OCAMLOPT) -a -o $@ $(LIB_ORDER)

deltac: $(BUILD)/main.cmx $(LIB_ARCHIVE) $(RUNTIME_ARCHIVE)
	$(OCAMLOPT) $(FLAGS) -o $@ $(LIBS) $(RUNTIME_ARCHIVE) $(LIB_ARCHIVE) $(BUILD)/main.cmx

$(RUNTIME_ARCHIVE) $(RUNTIME_NATIVE): $(RT_CMX)
ifeq ($(strip $(RT_ML)),)
	@echo "no runtime sources"
else
	$(OCAMLOPT) -a -o $@ $(RT_CMX)
endif

ifeq ($(strip $(TEST_ML)),)
TEST_EXE =
else
$(TEST_EXE): $(TEST_CMX) $(LIB_ARCHIVE) $(RUNTIME_ARCHIVE)
	$(OCAMLOPT) $(FLAGS) -o $@ $(LIBS) $(RUNTIME_ARCHIVE) $(LIB_ARCHIVE) $(TEST_ORDER)
endif

test: $(TEST_EXE)
	DELTA_ROOT=$(CURDIR) DELTA_BUILD_DIR=$(CURDIR)/$(BUILD) DELTA_OCAMLOPT="$(OCAMLOPT)" ./$(TEST_EXE)

bench: $(BENCH_EXE)
	DELTA_ROOT=$(CURDIR) DELTA_BUILD_DIR=$(CURDIR)/$(BUILD) ./$(BENCH_EXE)

install: deltac $(RUNTIME_ARCHIVE)
	$(INSTALL) -d $(DESTDIR)$(BINDIR) $(DESTDIR)$(LIBDIR)
	$(INSTALL) -m 755 deltac $(DESTDIR)$(BINDIR)/deltac
	$(INSTALL) -m 644 $(RUNTIME_ARTIFACTS) $(DESTDIR)$(LIBDIR)/

uninstall:
	$(RM) $(DESTDIR)$(BINDIR)/deltac
	$(RM) $(patsubst $(BUILD)/%,$(DESTDIR)$(LIBDIR)/%,$(RUNTIME_ARTIFACTS))

clean:
	rm -rf $(BUILD) deltac
