# OS detection and portable commands ifeq ($(OS),Windows_NT) EXE = .exe RM = rm -rf MKDIR = mkdir -p CP = cp -af LN = ln -sf INSTALL = install else EXE = RM = rm -rf MKDIR = mkdir -p CP = cp -af LN = ln -sf INSTALL = install endif # Compiler configuration # Default: gcc # To build with clang: make CC=clang # To build with zig: make CC="zig cc" GIT_DESC := $(shell git describe --tags --always --dirty 2>/dev/null) ifneq ($(GIT_DESC),) GIT_VERSION := $(GIT_DESC) else GIT_VERSION := $(shell cat .version 2>/dev/null || echo "unknown") endif # Re-check for CI overrides ifneq ($(GITHUB_RUN_NUMBER),) GIT_VERSION := $(GIT_VERSION)+ci.$(GITHUB_RUN_NUMBER) endif SHAREDIR ?= /usr/local/share/zenc DEFINES = -DZEN_VERSION=\"$(GIT_VERSION)\" -DZEN_SHARE_DIR=\"$(SHAREDIR)\" -DZC_ALLOW_INTERNAL WERROR ?= 1 # TCC does not support -MMD -MP, may not search /usr/local/include, and maxes out at C11 ifeq ($(findstring tcc,$(CC)),tcc) DEPFLAGS = TCC_EXTRA = -I/usr/local/include -L/usr/local/lib override C_STD := gnu11 else DEPFLAGS = -MMD -MP TCC_EXTRA = endif # Base flags shared by all compilers # Override C_STD to compile with a different standard, e.g.: make C_STD=gnu11 C_STD ?= gnu23 C_STD_SUPPORTED := $(shell echo "int x;" | $(CC) -std=$(C_STD) -x c - -c -o /dev/null 2>/dev/null && echo 1) ifeq ($(C_STD_SUPPORTED),) $(warning Compiler does not support -std=$(C_STD), falling back to gnu11) override C_STD := gnu11 endif # Probe for constexpr support (GCC 14+ supports it, Clang's C23 is partial) CONSTEXPR_SUPPORTED := $(shell echo "constexpr int x = 42; int main(void){return x;}" | $(CC) -std=$(C_STD) -x c - -c -o /dev/null 2>/dev/null && echo 1) ifneq ($(CONSTEXPR_SUPPORTED),) DEFINES += -DHAS_CONSTEXPR endif # Warning for returning the address of a local: GCC (and clang >= 14) call it # -Wreturn-local-addr, older clang only -Wreturn-stack-address. An unknown warning # option is an error under -Werror, so use the name the compiler accepts. RETURN_ADDR_WARN := $(shell echo "int x;" | $(CC) -Werror -Wreturn-local-addr -x c - -c -o /dev/null 2>/dev/null && echo -Wreturn-local-addr || (echo "int x;" | $(CC) -Werror -Wreturn-stack-address -x c - -c -o /dev/null 2>/dev/null && echo -Wreturn-stack-address)) CFLAGS = -std=$(C_STD) -g -Wall -Wextra -Wshadow -Wformat=2 -Wmissing-prototypes -Wstrict-prototypes -Wnull-dereference -Wundef -Wfloat-equal -Wmissing-field-initializers -Wsign-compare -Wtype-limits -Wuninitialized -Wdouble-promotion -Wtautological-compare -Wshift-negative-value -Wdangling-else $(RETURN_ADDR_WARN) -Wconversion -Wno-float-conversion -Wswitch-default -Wvla -Wimplicit-fallthrough -Wredundant-decls -Wcast-align -Wpacked -Wdisabled-optimization -fstack-protector-strong $(DEPFLAGS) $(TCC_EXTRA) $(if $(filter 1,$(WERROR)),-Werror -Wno-error=sign-conversion,) -I./src -I./src/ast -I./src/parser -I./src/codegen -I./plugins -I./src/zen -I./src/utils -I./src/lexer -I./src/analysis -I./src/lsp -I./src/diagnostics $(DEFINES) # Detect Clang by macro (works even when CC=cc on macOS, or CC=clang on Linux) # Uses recursive = so it re-evaluates with target-specific CC overrides (e.g. msan: CC=clang) # Fast path: skip shell probe when $(CC) explicitly contains "clang" CC_IS_CLANG = $(if $(findstring clang,$(CC)),1,$(shell echo 'int x = __clang__;' | $(CC) -x c - -c -o /dev/null 2>/dev/null && echo 1 || echo 0)) # Flags only GCC supports (filtered out when compiling with clang) GCC_WARN_FLAGS = -Wduplicated-cond -Wlogical-op -Wformat-signedness -Wunsafe-loop-optimizations -Wsuggest-attribute=noreturn -Wsuggest-attribute=const # Clang-specific flags (added back when forcing clang for e.g. fuzz targets) CLANG_WARN_FLAGS = -Wno-format-nonliteral -Wassign-enum -Wcomma -Wsometimes-uninitialized -Wloop-analysis -Wsizeof-array-div # Deferred $(if ...) evaluated at CFLAGS expansion time (respects target-specific CC overrides) CFLAGS += $(if $(filter 1,$(CC_IS_CLANG)),$(CLANG_WARN_FLAGS),$(GCC_WARN_FLAGS)) # TCC only supports a subset of -W flags; build a simplified CFLAGS to avoid unknown-flag errors ifeq ($(findstring tcc,$(CC)),tcc) TCC_BASE = -std=$(C_STD) -Wall -Wextra -Werror -g -DZC_ALLOW_INTERNAL -DZC_HAS_JIT TCC_DEFS = $(filter -DZC_% -DZEN_% -DHAS_%,$(DEFINES)) TCC_INCS = -I/usr/local/include -I./src -I./src/ast -I./src/parser -I./src/codegen -I./plugins -I./src/zen -I./src/utils -I./src/lexer -I./src/analysis -I./src/lsp -I./src/diagnostics CFLAGS = $(TCC_BASE) $(TCC_DEFS) $(TCC_INCS) endif # Toggle plugins ifeq ($(NO_PLUGINS), 1) CFLAGS += -DZC_NO_PLUGINS PLUGINS = LIBS = -lm -lpthread else LIBS = -lm -lpthread -ldl endif # The REPL's JIT needs libtcc. Unless ZC_HAS_JIT is given (1 forces it, 0 turns # it off), it is enabled only when a small program using libtcc compiles and links. ifeq ($(origin ZC_HAS_JIT),undefined) ifeq ($(findstring tcc,$(CC)),tcc) ZC_HAS_JIT := 1 else TCC_PROBE_HASH := \# TCC_PROBE_DIRS := $(if $(wildcard /usr/local/lib/libtcc.a),-I/usr/local/include -L/usr/local/lib) ZC_HAS_JIT := $(shell printf '$(TCC_PROBE_HASH)include \nint main(void) { tcc_delete(tcc_new()); return 0; }\n' | $(CC) -x c - -o /dev/null $(TCC_PROBE_DIRS) -ltcc >/dev/null 2>&1 && echo 1 || echo 0) endif endif ZC_RUN ?= ./zc ifeq ($(ZC_HAS_JIT), 1) CFLAGS += -DZC_HAS_JIT endif TARGET = zc$(EXE) # Standalone tools built alongside the compiler (Unix-style separation). zc-lsp and zc-format # need the LSP sources and zc-repl the REPL ones, so ZC_LSP=0 / ZC_REPL=0 leave them out. ALL_TOOLS = zc-lsp zc-repl zc-doc zc-format TOOLS = $(if $(filter 1,$(ZC_LSP)),zc-lsp zc-format) $(if $(filter 1,$(ZC_REPL)),zc-repl) zc-doc ifeq ($(OS),Windows_NT) LIBS = -lws2_32 ifeq ($(ZC_HAS_JIT), 1) LIBS += -ltcc endif else ifeq ($(ZC_HAS_JIT), 1) LIBS += -ltcc # afl-clang-fast/lld don't search /usr/local/lib by default ifneq ($(wildcard /usr/local/lib/libtcc.a),) LIBS += -L/usr/local/lib endif endif endif # Feature selection (default: all enabled) # Override with ZC_LSP=0, ZC_REPL=0, etc. for smaller builds. ZC_LSP ?= 1 ZC_REPL ?= 1 ZC_PLUGINS ?= 1 ZC_BACKENDS ?= 1 DEFINES += -DZC_HAS_PLUGINS=$(ZC_PLUGINS) DEFINES += -DZC_HAS_CPP_BACKEND=$(ZC_BACKENDS) DEFINES += -DZC_HAS_CUDA_BACKEND=$(ZC_BACKENDS) DEFINES += -DZC_HAS_OBJC_BACKEND=$(ZC_BACKENDS) DEFINES += -DZC_HAS_JSON_BACKEND=$(ZC_BACKENDS) DEFINES += -DZC_HAS_LISP_BACKEND=$(ZC_BACKENDS) DEFINES += -DZC_HAS_DOT_BACKEND=$(ZC_BACKENDS) DEFINES += -DZC_HAS_ASTDUMP_BACKEND=$(ZC_BACKENDS) # Source files read from src-sources.txt, filtered by feature selection. ALL_SRCS := $(shell cat src-sources.txt) ZC_FILTER_LSP = $(if $(filter-out 1,$(ZC_LSP)),src/lsp/lsp_main.c src/lsp/lsp_analysis.c src/lsp/lsp_semantic.c src/lsp/lsp_index.c src/lsp/lsp_locals.c src/lsp/lsp_formatter.c src/lsp/lsp_project.c src/lsp/lsp_c_headers.c src/lsp/lsp_config.c src/lsp/json_rpc.c) ZC_FILTER_REPL = $(if $(filter-out 1,$(ZC_REPL)),src/repl/% src/platform/console.c) ZC_FILTER_PLUGINS = $(if $(filter-out 1,$(ZC_PLUGINS)),src/plugins/% src/parser/utils/utils_plugins.c) ZC_FILTER_BACKENDS = $(if $(filter-out 1,$(ZC_BACKENDS)),src/codegen/codegen_backend_cpp.c src/codegen/codegen_backend_cuda.c src/codegen/codegen_backend_objc.c src/codegen/codegen_backend_json.c src/codegen/codegen_backend_lisp.c src/codegen/codegen_backend_dot.c src/codegen/codegen_backend_astdump.c) SRCS = $(filter-out $(ZC_FILTER_LSP) $(ZC_FILTER_REPL) $(ZC_FILTER_PLUGINS) $(ZC_FILTER_BACKENDS),$(ALL_SRCS)) OBJ_DIR = obj OBJS = $(patsubst %.c, $(OBJ_DIR)/%.o, $(SRCS)) DEPS = $(OBJS:.o=.d) # Pull in header dependency files (auto-generated by -MMD -MP) -include $(DEPS) # Installation paths PREFIX ?= /usr/local BINDIR = $(PREFIX)/bin MANDIR = $(PREFIX)/share/man SHAREDIR = $(PREFIX)/share/zenc INCLUDEDIR = $(PREFIX)/include/zenc PLUGINS = plugins/befunge.so plugins/brainfuck.so plugins/forth.so plugins/lisp.so plugins/sql.so # Fil-C configuration FILCC_DIR := $(firstword $(wildcard filc-*/build/bin)) FILCC ?= $(FILCC_DIR)/filcc FILC_LIB ?= $(firstword $(wildcard filc-*/pizfix/lib)) # APE (Actually Portable Executable) configuration COSMOCC = cosmocc OUT_STAGE = out/stage OUT_BIN = out/bin ZC_ENTRY_O = $(OUT_STAGE)/zc_entry.o ZC_COM_BIN = $(OUT_STAGE)/zc.com ZC_COM = $(OUT_BIN)/zc.com ZC_BOOT_SRC = ape/boot/boot.zc ZC_BOOT_COM_BIN = $(OUT_STAGE)/zc-boot.com ZC_BOOT_COM = $(OUT_BIN)/zc-boot.com # Discover plugins for static linking PLUGIN_FILES = $(wildcard plugins/*.zc) PLUGIN_NAMES = $(patsubst plugins/%.zc,%,$(PLUGIN_FILES)) PLUGIN_APE_OBJS = $(patsubst plugins/%.zc,obj-ape/plugins/%.o,$(PLUGIN_FILES)) # Function to sanitize plugin names for C identifiers # Usage: $(call sanitize_name,name) sanitize_name = $(shell echo $(1) | sed 's/[^a-zA-Z0-9]/_/g') # Default target .DEFAULT_GOAL := all all: $(TARGET) $(PLUGINS) $(TOOLS) # APE target ape: $(ZC_COM) $(ZC_BOOT_COM) # Build plugins plugins/%.so: plugins/%.zc $(TARGET) $(ZC_RUN) build $< -shared -o $@ # Library groupings by directory CORE_OBJS = $(filter $(OBJ_DIR)/src/ast/% $(OBJ_DIR)/src/parser/% $(OBJ_DIR)/src/lexer/%, $(OBJS)) ANALYSIS_OBJS = $(filter $(OBJ_DIR)/src/analysis/%, $(OBJS)) CODEGEN_OBJS = $(filter $(OBJ_DIR)/src/codegen/%, $(OBJS)) MISRA_OBJS = $(filter $(OBJ_DIR)/src/platform/misra%, $(OBJS)) PLATFORM_OBJS = $(filter $(OBJ_DIR)/src/platform/os% $(OBJ_DIR)/src/platform/console% $(OBJ_DIR)/src/platform/dylib%, $(OBJS)) UTILS_OBJS = $(filter $(OBJ_DIR)/src/utils/%, $(OBJS)) LSP_OBJS = $(filter $(OBJ_DIR)/src/lsp/%, $(OBJS)) REPL_OBJS = $(filter $(OBJ_DIR)/src/repl/%, $(OBJS)) ZEN_OBJS = $(filter $(OBJ_DIR)/src/zen/%, $(OBJS)) PLUGIN_OBJS = $(filter $(OBJ_DIR)/src/plugins/%, $(OBJS)) DIAG_OBJS = $(filter $(OBJ_DIR)/src/diagnostics/%, $(OBJS)) DRIVER_OBJS = $(filter $(OBJ_DIR)/src/driver/%, $(OBJS)) TOOLS_COMMON = $(OBJ_DIR)/src/tools/tool_common.o # Objects shared by zc and the standalone tools, excluding the tool-specific # lsp/repl/zen sources (those only ship in their respective binaries). TOOL_BASE_OBJS = $(CORE_OBJS) $(ANALYSIS_OBJS) $(CODEGEN_OBJS) $(MISRA_OBJS) \ $(PLATFORM_OBJS) $(UTILS_OBJS) $(DIAG_OBJS) $(PLUGIN_OBJS) ZC_BASE_OBJS = $(filter-out $(LSP_OBJS) $(REPL_OBJS) $(ZEN_OBJS), $(OBJS)) ALL_LIBS = libzc-core.a libzc-analysis.a libzc-codegen.a libzc-misra.a \ libzc-platform.a libzc-utils.a libzc-lsp.a libzc-repl.a \ libzc-zen.a libzc-plugin.a libzc-diag.a libzc-driver.a libzc-core.a: $(CORE_OBJS) ar rcs $@ $^ libzc-analysis.a: $(ANALYSIS_OBJS) ar rcs $@ $^ libzc-codegen.a: $(CODEGEN_OBJS) ar rcs $@ $^ libzc-misra.a: $(MISRA_OBJS) ar rcs $@ $^ libzc-platform.a: $(PLATFORM_OBJS) ar rcs $@ $^ libzc-utils.a: $(UTILS_OBJS) ar rcs $@ $^ libzc-lsp.a: $(LSP_OBJS) ar rcs $@ $^ libzc-repl.a: $(REPL_OBJS) ar rcs $@ $^ libzc-zen.a: $(ZEN_OBJS) ar rcs $@ $^ libzc-plugin.a: $(PLUGIN_OBJS) ar rcs $@ $^ libzc-diag.a: $(DIAG_OBJS) ar rcs $@ $^ libzc-driver.a: $(DRIVER_OBJS) ar rcs $@ $^ # Default: direct .o linking (reliable incremental builds). # zc links everything except the lsp/repl/zen sources, which now live in the # standalone tools (zc-lsp, zc-repl, zc-doc, zc-format). $(TARGET): $(ZC_BASE_OBJS) @$(MKDIR) $(dir $@) $(CC) $(CFLAGS) -o $@ $(ZC_BASE_OBJS) $(LIBS) @echo "=> Build complete: $(TARGET)" # Standalone tools (Unix-style: one focused program each) zc-lsp: $(TOOL_BASE_OBJS) $(LSP_OBJS) $(TOOLS_COMMON) $(OBJ_DIR)/src/tools/main_lsp.o @$(MKDIR) $(dir $@) $(CC) $(CFLAGS) -o $@ $^ $(LIBS) @echo "=> Build complete: $@" zc-repl: $(TOOL_BASE_OBJS) $(REPL_OBJS) $(TOOLS_COMMON) $(OBJ_DIR)/src/tools/main_repl.o @$(MKDIR) $(dir $@) $(CC) $(CFLAGS) -o $@ $^ $(LIBS) @echo "=> Build complete: $@" zc-doc: $(TOOL_BASE_OBJS) $(ZEN_OBJS) $(TOOLS_COMMON) $(OBJ_DIR)/src/tools/main_doc.o @$(MKDIR) $(dir $@) $(CC) $(CFLAGS) -o $@ $^ $(LIBS) @echo "=> Build complete: $@" zc-format: $(TOOL_BASE_OBJS) $(LSP_OBJS) $(TOOLS_COMMON) $(OBJ_DIR)/src/tools/main_format.o @$(MKDIR) $(dir $@) $(CC) $(CFLAGS) -o $@ $^ $(LIBS) @echo "=> Build complete: $@" $(OBJ_DIR)/src/tools/main_lsp.o: src/tools/main_lsp.c $(TOOLS_COMMON) @$(MKDIR) $(@D) $(CC) $(CFLAGS) -c $< -o $@ $(OBJ_DIR)/src/tools/main_repl.o: src/tools/main_repl.c $(TOOLS_COMMON) @$(MKDIR) $(@D) $(CC) $(CFLAGS) -c $< -o $@ $(OBJ_DIR)/src/tools/main_doc.o: src/tools/main_doc.c $(TOOLS_COMMON) @$(MKDIR) $(@D) $(CC) $(CFLAGS) -c $< -o $@ $(OBJ_DIR)/src/tools/main_format.o: src/tools/main_format.c $(TOOLS_COMMON) @$(MKDIR) $(@D) $(CC) $(CFLAGS) -c $< -o $@ # Library-based build (for systems without .o linking or for packaging) libs: $(ALL_LIBS) $(CC) $(CFLAGS) -o $(TARGET) $(OBJ_DIR)/src/main.o $(TOOLS_COMMON) \ libzc-driver.a libzc-repl.a libzc-lsp.a \ libzc-codegen.a libzc-analysis.a \ libzc-core.a libzc-misra.a libzc-zen.a libzc-plugin.a \ libzc-diag.a libzc-utils.a libzc-platform.a \ $(LIBS) @echo "=> Build complete: $(TARGET) (libraries)" # Alias for backward compatibility monolith: $(TARGET) @true # Compile $(OBJ_DIR)/%.o: %.c @$(MKDIR) $(dir $@) $(CC) $(CFLAGS) -c $< -o $@ # Rebuild the version-printing objects (and thus relink every tool) whenever # GIT_VERSION changes. The stamp only rewrites when the version differs, so a # plain `make` does not recompile anything. $(OBJ_DIR)/version.stamp: FORCE @$(MKDIR) $(OBJ_DIR) @if [ -f $@ ] && [ "$$(cat $@)" = "$(GIT_VERSION)" ]; then :; else echo "$(GIT_VERSION)" > $@; fi $(OBJ_DIR)/src/utils/cmd.o $(OBJ_DIR)/src/repl/repl.o: $(OBJ_DIR)/version.stamp # APE targets $(ZC_ENTRY_O): ape/zc_entry.c @$(MKDIR) $(@D) $(COSMOCC) -c $< -o $@ $(ZC_COM_BIN): $(ZC_ENTRY_O) $(SRCS) src/plugins/static_plugins.c $(PLUGIN_APE_OBJS) @$(MKDIR) $(@D) $(MAKE) \ PLUGINS= \ ZC_HAS_JIT=0 \ CC=$(COSMOCC) \ OBJ_DIR=obj-ape \ CFLAGS='$(CFLAGS) -DZC_STATIC_PLUGINS -fno-stack-protector -Wno-redundant-decls -Wno-switch-default -Wno-sign-conversion -Wno-sign-compare -Wno-cast-align -Wno-implicit-fallthrough' \ DEFINES='$(DEFINES) -DZC_STATIC_PLUGINS' \ LIBS="$(abspath $(ZC_ENTRY_O)) $(PLUGIN_APE_OBJS) -Wl,--wrap=main" \ SRCS="$(SRCS)" \ TARGET="$(abspath $@)" \ monolith src/plugins/static_plugins.c: $(PLUGIN_FILES) @echo "=> Generating static plugin registry: $@" @echo '#include "plugin_manager.h"' > $@ @echo '#include ' >> $@ @echo '#ifdef ZC_STATIC_PLUGINS' >> $@ @for p in $(PLUGIN_NAMES); do \ psafe=$$(echo $$p | sed 's/[^a-zA-Z0-9]/_/g'); \ echo "extern ZPlugin *z_plugin_init_$$psafe(void);" >> $@; \ done @echo 'ZPlugin *zptr_get_static_plugin(const char *name) {' >> $@ @for p in $(PLUGIN_NAMES); do \ psafe=$$(echo $$p | sed 's/[^a-zA-Z0-9]/_/g'); \ echo " if (strcmp(name, \"$$p\") == 0 || strcmp(name, \"plugins/$$p\") == 0) return z_plugin_init_$$psafe();" >> $@; \ done @echo ' return NULL;' >> $@ @echo '}' >> $@ @echo '#else' >> $@ @echo 'ZPlugin *zptr_get_static_plugin(const char *name) { (void)name; return NULL; }' >> $@ @echo '#endif' >> $@ obj-ape/plugins/%.c: plugins/%.zc $(TARGET) @$(MKDIR) $(@D) ./$(TARGET) transpile $< -o $@ obj-ape/plugins/%.o: obj-ape/plugins/%.c @$(MKDIR) $(@D) @psafe=$$(echo $* | sed 's/[^a-zA-Z0-9]/_/g'); \ $(COSMOCC) $(CFLAGS) \ -fno-stack-protector \ -DZC_STATIC_PLUGIN -Dz_plugin_init=z_plugin_init_$$psafe -c $< -o $@ $(ZC_COM): $(ZC_COM_BIN) @$(MKDIR) $(@D) @$(CP) $(ZC_COM_BIN) $(wildcard $(ZC_COM_BIN).*) "$(@D)"; \ cp src/misc/zenc.json zenc.json; \ cp src/zen/facts.json facts.json; \ cp src/repl/docs.json docs.json; \ zip -r "$(abspath $@)" std.zc std LICENSE zenc.json facts.json docs.json; \ rm -f zenc.json facts.json docs.json $(ZC_BOOT_COM_BIN): $(ZC_BOOT_SRC) $(ZC_COM) @$(MKDIR) $(@D) ./$(ZC_COM) build --cc $(COSMOCC) -o $@ $< || { \ echo "warning: boot.com build failed (non-fatal)"; \ touch $@; \ } $(ZC_BOOT_COM): $(ZC_BOOT_COM_BIN) ape/boot/.args @$(MKDIR) $(@D) @$(CP) $(ZC_BOOT_COM_BIN) $(wildcard $(ZC_BOOT_COM_BIN).*) "$(@D)"; \ cp src/misc/zenc.json zenc.json; \ cp src/repl/docs.json docs.json; \ (cd ape/boot && zip "$(abspath $@)" .args hello.zc instructions.txt Makefile); \ zip "$(abspath $@)" LICENSE zenc.json docs.json; \ rm -f zenc.json docs.json # Install install: $(TARGET) install-tools $(INSTALL) -d $(BINDIR) $(INSTALL) -m 755 $(TARGET) $(BINDIR)/$(TARGET) # Install man pages $(INSTALL) -d $(MANDIR)/man1 $(MANDIR)/man5 $(MANDIR)/man7 test -f man/zc.1 && $(INSTALL) -m 644 man/zc.1 $(MANDIR)/man1/zc.1 || true test -f man/zc.5 && $(INSTALL) -m 644 man/zc.5 $(MANDIR)/man5/zc.5 || true test -f man/zc.7 && $(INSTALL) -m 644 man/zc.7 $(MANDIR)/man7/zc.7 || true test -f man/zc-stdlib.7 && $(INSTALL) -m 644 man/zc-stdlib.7 $(MANDIR)/man7/zc-stdlib.7 || true # Install standard library $(INSTALL) -d $(SHAREDIR) $(INSTALL) -m 644 std.zc $(SHAREDIR)/std.zc $(CP) std $(SHAREDIR)/ # Install repl/compiler data $(INSTALL) -m 644 src/repl/docs.json $(SHAREDIR)/docs.json $(INSTALL) -m 644 src/misc/zenc.json $(SHAREDIR)/zenc.json # Install plugin headers $(INSTALL) -d $(INCLUDEDIR) $(INSTALL) -m 644 plugins/zprep_plugin.h $(INCLUDEDIR)/zprep_plugin.h # Install public API headers and their dependencies $(INSTALL) -m 644 src/public/*.h $(INCLUDEDIR)/ $(INSTALL) -m 644 src/token.h src/arena.h $(INCLUDEDIR)/ $(INSTALL) -m 644 src/utils/emitter.h src/utils/zvec.h src/utils/zalloc.h $(INCLUDEDIR)/ # Install compiled plugins $(INSTALL) -d $(SHAREDIR)/plugins $(CP) plugins/*.so $(SHAREDIR)/plugins/ @echo "=> Installed to $(BINDIR)/$(TARGET)" @echo "=> Man pages installed to $(MANDIR)" @echo "=> Standard library installed to $(SHAREDIR)/std" # Install the standalone tools install-tools: $(TOOLS) $(INSTALL) -d $(BINDIR) for t in $(TOOLS); do $(INSTALL) -m 755 $$t $(BINDIR)/$$t; done # Backward-compat alias: the LSP was previously installed as `zls` $(if $(filter zc-lsp,$(TOOLS)),$(INSTALL) -m 755 zc-lsp $(BINDIR)/zls) # Uninstall uninstall: $(RM) $(BINDIR)/$(TARGET) $(BINDIR)/zls for t in $(ALL_TOOLS); do $(RM) $(BINDIR)/$$t; done $(RM) $(MANDIR)/man1/zc.1 $(RM) $(MANDIR)/man5/zc.5 $(RM) $(MANDIR)/man7/zc.7 $(RM) $(MANDIR)/man7/zc-stdlib.7 $(RM) $(SHAREDIR) @echo "=> Uninstalled from $(BINDIR)/$(TARGET)" @echo "=> Removed man pages from $(MANDIR)" @echo "=> Removed $(SHAREDIR)" # Install APE install-ape: ape $(INSTALL) -d $(BINDIR) $(INSTALL) -m 755 $(ZC_COM) $(BINDIR)/zc.com $(INSTALL) -m 755 $(ZC_BOOT_COM) $(BINDIR)/zc-boot.com $(LN) $(BINDIR)/zc.com $(BINDIR)/zc # Install standard library (shared) $(INSTALL) -d $(SHAREDIR) $(INSTALL) -m 644 std.zc $(SHAREDIR)/std.zc $(CP) std $(SHAREDIR)/ @echo "=> Installed APE binaries to $(BINDIR)" @echo "=> Alias 'zc' points to zc.com" @echo "=> Standard library installed to $(SHAREDIR)/std" # Uninstall APE uninstall-ape: $(RM) $(BINDIR)/zc $(RM) $(BINDIR)/zc.com $(RM) $(BINDIR)/zc-boot.com $(RM) $(SHAREDIR) @echo "=> Uninstalled APE binaries from $(BINDIR)" @echo "=> Removed $(SHAREDIR)" # Clean clean: $(RM) $(OBJ_DIR) obj-ape obj-fuzz obj-fuzz-cmplog $(TARGET) $(ALL_TOOLS) libzc-*.a $(RM) out.c out.cpp out.m out.cu plugins/*.so a.out* out test_out_* rule_* $(RM) *.gcda *.gcno *.gcov coverage.info $(RM) -r coverage-report/ $(RM) .bench_* bench_* benchmarks_result.json $(RM) *_suite.c *_suite.cpp test_runner @echo "=> Clean complete!" # Code Formatting CLANG_FORMAT ?= clang-format SRC_FILES = $(filter %.c %.h, $(shell find src -type f \( -name '*.c' -o -name '*.h' \))) format: $(CLANG_FORMAT) -i $(SRC_FILES) @echo "=> Formatted $(words $(SRC_FILES)) source files" format-check: $(CLANG_FORMAT) --dry-run --Werror $(SRC_FILES) @echo "=> All source files are properly formatted" # Linting lint: format-check check-codebase: @echo "Running codebase convention checks..." @scripts/check-codebase.sh @echo "=> Running shellcheck on test scripts" -shellcheck tests/scripts/*.sh @echo "=> Lint complete" # Benchmarks bench: $(TARGET) ./tests/scripts/run_benchmarks.sh # Test # Supports running specific tests: # make test only="tests/std/test_hash.zc examples/arena_test.zc" test: $(TARGET) $(PLUGINS) ./tests/scripts/run_tests.sh -- $(filter %.zc,$(only)) ./tests/scripts/run_codegen_tests.sh $(filter %.zc,$(only)) ./tests/scripts/run_example_transpile.sh $(filter %.zc,$(only)) ./tests/scripts/run_example_build.sh $(filter %.zc,$(only)) ./tests/scripts/run_repl_tests.sh ./tests/scripts/run_cli_tests.sh ./tests/scripts/run_diagnostic_output_tests.sh ./tests/scripts/run_check_clean.sh $(MAKE) test-unit $(MAKE) test-misra # Files that the full typechecker (`zc check`) must accept with no errors. test-check: $(TARGET) ./tests/scripts/run_check_clean.sh test-misra: $(TARGET) ./tests/scripts/run_misra_tests.sh test-tcc: $(TARGET) $(PLUGINS) ./tests/scripts/run_tests.sh --cc tcc test-filcc: $(TARGET) $(PLUGINS) @if [ -z "$(FILCC_DIR)" ]; then \ echo "ERROR: Fil-C distribution not found. Expected 'filc-*/build/bin/' directory."; \ exit 1; \ fi FILC_LIBRARY_PATH="$(FILC_LIB)" ./tests/scripts/run_tests.sh --cc "$(FILCC)" test-lsp: $(TARGET) $(PLUGINS) zc-lsp @echo "=> Building LSP Test Runner" $(CC) $(CFLAGS) -DZC_NO_ARENA tests/compiler/lsp/lsp_test_runner.c src/utils/cJSON.c -o tests/compiler/lsp/test_runner @echo "=> Running LSP Tests" ./tests/compiler/lsp/test_runner # Unit tests in C of the compiler's internals (tests/unit/, with utest.h). They link # the compiler objects, without main.o: tests/unit/unit_main.c provides main(). UNIT_TEST_SRCS = $(wildcard tests/unit/*.c tests/unit/*/*.c) # tests/unit/lsp/ also links the LSP pieces that only need the core; skipped without the LSP. UNIT_TEST_LSP_OBJS = $(if $(filter 1,$(ZC_LSP)),$(OBJ_DIR)/src/lsp/lsp_index.o $(OBJ_DIR)/src/lsp/lsp_locals.o) UNIT_TEST_SRCS := $(if $(filter 1,$(ZC_LSP)),$(UNIT_TEST_SRCS),$(filter-out tests/unit/lsp/%,$(UNIT_TEST_SRCS))) test-unit: $(TOOL_BASE_OBJS) $(DRIVER_OBJS) $(UNIT_TEST_LSP_OBJS) @echo "=> Building unit tests" @$(MKDIR) $(OBJ_DIR)/tests/unit $(CC) $(CFLAGS) -isystem tests/vendor -I tests/unit $(UNIT_TEST_SRCS) $(TOOL_BASE_OBJS) $(DRIVER_OBJS) $(UNIT_TEST_LSP_OBJS) -o $(OBJ_DIR)/tests/unit/run_unit $(LIBS) @echo "=> Running unit tests" ./$(OBJ_DIR)/tests/unit/run_unit test-backends: $(TARGET) @echo "=> Testing all output backends" ./tests/scripts/test_backends.sh test-lisp: $(TARGET) @echo "=> Testing Lisp backend" @./tests/scripts/test_lisp.sh; echo "=> Lisp suite done" test-lsp-smoke: $(TARGET) $(PLUGINS) @echo "=> Running LSP Smoke Test" ./tests/scripts/run_lsp_smoke_test.py --zc ./zc # Build with alternative compilers zig: $(MAKE) CC="zig cc" clang: $(MAKE) CC=clang filcc: $(MAKE) CC="$(FILCC)" ZC_HAS_JIT=0 windows: $(MAKE) CC="x86_64-w64-mingw32-gcc" TARGET="zc.exe" LIBS="-static -lm -lpthread" asan: CFLAGS += -fsanitize=address,undefined -O1 -g -fno-omit-frame-pointer asan: LIBS += -fsanitize=address,undefined asan: ZC_RUN = ASAN_OPTIONS=detect_leaks=0 ./zc asan: $(TARGET) $(PLUGINS) test-asan: clean asan ASAN_OPTIONS=detect_leaks=0 ./tests/scripts/run_tests.sh ASAN_OPTIONS=detect_leaks=0 ./tests/scripts/run_codegen_tests.sh ASAN_OPTIONS=detect_leaks=0 ./tests/scripts/run_example_transpile.sh # TSAN (ThreadSanitizer), MSAN (MemorySanitizer), LSAN (LeakSanitizer), # and the GCC Static Analyzer are build-only targets. # They compile the compiler with extra instrumentation to catch bugs, # but do not run the test suite (test-generated code may trigger false positives). # LeakSanitizer — built into ASAN lsan: asan @echo "LeakSanitizer: see asan build above" # ThreadSanitizer tsan: CFLAGS += -fsanitize=thread -O1 -g -fno-omit-frame-pointer tsan: LIBS += -fsanitize=thread tsan: $(TARGET) $(PLUGINS) # MemorySanitizer (Clang only) — builds the compiler only, not plugins. # MSAN requires ALL linked libraries (including glibc) to be MSAN-instrumented, # which is not feasible in a standard CI environment. Plugins cannot be built # because the MSAN-instrumented compiler aborts on use-of-uninitialized-value # at runtime during compilation. msan: CC = clang msan: CFLAGS += -fsanitize=memory -O1 -g -fno-omit-frame-pointer -fsanitize-memory-track-origins msan: LIBS += -fsanitize=memory msan: $(TARGET) # GCC Static Analyzer (slow, ~5x build time) analyzer: CFLAGS += -fanalyzer -Wno-analyzer-infinite-recursion analyzer: $(TARGET) $(PLUGINS) # Code coverage (GCC only, incompatible with sanitizers) GCOV ?= gcov coverage: CFLAGS += --coverage -O0 coverage: LIBS += --coverage coverage: $(TARGET) $(PLUGINS) @echo "=> Running tests with coverage instrumentation..." ./tests/scripts/run_tests.sh --no-source -j 2 @echo "=> Coverage data generated" coverage-report: coverage lcov --capture --directory obj --output-file coverage.info \ --gcov-tool $(GCOV) --include '*/src/*' --ignore-errors mismatch genhtml coverage.info --output-directory coverage-report --ignore-errors empty @echo "=> Coverage report: coverage-report/index.html" test-plugins: $(TARGET) $(PLUGINS) ./zc run tests/language/features/test_plugins_suite.zc # Convenience targets for modular builds # zc no longer links lsp/repl/zen at all; these variants additionally strip # plugins/backends and skip building the standalone tools. core: $(MAKE) ZC_LSP=0 ZC_REPL=0 ZC_PLUGINS=0 ZC_BACKENDS=0 TOOLS= lite: $(MAKE) ZC_LSP=0 ZC_REPL=0 TOOLS= minimal: $(MAKE) ZC_LSP=0 ZC_REPL=0 ZC_PLUGINS=0 ZC_BACKENDS=0 TOOLS= # Fuzzing FUZZ_TARGET = zc-fuzz FUZZ_CMPLOG_TARGET = zc-fuzz-cmplog FUZZ_CC ?= afl-clang-fast FUZZ_DIR = fuzz FUZZ_OUT = $(FUZZ_DIR)/out FUZZ_CORPUS = $(FUZZ_DIR)/corpus FUZZ_DICT = $(FUZZ_DIR)/zen_c.dict # High-performance flags FUZZ_CFLAGS = -O3 -march=native -D__AFL_HAVE_MANUAL_CONTROL fuzz-build: @$(MKDIR) $(OBJ_DIR)/fuzz $(MAKE) CC=$(FUZZ_CC) CFLAGS='$(CFLAGS) $(FUZZ_CFLAGS)' OBJ_DIR=obj-fuzz TARGET=$(FUZZ_TARGET) SRCS="$(filter-out src/main.c,$(SRCS)) fuzz/harness.c" @echo "=> Fuzzing target built (Persistent Mode): $(FUZZ_TARGET)" fuzz-cmplog-build: @$(MKDIR) $(OBJ_DIR)/fuzz-cmplog AFL_LLVM_CMPLOG=1 $(MAKE) CC=$(FUZZ_CC) CFLAGS='$(CFLAGS) $(FUZZ_CFLAGS)' OBJ_DIR=obj-fuzz-cmplog TARGET=$(FUZZ_CMPLOG_TARGET) SRCS="$(filter-out src/main.c,$(SRCS)) fuzz/harness.c" @echo "=> CmpLog target built: $(FUZZ_CMPLOG_TARGET)" # LibFuzzer targets fuzz-libfuzzer-build: @$(MKDIR) $(OBJ_DIR)/fuzz-libfuzzer clang $(filter-out $(GCC_WARN_FLAGS),$(CFLAGS)) $(CLANG_WARN_FLAGS) \ -Wno-sign-conversion -Wno-switch-default -Wno-cast-align -Wno-implicit-fallthrough -Wno-redundant-decls \ -fsanitize=fuzzer,address,undefined \ $(filter-out src/main.c,$(SRCS)) fuzz/harness.c -o zc-fuzz-libfuzzer $(LIBS) @echo "=> LibFuzzer target built: zc-fuzz-libfuzzer" fuzz-corpus: @$(MKDIR) $(FUZZ_CORPUS) @find tests -name '*.zc' -exec cp {} $(FUZZ_CORPUS)/ \; 2>/dev/null || true @echo "=> Seed corpus created from existing tests ($(shell ls -1 $(FUZZ_CORPUS) 2>/dev/null | wc -l) files)" fuzz-run: fuzz-build fuzz-corpus @$(MKDIR) $(FUZZ_OUT) @echo "-> Starting fuzzer (Persistent Mode enabled)" @echo "-> Tip: For parallel runs, use '-M main' and '-S secondaryN'" AFL_SKIP_CPUFREQ=1 AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 afl-fuzz -i $(FUZZ_CORPUS) -o $(FUZZ_OUT) $(if $(wildcard $(FUZZ_DICT)),-x $(FUZZ_DICT),) -- ./$(FUZZ_TARGET) # Fuzz regression: replay known crash/leak/timeout inputs to verify they no longer crash FUZZ_CRASH_FILES = $(wildcard crash-*) $(wildcard leak-*) $(wildcard timeout-*) FUZZ_HARNESS = zc-fuzz-harness test-fuzz-regression: $(FUZZ_HARNESS) $(FUZZ_CRASH_FILES) @echo "=== Fuzz Regression Tests ===" @total=0; pass=0; \ for f in $(FUZZ_CRASH_FILES); do \ total=$$((total + 1)); \ result=0; \ ./$(FUZZ_HARNESS) "$$f" 2>/dev/null || result=$$?; \ if [ $$result -eq 0 ]; then \ echo " [PASS] $$f"; \ pass=$$((pass + 1)); \ else \ echo " [FAIL] $$f (exit code $$result)"; \ fi; \ done; \ echo "---"; \ if [ $$pass -eq $$total ]; then \ echo "All $$total fuzz regression tests passed."; \ else \ echo "$$pass/$$total fuzz regression tests passed."; \ exit 1; \ fi $(FUZZ_HARNESS): fuzz/harness.c $(OBJS) $(CC) $(CFLAGS) -D__AFL_HAVE_MANUAL_CONTROL -Wno-switch-default -Wno-sign-conversion -Wno-redundant-decls $(filter-out src/main.c,$(SRCS)) fuzz/harness.c -o $@ $(LIBS) fuzz-clean: rm -rf $(FUZZ_OUT)/* rm -f $(FUZZ_TARGET) $(FUZZ_CMPLOG_TARGET) $(FUZZ_HARNESS) rm -rf obj-fuzz obj-fuzz-cmplog .PHONY: all clean install uninstall install-ape uninstall-ape install-tools format format-check lint bench test test-misra test-tcc test-filcc test-lsp test-unit test-asan test-plugins zig clang filcc ape windows asan tsan msan lsan analyzer coverage coverage-report core lite minimal fuzz-build fuzz-run fuzz-clean test-fuzz-regression zc-lsp zc-repl zc-doc zc-format FORCE