mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-02 07:53:41 +08:00
Memory leaks fixed (0 leaks confirmed via leaks --atExit): - mcp.c resolve_store: cbm_project_free_fields was gated on proj.root_path[0] — empty string paths silently skipped free. Separated free from the watcher call; now always frees after successful cbm_store_get_project. - mcp.c handle_index_status: cbm_store_search_free skipped when dep_out.count==0 — cbm_store_search allocates even for empty results. Restructured to free whenever search succeeds. Same fix for cbm_project_free_fields call in ecosystem detection path. - pagerank.c: node_labels leaked on two early return paths (N==0 and id_map_init failure). Both paths now free node_ids and node_labels (with per-element free for strdup'd entries before the N==0 branch assigns any). - pass_envscan.c: 8 static regexes compiled once by compile_patterns() were never freed. Added cbm_envscan_free_patterns() that calls cbm_regfree on each and resets patterns_compiled=0. - pipeline.h/pipeline.c: public cbm_pipeline_global_cleanup() wraps cbm_envscan_free_patterns(). Called in main.c after ALL server threads joined (HTTP + stdio) to avoid racing with autoindex threads. Also called in run_cli() path and test_pipeline.c teardown. Ecosystem detection expanded from 8 to 17 package managers: - depindex.h: added CBM_PKG_MAKE, CBM_PKG_CMAKE, CBM_PKG_MESON, CBM_PKG_CONAN (C/C++ build systems). Expanded CBM_MANIFEST_FILES with build.gradle.kts, bun.lockb, global.json, Directory.Build.props, NuGet.Config, Makefile, GNUmakefile, Makefile.cbm, CMakeLists.txt, meson.build, conanfile.txt, conanfile.py, vcpkg.json. - depindex.c: rewrote cbm_detect_ecosystem to cover all 17 managers using CHECK() macro for exact filename matches and dir_contains_suffix() for wildcard patterns (*.csproj, *.fsproj). Added has_vendored_deps_dir() helper. Added discover_vendored_deps() which scans vendor/ vendored/ third_party/ thirdparty/ deps/ external/ ext/ contrib/ lib/ _vendor/ submodules/ for C/C++ and CBM_PKG_CUSTOM build systems. dep search hint in handle_search_graph: - When a dep project search (project:"dep", expanded to prefix "<session>.dep") returns 0 results, emits a "hint" field with an ecosystem-aware actionable message. If cbm_detect_ecosystem succeeds, the hint names the detected build system and instructs to re-run index_repository. If no ecosystem detected, lists all 17 supported manifest file types. Compact output improvements in mcp.c: - handle_search_graph: skip emitting "name" when it equals the last segment of qualified_name (ends_with_segment check) or when empty. - handle_trace_call_path: same fix for both outbound (callees) and inbound (callers) node arrays. Added callers_total emission to match callees_total (was documented in tool description but never emitted). - build_snippet_response: skip empty name, label, and file_path fields. Compact param now wired through all six call sites in handle_get_code. Zero-value numeric fields skipped in compact mode. - handle_get_architecture / build_resource_architecture: skip redundant name (when equals last qualified_name segment) and empty label/fp in key_functions arrays. Test coverage: - test_token_reduction.c: 504-line new file covering compact suppression of redundant name/label/empty fields, callers_total presence, get_code compact param propagation, architecture key_functions, and dep search hint emission. - test_mcp.c, test_pipeline.c: minor additions for new behaviors. Makefile.cbm: - Added nosan build (CFLAGS_NOSAN, LDFLAGS_NOSAN, MONGOOSE_CFLAGS_NOSAN, per-object NOSAN variants for sqlite3/lsp/grammar/ts_runtime/mongoose). - Added test-leak target: macOS uses leaks --atExit on test-runner-nosan; Linux uses ASAN_OPTIONS=detect_leaks=1 on regular test-runner. - Added test-analyze target: Clang --analyze on production + test sources (skipped with message when IS_GCC=yes). - Updated .PHONY with test-leak, test-analyze, test-runner-nosan. Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
677 lines
27 KiB
Makefile
677 lines
27 KiB
Makefile
# Makefile.cbm — Build system for pure C rewrite
|
|
#
|
|
# Usage:
|
|
# make -f Makefile.cbm test # Build + run all tests (ASan + UBSan)
|
|
# make -f Makefile.cbm test-foundation # Foundation tests only (fast)
|
|
# make -f Makefile.cbm test-tsan # Thread sanitizer build
|
|
# make -f Makefile.cbm cbm # Production binary (auto-signed on macOS)
|
|
# make -f Makefile.cbm install # Build + install to INSTALL_DIR (default ~/.local/bin)
|
|
# make -f Makefile.cbm clean-c # Remove build artifacts
|
|
#
|
|
# macOS signing note:
|
|
# macOS 25+ enforces ad-hoc code signatures on binaries. Copying a binary
|
|
# without re-signing causes immediate SIGKILL at runtime. This Makefile
|
|
# runs `codesign --force --sign -` automatically after every build and
|
|
# install step on macOS. On Linux and other platforms the step is a no-op.
|
|
|
|
# Compiler selection — override via: make CC=gcc CXX=g++
|
|
# macOS: cc (Apple Clang) — universal binary with ASan support
|
|
# Linux: gcc/g++ — system default with full sanitizer support
|
|
# CI scripts pass CC/CXX explicitly; don't rely on defaults here
|
|
|
|
# ── Common flags ─────────────────────────────────────────────────
|
|
|
|
# Include paths for:
|
|
# src/ — new foundation headers
|
|
# vendored/ — yyjson, xxhash, sqlite3
|
|
# internal/cbm — existing extraction headers (cbm.h, helpers.h, etc.)
|
|
# internal/cbm/vendored/ts_runtime/include — tree-sitter API
|
|
CBM_DIR = internal/cbm
|
|
TS_INCLUDE = $(CBM_DIR)/vendored/ts_runtime/include
|
|
# Vendored tree-sitter src/ contains unicode/ headers (umachine.h, utf.h, utf8.h).
|
|
# This ensures we use our vendored copies instead of requiring system libicu-dev.
|
|
TS_SRC = $(CBM_DIR)/vendored/ts_runtime/src
|
|
|
|
# ── Optional libgit2 (faster git history parsing) ────────────────
|
|
# Auto-detected via pkg-config. Falls back to popen("git log ...") if absent.
|
|
LIBGIT2_CFLAGS := $(shell pkg-config --cflags libgit2 2>/dev/null)
|
|
LIBGIT2_LIBS := $(shell pkg-config --libs libgit2 2>/dev/null)
|
|
ifneq ($(LIBGIT2_LIBS),)
|
|
LIBGIT2_FLAGS = -DHAVE_LIBGIT2 $(LIBGIT2_CFLAGS)
|
|
else
|
|
LIBGIT2_FLAGS =
|
|
LIBGIT2_LIBS =
|
|
endif
|
|
|
|
# ── Platform detection & code signing ───────────────────────────
|
|
# macOS 25+ kills unsigned or invalidly-signed binaries with SIGKILL.
|
|
# codesign --force --sign - applies an ad-hoc signature (no Apple Developer
|
|
# account required). On Linux/other platforms this entire block is a no-op.
|
|
UNAME_S := $(shell uname -s)
|
|
ifeq ($(UNAME_S),Darwin)
|
|
CODESIGN_BIN := $(shell command -v codesign 2>/dev/null)
|
|
ifneq ($(CODESIGN_BIN),)
|
|
# codesign is available — sign and report
|
|
define codesign_binary
|
|
@$(CODESIGN_BIN) --force --sign - $(1) 2>&1 && \
|
|
echo " ✓ signed $(1) (ad-hoc, macOS 25+ compatible)" || \
|
|
{ echo " ✗ WARNING: codesign failed for $(1) — binary may crash on macOS 25+"; true; }
|
|
endef
|
|
else
|
|
# codesign not found — warn but don't fail the build
|
|
define codesign_binary
|
|
@echo " ✗ WARNING: codesign not found — $(1) may crash on macOS 25+ (install Xcode CLT)"
|
|
endef
|
|
endif
|
|
else
|
|
# Non-macOS: signing is a documented no-op
|
|
define codesign_binary
|
|
@echo " (signing skipped — not macOS)"
|
|
endef
|
|
endif
|
|
|
|
# GCC-only warning suppressions (Clang rejects unknown -Wno-* with -Werror).
|
|
# Detect GCC by checking for __GNUC__ without __clang__ — handles all versions.
|
|
IS_GCC := $(shell echo | $(CC) -dM -E - 2>/dev/null | grep -q '__GNUC__' && ! echo | $(CC) -dM -E - 2>/dev/null | grep -q '__clang__' && echo yes || echo no)
|
|
GCC_ONLY_FLAGS :=
|
|
ifeq ($(IS_GCC),yes)
|
|
GCC_ONLY_FLAGS := -Wno-format-truncation -Wno-unused-result \
|
|
-Wno-stringop-truncation -Wno-alloc-size-larger-than
|
|
endif
|
|
|
|
CFLAGS_COMMON = -std=c11 -D_DEFAULT_SOURCE -Wall -Wextra -Werror \
|
|
-Wno-unused-parameter -Wno-sign-compare \
|
|
$(GCC_ONLY_FLAGS) \
|
|
-Isrc -Ivendored -Ivendored/sqlite3 \
|
|
-Ivendored/mimalloc/include \
|
|
-I$(CBM_DIR) -I$(TS_INCLUDE) \
|
|
$(LIBGIT2_FLAGS)
|
|
|
|
CXXFLAGS_COMMON = -std=c++14 -Wall -Wextra -Werror \
|
|
-Wno-unused-parameter \
|
|
-I$(CBM_DIR) -I$(TS_INCLUDE)
|
|
|
|
# Production flags (CFLAGS_EXTRA allows CI to inject -DCBM_VERSION)
|
|
CFLAGS_PROD = $(CFLAGS_COMMON) -O2 $(CFLAGS_EXTRA)
|
|
CXXFLAGS_PROD = $(CXXFLAGS_COMMON) -O2
|
|
|
|
# Test flags: debug + sanitizers (override SANITIZE= to disable on Windows)
|
|
SANITIZE = -fsanitize=address,undefined -fno-omit-frame-pointer
|
|
CFLAGS_TEST = $(CFLAGS_COMMON) -g -O1 $(SANITIZE)
|
|
CXXFLAGS_TEST = $(CXXFLAGS_COMMON) -g -O1 $(SANITIZE)
|
|
|
|
# TSan (can't combine with ASan)
|
|
CFLAGS_TSAN = $(CFLAGS_COMMON) -g -O1 \
|
|
-fsanitize=thread -fno-omit-frame-pointer
|
|
CXXFLAGS_TSAN = $(CXXFLAGS_COMMON) -g -O1 \
|
|
-fsanitize=thread -fno-omit-frame-pointer
|
|
|
|
# Windows needs ws2_32 (Winsock), psapi (GetProcessMemoryInfo), and
|
|
# --allow-multiple-definition (MinGW CRT symbol clashes).
|
|
# Auto-detected via compiler; no manual override needed.
|
|
IS_MINGW := $(shell echo | $(CC) -dM -E - 2>/dev/null | grep -q 'define _WIN32 ' && echo yes || echo no)
|
|
WIN32_LIBS :=
|
|
ifeq ($(IS_MINGW),yes)
|
|
WIN32_LIBS := -lws2_32 -lpsapi -Wl,--allow-multiple-definition -Wl,--stack,8388608 -static
|
|
endif
|
|
|
|
LDFLAGS = -lm -lstdc++ -lpthread -lz $(LIBGIT2_LIBS) $(WIN32_LIBS)
|
|
LDFLAGS_TEST = -lm -lstdc++ -lpthread -lz $(SANITIZE) $(LIBGIT2_LIBS) $(WIN32_LIBS)
|
|
LDFLAGS_TSAN = -lm -lstdc++ -lpthread -lz -fsanitize=thread $(LIBGIT2_LIBS) $(WIN32_LIBS)
|
|
# nosan: no ASan/UBSan — required for macOS 'leaks' tool (incompatible with ASan malloc replacement)
|
|
CFLAGS_NOSAN = $(CFLAGS_COMMON) -g -O1
|
|
LDFLAGS_NOSAN = -lm -lstdc++ -lpthread -lz $(LIBGIT2_LIBS) $(WIN32_LIBS)
|
|
|
|
# ── Source files ─────────────────────────────────────────────────
|
|
|
|
FOUNDATION_SRCS = \
|
|
src/foundation/arena.c \
|
|
src/foundation/hash_table.c \
|
|
src/foundation/str_intern.c \
|
|
src/foundation/log.c \
|
|
src/foundation/str_util.c \
|
|
src/foundation/platform.c \
|
|
src/foundation/system_info.c \
|
|
src/foundation/slab_alloc.c \
|
|
src/foundation/yaml.c \
|
|
src/foundation/compat.c \
|
|
src/foundation/compat_thread.c \
|
|
src/foundation/compat_fs.c \
|
|
src/foundation/compat_regex.c \
|
|
src/foundation/mem.c
|
|
|
|
# Existing extraction C code (compiled from current location)
|
|
EXTRACTION_SRCS = \
|
|
$(CBM_DIR)/cbm.c \
|
|
$(CBM_DIR)/extract_defs.c \
|
|
$(CBM_DIR)/extract_calls.c \
|
|
$(CBM_DIR)/extract_imports.c \
|
|
$(CBM_DIR)/extract_usages.c \
|
|
$(CBM_DIR)/extract_unified.c \
|
|
$(CBM_DIR)/extract_semantic.c \
|
|
$(CBM_DIR)/extract_type_refs.c \
|
|
$(CBM_DIR)/extract_type_assigns.c \
|
|
$(CBM_DIR)/extract_env_accesses.c \
|
|
$(CBM_DIR)/helpers.c \
|
|
$(CBM_DIR)/lang_specs.c
|
|
|
|
# LSP resolvers (compiled as one unit via lsp_all.c)
|
|
LSP_SRCS = $(CBM_DIR)/lsp_all.c
|
|
|
|
# Tree-sitter runtime
|
|
TS_RUNTIME_SRC = $(CBM_DIR)/ts_runtime.c
|
|
|
|
# All 64 grammar shim files
|
|
GRAMMAR_SRCS = $(wildcard $(CBM_DIR)/grammar_*.c)
|
|
|
|
# LZ4 + Aho-Corasick
|
|
AC_LZ4_SRCS = $(CBM_DIR)/ac.c $(CBM_DIR)/lz4_store.c
|
|
|
|
# Preprocessor (C++)
|
|
PREPROCESSOR_SRC = $(CBM_DIR)/preprocessor.cpp
|
|
|
|
# SQLite writer
|
|
SQLITE_WRITER_SRC = $(CBM_DIR)/sqlite_writer.c
|
|
|
|
# Store module (new)
|
|
STORE_SRCS = src/store/store.c
|
|
|
|
# Cypher module (new)
|
|
CYPHER_SRCS = src/cypher/cypher.c
|
|
|
|
# MCP server module (new)
|
|
MCP_SRCS = src/mcp/mcp.c
|
|
|
|
# Discover module (new)
|
|
DISCOVER_SRCS = \
|
|
src/discover/language.c \
|
|
src/discover/gitignore.c \
|
|
src/discover/discover.c
|
|
|
|
# Graph buffer module (new)
|
|
GRAPH_BUFFER_SRCS = src/graph_buffer/graph_buffer.c
|
|
|
|
# Pipeline module (new)
|
|
PIPELINE_SRCS = \
|
|
src/pipeline/fqn.c \
|
|
src/pipeline/registry.c \
|
|
src/pipeline/pipeline.c \
|
|
src/pipeline/worker_pool.c \
|
|
src/pipeline/pass_parallel.c \
|
|
src/pipeline/pass_definitions.c \
|
|
src/pipeline/pass_calls.c \
|
|
src/pipeline/pass_usages.c \
|
|
src/pipeline/pass_semantic.c \
|
|
src/pipeline/pass_tests.c \
|
|
src/pipeline/pass_githistory.c \
|
|
src/pipeline/pass_httplinks.c \
|
|
src/pipeline/pass_gitdiff.c \
|
|
src/pipeline/pass_configures.c \
|
|
src/pipeline/pass_configlink.c \
|
|
src/pipeline/pass_enrichment.c \
|
|
src/pipeline/pass_envscan.c \
|
|
src/pipeline/pass_compile_commands.c \
|
|
src/pipeline/pass_infrascan.c \
|
|
src/pipeline/httplink.c
|
|
|
|
# Depindex module (dependency/reference API indexing)
|
|
DEPINDEX_SRCS = src/depindex/depindex.c
|
|
|
|
# PageRank module (node + edge ranking)
|
|
PAGERANK_SRCS = src/pagerank/pagerank.c
|
|
|
|
# Traces module (new)
|
|
TRACES_SRCS = src/traces/traces.c
|
|
|
|
# Watcher module (new)
|
|
WATCHER_SRCS = src/watcher/watcher.c
|
|
|
|
# CLI module (new)
|
|
CLI_SRCS = src/cli/cli.c
|
|
|
|
# UI module (graph visualization)
|
|
UI_SRCS = \
|
|
src/ui/config.c \
|
|
src/ui/http_server.c \
|
|
src/ui/layout3d.c \
|
|
src/ui/embedded_stub.c
|
|
|
|
# Mongoose HTTP library (vendored, compiled with relaxed warnings)
|
|
MONGOOSE_SRC = vendored/mongoose/mongoose.c
|
|
MONGOOSE_CFLAGS = -std=c11 -D_DEFAULT_SOURCE -O2 -w -Ivendored -DMG_ENABLE_LOG=0
|
|
MONGOOSE_CFLAGS_TEST = -std=c11 -D_DEFAULT_SOURCE -g -O1 -w -Ivendored -DMG_ENABLE_LOG=0 \
|
|
$(SANITIZE)
|
|
MONGOOSE_CFLAGS_NOSAN = -std=c11 -D_DEFAULT_SOURCE -g -O1 -w -Ivendored -DMG_ENABLE_LOG=0
|
|
|
|
# mimalloc (vendored, global allocator override)
|
|
MIMALLOC_SRC = vendored/mimalloc/src/static.c
|
|
MIMALLOC_CFLAGS = -std=c11 -O2 -w \
|
|
-Ivendored/mimalloc/include \
|
|
-Ivendored/mimalloc/src \
|
|
-DMI_OVERRIDE=1
|
|
MIMALLOC_CFLAGS_TEST = -std=c11 -g -O1 -w \
|
|
-Ivendored/mimalloc/include \
|
|
-Ivendored/mimalloc/src \
|
|
-DMI_OVERRIDE=0
|
|
|
|
# sqlite3 (vendored amalgamation — compiled ourselves for ASan instrumentation)
|
|
SQLITE3_SRC = vendored/sqlite3/sqlite3.c
|
|
SQLITE3_CFLAGS = -std=c11 -O2 -w -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=1
|
|
SQLITE3_CFLAGS_TEST = -std=c11 -g -O1 -w -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=1
|
|
|
|
# TRE regex (vendored, Windows only — POSIX uses system <regex.h>)
|
|
TRE_SRC = vendored/tre/tre_all.c
|
|
TRE_CFLAGS = -std=c11 -g -O1 -w -Ivendored/tre
|
|
|
|
# yyjson (vendored)
|
|
YYJSON_SRC = vendored/yyjson/yyjson.c
|
|
|
|
# All production sources
|
|
PROD_SRCS = $(FOUNDATION_SRCS) $(STORE_SRCS) $(CYPHER_SRCS) $(MCP_SRCS) $(DISCOVER_SRCS) $(GRAPH_BUFFER_SRCS) $(PIPELINE_SRCS) $(DEPINDEX_SRCS) $(PAGERANK_SRCS) $(TRACES_SRCS) $(WATCHER_SRCS) $(CLI_SRCS) $(UI_SRCS) $(YYJSON_SRC)
|
|
EXISTING_C_SRCS = $(EXTRACTION_SRCS) $(LSP_SRCS) $(TS_RUNTIME_SRC) \
|
|
$(GRAMMAR_SRCS) $(AC_LZ4_SRCS) $(SQLITE_WRITER_SRC)
|
|
|
|
# ── Test sources ─────────────────────────────────────────────────
|
|
|
|
TEST_FOUNDATION_SRCS = \
|
|
tests/test_main.c \
|
|
tests/test_arena.c \
|
|
tests/test_hash_table.c \
|
|
tests/test_dyn_array.c \
|
|
tests/test_str_intern.c \
|
|
tests/test_log.c \
|
|
tests/test_str_util.c \
|
|
tests/test_platform.c
|
|
|
|
TEST_EXTRACTION_SRCS = \
|
|
tests/test_extraction.c \
|
|
tests/test_ac.c
|
|
|
|
TEST_STORE_SRCS = \
|
|
tests/test_store_nodes.c \
|
|
tests/test_store_edges.c \
|
|
tests/test_store_search.c \
|
|
tests/test_store_arch.c
|
|
|
|
TEST_CYPHER_SRCS = \
|
|
tests/test_cypher.c
|
|
|
|
TEST_MCP_SRCS = \
|
|
tests/test_mcp.c
|
|
|
|
TEST_DISCOVER_SRCS = \
|
|
tests/test_language.c \
|
|
tests/test_gitignore.c \
|
|
tests/test_discover.c
|
|
|
|
TEST_GRAPH_BUFFER_SRCS = tests/test_graph_buffer.c
|
|
|
|
TEST_PIPELINE_SRCS = tests/test_registry.c tests/test_pipeline.c tests/test_configlink.c tests/test_infrascan.c tests/test_worker_pool.c tests/test_parallel.c
|
|
|
|
TEST_WATCHER_SRCS = tests/test_watcher.c
|
|
|
|
TEST_LZ4_SRCS = tests/test_lz4.c
|
|
|
|
TEST_SQLITE_WRITER_SRCS = tests/test_sqlite_writer.c
|
|
|
|
TEST_GO_LSP_SRCS = tests/test_go_lsp.c
|
|
|
|
TEST_C_LSP_SRCS = tests/test_c_lsp.c
|
|
|
|
TEST_INTEGRATION_SRCS = tests/test_integration.c
|
|
|
|
TEST_TRACES_SRCS = tests/test_traces.c
|
|
|
|
TEST_HTTPLINK_SRCS = tests/test_httplink.c
|
|
|
|
TEST_CLI_SRCS = tests/test_cli.c
|
|
|
|
TEST_MEM_SRCS = tests/test_mem.c
|
|
|
|
TEST_UI_SRCS = tests/test_ui.c
|
|
|
|
TEST_DEPINDEX_SRCS = tests/test_depindex.c
|
|
|
|
TEST_PAGERANK_SRCS = tests/test_pagerank.c
|
|
|
|
TEST_TOKEN_REDUCTION_SRCS = tests/test_token_reduction.c
|
|
|
|
TEST_TOOL_CONSOLIDATION_SRCS = tests/test_tool_consolidation.c
|
|
|
|
ALL_TEST_SRCS = $(TEST_FOUNDATION_SRCS) $(TEST_EXTRACTION_SRCS) $(TEST_STORE_SRCS) $(TEST_CYPHER_SRCS) $(TEST_MCP_SRCS) $(TEST_DISCOVER_SRCS) $(TEST_GRAPH_BUFFER_SRCS) $(TEST_PIPELINE_SRCS) $(TEST_WATCHER_SRCS) $(TEST_LZ4_SRCS) $(TEST_SQLITE_WRITER_SRCS) $(TEST_GO_LSP_SRCS) $(TEST_C_LSP_SRCS) $(TEST_TRACES_SRCS) $(TEST_HTTPLINK_SRCS) $(TEST_CLI_SRCS) $(TEST_MEM_SRCS) $(TEST_UI_SRCS) $(TEST_DEPINDEX_SRCS) $(TEST_PAGERANK_SRCS) $(TEST_TOKEN_REDUCTION_SRCS) $(TEST_TOOL_CONSOLIDATION_SRCS) $(TEST_INTEGRATION_SRCS)
|
|
|
|
# ── Build directories ────────────────────────────────────────────
|
|
|
|
BUILD_DIR = build/c
|
|
|
|
# ── Object file compilation (grammars need relaxed warnings) ─────
|
|
|
|
# Grammar + tree-sitter runtime: compiled without -Werror (upstream code has warnings)
|
|
GRAMMAR_CFLAGS = -std=c11 -D_DEFAULT_SOURCE -O2 -w -I$(CBM_DIR) -I$(TS_INCLUDE) -I$(TS_SRC)
|
|
GRAMMAR_CFLAGS_TEST = -std=c11 -D_DEFAULT_SOURCE -g -O1 -w -I$(CBM_DIR) -I$(TS_INCLUDE) -I$(TS_SRC) \
|
|
$(SANITIZE)
|
|
GRAMMAR_CFLAGS_TSAN = -std=c11 -D_DEFAULT_SOURCE -g -O1 -w -I$(CBM_DIR) -I$(TS_INCLUDE) -I$(TS_SRC) \
|
|
-fsanitize=thread -fno-omit-frame-pointer
|
|
|
|
# Object files for grammars + ts_runtime + lsp_all + preprocessor
|
|
GRAMMAR_OBJS_TEST = $(patsubst $(CBM_DIR)/%.c,$(BUILD_DIR)/%.o,$(GRAMMAR_SRCS))
|
|
TS_RUNTIME_OBJ_TEST = $(BUILD_DIR)/ts_runtime.o
|
|
LSP_OBJ_TEST = $(BUILD_DIR)/lsp_all.o
|
|
PP_OBJ_TEST = $(BUILD_DIR)/preprocessor.o
|
|
|
|
# ── Targets ──────────────────────────────────────────────────────
|
|
|
|
.PHONY: test test-foundation test-tsan test-leak test-analyze cbm cbm-with-ui frontend embed clean-c lint lint-tidy lint-cppcheck lint-format install test-runner-nosan
|
|
|
|
$(BUILD_DIR):
|
|
mkdir -p $(BUILD_DIR)
|
|
|
|
# ── Foundation-only test (fast, no extraction) ───────────────────
|
|
|
|
$(BUILD_DIR)/test-foundation: $(TEST_FOUNDATION_SRCS) $(FOUNDATION_SRCS) | $(BUILD_DIR)
|
|
$(CC) $(CFLAGS_TEST) -o $@ $(TEST_FOUNDATION_SRCS) $(FOUNDATION_SRCS) $(LDFLAGS_TEST)
|
|
|
|
test-foundation: $(BUILD_DIR)/test-foundation
|
|
cd $(CURDIR) && $(BUILD_DIR)/test-foundation
|
|
|
|
# ── Grammar/TS/LSP object files (compiled with relaxed warnings) ─
|
|
|
|
$(BUILD_DIR)/%.o: $(CBM_DIR)/%.c | $(BUILD_DIR)
|
|
$(CC) $(GRAMMAR_CFLAGS_TEST) -c -o $@ $<
|
|
|
|
$(BUILD_DIR)/ts_runtime.o: $(CBM_DIR)/ts_runtime.c | $(BUILD_DIR)
|
|
$(CC) $(GRAMMAR_CFLAGS_TEST) -c -o $@ $<
|
|
|
|
$(BUILD_DIR)/lsp_all.o: $(CBM_DIR)/lsp_all.c | $(BUILD_DIR)
|
|
$(CC) $(GRAMMAR_CFLAGS_TEST) $(SANITIZE) -c -o $@ $<
|
|
|
|
$(BUILD_DIR)/preprocessor.o: $(CBM_DIR)/preprocessor.cpp | $(BUILD_DIR)
|
|
$(CXX) $(CXXFLAGS_TEST) -w -I$(CBM_DIR)/vendored -c -o $@ $<
|
|
|
|
# ── Full test binary ─────────────────────────────────────────────
|
|
|
|
# Mongoose object files (compiled with relaxed warnings, no -Werror)
|
|
MONGOOSE_OBJ_TEST = $(BUILD_DIR)/mongoose.o
|
|
MONGOOSE_OBJ_PROD = $(BUILD_DIR)/prod_mongoose.o
|
|
|
|
$(BUILD_DIR)/mongoose.o: $(MONGOOSE_SRC) | $(BUILD_DIR)
|
|
$(CC) $(MONGOOSE_CFLAGS_TEST) -c -o $@ $<
|
|
|
|
$(BUILD_DIR)/prod_mongoose.o: $(MONGOOSE_SRC) | $(BUILD_DIR)
|
|
$(CC) $(MONGOOSE_CFLAGS) -c -o $@ $<
|
|
|
|
# mimalloc object files
|
|
MIMALLOC_OBJ_TEST = $(BUILD_DIR)/mimalloc.o
|
|
MIMALLOC_OBJ_PROD = $(BUILD_DIR)/prod_mimalloc.o
|
|
|
|
$(BUILD_DIR)/mimalloc.o: $(MIMALLOC_SRC) | $(BUILD_DIR)
|
|
$(CC) $(MIMALLOC_CFLAGS_TEST) -c -o $@ $<
|
|
|
|
$(BUILD_DIR)/prod_mimalloc.o: $(MIMALLOC_SRC) | $(BUILD_DIR)
|
|
$(CC) $(MIMALLOC_CFLAGS) -c -o $@ $<
|
|
|
|
# sqlite3 object files (vendored amalgamation)
|
|
SQLITE3_OBJ_TEST = $(BUILD_DIR)/sqlite3.o
|
|
SQLITE3_OBJ_PROD = $(BUILD_DIR)/prod_sqlite3.o
|
|
|
|
$(BUILD_DIR)/sqlite3.o: $(SQLITE3_SRC) | $(BUILD_DIR)
|
|
$(CC) $(SQLITE3_CFLAGS_TEST) $(SANITIZE) -c -o $@ $<
|
|
|
|
$(BUILD_DIR)/prod_sqlite3.o: $(SQLITE3_SRC) | $(BUILD_DIR)
|
|
$(CC) $(SQLITE3_CFLAGS) -c -o $@ $<
|
|
|
|
# TRE regex (only compiled on Windows — POSIX uses system <regex.h>)
|
|
TRE_OBJ_TEST :=
|
|
TRE_OBJ_PROD :=
|
|
ifeq ($(IS_MINGW),yes)
|
|
TRE_OBJ_TEST = $(BUILD_DIR)/tre.o
|
|
TRE_OBJ_PROD = $(BUILD_DIR)/prod_tre.o
|
|
|
|
$(BUILD_DIR)/tre.o: $(TRE_SRC) | $(BUILD_DIR)
|
|
$(CC) $(TRE_CFLAGS) $(SANITIZE) -c -o $@ $<
|
|
|
|
$(BUILD_DIR)/prod_tre.o: $(TRE_SRC) | $(BUILD_DIR)
|
|
$(CC) $(TRE_CFLAGS) -O2 -c -o $@ $<
|
|
endif
|
|
|
|
OBJS_VENDORED_TEST = $(MIMALLOC_OBJ_TEST) $(SQLITE3_OBJ_TEST) $(TRE_OBJ_TEST) $(GRAMMAR_OBJS_TEST) $(TS_RUNTIME_OBJ_TEST) $(LSP_OBJ_TEST) $(PP_OBJ_TEST) $(MONGOOSE_OBJ_TEST)
|
|
|
|
# ── Nosan build: ASan-free test runner for macOS heap leak detection ─────────
|
|
#
|
|
# WHY THIS EXISTS:
|
|
# 'make test-leak' uses Apple's 'leaks --atExit' tool to find heap leaks.
|
|
# But leaks cannot inspect a process that uses a custom malloc (such as ASan).
|
|
# The regular test-runner is built with -fsanitize=address,undefined, which
|
|
# replaces malloc → leaks aborts with "unable to inspect heap ranges".
|
|
#
|
|
# HOW IT WORKS:
|
|
# We rebuild all ASan-instrumented vendored objects without -fsanitize flags
|
|
# into $(NOSAN_DIR), then link test-runner-nosan against them.
|
|
# The resulting binary runs the full test suite under Apple's heap profiler.
|
|
# Full leak report is written to $(LEAK_LOG) = build/c/leak-report.txt.
|
|
#
|
|
# HOW TO USE:
|
|
# make test-leak # runs full suite + heap check, saves report to LEAK_LOG
|
|
# cat build/c/leak-report.txt # review complete leak report after run
|
|
#
|
|
# WHICH OBJECTS NEED NOSAN VARIANTS (use SANITIZE in their *_TEST flags):
|
|
# sqlite3, lsp_all, preprocessor, grammar/*.c, ts_runtime, mongoose
|
|
# WHICH ARE REUSED AS-IS (never use SANITIZE):
|
|
# mimalloc (MIMALLOC_CFLAGS_TEST has no -fsanitize)
|
|
# tre (only on Windows; TRE_CFLAGS has no -fsanitize)
|
|
#
|
|
NOSAN_DIR = $(BUILD_DIR)/nosan
|
|
GRAMMAR_CFLAGS_NOSAN = -std=c11 -D_DEFAULT_SOURCE -g -O1 -w -I$(CBM_DIR) -I$(TS_INCLUDE) -I$(TS_SRC)
|
|
GRAMMAR_OBJS_NOSAN = $(patsubst $(CBM_DIR)/%.c,$(NOSAN_DIR)/%.o,$(GRAMMAR_SRCS))
|
|
|
|
$(NOSAN_DIR):
|
|
mkdir -p $(NOSAN_DIR)
|
|
|
|
# Grammar C files (tree-sitter parsers) — recompiled without ASan/UBSan
|
|
$(NOSAN_DIR)/%.o: $(CBM_DIR)/%.c | $(NOSAN_DIR)
|
|
$(CC) $(GRAMMAR_CFLAGS_NOSAN) -c -o $@ $<
|
|
|
|
$(NOSAN_DIR)/ts_runtime.o: $(CBM_DIR)/ts_runtime.c | $(NOSAN_DIR)
|
|
$(CC) $(GRAMMAR_CFLAGS_NOSAN) -c -o $@ $<
|
|
|
|
$(NOSAN_DIR)/lsp_all.o: $(CBM_DIR)/lsp_all.c | $(NOSAN_DIR)
|
|
$(CC) $(GRAMMAR_CFLAGS_NOSAN) -c -o $@ $<
|
|
|
|
$(NOSAN_DIR)/preprocessor.o: $(CBM_DIR)/preprocessor.cpp | $(NOSAN_DIR)
|
|
$(CXX) $(CXXFLAGS_COMMON) -g -O1 -w -I$(CBM_DIR)/vendored -c -o $@ $<
|
|
|
|
$(NOSAN_DIR)/sqlite3.o: $(SQLITE3_SRC) | $(NOSAN_DIR)
|
|
$(CC) $(SQLITE3_CFLAGS_TEST) -c -o $@ $<
|
|
|
|
$(NOSAN_DIR)/mongoose.o: $(MONGOOSE_SRC) | $(NOSAN_DIR)
|
|
$(CC) $(MONGOOSE_CFLAGS_NOSAN) -c -o $@ $<
|
|
|
|
OBJS_VENDORED_NOSAN = $(MIMALLOC_OBJ_TEST) $(NOSAN_DIR)/sqlite3.o $(TRE_OBJ_TEST) \
|
|
$(GRAMMAR_OBJS_NOSAN) $(NOSAN_DIR)/ts_runtime.o \
|
|
$(NOSAN_DIR)/lsp_all.o $(NOSAN_DIR)/preprocessor.o \
|
|
$(NOSAN_DIR)/mongoose.o
|
|
|
|
$(BUILD_DIR)/test-runner: $(ALL_TEST_SRCS) $(PROD_SRCS) $(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(SQLITE_WRITER_SRC) $(OBJS_VENDORED_TEST) | $(BUILD_DIR)
|
|
$(CC) $(CFLAGS_TEST) -o $@ \
|
|
$(ALL_TEST_SRCS) $(PROD_SRCS) \
|
|
$(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(SQLITE_WRITER_SRC) \
|
|
$(OBJS_VENDORED_TEST) \
|
|
$(LDFLAGS_TEST)
|
|
|
|
$(BUILD_DIR)/test-runner-nosan: $(ALL_TEST_SRCS) $(PROD_SRCS) $(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(SQLITE_WRITER_SRC) $(OBJS_VENDORED_NOSAN) | $(BUILD_DIR) $(NOSAN_DIR)
|
|
$(CC) $(CFLAGS_NOSAN) -o $@ \
|
|
$(ALL_TEST_SRCS) $(PROD_SRCS) \
|
|
$(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(SQLITE_WRITER_SRC) \
|
|
$(OBJS_VENDORED_NOSAN) \
|
|
$(LDFLAGS_NOSAN)
|
|
|
|
test: $(BUILD_DIR)/test-runner
|
|
cd $(CURDIR) && $(BUILD_DIR)/test-runner
|
|
|
|
# ── TSan full test ───────────────────────────────────────────────
|
|
|
|
test-tsan:
|
|
@echo "TSan not yet wired for full extraction tests"
|
|
|
|
# ── Leak detection ───────────────────────────────────────────────
|
|
# macOS: uses `leaks --atExit` (Apple Clang LSan not available on all versions)
|
|
# Linux: ASAN_OPTIONS=detect_leaks=1 (GCC/Clang ASan always includes LSan)
|
|
# Note: if false positives appear from system libraries on Linux, create lsan.supp
|
|
# and set LSAN_OPTIONS=suppressions=lsan.supp
|
|
LEAK_LOG = $(BUILD_DIR)/leak-report.txt
|
|
ifeq ($(UNAME_S),Darwin)
|
|
# macOS: 'leaks' cannot inspect ASan-instrumented processes (ASan replaces malloc).
|
|
# Use test-runner-nosan (no ASan/UBSan) so leaks can walk the heap.
|
|
test-leak: $(BUILD_DIR)/test-runner-nosan
|
|
@echo "Running heap leak detection via 'leaks --atExit' on nosan build (macOS). May take 2-5 minutes."
|
|
@echo "Full report saved to $(LEAK_LOG). Exit 0 = no leaks."
|
|
leaks --atExit -- $(BUILD_DIR)/test-runner-nosan 2>&1 | tee $(LEAK_LOG); exit $${PIPESTATUS[0]}
|
|
else
|
|
test-leak: $(BUILD_DIR)/test-runner
|
|
@echo "Running heap leak detection via ASan/LSan (Linux). Full report saved to $(LEAK_LOG). Exit 0 = no leaks."
|
|
ASAN_OPTIONS=detect_leaks=1 $(BUILD_DIR)/test-runner 2>&1 | tee $(LEAK_LOG); exit $${PIPESTATUS[0]}
|
|
endif
|
|
|
|
# ── Static analysis (Clang analyzer only — GCC has no --analyze flag) ──────
|
|
ifeq ($(IS_GCC),no)
|
|
test-analyze: $(ALL_TEST_SRCS) $(PROD_SRCS)
|
|
@echo "Running Clang static analyzer..."
|
|
$(CC) --analyze $(CFLAGS_COMMON) \
|
|
$(ALL_TEST_SRCS) $(PROD_SRCS) $(EXTRACTION_SRCS) 2>&1 | \
|
|
grep -E "warning:|error:|note:" || echo "No issues found."
|
|
else
|
|
test-analyze:
|
|
@echo "Static analysis skipped: requires Clang (not GCC). Install clang and re-run."
|
|
endif
|
|
|
|
# ── Production binary ────────────────────────────────────────────
|
|
|
|
# Grammar/TS/LSP objects for production (compiled with relaxed warnings, -O2)
|
|
GRAMMAR_OBJS_PROD = $(patsubst $(CBM_DIR)/%.c,$(BUILD_DIR)/prod_%.o,$(GRAMMAR_SRCS))
|
|
TS_RUNTIME_OBJ_PROD = $(BUILD_DIR)/prod_ts_runtime.o
|
|
LSP_OBJ_PROD = $(BUILD_DIR)/prod_lsp_all.o
|
|
PP_OBJ_PROD = $(BUILD_DIR)/prod_preprocessor.o
|
|
|
|
$(BUILD_DIR)/prod_%.o: $(CBM_DIR)/%.c | $(BUILD_DIR)
|
|
$(CC) $(GRAMMAR_CFLAGS) -c -o $@ $<
|
|
|
|
$(BUILD_DIR)/prod_ts_runtime.o: $(CBM_DIR)/ts_runtime.c | $(BUILD_DIR)
|
|
$(CC) $(GRAMMAR_CFLAGS) -c -o $@ $<
|
|
|
|
$(BUILD_DIR)/prod_lsp_all.o: $(CBM_DIR)/lsp_all.c | $(BUILD_DIR)
|
|
$(CC) $(GRAMMAR_CFLAGS) -c -o $@ $<
|
|
|
|
$(BUILD_DIR)/prod_preprocessor.o: $(CBM_DIR)/preprocessor.cpp | $(BUILD_DIR)
|
|
$(CXX) $(CXXFLAGS_PROD) -w -I$(CBM_DIR)/vendored -c -o $@ $<
|
|
|
|
OBJS_VENDORED_PROD = $(MIMALLOC_OBJ_PROD) $(SQLITE3_OBJ_PROD) $(TRE_OBJ_PROD) $(GRAMMAR_OBJS_PROD) $(TS_RUNTIME_OBJ_PROD) $(LSP_OBJ_PROD) $(PP_OBJ_PROD) $(MONGOOSE_OBJ_PROD)
|
|
|
|
MAIN_SRC = src/main.c
|
|
|
|
$(BUILD_DIR)/codebase-memory-mcp: $(MAIN_SRC) $(PROD_SRCS) $(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(SQLITE_WRITER_SRC) $(OBJS_VENDORED_PROD) | $(BUILD_DIR)
|
|
$(CC) $(CFLAGS_PROD) -o $@ \
|
|
$(MAIN_SRC) $(PROD_SRCS) \
|
|
$(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(SQLITE_WRITER_SRC) \
|
|
$(OBJS_VENDORED_PROD) \
|
|
$(LDFLAGS)
|
|
|
|
cbm: $(BUILD_DIR)/codebase-memory-mcp
|
|
@echo "Built: $(BUILD_DIR)/codebase-memory-mcp"
|
|
$(call codesign_binary,$(BUILD_DIR)/codebase-memory-mcp)
|
|
|
|
# ── Install to INSTALL_DIR (default ~/.local/bin) ────────────────
|
|
# Re-signs after copy — required on macOS 25+ where cp invalidates the
|
|
# existing ad-hoc signature and an unsigned binary gets SIGKILL at startup.
|
|
INSTALL_DIR ?= $(HOME)/.local/bin
|
|
install: cbm
|
|
@echo "Installing to $(INSTALL_DIR)/codebase-memory-mcp ..."
|
|
cp $(BUILD_DIR)/codebase-memory-mcp $(INSTALL_DIR)/codebase-memory-mcp
|
|
$(call codesign_binary,$(INSTALL_DIR)/codebase-memory-mcp)
|
|
@echo "Done. Run: $(INSTALL_DIR)/codebase-memory-mcp"
|
|
|
|
# ── Build with embedded UI (requires Node.js) ───────────────────
|
|
|
|
# Swap embedded_stub.c for the generated embedded_assets.c
|
|
UI_SRCS_WITH_ASSETS = $(subst src/ui/embedded_stub.c,src/ui/embedded_assets.c,$(UI_SRCS))
|
|
PROD_SRCS_WITH_ASSETS = $(subst src/ui/embedded_stub.c,src/ui/embedded_assets.c,$(PROD_SRCS))
|
|
|
|
# Embedded asset object files (generated by embed script)
|
|
EMBED_OBJS = $(wildcard $(BUILD_DIR)/embedded/embed_*.o)
|
|
|
|
frontend:
|
|
cd graph-ui && npm ci && npm run build
|
|
|
|
embed: frontend
|
|
scripts/embed-frontend.sh graph-ui/dist $(BUILD_DIR)/embedded
|
|
|
|
cbm-with-ui: embed $(OBJS_VENDORED_PROD)
|
|
$(CC) $(CFLAGS_PROD) -o $(BUILD_DIR)/codebase-memory-mcp \
|
|
$(MAIN_SRC) $(PROD_SRCS_WITH_ASSETS) \
|
|
$(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(SQLITE_WRITER_SRC) \
|
|
$(OBJS_VENDORED_PROD) \
|
|
$(wildcard $(BUILD_DIR)/embedded/embed_*.o) \
|
|
$(LDFLAGS)
|
|
@echo "Built with UI: $(BUILD_DIR)/codebase-memory-mcp"
|
|
|
|
clean-c:
|
|
rm -rf $(BUILD_DIR)
|
|
|
|
# ── Linting ─────────────────────────────────────────────────────
|
|
|
|
# clang-tidy and clang-format: use Homebrew LLVM on macOS, overridable via make args
|
|
# e.g. make lint-format CLANG_FORMAT=clang-format-18
|
|
LLVM_BIN := $(shell brew --prefix llvm 2>/dev/null)/bin
|
|
CLANG_TIDY ?= $(shell [ -x "$(LLVM_BIN)/clang-tidy" ] && echo "$(LLVM_BIN)/clang-tidy" || echo clang-tidy)
|
|
CLANG_FORMAT ?= $(shell [ -x "$(LLVM_BIN)/clang-format" ] && echo "$(LLVM_BIN)/clang-format" || echo clang-format)
|
|
CPPCHECK ?= cppcheck
|
|
|
|
# macOS SDK sysroot (needed for Homebrew LLVM to find system headers)
|
|
SYSROOT = $(shell xcrun --show-sdk-path 2>/dev/null)
|
|
SYSROOT_FLAG = $(if $(SYSROOT),-isysroot $(SYSROOT),)
|
|
|
|
# Our source files (excluding vendored, grammars, tree-sitter runtime)
|
|
LINT_SRCS = $(FOUNDATION_SRCS) $(STORE_SRCS) $(CYPHER_SRCS) $(MCP_SRCS) \
|
|
$(DISCOVER_SRCS) $(GRAPH_BUFFER_SRCS) $(PIPELINE_SRCS) $(TRACES_SRCS) \
|
|
$(WATCHER_SRCS) $(CLI_SRCS) $(EXTRACTION_SRCS) $(AC_LZ4_SRCS) \
|
|
$(SQLITE_WRITER_SRC) $(MAIN_SRC)
|
|
LINT_HDRS = $(wildcard src/**/*.h src/*.h $(CBM_DIR)/*.h)
|
|
LINT_TEST_SRCS = $(ALL_TEST_SRCS)
|
|
|
|
# clang-tidy: deep static analysis (config in .clang-tidy)
|
|
lint-tidy:
|
|
@echo "=== clang-tidy ==="
|
|
@$(CLANG_TIDY) --quiet $(LINT_SRCS) -- $(CFLAGS_COMMON) $(SYSROOT_FLAG)
|
|
|
|
# cppcheck: complementary analysis (config in .cppcheck)
|
|
lint-cppcheck:
|
|
@echo "=== cppcheck ==="
|
|
@$(CPPCHECK) --enable=warning,style,performance,portability \
|
|
--std=c11 --language=c \
|
|
--suppressions-list=.cppcheck \
|
|
--error-exitcode=1 \
|
|
--inline-suppr \
|
|
--quiet \
|
|
--suppress=varFuncNullUB \
|
|
--suppress=intToPointerCast \
|
|
--suppress=unusedStructMember \
|
|
--suppress=nullPointerOutOfMemory \
|
|
--suppress=nullPointerArithmeticOutOfMemory \
|
|
--suppress=ctunullpointerOutOfMemory \
|
|
--suppress='nullPointer:internal/cbm/sqlite_writer.c' \
|
|
-Isrc -Ivendored -Ivendored/sqlite3 \
|
|
-I$(CBM_DIR) -I$(TS_INCLUDE) \
|
|
$(LINT_SRCS)
|
|
|
|
# clang-format: formatting check (config in .clang-format, dry-run = no changes)
|
|
lint-format:
|
|
@echo "=== clang-format ==="
|
|
@$(CLANG_FORMAT) --dry-run --Werror $(LINT_SRCS) $(LINT_HDRS)
|
|
|
|
# All linters (run with make -j3 lint for parallel execution)
|
|
lint: lint-tidy lint-cppcheck lint-format
|
|
@echo "=== All linters passed ==="
|
|
|
|
# CI linters (no clang-tidy — platform-dependent, enforced locally via pre-commit)
|
|
lint-ci: lint-cppcheck lint-format
|
|
@echo "=== CI linters passed ==="
|