-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathMakefile
More file actions
405 lines (354 loc) · 18.2 KB
/
Copy pathMakefile
File metadata and controls
405 lines (354 loc) · 18.2 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
# Suzume Makefile
# Convenience wrapper for CMake build system
.PHONY: help build test native-test native-bench mcp-test oracle-sync-check guardrails asan clean clean-build rebuild format format-check lint configure \
wasm wasm-configure wasm-dict wasm-test wasm-bench wasm-clean wasm-rebuild dict \
python-build python-test python-wheel python-sync python-examples version-check binding-parity \
install uninstall examples examples-test embedded consumer-smoke cmake-smoke
# Build directories
BUILD_DIR := build
WASM_BUILD_DIR := build-wasm
ASAN_BUILD_DIR := build-asan
# Apple's ASan runtime aborts when leak detection is requested. Linux enables
# LeakSanitizer in the same target; the CI sanitizer job is the authoritative
# leak gate.
ASAN_DETECT_LEAKS ?= $(if $(filter Darwin,$(shell uname -s)),0,1)
# clang-format command (can be overridden: make CLANG_FORMAT=clang-format-18 format)
CLANG_FORMAT ?= clang-format
# GNU make runs a recipe line with no shell metacharacters directly, doing its
# own PATH search that stops at the first name match even when that match is a
# directory - the exec then fails with "Permission denied". The Emscripten SDK
# puts two such directories on PATH (`<emsdk>/node` and
# `<emsdk>/upstream/emscripten/cmake`), so any target invoking these tools
# breaks once emsdk is active. Resolve the real executables the way a shell
# would, and keep the bare name as a fallback so the error still names the tool.
CMAKE := $(shell command -v cmake 2>/dev/null || echo cmake)
NODE := $(shell command -v node 2>/dev/null || echo node)
# Python environments. `rye run` does not provision on demand the way `uv run`
# did, so every Python target depends on its venv and re-syncs when the lock
# moves. `rye sync` touches the venv directory, which is what make compares.
MCP_VENV := scripts/mcp/.venv
PYBINDING_VENV := bindings/python/.venv
$(MCP_VENV): scripts/mcp/requirements-dev.lock scripts/mcp/pyproject.toml
cd scripts/mcp && rye sync
$(PYBINDING_VENV): bindings/python/requirements-dev.lock bindings/python/pyproject.toml
cd bindings/python && rye sync
# Force a re-provision of both Python environments.
python-sync:
cd scripts/mcp && rye sync --force
cd bindings/python && rye sync --force
# Default target
.DEFAULT_GOAL := build
help:
@echo "Suzume Build System"
@echo ""
@echo "Available targets:"
@echo " make build - Build the project (default)"
@echo " make dict - Build dictionaries"
@echo " make test - Run all tests (includes dict)"
@echo " make mcp-test - Run MCP server/oracle tests"
@echo " make asan - Run native tests under ASan/LSan/UBSan"
@echo " make native-bench - Measure native token throughput and long-input scaling"
@echo " make clean - Remove $(BUILD_DIR) and every scratch build-* directory"
@echo " make clean-build - Remove $(BUILD_DIR) only"
@echo " make rebuild - Clean $(BUILD_DIR) and rebuild"
@echo " make format - Auto-fix format/lint: C++, scripts/MCP, WASM, Python"
@echo " make format-check - Check formatting across all languages"
@echo " make lint - Run read-only scripts/MCP, WASM, and Python static checks"
@echo " make configure - Configure CMake"
@echo " make python-sync - Re-provision both rye-managed Python environments"
@echo " make version-check - Verify version is consistent across binding manifests"
@echo " make cmake-smoke - Verify supported CMake build/install configurations"
@echo ""
@echo "C/C++ integration targets:"
@echo " make install - Install libs + headers + find_package/pkg-config (PREFIX=/usr/local)"
@echo " make uninstall - Remove the installed files (run before 'make clean')"
@echo " make examples - Build the in-tree C and C++ examples"
@echo " make embedded - Build the embedded (no-filesystem, dict baked-in) static library"
@echo " make consumer-smoke - Install to a temp prefix and build examples via find_package"
@echo ""
@echo "Python binding targets:"
@echo " make python-build - Build the shared C-ABI library (libsuzume)"
@echo " make python-test - Run Python binding tests (pytest/ruff/mypy)"
@echo " make python-wheel - Build a platform-tagged wheel"
@echo ""
@echo "WASM targets (debug info disabled for smaller binary):"
@echo " make wasm-configure - Configure the Emscripten build"
@echo " make wasm - Build WASM module (includes wasm-dict)"
@echo " make wasm-dict - Build dictionaries for the WASM link"
@echo " make wasm-test - Run WASM tests"
@echo " make wasm-bench - Measure public-API WASM throughput and memory"
@echo " make wasm-clean - Clean WASM build"
@echo " make wasm-rebuild - Clean and rebuild WASM"
@echo ""
@echo "Options:"
@echo " CMAKE_OPTIONS - Extra CMake options (e.g., -DENABLE_DEBUG_INFO=OFF)"
@echo ""
@echo " make help - Show this help message"
@echo ""
@echo "Examples:"
@echo " make # Build the project"
@echo " make test # Run all tests"
@echo " make wasm # Build WASM module"
@echo " make CMAKE_OPTIONS=-DENABLE_DEBUG_INFO=OFF # Build without debug info"
# Configure CMake (always runs to pick up new test files)
configure:
@mkdir -p $(BUILD_DIR)
@$(CMAKE) -B $(BUILD_DIR) -DCMAKE_BUILD_TYPE=Release $(CMAKE_OPTIONS)
# Build the project
build: configure
@echo "Building Suzume..."
$(CMAKE) --build $(BUILD_DIR) --parallel
@echo "Build complete!"
# Build dictionaries
dict: build
@echo "Building dictionaries..."
$(CMAKE) --build $(BUILD_DIR) --target build-dict
@echo "Dictionary build complete!"
# Run every shipped surface, consumer example, and repository invariant. Keep the native binary as
# the source of truth: CTest's configure-time discovery can miss newly added
# JSON golden cases and otherwise starts one process per gtest case.
test: native-test mcp-test oracle-sync-check python-test wasm-test examples-test consumer-smoke binding-parity guardrails
@echo "All tests complete!"
native-test: dict
@echo "Running native tests..."
$(BUILD_DIR)/bin/suzume_test
ctest --test-dir $(BUILD_DIR) --output-on-failure -R native_cli_contract
native-bench: dict
python3 scripts/measure_native_metrics.py
# Keep local verification aligned with the CI guardrails, including the WASM
# artifact-size check that needs the shipped module built first.
guardrails: wasm
LC_ALL=C scripts/check_code_guardrails.sh
python3 scripts/check_oracle_overrides.py
python3 scripts/check_binding_labels.py
python3 scripts/check_compound_v2_sync.py
python3 scripts/check_oracle_mirrors.py
scripts/check_version_mirror.sh
scripts/check_wasm_size.sh bindings/wasm/dist/suzume.wasm
# AddressSanitizer includes LeakSanitizer on supported platforms. Keep this in
# a separate build tree so normal and sanitized objects can never mix.
asan:
@echo "Running sanitizers (ASan leak detection=$(ASAN_DETECT_LEAKS), UBSan=on)..."
$(CMAKE) -B $(ASAN_BUILD_DIR) -DBUILD_TESTING=ON -DCMAKE_BUILD_TYPE=Debug \
-DENABLE_SANITIZER=ON -DENABLE_ASAN=ON -DENABLE_UBSAN=ON
$(CMAKE) --build $(ASAN_BUILD_DIR) --parallel
$(CMAKE) --build $(ASAN_BUILD_DIR) --target build-dict
ASAN_OPTIONS=detect_leaks=$(ASAN_DETECT_LEAKS):halt_on_error=1 \
UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 \
$(ASAN_BUILD_DIR)/bin/suzume_test
ASAN_OPTIONS=detect_leaks=$(ASAN_DETECT_LEAKS):halt_on_error=1 \
UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 \
ctest --test-dir $(ASAN_BUILD_DIR) --output-on-failure -R native_cli_contract
# Run the MCP server/oracle test suite from its project root so pytest uses
# scripts/mcp/pyproject.toml and imports the local package correctly.
mcp-test: $(MCP_VENV)
@echo "Running MCP server/oracle tests..."
cd scripts/mcp && rye run pytest -q
oracle-sync-check: build $(MCP_VENV)
@echo "Checking golden expectations against a fresh oracle process..."
cd scripts/mcp && rye run python ../../scripts/check_oracle_sync.py
# Clean the primary build directory only
clean-build:
@echo "Cleaning $(BUILD_DIR)..."
rm -rf $(BUILD_DIR)
# Clean the primary build directory plus every scratch build-* directory produced by
# the ad-hoc CMake configurations across the repo (build-wasm, build-python,
# build-embedded, build-smoke, ...). All of them are gitignored.
# Run `make uninstall` BEFORE this if an install needs undoing: the manifest that
# uninstall reads lives under build-install and is removed here.
clean: clean-build
@echo "Cleaning scratch build directories..."
rm -rf build-*/ cmake-build-*/
@echo "Clean complete!"
# Rebuild from scratch. Deliberately only recreates $(BUILD_DIR) — a full clean would
# also drop build-wasm and force an expensive Emscripten rebuild.
rebuild: clean-build build
# ============================================
# C/C++ integration targets
# ============================================
# Install prefix for `make install` (override: make PREFIX=/opt/suzume install)
PREFIX ?= /usr/local
# Build the shared + static libraries, headers, CMake package config, pkg-config,
# and dictionaries, then install them under $(PREFIX) for find_package / pkg-config.
install:
@echo "Configuring install build (shared + static) ..."
$(CMAKE) -B build-install -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED=ON -DBUILD_TESTING=OFF \
-DCMAKE_INSTALL_PREFIX=$(PREFIX) $(CMAKE_OPTIONS)
$(CMAKE) --build build-install --parallel
$(CMAKE) --build build-install --target build-dict
$(CMAKE) --install build-install
@echo "Installed suzume under $(PREFIX)"
# Remove what `make install` placed under $(PREFIX), using the manifest CMake wrote.
# Must run before `make clean`, which deletes build-install along with the manifest.
uninstall:
@if [ -f build-install/install_manifest.txt ]; then \
xargs rm -f < build-install/install_manifest.txt; \
echo "Uninstalled suzume from the recorded prefix."; \
else \
echo "build-install/install_manifest.txt not found - nothing to uninstall."; \
echo "It is written by 'make install' and removed by 'make clean'."; \
exit 1; \
fi
# Build the in-tree C and C++ examples.
examples: dict
$(CMAKE) -B $(BUILD_DIR) -DCMAKE_BUILD_TYPE=Release -DSUZUME_BUILD_EXAMPLES=ON $(CMAKE_OPTIONS)
$(CMAKE) --build $(BUILD_DIR) --target \
suzume_example_c suzume_example_cpp suzume_example_cpp_basic \
suzume_example_cpp_search_indexer suzume_example_cpp_tags \
suzume_example_cpp_user_dictionary --parallel
@echo "Built all C/C++ examples under $(BUILD_DIR)/bin"
examples-test: examples
ctest --test-dir $(BUILD_DIR) --output-on-failure -R '^suzume_example_'
# Build the embedded (no-filesystem) configuration: dictionaries baked in, static
# library, no CLI/tests. Useful for hosted-embedded / RTOS targets.
embedded:
$(CMAKE) -B build-embedded -DCMAKE_BUILD_TYPE=Release -DSUZUME_EMBED_DICT=ON \
-DBUILD_TESTING=OFF -DSUZUME_INSTALL=OFF $(CMAKE_OPTIONS)
$(CMAKE) --build build-embedded --target suzume --parallel
@echo "Embedded static library built: build-embedded/lib/"
# Packaging smoke test: install both static and shared configurations, then
# build + run C/C++ consumers against each package via find_package.
consumer-smoke:
$(CMAKE) -E remove_directory build-smoke-static
$(CMAKE) -E remove_directory build-smoke-prefix-static
$(CMAKE) -E remove_directory build-smoke-consumer-static
$(CMAKE) -B build-smoke-static -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF \
-DCMAKE_INSTALL_PREFIX=$(CURDIR)/build-smoke-prefix-static
$(CMAKE) --build build-smoke-static --parallel
$(CMAKE) --build build-smoke-static --target build-dict
$(CMAKE) --install build-smoke-static
$(CMAKE) -S examples/consumer -B build-smoke-consumer-static \
-DCMAKE_PREFIX_PATH=$(CURDIR)/build-smoke-prefix-static
$(CMAKE) --build build-smoke-consumer-static
ctest --test-dir build-smoke-consumer-static --output-on-failure
$(CMAKE) -E remove_directory build-smoke-shared
$(CMAKE) -E remove_directory build-smoke-prefix-shared
$(CMAKE) -E remove_directory build-smoke-consumer-shared
$(CMAKE) -B build-smoke-shared -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF -DBUILD_SHARED=ON \
-DCMAKE_INSTALL_PREFIX=$(CURDIR)/build-smoke-prefix-shared
$(CMAKE) --build build-smoke-shared --parallel
$(CMAKE) --build build-smoke-shared --target build-dict
$(CMAKE) --install build-smoke-shared
$(CMAKE) -S examples/consumer -B build-smoke-consumer-shared \
-DCMAKE_PREFIX_PATH=$(CURDIR)/build-smoke-prefix-shared
$(CMAKE) --build build-smoke-consumer-shared
ctest --test-dir build-smoke-consumer-shared --output-on-failure
@echo "Static and shared consumer smoke tests passed."
# Reproducible build/install matrix covering library-only, no-install, and
# multi-config dictionary builds without touching the shared build directories.
cmake-smoke:
scripts/check_cmake_configurations.sh
# Auto-fix formatting/lint across every language in the repo:
# C++ core (clang-format), MCP/repository scripts (ruff), WASM binding (biome),
# Python binding (ruff).
format: $(MCP_VENV) $(PYBINDING_VENV)
@echo "Formatting C++ (clang-format)..."
@find src include tools tests examples -type f \( -name "*.cpp" -o -name "*.h" -o -name "*.hpp" -o -name "*.c" \) | xargs $(CLANG_FORMAT) -i
@echo "Formatting MCP server and repository scripts (ruff)..."
cd scripts/mcp && rye run ruff format . ../../scripts/*.py && rye run ruff check --fix . ../../scripts/*.py
@echo "Formatting WASM binding (biome)..."
cd bindings/wasm && yarn lint:fix
@echo "Formatting Python binding (ruff)..."
cd bindings/python && rye run ruff format . && rye run ruff check --fix .
$(MAKE) lint
@echo "Format complete!"
# Check-only counterpart for CI (same language fan-out, no writes).
format-check: $(MCP_VENV) $(PYBINDING_VENV)
@echo "Checking C++ formatting (clang-format)..."
@find src include tools tests examples -type f \( -name "*.cpp" -o -name "*.h" -o -name "*.hpp" -o -name "*.c" \) | xargs $(CLANG_FORMAT) --dry-run --Werror
@echo "Checking MCP server and repository script formatting (ruff)..."
cd scripts/mcp && rye run ruff format --check . ../../scripts/*.py
@echo "Checking Python binding formatting (ruff)..."
cd bindings/python && rye run ruff format --check .
$(MAKE) lint
@echo "Format check passed!"
# Read-only static analysis. This repository does not configure clang-tidy, so
# C++ is covered by clang-format in format-check rather than a nominal lint step.
lint: $(MCP_VENV) $(PYBINDING_VENV)
@echo "Linting MCP server and repository scripts (ruff)..."
cd scripts/mcp && rye run ruff check . ../../scripts/*.py
@echo "Linting WASM binding (biome)..."
cd bindings/wasm && yarn lint
@echo "Linting Python binding (ruff)..."
cd bindings/python && rye run ruff check .
# ============================================
# Python binding targets
# ============================================
# Build shared library + Python package (editable) into a local venv
python-build:
@echo "Building shared library for Python binding..."
$(CMAKE) -B build-shared -DBUILD_SHARED=ON -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release \
-DENABLE_DEBUG_INFO=OFF -DENABLE_DEBUG_LOG=OFF -DSUZUME_LIB_SOVERSION=OFF
$(CMAKE) --build build-shared --target suzume_shared --parallel
@echo "Shared library built: build-shared/lib/"
# Run the Python binding test suite in the rye-managed venv
python-dict: build
@echo "Building dictionaries for the Python test package (git-tracked user entries only)..."
$(BUILD_DIR)/bin/suzume-cli dict compile data/core/*.tsv data/core.dic
$(BUILD_DIR)/bin/suzume-cli dict compile $$(git ls-files 'data/user/*.tsv') data/user.dic
$(CMAKE) -E copy_if_different data/core.dic bindings/python/src/suzume/core.dic
$(CMAKE) -E copy_if_different data/user.dic bindings/python/src/suzume/user.dic
python-test: python-build python-dict $(PYBINDING_VENV)
@echo "Running Python binding tests..."
cd bindings/python && rye run pytest -q \
&& rye run ruff check . \
&& rye run ruff format --check . \
&& rye run mypy src/suzume
$(MAKE) python-examples
python-examples: python-build $(PYBINDING_VENV)
cd bindings/python && rye run python ../../examples/python/basic.py
# Build a platform-tagged wheel
python-wheel:
cd bindings/python && ./build_wheel.sh
# Verify the version is consistent across every binding manifest
version-check:
scripts/check_version_mirror.sh
# ============================================
# WASM Targets
# ============================================
# Configure WASM build (debug info disabled for smaller binary)
wasm-configure:
@echo "Configuring WASM build..."
emcmake cmake -B $(WASM_BUILD_DIR) -DBUILD_WASM=ON -DENABLE_DEBUG_INFO=OFF -DCMAKE_BUILD_TYPE=Release
# Build dictionaries for the WASM link using the native CLI.
# Produces the same full data/*.dic that the native build-dict target and CI/publish
# generate, so the embedded WASM dictionary matches every other target exactly.
wasm-dict:
@if [ ! -f $(BUILD_DIR)/bin/suzume-cli ]; then \
echo "Native CLI not found. Run 'make build' first."; \
exit 1; \
fi
@echo "Building dictionaries for WASM (git-tracked entries only)..."
$(BUILD_DIR)/bin/suzume-cli dict compile data/core/*.tsv data/core.dic
@# The WASM binary ships publicly, so embed only git-tracked user
@# dictionaries. Any gitignored, local-only dictionary is excluded here
@# without being named, matching the CI checkout (which lacks ignored files).
$(BUILD_DIR)/bin/suzume-cli dict compile $$(git ls-files 'data/user/*.tsv') data/user.dic
# Build WASM module
wasm: wasm-dict wasm-configure
@echo "Building WASM module..."
@# Force re-link so updated embedded dictionaries are picked up
@rm -f $(WASM_BUILD_DIR)/bin/suzume.wasm $(WASM_BUILD_DIR)/bin/suzume.js
$(CMAKE) --build $(WASM_BUILD_DIR) --parallel
@echo "WASM build complete!"
@ls -lh bindings/wasm/dist/suzume.wasm bindings/wasm/dist/suzume.js
# Run WASM tests
wasm-test: wasm
@echo "Running WASM tests..."
cd bindings/wasm && yarn test
cd bindings/wasm && yarn test:examples
@echo "WASM tests complete!"
binding-parity: build python-build wasm $(PYBINDING_VENV)
cd bindings/python && rye run python ../../scripts/check_binding_parity.py
# Measure the shipped public JS API, including result decoding. Override the
# workload with WASM_BENCH_ARGS, for example:
# make wasm-bench WASM_BENCH_ARGS="--iterations=100 --samples=3"
wasm-bench: wasm
$(NODE) scripts/measure_wasm_metrics.mjs $(WASM_BENCH_ARGS)
# Clean WASM build
wasm-clean:
@echo "Cleaning WASM build..."
rm -rf $(WASM_BUILD_DIR)
@echo "WASM clean complete!"
# Rebuild WASM from scratch
wasm-rebuild: wasm-clean wasm