summaryrefslogtreecommitdiffstats
path: root/Makefile
diff options
context:
space:
mode:
Diffstat (limited to 'Makefile')
-rw-r--r--Makefile224
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)