-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCMakeLists.txt
More file actions
421 lines (388 loc) · 17.7 KB
/
Copy pathCMakeLists.txt
File metadata and controls
421 lines (388 loc) · 17.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
cmake_minimum_required(VERSION 3.25)
# Use ccache as the compiler launcher when it is installed — automatic for
# local dev builds, a no-op where absent. Must be set before project()/
# enable_language. `NOT DEFINED` respects an explicit -DCMAKE_CXX_COMPILER_LAUNCHER
# passed by CI, so this only fills the gap for developers.
if(NOT DEFINED CMAKE_CXX_COMPILER_LAUNCHER)
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
message(STATUS "lci: using ccache compiler launcher (${CCACHE_PROGRAM})")
endif()
endif()
project(lci
VERSION 0.7.0
DESCRIPTION "Lightning Code Index - fast semantic code search"
LANGUAGES C CXX
)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# macOS universal binary support (arm64 + x86_64)
option(LCI_UNIVERSAL_BINARY "Build macOS universal binary (arm64 + x86_64)" OFF)
if(LCI_UNIVERSAL_BINARY AND APPLE)
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "macOS architectures" FORCE)
endif()
# Compiler detection and warning flag list.
#
# Flags live in LCI_WARNING_FLAGS and are applied PER-TARGET in src/ and tests/,
# NOT at directory scope. Directory-scope add_compile_options propagates into
# FetchContent vendored libraries (tree-sitter, re2, abseil, nlohmann_json_schema_validator,
# etc.) which have their own warning hygiene policies — combining -Werror with
# their existing warnings tanks the build for reasons orthogonal to our code.
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "13.0")
message(FATAL_ERROR "GCC 13+ required, found ${CMAKE_CXX_COMPILER_VERSION}")
endif()
# -Wno-unused-{parameter,function} on purpose: stubs / deferred-handler
# surfaces are tracked in Dart with explicit follow-up tasks (Karpathy rule
# 6 — surface signal, don't silence). Compile-time noise on those interferes
# with -Werror gating on the warnings that DO catch real bugs.
set(LCI_WARNING_FLAGS -Wall -Wextra -Wpedantic -Wshadow -Wconversion
-Wno-unused-parameter -Wno-unused-function)
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
# Apple Clang uses its own version numbering (e.g. 15.x ships with C++20 support)
# -Wno-unused-{parameter,function} on purpose: stubs / deferred-handler
# surfaces are tracked in Dart with explicit follow-up tasks (Karpathy rule
# 6 — surface signal, don't silence). Compile-time noise on those interferes
# with -Werror gating on the warnings that DO catch real bugs.
set(LCI_WARNING_FLAGS -Wall -Wextra -Wpedantic -Wshadow -Wconversion
-Wno-unused-parameter -Wno-unused-function)
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "17.0")
message(FATAL_ERROR "Clang 17+ required, found ${CMAKE_CXX_COMPILER_VERSION}")
endif()
# -Wno-unused-{parameter,function} on purpose: stubs / deferred-handler
# surfaces are tracked in Dart with explicit follow-up tasks (Karpathy rule
# 6 — surface signal, don't silence). Compile-time noise on those interferes
# with -Werror gating on the warnings that DO catch real bugs.
set(LCI_WARNING_FLAGS -Wall -Wextra -Wpedantic -Wshadow -Wconversion
-Wno-unused-parameter -Wno-unused-function)
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
# /wd4100 = unused parameter, /wd4505 = unused static function (match the
# -Wno-unused-{parameter,function} rationale on GCC/Clang).
set(LCI_WARNING_FLAGS /W4 /permissive- /wd4100 /wd4505)
endif()
option(LCI_WARNINGS_AS_ERRORS "Treat warnings as errors (for CI)" OFF)
if(LCI_WARNINGS_AS_ERRORS)
if(MSVC)
list(APPEND LCI_WARNING_FLAGS /WX)
else()
list(APPEND LCI_WARNING_FLAGS -Werror)
endif()
endif()
# Prefer a fast linker (mold > lld) when installed — the link of `lci` and the
# monolithic `lci_tests` (links all deps + ~1700 tests) is a real slice of
# build time, more so with debug info. Linux/ELF only: mold targets ELF (no
# Mach-O), and Apple's default ld64 is fine, so don't override the linker on
# macOS. No-op if neither is present.
if(NOT MSVC AND NOT APPLE)
find_program(MOLD_LINKER mold)
find_program(LLD_LINKER ld.lld)
if(MOLD_LINKER)
add_link_options(-fuse-ld=mold)
message(STATUS "lci: using mold linker (${MOLD_LINKER})")
elseif(LLD_LINKER)
add_link_options(-fuse-ld=lld)
message(STATUS "lci: using lld linker")
endif()
endif()
# Version header generation
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.h.in"
"${CMAKE_CURRENT_BINARY_DIR}/generated/lci/version.h"
@ONLY
)
# MCP JSON Schema embedding — schemas live as plain .json under share/lci/mcp-schemas/
# (human-reviewable, single source of truth) and are embedded into generated headers
# as C++ raw string literals so the binary has no runtime FS lookup. Mirrors Go's
# go:embed pattern. To add a schema: drop file in share/lci/mcp-schemas/, append to
# LCI_MCP_SCHEMAS, include the generated header (#include <lci/mcp/schemas/<name>.h>).
set(LCI_MCP_SCHEMAS search)
foreach(_schema IN LISTS LCI_MCP_SCHEMAS)
set(_src "${CMAKE_CURRENT_SOURCE_DIR}/share/lci/mcp-schemas/${_schema}.json")
set(_dst "${CMAKE_CURRENT_BINARY_DIR}/generated/lci/mcp/schemas/${_schema}.h")
# Embedding happens at configure time; without this dependency, editing
# the .json silently keeps the stale generated header until the next
# manual reconfigure.
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${_src}")
file(READ "${_src}" _schema_text)
# Guard: raw string literal terminator R"LCI(...)LCI" — pick a delimiter no schema uses.
string(TOUPPER "${_schema}" _schema_up)
file(CONFIGURE
OUTPUT "${_dst}"
CONTENT "// Auto-generated from share/lci/mcp-schemas/${_schema}.json — DO NOT EDIT.\n// Edit the .json source; CMake regenerates on configure.\n#pragma once\nnamespace lci { namespace mcp { namespace schemas {\nconstexpr const char* k${_schema_up}_SCHEMA = R\"LCISCHEMA(${_schema_text})LCISCHEMA\";\n}}}\n"
@ONLY
)
endforeach()
# Dependencies via vcpkg (preferred) or FetchContent (fallback)
include(FetchContent)
# Force vendored FetchContent dependencies to build static so the released
# binary is self-contained (the CPack tarball ships only bin/lci). On a fresh
# CI configure efsw and json-schema-validator otherwise build as shared
# libraries, and the packaged binary then fails at runtime with a missing
# libefsw.so / libnlohmann_json_schema_validator.so.2. Local builds already
# cache this OFF; pin it explicitly so CI matches.
set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build vendored dependencies static" FORCE)
find_package(nlohmann_json CONFIG QUIET)
if(NOT nlohmann_json_FOUND)
FetchContent_Declare(nlohmann_json
SYSTEM
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.3
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(nlohmann_json)
endif()
find_package(CLI11 CONFIG QUIET)
if(NOT CLI11_FOUND)
FetchContent_Declare(cli11
SYSTEM
GIT_REPOSITORY https://github.com/CLIUtils/CLI11.git
GIT_TAG v2.4.2
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(cli11)
endif()
find_package(httplib CONFIG QUIET)
if(NOT httplib_FOUND)
FetchContent_Declare(httplib
SYSTEM
GIT_REPOSITORY https://github.com/yhirose/cpp-httplib.git
GIT_TAG v0.18.3
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(httplib)
endif()
# Raise the listen backlog from httplib's default of 5. The IndexServer accepts
# bursts of concurrent local clients (CLI fan-out, tests); a backlog of 5 drops
# connections under an 8+-connection burst (ECONNREFUSED -> intermittent
# ServerTest.ConcurrentRequests failures). 128 matches typical SOMAXCONN.
if(TARGET httplib)
target_compile_definitions(httplib INTERFACE CPPHTTPLIB_LISTEN_BACKLOG=128)
elseif(TARGET httplib::httplib)
target_compile_definitions(httplib::httplib INTERFACE CPPHTTPLIB_LISTEN_BACKLOG=128)
endif()
find_package(xxHash CONFIG QUIET)
if(NOT xxHash_FOUND)
FetchContent_Declare(xxhash
SYSTEM
GIT_REPOSITORY https://github.com/Cyan4973/xxHash.git
GIT_TAG v0.8.2
GIT_SHALLOW TRUE
SOURCE_SUBDIR cmake_unofficial
)
FetchContent_MakeAvailable(xxhash)
endif()
find_package(absl CONFIG QUIET)
if(NOT absl_FOUND)
set(ABSL_PROPAGATE_CXX_STD ON)
set(ABSL_BUILD_TESTING OFF)
FetchContent_Declare(abseil-cpp
SYSTEM
GIT_REPOSITORY https://github.com/abseil/abseil-cpp.git
GIT_TAG 20240722.0
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(abseil-cpp)
endif()
# efsw — cross-platform file watcher (Linux inotify / macOS FSEvents / Windows ReadDirectoryChangesW)
# License: MIT. Replaces hand-rolled per-platform watcher in src/indexing/watcher.cpp.
find_package(efsw CONFIG QUIET)
if(NOT efsw_FOUND)
set(VERBOSE OFF CACHE BOOL "" FORCE)
set(BUILD_TEST_APP OFF CACHE BOOL "" FORCE)
FetchContent_Declare(efsw
SYSTEM
GIT_REPOSITORY https://github.com/SpartanJ/efsw.git
GIT_TAG 1.4.1
GIT_SHALLOW TRUE
# Upstream data race (TSan-confirmed): FileWatcherInotify's destructor
# spins `while (mIsTakingAction)` to hand off with its inotify worker
# thread (which writes the flag in handleAction), but mIsTakingAction is
# a plain bool — unsynchronised cross-thread access. efsw already wraps
# mInitOK in its own Atomic<bool>; this makes mIsTakingAction match.
# Applied via `cmake -P` (file READ/WRITE, not sed) so it is portable
# across GNU/BSD/Windows and idempotent on an already-patched tree.
PATCH_COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/patch-efsw.cmake
)
FetchContent_MakeAvailable(efsw)
endif()
# rapidfuzz-cpp — header-only SIMD-accelerated string similarity.
# License: MIT. Replaces hand-rolled Jaro-Winkler / Levenshtein / cosine
# in src/semantic/fuzzy_matcher.cpp. Matches the algorithm scale used by
# the rapidfuzz/Python ecosystem most users compare against.
find_package(rapidfuzz CONFIG QUIET)
if(NOT rapidfuzz_FOUND)
FetchContent_Declare(rapidfuzz
SYSTEM
GIT_REPOSITORY https://github.com/rapidfuzz/rapidfuzz-cpp.git
GIT_TAG v3.0.5
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(rapidfuzz)
endif()
# libstemmer (Snowball) — canonical Porter2 stemming algorithm.
# License: BSD-3-Clause. Replaces 449-LOC hand-port of Porter2 in
# src/semantic/stemmer.cpp. libstemmer == Go surgebase/porter2 fixture
# (29,417-word voc.txt/output.txt, used by both Snowball upstream and the
# surgebase Go test suite — see tests/data/porter2_fixture/).
#
# Karpathy: stemmer is on hot index/query path. Wrapper uses thread-local
# sb_stemmer (one per thread, reused across calls) + thread-local output
# buffer — zero alloc per token. See src/semantic/stemmer.cpp.
#
# Build mode: prefer vcpkg/system libstemmer; fall back to fetching the
# snowballstem/snowball-website libstemmer_c distribution and building an
# English-only static lib (avoid 30-language bloat). The distribution ships
# pre-generated C source so no Snowball compiler dependency is needed.
find_package(unofficial-libstemmer CONFIG QUIET)
if(unofficial-libstemmer_FOUND)
# vcpkg target name
set(LCI_LIBSTEMMER_TARGET unofficial::libstemmer::libstemmer)
elseif(EXISTS /usr/include/libstemmer.h)
add_library(libstemmer_system INTERFACE)
target_link_libraries(libstemmer_system INTERFACE stemmer)
set(LCI_LIBSTEMMER_TARGET libstemmer_system)
else()
FetchContent_Declare(libstemmer_c
SYSTEM
URL https://snowballstem.org/dist/libstemmer_c-2.2.0.tar.gz
URL_HASH SHA256=b941d9fe9cf36b4e2f8d3873cd4d8b8775bd94867a1df8d8c001bb8b688377c3
)
FetchContent_MakeAvailable(libstemmer_c)
# English-only build: runtime + UTF-8 English stemmer + custom thin
# libstemmer_c.c that registers only english. Skips ~30 unused languages.
set(LCI_LIBSTEMMER_SRC_DIR "${libstemmer_c_SOURCE_DIR}")
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/libstemmer_english_only.c.in"
"${CMAKE_CURRENT_BINARY_DIR}/generated/libstemmer_english_only.c"
@ONLY
)
add_library(libstemmer_lci STATIC
"${LCI_LIBSTEMMER_SRC_DIR}/runtime/api.c"
"${LCI_LIBSTEMMER_SRC_DIR}/runtime/utilities.c"
"${LCI_LIBSTEMMER_SRC_DIR}/src_c/stem_UTF_8_english.c"
"${CMAKE_CURRENT_BINARY_DIR}/generated/libstemmer_english_only.c"
)
target_include_directories(libstemmer_lci PUBLIC
"${LCI_LIBSTEMMER_SRC_DIR}/include"
)
target_include_directories(libstemmer_lci PRIVATE
"${LCI_LIBSTEMMER_SRC_DIR}/runtime"
"${LCI_LIBSTEMMER_SRC_DIR}"
)
# libstemmer C source predates strict warnings — silence to keep -Werror builds clean.
if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
target_compile_options(libstemmer_lci PRIVATE
-Wno-unused-parameter -Wno-unused-function
-Wno-sign-compare -Wno-conversion -Wno-pedantic
)
endif()
set(LCI_LIBSTEMMER_TARGET libstemmer_lci)
endif()
# re2 — Google's RE2 regex engine. License: BSD-3-Clause. Replaces std::regex
# in lci-cpp hot paths (regex_analyzer/engine, search.cpp regex_filter,
# semantic_annotator, vocabulary_analyzer, git/pattern_detector).
#
# Karpathy rule: std::regex is order-of-magnitude slower than RE2 on the search
# hot path. RE2 guarantees linear-time matching, has stable memory profile,
# and is reusable across threads when compiled once (immutable after ctor).
#
# Build mode: prefer vcpkg/system re2; fall back to FetchContent from the
# upstream Google repo at a pinned tag.
find_package(re2 CONFIG QUIET)
if(NOT re2_FOUND)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/AddRe2.cmake)
endif()
# nlohmann-json-schema-validator — JSON Schema draft-7/2019-09 validator built
# on nlohmann::json. License: MIT. Slated to replace hand-rolled MCP request
# validation (FIX-B). Dep wiring lives here; handler swap is a separate task.
find_package(nlohmann_json_schema_validator CONFIG QUIET)
if(NOT nlohmann_json_schema_validator_FOUND)
set(JSON_VALIDATOR_INSTALL OFF CACHE BOOL "" FORCE)
set(JSON_VALIDATOR_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(JSON_VALIDATOR_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
FetchContent_Declare(nlohmann_json_schema_validator
SYSTEM
GIT_REPOSITORY https://github.com/pboettch/json-schema-validator.git
GIT_TAG 2.3.0
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(nlohmann_json_schema_validator)
endif()
# tree-sitter is a C library.
#
# Always build from FetchContent at a pinned tag (v0.24.6). System / vcpkg
# tree-sitter is unreliable across distros: the pinned CI vcpkg baseline
# (01f602195) ships a stripped-down api.h that forward-declares TSNode as
# an opaque type, which breaks every TU using TSNode by value. Pinning to
# the upstream tag keeps Go-parity and Karpathy-rule hot paths deterministic.
FetchContent_Declare(tree-sitter
SYSTEM
GIT_REPOSITORY https://github.com/tree-sitter/tree-sitter.git
GIT_TAG v0.24.6
GIT_SHALLOW TRUE
)
FetchContent_GetProperties(tree-sitter)
if(NOT tree-sitter_POPULATED)
FetchContent_Populate(tree-sitter)
add_library(tree-sitter STATIC
"${tree-sitter_SOURCE_DIR}/lib/src/lib.c"
)
target_include_directories(tree-sitter PUBLIC
"${tree-sitter_SOURCE_DIR}/lib/include"
)
target_include_directories(tree-sitter PRIVATE
"${tree-sitter_SOURCE_DIR}/lib/src"
)
endif()
# Tree-sitter language grammars (13 languages)
include(cmake/TreeSitterGrammars.cmake)
build_all_ts_grammars()
add_subdirectory(src)
enable_testing()
add_subdirectory(tests)
# ---------------------------------------------------------------------------
# CPack packaging (DEB, RPM, TGZ)
# ---------------------------------------------------------------------------
set(CPACK_PACKAGE_NAME "lci")
set(CPACK_PACKAGE_VENDOR "StandardBeagle")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY
"Lightning Code Index - fast semantic code search for AI assistants")
set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH})
set(CPACK_PACKAGE_CONTACT "andy@standardbeagle.com")
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
endif()
set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/standardbeagle/lci")
install(TARGETS lci RUNTIME DESTINATION bin COMPONENT lci_runtime)
# FetchContent dependencies (httplib, absl, nlohmann, gtest, benchmark, etc.)
# unconditionally register their own install() rules. Restrict CPack to only
# the lci_runtime component so the package contains exactly bin/lci and no
# third-party headers or static libs. Both CPACK_COMPONENTS_ALL and the
# per-generator *_COMPONENT_INSTALL flags are required; the latter switches
# the generator out of "dump everything" mode.
set(CPACK_COMPONENTS_ALL lci_runtime)
set(CPACK_COMPONENT_LCI_RUNTIME_DESCRIPTION "Lightning Code Index binary")
set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE)
set(CPACK_ARCHIVE_COMPONENT_INSTALL ON)
set(CPACK_DEB_COMPONENT_INSTALL ON)
set(CPACK_RPM_COMPONENT_INSTALL ON)
set(CPACK_GENERATOR "TGZ")
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
list(APPEND CPACK_GENERATOR "DEB" "RPM")
endif()
# DEB-specific
set(CPACK_DEBIAN_PACKAGE_SECTION "devel")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.35)")
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "amd64")
# RPM-specific
set(CPACK_RPM_PACKAGE_LICENSE "MIT")
set(CPACK_RPM_PACKAGE_GROUP "Development/Tools")
set(CPACK_RPM_PACKAGE_REQUIRES "glibc >= 2.35")
include(CPack)