ifndef VERBOSE
MAKEFLAGS += --no-print-directory
endif
PRIV_DIR := $(abspath priv)
OBJ_DIR := $(abspath obj)
DEBUG ?= 0
REBAR ?= rebar3
APP := $(shell sed -nE 's/^\{application, ([a-zA-Z0-9_]+),.*/\1/p' src/*.app.src | head -n1)
OPTIMIZE ?= 0
ifneq ($(filter $(OPTIMIZE),1 true),)
override OPTIMIZE := 1
else
override OPTIMIZE := 0
endif
all: compile
help:
@echo "Usage: make [target] [VAR=value ...]"
@echo ""
@echo "Build targets:"
@echo " all Build NIF and compile Erlang (default)"
@echo " nif Build only the C NIF shared library"
@echo " compile Build NIF + run rebar3 compile"
@echo " optimize PGO build: instrument → benchmark → rebuild (fastest binary)"
@echo " clean Remove build artifacts"
@echo " distclean Remove all generated files including deps"
@echo ""
@echo "Development:"
@echo " test Run eunit test suite"
@echo " cover Run eunit with coverage analysis and print a summary"
@echo " check Run xref and dialyzer"
@echo " dialyzer Run dialyzer"
@echo " memcheck Build with ASan (-fsanitize=address) and run eunit (leak=1 adds LSan)"
@echo " benchmark Run benchmarks via mix bench"
@echo " deps Fetch mix dependencies"
@echo " doc Generate documentation"
@echo ""
@echo "Publishing:"
@echo " bump-version Increment patch version and commit"
@echo " publish Publish to hex.pm (pass replace=1 to replace existing)"
@echo " deprecate Deprecate a hex.pm release (pass vsn=X.Y.Z)"
@echo ""
@echo "Variables:"
@echo " DEBUG=1 Build NIF without optimisations (-O0 -g)"
@echo " ASAN=1 Build with AddressSanitizer (implied by memcheck)"
@echo " leak=1 Enable LeakSanitizer during memcheck (off by default)"
@echo " VERBOSE=1 Show full compiler command lines"
@echo " OPTIMIZE=1 Make all/compile run the PGO 'optimize' build (same as 'make optimize')"
nif: $(PRIV_DIR)/glazer.so
$(PRIV_DIR)/glazer.so: $(wildcard c_src/*.cpp c_src/*.hpp)
@$(MAKE) -C c_src PRIV_DIR=$(PRIV_DIR) OBJ_DIR=$(OBJ_DIR) DEBUG=$(DEBUG) \
$(if $(VERBOSE),VERBOSE=1,) --no-print-directory
ifeq ($(OPTIMIZE),1)
compile: optimize
else
compile: $(PRIV_DIR)/glazer.so
$(REBAR) compile
endif
clean:
$(REBAR) clean
@$(MAKE) --no-print-directory -C c_src PRIV_DIR=$(PRIV_DIR) OBJ_DIR=$(OBJ_DIR) DEBUG=$(DEBUG) clean 2>/dev/null || true
distclean: clean
@rm -rf obj priv/glazer.so _build .perf.txt
test:
$(REBAR) eunit
# Locate the ASan runtime for LD_PRELOAD on Linux.
# macOS memcheck is not supported (DYLD_INSERT_LIBRARIES is dropped by the
# rebar3 shell wrapper before reaching erl), so this block is Linux-only.
# Try Clang's resource-dir path first; fall back to GCC's -print-file-name.
_CXX_RESOURCE_DIR := $(shell $(CXX) -print-resource-dir 2>/dev/null)
_HOST_ARCH := $(shell uname -m)
_ASAN_CLANG := $(_CXX_RESOURCE_DIR)/lib/linux/libclang_rt.asan-$(_HOST_ARCH).so
_ASAN_GCC := $(shell $(CXX) -print-file-name=libasan.so 2>/dev/null)
ASAN_RT := $(strip $(if $(wildcard $(_ASAN_CLANG)),$(_ASAN_CLANG),\
$(if $(filter-out $(notdir $(_ASAN_GCC)),$(_ASAN_GCC)),$(_ASAN_GCC))))
ASAN_PRELOAD = LD_PRELOAD="$(ASAN_RT)"
LSAN_SUPPRESSIONS := $(abspath c_src/lsan_suppressions.txt)
leak ?= 0
ifeq ($(leak),0)
DETECT_LEAKS := 0
else
DETECT_LEAKS := 1
endif
check:
$(REBAR) xref
$(REBAR) dialyzer
memcheck:
@echo "==> Building NIF with AddressSanitizer"
@$(MAKE) -C c_src PRIV_DIR=$(PRIV_DIR) OBJ_DIR=$(OBJ_DIR) ASAN=1 \
$(if $(VERBOSE),VERBOSE=1,) clean all
@$(REBAR) compile
@echo "==> Running eunit under ASan$(if $(filter 1,$(DETECT_LEAKS)), + LeakSanitizer,) ($(ASAN_PRELOAD))"
ERL_FLAGS="+A 1" ASAN_OPTIONS="detect_leaks=$(DETECT_LEAKS)" \
LSAN_OPTIONS="suppressions=$(LSAN_SUPPRESSIONS)" \
$(ASAN_PRELOAD) \
$(REBAR) eunit
@echo "==> Rebuilding normal NIF (removing ASan instrumentation)"
@$(MAKE) -C c_src PRIV_DIR=$(PRIV_DIR) OBJ_DIR=$(OBJ_DIR) \
$(if $(VERBOSE),VERBOSE=1,) clean all
@$(REBAR) compile
doc docs:
$(REBAR) ex_doc
benchmark bench: do-bench
do-bench: deps
@rm -f .perf.txt
@$(MAKE) --no-print-directory optimize
PARALLEL=$(if $(PARALLEL),$(PARALLEL),1) MIX_ENV=bench mix bench | tee .perf.txt;
# ELIXIR_ERL_OPTIONS quiets rustler_precompiled's [debug] "Copying NIF from
# cache and extracting..." noise (torque/yaml_rustler/rusty_csv all use it)
# without touching the project-wide Logger level — scoped to bench targets
# only via this env var, not a global :logger config in mix.exs.
bench-json bench-yaml bench-csv: export ELIXIR_ERL_OPTIONS = -logger level warning
bench-json bench-yaml bench-csv: deps
PARALLEL=$(if $(PARALLEL),$(PARALLEL),1) MIX_ENV=bench mix $@
# Profile-guided optimisation: instrument → run tests as workload → rebuild.
# Usage: make optimize
optimize:
@echo "==> PGO step 1/3: build instrumented binary"
@$(MAKE) -C c_src PRIV_DIR=$(PRIV_DIR) OBJ_DIR=$(OBJ_DIR) PGO=generate clean all
@$(REBAR) compile
@echo "==> PGO step 2/3: collect profile data"
@./bin/pgo-profile.es
@echo "==> PGO step 3/3: rebuild with profile data"
@rm -f $(OBJ_DIR)/glazer_nif.o $(PRIV_DIR)/glazer.so
@$(MAKE) -C c_src PRIV_DIR=$(PRIV_DIR) OBJ_DIR=$(OBJ_DIR) PGO=use all
@$(REBAR) compile
@echo "==> PGO build complete"
deps:
@mix deps.get
publish: docs
$(REBAR) hex publish$(if $(replace), --replace)
deprecate:
@if [ -z $(vsn) ]; then \
echo "Usage: $(MAKE) $@ vsn=X.Y.Z - Deprecate version X.Y.Z"; \
exit 1; \
fi
$(REBAR) hex retire $(APP) $(vsn) deprecated --message Deprecated
cover:
$(REBAR) cover --verbose
bump-version:
@FILE=$$(ls -1 src/*.app.src | head -n1); \
CURRENT=$$(grep -m1 '{vsn,' $$FILE | sed -E 's/.*"([0-9]+\.[0-9]+\.[0-9]+)".*/\1/'); \
MAJOR=$$(echo $$CURRENT | cut -d. -f1); \
MINOR=$$(echo $$CURRENT | cut -d. -f2); \
PATCH=$$(echo $$CURRENT | cut -d. -f3); \
NEW=$$(echo "$${MAJOR}.$${MINOR}.$$((PATCH + 1))" | tr -d '\n'); \
echo "Bumping version from $${CURRENT} to $${NEW}"; \
sed -i "s/{vsn, \"$${CURRENT}\"}/{vsn, \"$${NEW}\"}/" $$FILE; \
echo "Changed: {vsn, \"$${CURRENT}\"} -> {vsn, \"$${NEW}\"}"; \
sed -i 's/\({:\?glazer,[[:space:]]*"~>\)[^"]*/\1 '"$${MAJOR}.$${MINOR}"'/' README.md; \
echo ""; \
read -p "Commit this change? [Y/n] " -n 1 -r || true; \
echo ""; \
if [[ $$REPLY =~ ^[Yy]$$ ]] || [[ -z $$REPLY ]]; then \
git commit -am "Bump version to $${NEW}"; \
fi
.PHONY: all help doc compile clean distclean test cover check dialyzer memcheck nif \
optimize benchmark bench publish deprecate bump-version deps \
bench-yaml bench-json bench-csv do-bench