# Makefile for NetRunner native code
#
# Builds:
#   priv/shepherd       - persistent child-process shepherd binary
#   priv/net_runner_nif - NIF shared library for async I/O

PRIV_DIR = priv
C_SRC_DIR = c_src

# Erlang NIF include paths
ERTS_INCLUDE_DIR ?= $(shell erl -noshell -eval "io:format(\"~ts/erts-~ts/include\", [code:root_dir(), erlang:system_info(version)])." -s init stop)
ERL_INTERFACE_INCLUDE_DIR ?= $(shell erl -noshell -eval "io:format(\"~ts\", [code:lib_dir(erl_interface, include)])." -s init stop)
ERL_INTERFACE_LIB_DIR ?= $(shell erl -noshell -eval "io:format(\"~ts\", [code:lib_dir(erl_interface, lib)])." -s init stop)

# Platform detection
UNAME_S := $(shell uname -s)

CC ?= cc

# Opt-in sanitizer build. Usage:
#   make clean && SANITIZE=1 make all
#   mix test (from Elixir — the NIF and shepherd are rebuilt with ASan/UBSan)
#
# Requires LD_PRELOAD of libasan at runtime on Linux when the BEAM isn't
# built with sanitizers; see ci.yml for the invocation.
ifeq ($(SANITIZE),1)
	# _FORTIFY_SOURCE is incompatible with ASan (ASan already intercepts
	# memcpy/etc.). Disable optimisation to -O1 and skip FORTIFY.
	SAN_FLAGS = -fsanitize=address,undefined -fno-omit-frame-pointer -g
	CFLAGS_BASE = -O1 -Wall -Wextra -Werror -std=c99 -fstack-protector-strong $(SAN_FLAGS)
else
	CFLAGS_BASE = -O2 -Wall -Wextra -Werror -std=c99 -fstack-protector-strong -D_FORTIFY_SOURCE=2
endif

ifeq ($(UNAME_S),Darwin)
	# macOS needs _DARWIN_C_SOURCE for SCM_RIGHTS, CMSG_SPACE, etc.
	CFLAGS = $(CFLAGS_BASE) -D_DARWIN_C_SOURCE
	NIF_LDFLAGS = -dynamiclib -undefined dynamic_lookup
	SHEPHERD_LDFLAGS = -fPIE
	NIF_EXT = .so
else
	CFLAGS = $(CFLAGS_BASE) -D_GNU_SOURCE
	NIF_LDFLAGS = -shared -Wl,-z,relro,-z,now -Wl,-z,noexecstack
	SHEPHERD_LDFLAGS = -fPIE -pie -Wl,-z,relro,-z,now -Wl,-z,noexecstack
	NIF_EXT = .so
endif

ifeq ($(SANITIZE),1)
	NIF_LDFLAGS += $(SAN_FLAGS)
	SHEPHERD_LDFLAGS += $(SAN_FLAGS)
endif

NIF_CFLAGS = $(CFLAGS) -I$(ERTS_INCLUDE_DIR) -I$(C_SRC_DIR) -fPIC

# Targets
SHEPHERD = $(PRIV_DIR)/shepherd
NIF_LIB = $(PRIV_DIR)/net_runner_nif$(NIF_EXT)

SHEPHERD_SRC = $(C_SRC_DIR)/shepherd.c
NIF_SRC = $(C_SRC_DIR)/net_runner_nif.c

SHEPHERD_OBJ = $(C_SRC_DIR)/shepherd.o
NIF_OBJ = $(C_SRC_DIR)/net_runner_nif.o

HEADERS = $(C_SRC_DIR)/protocol.h $(C_SRC_DIR)/utils.h

.PHONY: all clean asan

all: $(PRIV_DIR) $(SHEPHERD) $(NIF_LIB)

# Convenience: force a sanitizer rebuild. Same as SANITIZE=1 make clean all.
asan:
	$(MAKE) clean
	$(MAKE) SANITIZE=1 all

$(PRIV_DIR):
	mkdir -p $(PRIV_DIR)

# Shepherd binary
$(SHEPHERD): $(SHEPHERD_OBJ)
	$(CC) $(SHEPHERD_LDFLAGS) -o $@ $<

$(SHEPHERD_OBJ): $(SHEPHERD_SRC) $(HEADERS)
	$(CC) $(CFLAGS) -I$(C_SRC_DIR) -c -o $@ $<

# NIF shared library
$(NIF_LIB): $(NIF_OBJ)
	$(CC) $(NIF_LDFLAGS) -o $@ $<

$(NIF_OBJ): $(NIF_SRC) $(HEADERS)
	$(CC) $(NIF_CFLAGS) -c -o $@ $<

clean:
	rm -f $(SHEPHERD) $(NIF_LIB) $(SHEPHERD_OBJ) $(NIF_OBJ)
