diff options
Diffstat (limited to 'Makefile')
| -rw-r--r-- | Makefile | 224 |
1 files changed, 224 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..572e23b --- /dev/null +++ b/Makefile @@ -0,0 +1,224 @@ +CC ?= gcc +PREFIX ?= /usr/local +VERSION := $(or $(shell tr -d '[:space:]' < VERSION 2>/dev/null), 0.0.0) +SOVERSION := $(word 1,$(subst ., ,$(VERSION))) +PYTHON3 := $(shell command -v python3 2>/dev/null) +ifeq ($(PYTHON3),) + $(error python3 is required but not found) +endif + +# Base flags +CFLAGS_BASE = -std=c11 -Wall -Wextra -Wshadow -Wdouble-promotion -Wnull-dereference -Wimplicit-fallthrough -Wmisleading-indentation -pedantic -O2 -I. -Ilib -MMD -MP + +# Platform-specific +UNAME_S := $(shell uname -s) +ifeq ($(UNAME_S),Linux) + CFLAGS_BASE += -D_DEFAULT_SOURCE +else ifeq ($(UNAME_S),Darwin) + CFLAGS_BASE += -D_DARWIN_C_SOURCE +else + # BSD and others + CFLAGS_BASE += -D_BSD_SOURCE +endif + +# Security hardening (optimized builds only) +CFLAGS_HARDENING = -D_FORTIFY_SOURCE=2 -fstack-protector-strong -Wformat-security -fno-common + +CFLAGS := $(CFLAGS_BASE) $(CFLAGS_HARDENING) +LDLIBS = -lm + +SRCDIR = lib +BUILDDIR = build +TESTDIR = test +SCRIPTDIR = scripts +TEST_BUILDDIR = $(BUILDDIR)/test + +SRCS = $(filter-out $(SRCDIR)/ts.c $(SRCDIR)/ts_flat_generated.c $(SRCDIR)/ts_registry_generated.c $(SRCDIR)/test.c $(wildcard $(SRCDIR)/ts_*.c),$(wildcard $(SRCDIR)/*.c)) +OBJS = $(patsubst $(SRCDIR)/%.c,$(BUILDDIR)/%.o,$(SRCS)) +DEPS = $(OBJS:.o=.d) $(BUILDDIR)/ts.d +TS_OBJ = $(BUILDDIR)/ts.o + +SHARED_OBJS = $(patsubst $(SRCDIR)/%.c,$(BUILDDIR)/%.so.o,$(SRCS)) +SHARED_TS_OBJ = $(BUILDDIR)/ts.so.o +FLAT_OBJ = $(BUILDDIR)/ts_flat_generated.so.o +REG_OBJ = $(BUILDDIR)/ts_registry_generated.so.o + +TEST_SRCS = $(TESTDIR)/main.c $(filter-out $(TESTDIR)/main.c $(TESTDIR)/main_generated.c $(TESTDIR)/gen_test_data.c $(TESTDIR)/bench.c $(TESTDIR)/fuzz_table_generated.c,$(wildcard $(TESTDIR)/*.c)) +TEST_OBJS = $(patsubst $(TESTDIR)/%.c,$(BUILDDIR)/test/%.o,$(TEST_SRCS)) + +MAIN = $(BUILDDIR)/ts +TEST_BIN = $(BUILDDIR)/run_tests +BENCH_BIN = $(BUILDDIR)/bench +LIB = $(BUILDDIR)/libts.a +SHLIB = $(BUILDDIR)/libts.so + +.PHONY: all test test-one test-python check valgrind clean sanitize sanitize-test lib shared install uninstall distclean dist bench gen-bindings + +all: $(MAIN) + +lib: $(LIB) + +shared: $(SHLIB) gen-bindings + +$(LIB): $(OBJS) $(TS_OBJ) | $(BUILDDIR) + ar rcs $@ $^ + +ifeq ($(UNAME_S),Linux) + SHLIB_SONAME = -Wl,-soname,libts.so.$(SOVERSION) +else ifeq ($(UNAME_S),Darwin) + SHLIB_SONAME = -Wl,-install_name,@rpath/libts.so.$(SOVERSION) +else + SHLIB_SONAME = -Wl,-soname,libts.so.$(SOVERSION) +endif + +$(SHLIB): $(SHARED_OBJS) $(SHARED_TS_OBJ) $(FLAT_OBJ) $(REG_OBJ) | $(BUILDDIR) + $(CC) -shared $(SHLIB_SONAME) -Wl,-rpath,'$$ORIGIN' -o $@ $^ $(LDLIBS) + +$(BUILDDIR): + @mkdir -p $@ + +$(BUILDDIR)/%.o: $(SRCDIR)/%.c ts.h ts_generated.h | $(BUILDDIR) + $(CC) $(CFLAGS) -c $< -o $@ + +$(BUILDDIR)/%.so.o: $(SRCDIR)/%.c ts.h ts_generated.h | $(BUILDDIR) + $(CC) $(CFLAGS) -fPIC -c $< -o $@ + +$(BUILDDIR)/ts.o: ts.c | $(BUILDDIR) + $(CC) $(CFLAGS) -c ts.c -o $@ + +$(BUILDDIR)/ts.so.o: ts.c | $(BUILDDIR) + $(CC) $(CFLAGS) -fPIC -c ts.c -o $@ + +ts_generated.h: $(SCRIPTDIR)/gen_all.py $(filter-out $(SRCDIR)/ts_flat_generated.c $(SRCDIR)/ts_registry_generated.c,$(wildcard $(SRCDIR)/*.c)) + python3 $< || (echo "ERROR: gen_all.py failed" && exit 1) + +test/main_generated.c: ts_generated.h + @touch $@ + +$(SRCDIR)/ts_flat_generated.c: $(SCRIPTDIR)/gen_flat.py $(filter-out $(SRCDIR)/ts_flat_generated.c $(SRCDIR)/ts_registry_generated.c,$(wildcard $(SRCDIR)/*.c)) + python3 $< || (echo "ERROR: gen_flat.py failed" && exit 1) + +$(FLAT_OBJ): $(SRCDIR)/ts_flat_generated.c ts.h | $(BUILDDIR) + $(CC) $(CFLAGS) -fPIC -c $< -o $@ + +$(SRCDIR)/ts_registry_generated.c: $(SCRIPTDIR)/gen_registry.py $(filter-out $(SRCDIR)/ts_registry_generated.c $(SRCDIR)/ts_flat_generated.c,$(wildcard $(SRCDIR)/*.c)) + python3 $< || (echo "ERROR: gen_registry.py failed" && exit 1) + +$(REG_OBJ): $(SRCDIR)/ts_registry_generated.c ts.h | $(BUILDDIR) + $(CC) $(CFLAGS) -fPIC -c $< -o $@ + +$(TESTDIR)/fuzz_table_generated.c: $(SCRIPTDIR)/gen_fuzz.py $(filter-out $(SRCDIR)/ts_flat_generated.c $(SRCDIR)/ts_registry_generated.c,$(wildcard $(SRCDIR)/*.c)) + python3 $< || (echo "ERROR: gen_fuzz.py failed" && exit 1) + +$(BUILDDIR)/test/fuzz.o: $(TESTDIR)/fuzz.c $(TESTDIR)/fuzz_table_generated.c ts.h | $(TEST_BUILDDIR) + $(CC) $(CFLAGS) -DTS_IMPLEMENTATION -c $< -o $@ + +$(TEST_BUILDDIR): + @mkdir -p $@ + +$(BUILDDIR)/test/%.o: $(TESTDIR)/%.c ts.h | $(TEST_BUILDDIR) + $(CC) $(CFLAGS) -DTS_IMPLEMENTATION -c $< -o $@ + +$(MAIN): $(OBJS) $(TS_OBJ) main.c | $(BUILDDIR) + $(CC) $(CFLAGS) main.c $(OBJS) $(TS_OBJ) -o $@ $(LDLIBS) + +$(TEST_BIN): $(OBJS) $(TEST_OBJS) $(TS_OBJ) | $(BUILDDIR) + $(CC) $(CFLAGS) $(TEST_OBJS) $(OBJS) $(TS_OBJ) -o $@ $(LDLIBS) + +test: $(TEST_BIN) + ./$(TEST_BIN) + +$(BENCH_BIN): $(OBJS) $(TS_OBJ) test/bench.c | $(BUILDDIR) + $(CC) $(CFLAGS) test/bench.c $(OBJS) $(TS_OBJ) -o $@ $(LDLIBS) + +bench: $(BENCH_BIN) + ./$(BENCH_BIN) + +python/ts/__init__.py: $(SCRIPTDIR)/gen_bindings.py $(SCRIPTDIR)/ts_parser.py $(filter-out $(SRCDIR)/ts_flat_generated.c $(SRCDIR)/ts_registry_generated.c,$(wildcard $(SRCDIR)/*.c)) + python3 $(SCRIPTDIR)/gen_bindings.py || (echo "ERROR: gen_bindings.py failed" && exit 1) + +gen-bindings: python/ts/__init__.py + +test-python: shared gen-bindings + LD_LIBRARY_PATH=$(BUILDDIR):$$LD_LIBRARY_PATH python3 $(TESTDIR)/test_python.py + +check: + $(MAKE) clean + $(MAKE) test CFLAGS="$(CFLAGS_BASE) $(CFLAGS_HARDENING) -Werror" + +test-one: $(OBJS) $(TS_OBJ) +ifndef TEST + $(error TEST is not set. Usage: make test-one TEST=<name>) +endif + @echo "$(TEST)" | grep -qE '^[a-z0-9_]+$$' || { echo "ERROR: TEST must match [a-z0-9_]+"; exit 1; } + @mkdir -p $(BUILDDIR) + @printf '#include <stdbool.h>\nextern bool ts_test_$(TEST)(void);\nint main(void){return ts_test_$(TEST)() ? 0 : 1;}\n' > $(BUILDDIR)/stub.c + $(CC) $(CFLAGS) -DTS_IMPLEMENTATION $(BUILDDIR)/stub.c $(TESTDIR)/$(TEST).c $(OBJS) $(TS_OBJ) -o $(BUILDDIR)/test_$(TEST) $(LDLIBS) + ./$(BUILDDIR)/test_$(TEST) + +valgrind: $(TEST_BIN) + valgrind --leak-check=full --error-exitcode=1 ./$(TEST_BIN) + +sanitize: + $(MAKE) clean + $(MAKE) CFLAGS="$(CFLAGS_BASE) -O0 -g -fsanitize=address,undefined -fno-common" + +sanitize-test: + @$(CC) -print-file-name=libasan.so || { echo "ERROR: AddressSanitizer (libasan) not found. Install libasan."; exit 1; } + $(MAKE) clean + $(MAKE) test CFLAGS="$(CFLAGS_BASE) -O0 -g -fsanitize=address,undefined -fno-common" + +install: ts_generated.h lib shared + install -d $(PREFIX)/include/ts + install -m 644 ts.h $(PREFIX)/include/ts/ + install -m 644 ts_generated.h $(PREFIX)/include/ts/ + install -d $(PREFIX)/lib + install -m 644 $(LIB) $(PREFIX)/lib/ + install -m 755 $(SHLIB) $(PREFIX)/lib/libts.so.$(VERSION) + ln -sf libts.so.$(VERSION) $(PREFIX)/lib/libts.so.$(SOVERSION) + ln -sf libts.so.$(SOVERSION) $(PREFIX)/lib/libts.so + @if [ "$(shell id -u)" = "0" ]; then ldconfig 2>/dev/null || true; else echo "WARNING: run 'sudo ldconfig' to update linker cache"; fi + install -d $(PREFIX)/lib/pkgconfig + @test -f ts.pc.in || { echo "ERROR: ts.pc.in not found" && exit 1; } + sed 's|@PREFIX@|$(PREFIX)|;s|@VERSION@|$(VERSION)|' ts.pc.in > $(PREFIX)/lib/pkgconfig/ts.pc + +uninstall: + rm -f $(PREFIX)/include/ts/ts.h + rm -f $(PREFIX)/include/ts/ts_generated.h + rm -rf $(PREFIX)/include/ts + rm -f $(PREFIX)/lib/libts.a + rm -f $(PREFIX)/lib/libts.so $(PREFIX)/lib/libts.so.$(SOVERSION) $(PREFIX)/lib/libts.so.$(VERSION) + rm -f $(PREFIX)/lib/pkgconfig/ts.pc + @if [ "$(shell id -u)" = "0" ]; then ldconfig 2>/dev/null || true; fi + +clean: + rm -rf $(BUILDDIR) + +distclean: clean + rm -f $(SRCDIR)/ts_flat_generated.c + rm -f $(TESTDIR)/fuzz_table_generated.c + rm -f ts_generated.h + rm -f python/ts/__init__.py + +DIST_NAME = ts-$(VERSION) +DIST_FILES = Makefile VERSION ts.pc.in LICENSE README.md ts.h ts_generated.h ts.c main.c .gitignore +DIST_LIB = $(filter-out $(SRCDIR)/ts_flat_generated.c $(SRCDIR)/ts_registry_generated.c $(SRCDIR)/test.c,$(wildcard $(SRCDIR)/*.c)) +DIST_SCRIPTS = $(wildcard $(SCRIPTDIR)/*.py) +DIST_TEST = $(filter-out $(TESTDIR)/fuzz_table_generated.c,$(wildcard $(TESTDIR)/*.c)) $(wildcard $(TESTDIR)/*.py) + +dist: + $(MAKE) clean + $(MAKE) lib + @test -s ts_generated.h || { echo "ERROR: ts_generated.h not found or empty" && exit 1; } + rm -rf $(DIST_NAME) + mkdir -p $(DIST_NAME)/lib $(DIST_NAME)/test $(DIST_NAME)/scripts + cp $(DIST_FILES) $(DIST_NAME)/ + cp $(DIST_LIB) $(DIST_NAME)/lib/ + cp $(DIST_SCRIPTS) $(DIST_NAME)/scripts/ + cp $(DIST_TEST) $(DIST_NAME)/test/ + tar czf $(DIST_NAME).tar.gz $(DIST_NAME) + rm -rf $(DIST_NAME) + @echo "Created $(DIST_NAME).tar.gz" + +-include $(DEPS) +-include $(TEST_OBJS:.o=.d) |