# Compiler Flags CC := gcc CFLAGS := -g -Wall -Wextra -Werror -pedantic -fno-omit-frame-pointer DEPFLAGS = -MMD -MP # Directory Variables LIBDIR := lib OBJ := obj INC := include SRC := src TEST := tests # Install Paths (override with: make install PREFIX=/usr/local) PREFIX ?= /usr/local INSTALL_LIB = $(PREFIX)/lib INSTALL_INC = $(PREFIX)/include # Platform Detection and Library Linking ifeq ($(OS), Windows_NT) PLATFORM := win32 PLATFORM_SRCS := $(wildcard $(SRC)/win32_*.c) PLATFORM_OBJS := $(patsubst $(SRC)/%.c, $(OBJ)/%.o, $(PLATFORM_SRCS)) PLATFORM_FLAGS := -D WIN32 PLATFORM_LIBS := -mwindows else UNAME_S := $(shell uname -s) ifeq ($(UNAME_S), Linux) PLATFORM := linux PLATFORM_SRCS := $(wildcard $(SRC)/linux_*.c) PLATFORM_OBJS := $(patsubst $(SRC)/%.c, $(OBJ)/%.o, $(PLATFORM_SRCS)) PLATFORM_FLAGS := -D LINUX PLATFORM_LIBS := -lX11 else ifeq ($(UNAME_S), Darwin) PLATFORM := mac PLATFORM_SRCS := $(wildcard $(SRC)/mac_*.c) PLATFORM_OBJS := $(patsubst $(SRC)/%.c, $(OBJ)/%.o, $(PLATFORM_SRCS)) PLATFORM_FLAGS := -D MACOS PLATFORM_LIBS := else $(error Unsupported platform: $(UNAME_S)) endif endif # Add platform flags CFLAGS += $(PLATFORM_FLAGS) # Filepath Pattern Matching LIB := $(LIBDIR)/lib.a # Filter out ALL platform-prefixed files, then add back the correct platform's SRCS := $(filter-out \ $(wildcard $(SRC)/win32_*.c) \ $(wildcard $(SRC)/linux_*.c) \ $(wildcard $(SRC)/macos_*.c), \ $(wildcard $(SRC)/*.c)) \ $(PLATFORM_SRCS) OBJS := $(patsubst $(SRC)/%.c, $(OBJ)/%.o, $(SRCS)) TESTS := $(wildcard $(TEST)/*.c) TESTBINS := $(patsubst $(TEST)/%.c, $(TEST)/bin/%, $(TESTS)) # Commands must be labeled PHONY .PHONY: all release clean test install uninstall # Compiler Release Flags (no sanitizers — those belong in CI/debug builds) release: CFLAGS := -Wall -Wextra -Werror -pedantic -fno-omit-frame-pointer -O2 -DNDEBUG $(PLATFORM_FLAGS) release: clean $(LIB) @echo "\nRelease build complete for platform: $(PLATFORM)\n" # Target for compilation all: $(LIB) @echo "\nBuilt for platform: $(PLATFORM)\n" # Target / Dependencies $(LIB): $(OBJS) | $(LIBDIR) ar -cvrs $@ $^ # Compile with automatic dependency tracking $(OBJ)/%.o: $(SRC)/%.c $(SRC)/%.h | $(OBJ) $(CC) $(CFLAGS) $(DEPFLAGS) -c $< -o $@ $(OBJ)/%.o: $(SRC)/%.c | $(OBJ) $(CC) $(CFLAGS) $(DEPFLAGS) -c $< -o $@ $(TEST)/bin/%: $(TEST)/%.c $(LIB) | $(TEST)/bin $(CC) $(CFLAGS) $< $(LIB) $(PLATFORM_LIBS) -o $@ # Make directories if none $(LIBDIR): mkdir $@ $(OBJ): mkdir $@ $(TEST)/bin: mkdir -p $@ # Run the tests in the bin folder and track results # Uses perl for nanosecond timing — portable across Linux and macOS test: $(LIB) $(TESTBINS) @SUCCESS=0; FAILURE=0; \ RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'; \ for t in $(TESTBINS); do \ NAME=$$(basename $$t); \ START=$$(perl -MTime::HiRes=time -e 'printf "%d\n", time()*1000'); \ if $$t; then \ RET=0; \ else \ RET=$$?; \ fi; \ END=$$(perl -MTime::HiRes=time -e 'printf "%d\n", time()*1000'); \ ELAPSED_MS=$$((END - START)); \ if [ $$RET -eq 0 ]; then \ printf "%-20s %bPASS%b (%b%4d ms%b)\n" "$$NAME" "$$GREEN" "$$NC" "$$YELLOW" "$$ELAPSED_MS" "$$NC"; \ SUCCESS=$$((SUCCESS + 1)); \ else \ printf "%-20s %bFAIL%b (%b%4d ms%b)\n" "$$NAME" "$$RED" "$$NC" "$$YELLOW" "$$ELAPSED_MS" "$$NC"; \ FAILURE=$$((FAILURE + 1)); \ fi; \ done; \ printf "\nTests completed\n"; \ printf "SUCCESS: %b%d%b\n" "$$GREEN" "$$SUCCESS" "$$NC"; \ printf "FAILURE: %b%d%b\n" "$$RED" "$$FAILURE" "$$NC"; \ test $$FAILURE -eq 0 # Install library and public headers install: $(LIB) @echo "Installing to $(PREFIX)..." install -d $(INSTALL_LIB) install -d $(INSTALL_INC) install -m 644 $(LIB) $(INSTALL_LIB)/ @if [ -d $(INC) ]; then \ install -m 644 $(INC)/*.h $(INSTALL_INC)/; \ echo "Installed headers from $(INC)/"; \ else \ echo "Warning: no $(INC)/ directory found — skipping header install"; \ fi @echo "Install complete: lib -> $(INSTALL_LIB) headers -> $(INSTALL_INC)" uninstall: @echo "Removing installed files from $(PREFIX)..." $(RM) $(INSTALL_LIB)/lib.a @if [ -d $(INC) ]; then \ for h in $(INC)/*.h; do \ $(RM) $(INSTALL_INC)/$$(basename $$h); \ done; \ fi @echo "Uninstall complete" clean: $(RM) -r $(LIBDIR) $(OBJ) $(TEST)/bin/ # Pull in generated dependency files (silently ignored if absent) -include $(DEPS)