CC ?= clang
CLANG ?= clang
CLANGXX ?= clang++
STD = c17
AR = ar
TARGET = rayforce
RAY_VERSION ?= $(shell git describe --tags --match 'v[0-9]*.[0-9]*.[0-9]*' --abbrev=0 2>/dev/null | sed 's/^v//')
ifeq ($(strip $(RAY_VERSION)),)
RAY_VERSION := 0.0.0
endif
VERSION = $(RAY_VERSION)
VERSION_MAJOR := $(word 1,$(subst ., ,$(RAY_VERSION)))
VERSION_MINOR := $(word 2,$(subst ., ,$(RAY_VERSION)))
VERSION_PATCH := $(word 3,$(subst ., ,$(RAY_VERSION)))
GIT_HASH := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
GIT_HASH_STAMP := build/.git-hash
$(shell mkdir -p build && { [ "$$(cat $(GIT_HASH_STAMP) 2>/dev/null)" = "$(GIT_HASH)" ] || echo "$(GIT_HASH)" > $(GIT_HASH_STAMP); })
BUILD_DATE := $(shell date -u +%Y-%m-%d)
WARNS = -Wall -Wextra -Werror -Wstrict-prototypes -Wno-unused-parameter
DEFS = -DRAYFORCE_GIT_COMMIT=\"$(GIT_HASH)\" -DRAYFORCE_BUILD_DATE=\"$(BUILD_DATE)\" \
-DRAY_VERSION_MAJOR=$(VERSION_MAJOR) -DRAY_VERSION_MINOR=$(VERSION_MINOR) \
-DRAY_VERSION_PATCH=$(VERSION_PATCH) -DRAYFORCE_VERSION=\"$(RAY_VERSION)\"
INCLUDES = -Iinclude -Isrc
DEPFLAGS = -MMD -MP
UNAME_S := $(shell uname -s)
RAY_MARCH ?= native
DEBUG_CFLAGS = -fPIC $(WARNS) -std=$(STD) -g -O0 -march=$(RAY_MARCH) -DDEBUG \
-fsanitize=address,undefined -fno-sanitize-recover=undefined \
-fno-omit-frame-pointer
RELEASE_CFLAGS = -fPIC $(WARNS) -std=$(STD) -O3 -march=$(RAY_MARCH) \
-funroll-loops -fomit-frame-pointer -fno-math-errno -falign-functions=64 \
-fassociative-math -ffp-contract=fast -fno-signed-zeros -fno-trapping-math
COVERAGE_CFLAGS = -fPIC $(WARNS) -std=$(STD) -g -O0 -march=$(RAY_MARCH) -DDEBUG \
-fno-omit-frame-pointer -fprofile-instr-generate -fcoverage-mapping
COVERAGE_LDFLAGS = -fprofile-instr-generate -fcoverage-mapping
RAY_WINDOWS := $(if $(filter Windows_NT,$(OS))$(findstring _NT-,$(UNAME_S)),1,)
ifeq ($(RAY_WINDOWS),1)
DEFS += -D_FILE_OFFSET_BITS=64
LIBS = -lws2_32 -lmswsock -lkernel32 -ladvapi32 \
-Wl,-Bstatic -lpthread -Wl,-Bdynamic \
-Wl,--stack,8388608
RELEASE_LDFLAGS = -Wl,--gc-sections
else ifeq ($(UNAME_S),Linux)
LIBS = -lm -lpthread
RELEASE_LDFLAGS = -Wl,--gc-sections -Wl,--as-needed
else
LIBS = -lm
RELEASE_LDFLAGS = -Wl,-dead_strip
endif
DEBUG_LDFLAGS = -fsanitize=address,undefined
FUZZ_CFLAGS = -fPIC $(WARNS) -std=$(STD) -g -O1 -march=$(RAY_MARCH) -DDEBUG -DRAY_FUZZING \
-fsanitize=fuzzer-no-link,address,undefined -fno-omit-frame-pointer
TSAN_CFLAGS = -fPIC $(WARNS) -std=$(STD) -g -O1 -march=$(RAY_MARCH) -DDEBUG \
-fsanitize=thread -fno-omit-frame-pointer
TSAN_LDFLAGS = -fsanitize=thread
HARDENED_CFLAGS = -fPIC $(WARNS) -std=$(STD) -O3 -march=$(RAY_MARCH) -g \
-fno-omit-frame-pointer -DRAY_HARDENED \
-funroll-loops -fno-math-errno -fassociative-math -ffp-contract=fast \
-fno-signed-zeros -fno-trapping-math -falign-functions=64
NDEBUG_CFLAGS = $(DEBUG_CFLAGS) -DNDEBUG
CFLAGS = $(DEBUG_CFLAGS)
LDFLAGS = $(DEBUG_LDFLAGS)
LIB_SRC = $(wildcard src/*/*.c)
LIB_SRC := $(filter-out src/app/main.c, $(LIB_SRC))
LIB_OBJ = $(LIB_SRC:.c=.o)
MAIN_SRC = src/app/main.c
MAIN_OBJ = $(MAIN_SRC:.c=.o)
TEST_SRC = $(wildcard test/*.c)
TEST_OBJ = $(TEST_SRC:.c=.o)
REL_LIB_OBJ = $(LIB_SRC:.c=.rel.o)
REL_MAIN_OBJ = $(MAIN_SRC:.c=.rel.o)
FUZZ_LIB_OBJ = $(LIB_SRC:.c=.fuzz.o)
TSAN_LIB_OBJ = $(LIB_SRC:.c=.tsan.o)
TSAN_MAIN_OBJ = $(MAIN_SRC:.c=.tsan.o)
TSAN_TEST_OBJ = $(TEST_SRC:.c=.tsan.o)
HARD_LIB_OBJ = $(LIB_SRC:.c=.hard.o)
HARD_MAIN_OBJ = $(MAIN_SRC:.c=.hard.o)
NDEBUG_LIB_OBJ = $(LIB_SRC:.c=.ndebug.o)
NDEBUG_MAIN_OBJ = $(MAIN_SRC:.c=.ndebug.o)
NDEBUG_TEST_OBJ = $(TEST_SRC:.c=.ndebug.o)
DEPS = $(LIB_OBJ:.o=.d) $(MAIN_OBJ:.o=.d) $(TEST_OBJ:.o=.d) \
$(REL_LIB_OBJ:.o=.d) $(REL_MAIN_OBJ:.o=.d) \
$(FUZZ_LIB_OBJ:.o=.d) \
$(TSAN_LIB_OBJ:.o=.d) $(TSAN_MAIN_OBJ:.o=.d) $(TSAN_TEST_OBJ:.o=.d) \
$(HARD_LIB_OBJ:.o=.d) $(HARD_MAIN_OBJ:.o=.d) \
$(NDEBUG_LIB_OBJ:.o=.d) $(NDEBUG_MAIN_OBJ:.o=.d) $(NDEBUG_TEST_OBJ:.o=.d)
.DEFAULT_GOAL := default
default: debug
%.o: %.c
$(CC) -c $(CFLAGS) $(DEPFLAGS) $(DEFS) $(INCLUDES) -o $@ $<
%.rel.o: %.c
$(CC) -c $(RELEASE_CFLAGS) $(DEPFLAGS) $(DEFS) $(INCLUDES) -o $@ $<
%.fuzz.o: %.c
$(CLANG) -c $(FUZZ_CFLAGS) $(DEPFLAGS) $(DEFS) $(INCLUDES) -o $@ $<
%.tsan.o: %.c
$(CLANG) -c $(TSAN_CFLAGS) $(DEPFLAGS) $(DEFS) $(INCLUDES) -o $@ $<
%.hard.o: %.c
$(CC) -c $(HARDENED_CFLAGS) $(DEPFLAGS) $(DEFS) $(INCLUDES) -o $@ $<
%.ndebug.o: %.c
$(CC) -c $(NDEBUG_CFLAGS) $(DEPFLAGS) $(DEFS) $(INCLUDES) -o $@ $<
GIT_HASH_SRCS := src/core/types src/app/repl src/core/crash test/test_types
GIT_HASH_OBJS := $(foreach v,.o .rel.o .fuzz.o .tsan.o .hard.o .ndebug.o,$(addsuffix $(v),$(GIT_HASH_SRCS)))
$(GIT_HASH_OBJS): $(GIT_HASH_STAMP)
$(TARGET): $(LIB_OBJ) $(MAIN_OBJ)
$(CC) $(CFLAGS) -o $(TARGET) $(LIB_OBJ) $(MAIN_OBJ) $(LIBS) $(LDFLAGS)
debug: CFLAGS = $(DEBUG_CFLAGS)
debug: LDFLAGS = $(DEBUG_LDFLAGS)
debug: $(LIB_OBJ) $(MAIN_OBJ)
$(CC) $(CFLAGS) -o $(TARGET) $(LIB_OBJ) $(MAIN_OBJ) $(LIBS) $(LDFLAGS)
release: $(REL_LIB_OBJ) $(REL_MAIN_OBJ)
$(CC) $(RELEASE_CFLAGS) -o $(TARGET) $(REL_LIB_OBJ) $(REL_MAIN_OBJ) $(LIBS) $(RELEASE_LDFLAGS)
lib: $(REL_LIB_OBJ)
$(AR) rc lib$(TARGET).a $(REL_LIB_OBJ)
dist: release
@mkdir -p dist
@os=$$(uname -s | tr 'A-Z' 'a-z'); arch=$$(uname -m); \
name=$(TARGET)-$(VERSION)-$$os-$$arch; stage=dist/$$name; \
mkdir -p $$stage; \
cp $(TARGET) LICENSE README.md include/rayforce.h $$stage/; \
tar -czf dist/$$name.tar.gz -C dist $$name; \
rm -rf $$stage; \
( cd dist && { command -v sha256sum >/dev/null 2>&1 && sha256sum $$name.tar.gz || shasum -a 256 $$name.tar.gz; } > $$name.tar.gz.sha256 ); \
echo "built dist/$$name.tar.gz"
TEST_CORES ?= 2
TEST_FILTER ?=
TEST_ARGS = $(if $(TEST_FILTER),-f $(TEST_FILTER),)
test: CFLAGS = $(DEBUG_CFLAGS)
test: LDFLAGS = $(DEBUG_LDFLAGS)
test: $(LIB_OBJ) $(MAIN_OBJ) $(TEST_OBJ)
$(CC) $(CFLAGS) -o $(TARGET) $(LIB_OBJ) $(MAIN_OBJ) $(LIBS) $(LDFLAGS)
$(CC) $(CFLAGS) -o $(TARGET).test $(LIB_OBJ) $(TEST_OBJ) $(LIBS) $(LDFLAGS) -Itest
RAYFORCE_CORES=$(TEST_CORES) ./$(TARGET).test $(TEST_ARGS)
test-ndebug: $(NDEBUG_LIB_OBJ) $(NDEBUG_MAIN_OBJ) $(NDEBUG_TEST_OBJ)
$(CC) $(NDEBUG_CFLAGS) -o $(TARGET) $(NDEBUG_LIB_OBJ) $(NDEBUG_MAIN_OBJ) $(LIBS) $(DEBUG_LDFLAGS)
$(CC) $(NDEBUG_CFLAGS) -o $(TARGET).test $(NDEBUG_LIB_OBJ) $(NDEBUG_TEST_OBJ) $(LIBS) $(DEBUG_LDFLAGS) -Itest
RAYFORCE_CORES=$(TEST_CORES) ./$(TARGET).test $(TEST_ARGS)
TSAN_CORES ?= 4
TSAN_FILTER ?= stress
TSAN_ENV = TSAN_OPTIONS="suppressions=$(PWD)/.tsan-suppressions history_size=7 second_deadlock_stack=1 halt_on_error=0"
tsan: $(TSAN_LIB_OBJ) $(TSAN_MAIN_OBJ)
$(CLANG) $(TSAN_CFLAGS) -o $(TARGET).tsan $(TSAN_LIB_OBJ) $(TSAN_MAIN_OBJ) $(LIBS) $(TSAN_LDFLAGS)
tsan-test: $(TSAN_LIB_OBJ) $(TSAN_TEST_OBJ)
$(CLANG) $(TSAN_CFLAGS) -o $(TARGET).test.tsan $(TSAN_LIB_OBJ) $(TSAN_TEST_OBJ) $(LIBS) $(TSAN_LDFLAGS) -Itest
RAYFORCE_CORES=$(TSAN_CORES) $(TSAN_ENV) ./$(TARGET).test.tsan $(if $(TSAN_FILTER),-f $(TSAN_FILTER),)
hardened: $(HARD_LIB_OBJ) $(HARD_MAIN_OBJ)
$(CC) $(HARDENED_CFLAGS) -o $(TARGET) $(HARD_LIB_OBJ) $(HARD_MAIN_OBJ) $(LIBS) $(RELEASE_LDFLAGS) -rdynamic
coverage:
@command -v clang >/dev/null || { echo "coverage: clang not found"; exit 1; }
@command -v llvm-profdata >/dev/null || { echo "coverage: llvm-profdata not found"; exit 1; }
@command -v llvm-cov >/dev/null || { echo "coverage: llvm-cov not found"; exit 1; }
$(MAKE) clean
rm -f cov-*.profraw default.profraw coverage.profdata
rm -rf coverage_html
LLVM_PROFILE_FILE='cov-%p.profraw' $(MAKE) test \
CC=clang \
DEBUG_CFLAGS='$(COVERAGE_CFLAGS)' \
DEBUG_LDFLAGS='$(COVERAGE_LDFLAGS)'
llvm-profdata merge -sparse cov-*.profraw -o coverage.profdata
llvm-cov show ./$(TARGET).test \
-instr-profile=coverage.profdata \
-format=html -output-dir=coverage_html \
-show-line-counts-or-regions \
-ignore-filename-regex='test/.*|/usr/.*|.*_alloc_stub\.c|include/rayforce\.h'
@echo
@echo "=== coverage summary ==="
@llvm-cov report ./$(TARGET).test \
-instr-profile=coverage.profdata \
-ignore-filename-regex='test/.*|/usr/.*|.*_alloc_stub\.c|include/rayforce\.h' 2>/dev/null | tail -3
@echo
@echo "→ coverage_html/index.html"
compdb:
@rm -rf build_compdb && mkdir -p build_compdb
@i=0; for f in $(LIB_SRC) $(MAIN_SRC); do \
i=$$((i+1)); \
$(CLANG) -c -o /dev/null -std=$(STD) -DDEBUG $(DEFS) $(INCLUDES) \
-MJ build_compdb/$$i.json $$f || exit 1; \
done
@{ echo '['; cat build_compdb/*.json | sed '$$s/,$$//'; echo ']'; } > compile_commands.json
@rm -rf build_compdb
@echo "compile_commands.json: $$(grep -c '"file"' compile_commands.json) entries"
FUZZ_RUNTIME ?= 60
FUZZ_OPTS = -rss_limit_mb=4096 -timeout=10 -max_len=65536 -print_final_stats=1
FUZZ_TARGETS = parse numparse de eval csv journal parquet
FUZZ_LDEXTRA ?=
DICT_parse = rayfall
DICT_eval = rayfall
DICT_de = frame
DICT_journal = frame
DICT_csv = csv
build_fuzz/fuzz_%: fuzz/fuzz_%.c $(FUZZ_LIB_OBJ)
@mkdir -p build_fuzz
$(CLANG) -c $(FUZZ_CFLAGS) -fsanitize=fuzzer $(DEFS) $(INCLUDES) -Ifuzz \
-o build_fuzz/$*.o $<
$(CLANGXX) -fsanitize=fuzzer,address,undefined $(FUZZ_LDEXTRA) \
-o $@ build_fuzz/$*.o $(FUZZ_LIB_OBJ) $(LIBS)
.SECONDARY: $(FUZZ_LIB_OBJ)
.PRECIOUS: build_fuzz/fuzz_%
fuzz-%: build_fuzz/fuzz_%
@mkdir -p fuzz/corpus/$*
@dict=$(DICT_$*); \
dictopt=$${dict:+-dict=fuzz/dict/$$dict.dict}; \
seeds=$$( [ -d fuzz/seeds/$* ] && echo fuzz/seeds/$* ); \
set -x; \
ASAN_OPTIONS=detect_leaks=1:abort_on_error=1 \
UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 \
./build_fuzz/fuzz_$* fuzz/corpus/$* $$seeds \
$$dictopt $(FUZZ_OPTS) -max_total_time=$(FUZZ_RUNTIME)
fuzz-smoke:
$(MAKE) fuzz-parse fuzz-numparse fuzz-de FUZZ_RUNTIME=60
.PHONY: fuzz-smoke
TIDY_FLAGS = -std=$(STD) -DDEBUG $(DEFS) $(INCLUDES)
FILES ?= $(LIB_SRC) $(MAIN_SRC)
tidy:
@command -v clang-tidy >/dev/null || { echo "tidy: clang-tidy not found"; exit 1; }
clang-tidy --quiet $(FILES) -- $(TIDY_FLAGS)
CPPCHECK_DEFS = -D__linux__ -D__GNUC__ -D__x86_64__ -D__SIZEOF_INT128__=16 \
-D__ORDER_LITTLE_ENDIAN__=1234 -D__BYTE_ORDER__=1234 -D__GLIBC__ -DDEBUG
cppcheck:
@command -v cppcheck >/dev/null || { echo "cppcheck: not found"; exit 1; }
cppcheck --enable=warning,portability --inline-suppr --error-exitcode=1 \
-j $(shell nproc 2>/dev/null || echo 4) \
--suppress=missingIncludeSystem \
--suppress=assignBoolToPointer \
--suppress=nullPointerRedundantCheck \
--std=c17 -q $(CPPCHECK_DEFS) $(INCLUDES) src/
.PHONY: tidy cppcheck
clean:
-rm -f $(LIB_OBJ) $(MAIN_OBJ) $(TEST_OBJ) $(REL_LIB_OBJ) $(REL_MAIN_OBJ)
-rm -f $(FUZZ_LIB_OBJ) $(TSAN_LIB_OBJ) $(TSAN_MAIN_OBJ) $(TSAN_TEST_OBJ) \
$(HARD_LIB_OBJ) $(HARD_MAIN_OBJ) \
$(NDEBUG_LIB_OBJ) $(NDEBUG_MAIN_OBJ) $(NDEBUG_TEST_OBJ)
-rm -f $(DEPS)
-rm -f $(TARGET) $(TARGET).test lib$(TARGET).a
-rm -f $(TARGET).tsan $(TARGET).test.tsan
-rm -rf build build_release build_fuzz build_compdb dist
-rm -f rf_test_*.csv
-rm -f cov-*.profraw default.profraw coverage.profdata
-rm -rf coverage_html
.PHONY: default debug release lib dist test test-ndebug coverage compdb tsan tsan-test hardened fuzz-smoke tidy cppcheck clean
-include $(DEPS)