# Pipe leak Makefile v1.2 [191114]


# Variables =====================================

# Compiler options
CC=cc
CXX=g++
LINKER=$(CC)
DEFINES=
FLAGS=-Wall -Wextra -Wpedantic $(DEFINES)
CFLAGS=$(FLAGS) -std=c11
CXXFLAGS=$(FLAGS) -std=c++11
LIBS=

# Configure flags according to the target
debug: FLAGS += -g
release: FLAGS += -O3 -DNDEBUG

# Directories
BIN_DIR=bin
BUILD_DIR=build
DOC_DIR=doc
LIB_DIR=lib
SRC_DIR=src
SRC_CLI_NAME=cli
SRC_LIB_NAME=pipeleak
SRC_TST_NAME=tester
SRC_CLI_DIR=$(SRC_DIR)/$(SRC_CLI_NAME)
SRC_LIB_DIR=$(SRC_DIR)/$(SRC_LIB_NAME)
SRC_TST_DIR=$(SRC_DIR)/$(SRC_TST_NAME)
TEST_DIR=tests
TEST_OUT_DIR=$(BUILD_DIR)/$(TEST_DIR)
BUILD_CLI_DIR=$(BUILD_DIR)/$(SRC_CLI_NAME)
BUILD_LIB_DIR=$(BUILD_DIR)/$(SRC_LIB_NAME)
BUILD_TST_DIR=$(BUILD_DIR)/$(SRC_TST_NAME)
INST_DIR=$(HOME)/bin

# Project variables
APP_NAME=pipeleak
CLI_PATH=$(BIN_DIR)/$(APP_NAME)
LIB_NAME=lib$(APP_NAME).so
LIB_PATH=$(LIB_DIR)/$(LIB_NAME)
LIB_LINK=$(BIN_DIR)/$(LIB_NAME)
TST_PATH=$(BIN_DIR)/$(SRC_TST_NAME)

# Files and test cases
HEADERS_CLI=$(shell find $(SRC_CLI_DIR) -type f -name '*.h')
HEADERS_LIB=$(shell find $(SRC_LIB_DIR) -type f -name '*.h')
HEADERS_TST=$(shell find $(SRC_TST_DIR) -type f -name '*.h')
SOURCES_CLI=$(shell find $(SRC_CLI_DIR) -type f -name '*.c' -o -name '*.cpp')
SOURCES_LIB=$(shell find $(SRC_LIB_DIR) -type f -name '*.c' -o -name '*.cpp')
SOURCES_TST=$(shell find $(SRC_TST_DIR) -type f -name '*.c' -o -name '*.cpp')
OBJECTS_CLI=$(SOURCES_CLI:$(SRC_CLI_DIR)/%.c=$(BUILD_CLI_DIR)/%.o)
OBJECTS_LIB=$(SOURCES_LIB:$(SRC_LIB_DIR)/%.c=$(BUILD_LIB_DIR)/%.o)
OBJECTS_TST=$(SOURCES_TST:$(SRC_TST_DIR)/%.c=$(BUILD_TST_DIR)/%.o)

# Test cases input are arguments, not stdin
TEST_IN=$(wildcard $(TEST_DIR)/level0*)
TEST_OUT=$(TEST_IN:$(TEST_DIR)/level%=$(TEST_OUT_DIR)/level%)


# Building ======================================

# Default rule is debug target
debug: $(LIB_PATH) $(CLI_PATH) $(TST_PATH)
release: $(CLI_PATH) $(TST_PATH)

# All targets
all: debug doc test
.SECONDEXPANSION:

#Making .so relative to executable path (for easy installation in $HOME/bin):
# See commit f9e98baa552f411fd6508c28144a63401e7a0d36 for adjusting both .so and executable
# https://aimlesslygoingforward.com/blog/2014/01/19/bundling-shared-libraries-on-linux/
# https://unix.stackexchange.com/questions/137492/load-shared-objects-relative-to-executable-path
# https://stackoverflow.com/questions/51839902/using-a-shared-library-in-another-shared-library

# Linker call
#!Outdated: $(CXX) $(CFLAGS) -o $@ $^ $(LIBS)
$(CLI_PATH): $(OBJECTS_CLI) $(LIB_PATH) | $(LIB_LINK)
	$(LINKER) $(CFLAGS) -o $@ $(OBJECTS_CLI) -L$(LIB_DIR) -l$(APP_NAME) -Wl,-rpath,'$$ORIGIN' $(LIBS)
ifeq ($(shell uname -s),Darwin)
	install_name_tool -add_rpath @executable_path/. $(CLI_PATH)
	install_name_tool -change $(LIB_PATH) @rpath/$(LIB_NAME) $(CLI_PATH)
endif

$(LIB_PATH): $(OBJECTS_LIB) | $$(@D)/.
	$(LINKER) -shared $(CFLAGS) -o $@ $^

$(TST_PATH): $(OBJECTS_TST) $(LIB_PATH) | $$(@D)/.
	$(LINKER) $(CFLAGS) -o $@ $^ $(LIBS)

# Link to the library
$(LIB_LINK): $(LIB_PATH) | $$(@D)/.
	cd $(BIN_DIR) && ln -s ../$(LIB_PATH)

# Compile C source into .o object file
$(BUILD_LIB_DIR)/%.o: $(SRC_LIB_DIR)/%.c $(HEADERS_LIB) | $$(@D)/.
	$(CC) -c $(CFLAGS) -fPIC $< -o $@

$(BUILD_CLI_DIR)/%.o: $(SRC_CLI_DIR)/%.c $(HEADERS_CLI) | $$(@D)/.
	$(CC) -c $(CFLAGS) -I $(SRC_LIB_DIR) $< -o $@

$(BUILD_TST_DIR)/%.o: $(SRC_TST_DIR)/%.c $(HEADERS_TST) | $$(@D)/.
	$(CC) -c $(CFLAGS) -I $(SRC_LIB_DIR) $< -o $@

# Compile C++ source into .o object file
#!Outdated rule
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp $(HEADERS) | $$(@D)/.
	$(CXX) -c $(CXXFLAGS) $< -o $@

# Documentation
.PHONY: doc | $(DOC_DIR)/.
doc:
	doxygen Doxyfile


# Testing =======================================

# Testing
.PHONY: test
test: debug $(TEST_OUT)
	@rm -rf $(TEST_OUT)

.PHONY: $(TEST_OUT_DIR)/level%
$(TEST_OUT_DIR)/level%: $(TEST_DIR)/level% | $$(@D)/$$(@F)/.
	$(CLI_PATH) convert -ib $</input.bin -ot $@/input.txt
	@icdiff --no-headers $</input.txt $@/input.txt

# bash -c "icdiff --no-headers <($(CLI_PATH) validate -it $</input.txt) $</validation.txt"


# Misc ==========================================

# Remove output directories
.PHONY: clean
clean:
	rm -rf $(BIN_DIR) $(BUILD_DIR) $(DOC_DIR) $(LIB_DIR)

# Install a copy of the executable in ~/bin
install: release | $(INST_DIR)/.
	cp -p $(CLI_PATH) $(INST_DIR)
	cp -p $(LIB_PATH) $(INST_DIR)

# Uninstall the copy of the executable in ~/bin
.PHONY: uninstall
uninstall:
	rm -f $(INST_DIR)/$(APP_NAME)
	rm -f $(INST_DIR)/$(LIB_NAME)


# Directories ===================================
# Rules for creating output directories

.PRECIOUS: %/.
%/.:
	mkdir -p $(dir $@)


# 3rd party =====================================

icdiff: $(HOME)/bin/icdiff

$(HOME)/bin/icdiff:
	mkdir -p $(HOME)/bin
	wget -q https://raw.githubusercontent.com/jeffkaufman/icdiff/release-1.9.2/icdiff -O $@
	chmod +x $@
