-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathMakefile
More file actions
512 lines (468 loc) · 24.6 KB
/
Copy pathMakefile
File metadata and controls
512 lines (468 loc) · 24.6 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
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
# ---------------------------------------------------------------------------
# DSM Protocol — Makefile
# All developer tasks run through here. Bash scripts in scripts/ are
# implementation details; this file is the only documented entrypoint.
#
# Usage:
# make help — list all targets
# make menu — interactive launcher for common tasks
# make doctor — check local prerequisites without changing files
# make setup — first-time onboarding
# make build — Rust workspace build
# make nodes-up — start local storage nodes
# make android — build debug APK (includes native libs)
# make install — build + install APK on all connected adb devices
# make test — Rust + frontend tests
# make clean — wipe all build artifacts
#
# Windows developers: use scripts/dev.ps1 instead of this Makefile.
# .\scripts\dev.ps1 help
# Android builds on Windows require WSL2 — see QUICKSTART.md.
# ---------------------------------------------------------------------------
# Prefer homebrew bash (4+) for associative arrays used by SBOM scripts.
# Fall back to system bash if homebrew isn't installed.
SHELL := $(shell command -v /opt/homebrew/bin/bash 2>/dev/null || echo /bin/bash)
REPO_ROOT := $(shell cd "$(dir $(abspath $(lastword $(MAKEFILE_LIST))))" && pwd)
ANDROID_DIR := $(REPO_ROOT)/dsm_client/android
NDK_DIR := $(REPO_ROOT)/dsm_client/deterministic_state_machine
FRONTEND_DIR := $(REPO_ROOT)/dsm_client/frontend
STORAGE_NODE_DIR := $(REPO_ROOT)/dsm_storage_node
JNILIBS_DIR := $(ANDROID_DIR)/app/src/main/jniLibs
REPO_JNILIBS_DIR := $(NDK_DIR)/jniLibs
CARGO_CONFIG := $(NDK_DIR)/dsm_sdk/.cargo/config.toml
CARGO_CONFIG_TEMPLATE := $(NDK_DIR)/dsm_sdk/.cargo/config.toml.template
ANDROID_LOCAL_PROPERTIES := $(ANDROID_DIR)/local.properties
RUST_TOOLCHAIN_FILE := $(REPO_ROOT)/rust-toolchain.toml
ANDROID_APP_GRADLE := $(ANDROID_DIR)/app/build.gradle.kts
.DEFAULT_GOAL := help
.PHONY: menu
menu: ## Interactive launcher for common developer tasks
@bash -lc 'set -e; \
options=(setup doctor build frontend android install test lint nodes-up nodes-down nodes-status clean quit); \
PS3="Select DSM task: "; \
select opt in "$${options[@]}"; do \
case "$$opt" in \
quit) exit 0 ;; \
"") echo "Invalid selection" ;; \
*) $(MAKE) "$$opt"; break ;; \
esac; \
done'
.PHONY: help
help: ## Show this help
@echo ""
@echo "DSM Protocol — available targets:"
@echo ""
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-22s\033[0m %s\n", $$1, $$2}'
@echo ""
# ---------------------------------------------------------------------------
# SETUP
# ---------------------------------------------------------------------------
.PHONY: doctor
doctor: ## Check local prerequisites and repo state without changing files
@echo "==> DSM doctor"
@PINNED_RUST="$$(sed -n 's/^channel = \"\([^\"]*\)\".*/\1/p' "$(RUST_TOOLCHAIN_FILE)" | head -1)"; \
command -v cargo >/dev/null 2>&1 && echo " cargo: $$(cargo --version)" || echo " MISSING: cargo (install from https://rustup.rs)"; \
command -v rustc >/dev/null 2>&1 && echo " rustc: $$(rustc --version)" || echo " MISSING: rustc (install from https://rustup.rs)"; \
[ -n "$$PINNED_RUST" ] && echo " rust toolchain pin: $$PINNED_RUST ($(notdir $(RUST_TOOLCHAIN_FILE)))" || true
@command -v rustfmt >/dev/null 2>&1 && echo " rustfmt: installed" || echo " MISSING: rustfmt (run: rustup component add rustfmt)"
@command -v cargo-clippy >/dev/null 2>&1 && echo " clippy: installed" || echo " MISSING: clippy (run: rustup component add clippy)"
@command -v protoc >/dev/null 2>&1 && echo " protoc: $$(protoc --version)" || echo " MISSING: protoc"
@command -v node >/dev/null 2>&1 && echo " node: $$(node --version)" || echo " MISSING: node"
@command -v npm >/dev/null 2>&1 && echo " npm: $$(npm --version)" || echo " MISSING: npm"
@BASH_VER=$$(bash --version | head -1 | sed 's/.*version \([0-9]*\).*/\1/'); \
if [ "$$BASH_VER" -ge 4 ] 2>/dev/null; then \
echo " bash: version $$BASH_VER (OK)"; \
else \
echo " bash: version $$BASH_VER (SBOM scripts need bash 4+; brew install bash)"; \
fi
@command -v adb >/dev/null 2>&1 && echo " adb: available" || echo " adb: optional (needed only for device install/debug)"
@command -v psql >/dev/null 2>&1 && echo " psql: available" || echo " psql: optional until you run local storage nodes"
@command -v java >/dev/null 2>&1 && echo " java: $$(java -version 2>&1 | head -1)" || echo " java: optional until Android builds"
@command -v cargo-ndk >/dev/null 2>&1 && echo " cargo-ndk: $$(cargo ndk --version)" || echo " cargo-ndk: optional until Android builds (install via cargo install cargo-ndk)"
@GRADLE_NDK_VERSION="$$(sed -n 's/.*ndkVersion = \"\([^\"]*\)\".*/\1/p' "$(ANDROID_APP_GRADLE)" | head -1)"; \
NDK=""; \
NDK_SOURCE=""; \
NDK_VERSION=""; \
HOST_TAG=""; \
if [ -n "$$ANDROID_NDK_HOME" ]; then \
NDK="$$ANDROID_NDK_HOME"; \
NDK_SOURCE="ANDROID_NDK_HOME"; \
elif [ -n "$$ANDROID_NDK_ROOT" ]; then \
NDK="$$ANDROID_NDK_ROOT"; \
NDK_SOURCE="ANDROID_NDK_ROOT"; \
fi; \
if [ -n "$$NDK" ]; then \
if [ -d "$$NDK" ]; then \
NDK_VERSION="$$(basename "$$NDK")"; \
HOST_TAG="$$(ls "$$NDK/toolchains/llvm/prebuilt" 2>/dev/null | head -1)"; \
echo " android ndk: $$NDK_SOURCE -> $$NDK"; \
echo " android ndk version: $$NDK_VERSION"; \
[ -n "$$HOST_TAG" ] && echo " android ndk host tag: $$HOST_TAG" || echo " android ndk host tag: unresolved"; \
else \
echo " android ndk: $$NDK_SOURCE points to missing directory ($$NDK)"; \
fi; \
else \
echo " android ndk: not configured (run make setup before Android builds)"; \
fi; \
[ -n "$$GRADLE_NDK_VERSION" ] && echo " gradle ndkVersion: $$GRADLE_NDK_VERSION" || true; \
if [ -n "$$NDK_VERSION" ] && [ -n "$$GRADLE_NDK_VERSION" ] && [ "$$NDK_VERSION" != "$$GRADLE_NDK_VERSION" ]; then \
echo " WARNING: configured NDK version ($$NDK_VERSION) does not match Gradle ndkVersion ($$GRADLE_NDK_VERSION)"; \
fi
@SDK=""; \
SDK_SOURCE=""; \
if [ -n "$$ANDROID_HOME" ] && [ -d "$$ANDROID_HOME" ]; then \
SDK="$$ANDROID_HOME"; \
SDK_SOURCE="ANDROID_HOME"; \
elif [ -n "$$ANDROID_SDK_ROOT" ] && [ -d "$$ANDROID_SDK_ROOT" ]; then \
SDK="$$ANDROID_SDK_ROOT"; \
SDK_SOURCE="ANDROID_SDK_ROOT"; \
elif [ -f "$(ANDROID_LOCAL_PROPERTIES)" ]; then \
SDK="$$(sed -n 's/^sdk.dir=//p' "$(ANDROID_LOCAL_PROPERTIES)" | tail -1 | sed 's/\\\\/\\/g; s/\\:/:/g; s/\\ / /g')"; \
SDK_SOURCE="local.properties"; \
fi; \
if [ -n "$$SDK" ] && [ -d "$$SDK" ]; then \
echo " android sdk: configured via $$SDK_SOURCE"; \
else \
echo " android sdk: not configured (make setup will try to detect it)"; \
fi
@if [ -d "$(FRONTEND_DIR)/node_modules" ]; then \
echo " frontend deps: present"; \
else \
echo " frontend deps: missing (run make setup)"; \
fi
@CURRENT_NDK="$${ANDROID_NDK_HOME:-$$ANDROID_NDK_ROOT}"; \
if [ -f "$(CARGO_CONFIG)" ]; then \
CFG_NDK="$$(sed -n 's/^ANDROID_NDK_ROOT = { value = \"\([^\"]*\)\",.*/\1/p' "$(CARGO_CONFIG)" | head -1)"; \
CFG_VERSION=""; \
CFG_HOST_TAG="$$(sed -n 's|.*toolchains/llvm/prebuilt/\([^/]*\)/bin/.*|\1|p' "$(CARGO_CONFIG)" | head -1)"; \
[ -n "$$CFG_NDK" ] && CFG_VERSION="$$(basename "$$CFG_NDK")"; \
echo " android cargo config: present"; \
[ -n "$$CFG_NDK" ] && echo " ndk root: $$CFG_NDK" || true; \
[ -n "$$CFG_VERSION" ] && echo " ndk version: $$CFG_VERSION" || true; \
[ -n "$$CFG_HOST_TAG" ] && echo " host tag: $$CFG_HOST_TAG" || true; \
if [ -n "$$CURRENT_NDK" ] && [ -n "$$CFG_NDK" ] && [ "$$CFG_NDK" != "$$CURRENT_NDK" ]; then \
echo " WARNING: .cargo/config.toml NDK root differs from the current environment"; \
fi; \
else \
echo " android cargo config: missing (run make setup)"; \
fi
.PHONY: android-sdk-config
android-sdk-config: ## Detect Android SDK and write ignored local.properties for Gradle
@SDK=""; \
if [ -n "$$ANDROID_HOME" ] && [ -d "$$ANDROID_HOME" ]; then \
SDK="$$ANDROID_HOME"; \
elif [ -n "$$ANDROID_SDK_ROOT" ] && [ -d "$$ANDROID_SDK_ROOT" ]; then \
SDK="$$ANDROID_SDK_ROOT"; \
elif [ -d "$$HOME/Library/Android/sdk" ]; then \
SDK="$$HOME/Library/Android/sdk"; \
elif [ -d "$$HOME/Android/sdk" ]; then \
SDK="$$HOME/Android/sdk"; \
fi; \
if [ -z "$$SDK" ]; then \
echo "WARNING: Android SDK not found. Set ANDROID_HOME or ANDROID_SDK_ROOT, or install the SDK in a standard location."; \
exit 0; \
fi; \
ESCAPED_SDK="$$SDK"; \
ESCAPED_SDK="$${ESCAPED_SDK//\\/\\\\}"; \
ESCAPED_SDK="$${ESCAPED_SDK//:/\\:}"; \
ESCAPED_SDK="$${ESCAPED_SDK// /\\ }"; \
mkdir -p "$(ANDROID_DIR)"; \
printf 'sdk.dir=%s\n' "$$ESCAPED_SDK" > "$(ANDROID_LOCAL_PROPERTIES)"; \
echo " Android SDK configured for Gradle (local.properties updated)"
.PHONY: setup
setup: ## First-time developer setup: check deps, auto-configure Android, install frontend deps
@echo "==> Checking prerequisites..."
@PINNED_RUST="$$(sed -n 's/^channel = \"\([^\"]*\)\".*/\1/p' "$(RUST_TOOLCHAIN_FILE)" | head -1)"; \
[ -n "$$PINNED_RUST" ] && echo "==> Rust toolchain pin: $$PINNED_RUST ($(notdir $(RUST_TOOLCHAIN_FILE)))" || true
@command -v cargo >/dev/null 2>&1 || { echo "ERROR: Rust/cargo not found. Install from https://rustup.rs"; exit 1; }
@command -v rustc >/dev/null 2>&1 || { echo "ERROR: rustc not found. Install from https://rustup.rs"; exit 1; }
@echo " cargo: $$(cargo --version)"
@echo " rustc: $$(rustc --version)"
@command -v npm >/dev/null 2>&1 || { echo "ERROR: npm not found. Install Node.js 20+"; exit 1; }
@command -v protoc >/dev/null 2>&1 || { echo "ERROR: protoc not found. Install protobuf compiler"; exit 1; }
@command -v adb >/dev/null 2>&1 || echo "WARNING: adb not found — install Android Platform Tools for device installs"
@$(MAKE) android-sdk-config
@GRADLE_NDK_VERSION="$$(sed -n 's/.*ndkVersion = \"\([^\"]*\)\".*/\1/p' "$(ANDROID_APP_GRADLE)" | head -1)"; \
if [ -z "$$ANDROID_NDK_HOME" ] && [ -z "$$ANDROID_NDK_ROOT" ]; then \
echo "WARNING: ANDROID_NDK_HOME is not set — skipping Android cargo config generation."; \
echo " Set it later and re-run 'make setup' before Android builds."; \
else \
NDK="$${ANDROID_NDK_HOME:-$$ANDROID_NDK_ROOT}"; \
[ -d "$$NDK" ] || { echo "ERROR: configured Android NDK path does not exist: $$NDK"; exit 1; }; \
HOST_TAG="$$(ls "$$NDK/toolchains/llvm/prebuilt" 2>/dev/null | head -1)"; \
[ -n "$$HOST_TAG" ] || { echo "ERROR: could not detect Android NDK host tag under $$NDK/toolchains/llvm/prebuilt"; exit 1; }; \
NDK_VERSION="$$(basename "$$NDK")"; \
echo "==> Android NDK resolved: $$NDK"; \
echo " version: $$NDK_VERSION"; \
echo " host tag: $$HOST_TAG"; \
[ -n "$$GRADLE_NDK_VERSION" ] && echo " gradle ndkVersion: $$GRADLE_NDK_VERSION" || true; \
if [ -n "$$GRADLE_NDK_VERSION" ] && [ "$$NDK_VERSION" != "$$GRADLE_NDK_VERSION" ]; then \
echo "WARNING: configured NDK version ($$NDK_VERSION) does not match Gradle ndkVersion ($$GRADLE_NDK_VERSION)."; \
fi; \
command -v cargo-ndk >/dev/null 2>&1 || { echo "Installing cargo-ndk..."; cargo install cargo-ndk; }; \
echo " cargo-ndk: $$(cargo ndk --version)"; \
mkdir -p "$$(dirname "$(CARGO_CONFIG)")"; \
echo "==> Refreshing dsm_client/deterministic_state_machine/dsm_sdk/.cargo/config.toml from template..."; \
sed \
-e "s|__NDK_ROOT__|$$NDK|g" \
-e "s|__NDK_HOST_TAG__|$$HOST_TAG|g" \
$(CARGO_CONFIG_TEMPLATE) > "$(CARGO_CONFIG).tmp"; \
mv "$(CARGO_CONFIG).tmp" "$(CARGO_CONFIG)"; \
echo " Refreshed: dsm_client/deterministic_state_machine/dsm_sdk/.cargo/config.toml"; \
echo " Configured NDK root: $$NDK"; \
echo " Configured NDK host tag: $$HOST_TAG"; \
fi
@if [ ! -d "$(FRONTEND_DIR)/node_modules" ]; then \
echo "==> Installing frontend dependencies..."; \
cd $(FRONTEND_DIR) && \
[ -s $$HOME/.nvm/nvm.sh ] && . $$HOME/.nvm/nvm.sh; \
nvm use --silent 2>/dev/null || true; \
npm ci; \
else \
echo " Frontend dependencies already installed — skipping npm ci"; \
fi
@echo "==> Setup complete."
# ---------------------------------------------------------------------------
# BUILD
# ---------------------------------------------------------------------------
.PHONY: build
build: ## Build the full Rust workspace
cargo build --locked --workspace --all-features
.PHONY: build-release
build-release: ## Build Rust workspace in release mode
cargo build --locked --workspace --all-features --release
.PHONY: android-libs
android-libs: ## Build native .so libs for all Android ABIs (requires NDK)
@echo "==> Building Android native libs..."
@rm -f $(JNILIBS_DIR)/arm64-v8a/libdsm_sdk.so \
$(JNILIBS_DIR)/armeabi-v7a/libdsm_sdk.so \
$(JNILIBS_DIR)/x86_64/libdsm_sdk.so \
$(REPO_JNILIBS_DIR)/arm64-v8a/libdsm_sdk.so \
$(REPO_JNILIBS_DIR)/armeabi-v7a/libdsm_sdk.so \
$(REPO_JNILIBS_DIR)/x86_64/libdsm_sdk.so
@mkdir -p $(REPO_JNILIBS_DIR)/arm64-v8a \
$(REPO_JNILIBS_DIR)/armeabi-v7a \
$(REPO_JNILIBS_DIR)/x86_64
cd $(NDK_DIR) && \
DSM_PROTO_ROOT=$(REPO_ROOT)/proto \
cargo ndk \
-t arm64-v8a -t armeabi-v7a -t x86_64 \
-o $(JNILIBS_DIR) \
--platform 23 \
build --release --package dsm_sdk --features=jni,bluetooth
@cp $(NDK_DIR)/target/aarch64-linux-android/release/libdsm_sdk.so $(REPO_JNILIBS_DIR)/arm64-v8a/
@cp $(NDK_DIR)/target/armv7-linux-androideabi/release/libdsm_sdk.so $(REPO_JNILIBS_DIR)/armeabi-v7a/
@cp $(NDK_DIR)/target/x86_64-linux-android/release/libdsm_sdk.so $(REPO_JNILIBS_DIR)/x86_64/
@echo "==> Native libs built."
.PHONY: frontend
frontend: ## Build the React frontend (copies assets into Android)
@echo "==> Building fresh frontend bundle and deploying assets into Android..."
cd $(FRONTEND_DIR) && \
[ -s $$HOME/.nvm/nvm.sh ] && . $$HOME/.nvm/nvm.sh; \
nvm use --silent 2>/dev/null || true; \
npm run build:full-deploy
@echo "==> Frontend built."
.PHONY: android
android: android-sdk-config android-libs frontend ## Build fresh debug APK (native libs + frontend + clean gradle)
@echo "==> Assembling fresh Android debug APK..."
cd $(ANDROID_DIR) && ./gradlew clean :app:assembleDebug --no-daemon --console=plain
@echo "==> APK: $(ANDROID_DIR)/app/build/outputs/apk/debug/app-debug.apk"
.PHONY: android-release
android-release: android-sdk-config android-libs frontend ## Build fresh release APK
@echo "==> Assembling fresh Android release APK..."
bash scripts/fast_deploy_android_release.sh --build-only
@echo "==> APK: $(ANDROID_DIR)/app/build/outputs/apk/release/app-release.apk"
# ---------------------------------------------------------------------------
# INSTALL
# ---------------------------------------------------------------------------
.PHONY: install
install: android ## Build debug APK and install on all connected adb devices
@echo "==> Installing on connected devices..."
@bash $(REPO_ROOT)/scripts/install_apk_connected_devices.sh
.PHONY: install-only
install-only: ## Install existing APK without rebuilding (fast iteration)
@bash $(REPO_ROOT)/scripts/install_apk_connected_devices.sh
# ---------------------------------------------------------------------------
# TEST
# ---------------------------------------------------------------------------
.PHONY: test
test: ## Run Rust workspace tests + frontend jest tests
@echo "==> Running Rust tests..."
# --test-threads=1: dsm_sdk/storage suites share process-global singletons; a
# non-serial test racing a #[serial] one flakes nondeterministically. Match CI.
cargo test --workspace --exclude dsm_storage_node -- --nocapture --test-threads=1
cargo test -p dsm_storage_node --no-default-features --features local-dev,strict -- --nocapture
@echo "==> Running frontend tests..."
cd $(FRONTEND_DIR) && \
[ -s $$HOME/.nvm/nvm.sh ] && . $$HOME/.nvm/nvm.sh; \
nvm use --silent 2>/dev/null || true; \
npm test -- --passWithNoTests
.PHONY: test-rust
test-rust: ## Run Rust tests only
# Most of the workspace uses the default (postgres-feature) build.
# dsm_storage_node integration tests use the local-dev (SQLite) build so
# they run without a live PostgreSQL instance.
cargo test --workspace --exclude dsm_storage_node -- --nocapture
cargo test -p dsm_storage_node --no-default-features --features local-dev,strict -- --nocapture
.PHONY: test-frontend
test-frontend: ## Run frontend jest tests only
cd $(FRONTEND_DIR) && \
[ -s $$HOME/.nvm/nvm.sh ] && . $$HOME/.nvm/nvm.sh; \
nvm use --silent 2>/dev/null || true; \
npm test -- --passWithNoTests
.PHONY: typecheck
typecheck: ## Run frontend TypeScript type-check
cd $(FRONTEND_DIR) && \
[ -s $$HOME/.nvm/nvm.sh ] && . $$HOME/.nvm/nvm.sh; \
nvm use --silent 2>/dev/null || true; \
npm run type-check
# ---------------------------------------------------------------------------
# LINT / QUALITY
# ---------------------------------------------------------------------------
.PHONY: lint
# THE canonical toolchain, read from rust-toolchain.toml — never hardcoded here.
# A second copy of the version in this file would be exactly the drift that made
# `make lint` and CI disagree in the first place.
RUST_PIN := $(shell awk -F'"' '/^channel/{print $$2}' rust-toolchain.toml)
# The pinned toolchain's bin directory, resolved through rustup.
#
# `rustup run $(RUST_PIN) bash ...` is NOT sufficient: the nested shell
# re-sources profile files, which can put another cargo (e.g. Homebrew's) back
# ahead of the toolchain on PATH. Prepending the real bin directory is what
# actually guarantees the pinned cargo wins.
RUST_PIN_BIN := $(shell rustup which --toolchain $(RUST_PIN) cargo 2>/dev/null | xargs dirname 2>/dev/null)
lint: ## Run all linters (cargo fmt, clippy, frontend) on the PINNED toolchain
@if ! rustup toolchain list 2>/dev/null | grep -q '^$(RUST_PIN)'; then \
echo "ERROR: pinned toolchain $(RUST_PIN) is not installed."; \
echo " rustup toolchain install $(RUST_PIN) --profile minimal --component rustfmt --component clippy"; \
exit 1; \
fi
@echo "==> Linting with pinned toolchain $(RUST_PIN)"
@PATH="$(RUST_PIN_BIN):$$PATH" bash ci/check_toolchain_consistency.sh
rustup run $(RUST_PIN) cargo fmt --all -- --check
rustup run $(RUST_PIN) cargo clippy --all-targets -- -D warnings
cd $(FRONTEND_DIR) && \
[ -s $$HOME/.nvm/nvm.sh ] && . $$HOME/.nvm/nvm.sh; \
nvm use --silent 2>/dev/null || true; \
npm run lint
@echo "==> Lint passed."
.PHONY: fmt
fmt: ## Auto-format Rust code
cargo fmt --all
.PHONY: audit
audit: ## Security audit (cargo-audit on every tracked Cargo.lock + cargo-deny)
cargo install cargo-audit --quiet || true
bash ci/audit_every_lockfile.sh
cargo install cargo-deny --quiet || true
cargo deny check
.PHONY: deny
deny: ## Run cargo-deny license and advisory checks
cargo install cargo-deny --quiet || true
cargo deny check
# ---------------------------------------------------------------------------
# STORAGE NODES (local dev)
# ---------------------------------------------------------------------------
.PHONY: nodes-up
nodes-up: ## Set up the dev database and start the 5 local storage nodes
@bash $(REPO_ROOT)/scripts/setup_dev_db.sh
@cd $(STORAGE_NODE_DIR) && ./scripts/dev/start_dev_nodes.sh
.PHONY: nodes-down
nodes-down: ## Stop the 5 local storage dev nodes
@bash $(STORAGE_NODE_DIR)/scripts/dev/stop_dev_nodes.sh
.PHONY: nodes-status
nodes-status: ## Check local storage node health and port status
@bash $(STORAGE_NODE_DIR)/scripts/dev/check_node_status.sh
.PHONY: nodes-reset
nodes-reset: ## Stop nodes and clean local node logs and pid files
@bash $(REPO_ROOT)/scripts/dev_nodes_reset.sh
# ---------------------------------------------------------------------------
# PROTO / CODEGEN
# ---------------------------------------------------------------------------
.PHONY: proto-guard
proto-guard: ## Verify protobuf sources are in sync
@bash scripts/guard_protos.sh
.PHONY: ci-scan
ci-scan: ## Run all CI gate scripts (flow assertions, symbol checks)
bash scripts/ci_scan.sh
bash scripts/flow_assertions.sh
bash scripts/flow_mapping_assertions.sh
.PHONY: flow-assertions
flow-assertions: ## Run flow assertion checks
bash scripts/flow_assertions.sh
bash scripts/flow_mapping_assertions.sh
.PHONY: flow-mapping-assertions
flow-mapping-assertions: ## Run flow mapping assertion checks
bash scripts/flow_mapping_assertions.sh
# ---------------------------------------------------------------------------
# RELEASE PREFLIGHT
# ---------------------------------------------------------------------------
.PHONY: release-preflight
release-preflight: ## Run the full pre-tag release gate (lint, test, audit, CI scan, PII scan, formal verification, frontend, SBOM)
@echo ""
@echo "╔══════════════════════════════════════════════════════════╗"
@echo "║ DSM Release Preflight ║"
@echo "╚══════════════════════════════════════════════════════════╝"
@echo ""
@echo "── [1/8] Lint ──────────────────────────────────────────────"
cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
@echo ""
@echo "── [2/8] Rust tests ────────────────────────────────────────"
cargo test --workspace --exclude dsm_storage_node -- --nocapture
cargo test -p dsm_storage_node --no-default-features --features local-dev,strict -- --nocapture
@echo ""
@echo "── [3/8] Security audit ────────────────────────────────────"
cargo deny check
bash ci/audit_every_lockfile.sh
@echo ""
@echo "── [4/8] Protocol purity (CI scan) ─────────────────────────"
bash scripts/ci_scan.sh
bash scripts/flow_assertions.sh
bash scripts/flow_mapping_assertions.sh
bash scripts/check_forbidden_symbols.sh
@echo ""
@echo "── [5/8] PII / personal info scan ──────────────────────────"
@PII_FOUND=0; \
echo " Checking for local file paths in source..."; \
PATH_HITS=$$(git grep -lE '/Users/[a-zA-Z]+/(Desktop|Documents|Downloads)' -- ':(exclude).claude/' ':(exclude)*.lock' 2>/dev/null | head -5); \
if [ -n "$$PATH_HITS" ]; then \
echo "FAIL: Local file paths found:"; echo "$$PATH_HITS"; PII_FOUND=1; \
fi; \
echo " Checking for hardcoded credentials..."; \
CRED_HITS=$$(git grep -lEi '(password|secret|api_key)\s*=\s*"[^"]{4,}"' -- ':(exclude).claude/' ':(exclude)*.lock' ':(exclude)*.md' ':(exclude)*.sh' 2>/dev/null | head -5); \
if [ -n "$$CRED_HITS" ]; then \
echo "FAIL: Hardcoded credentials found:"; echo "$$CRED_HITS"; PII_FOUND=1; \
fi; \
echo " Checking for SBOM scatter files..."; \
SCATTER=$$(find . -name 'dsm-*-beta*.json' -o -name 'dsm-*-preflight*.json' 2>/dev/null | grep -v node_modules | grep -v target | head -5); \
if [ -n "$$SCATTER" ]; then \
echo "FAIL: SBOM scatter files with local paths found:"; echo "$$SCATTER"; PII_FOUND=1; \
fi; \
if [ $$PII_FOUND -eq 0 ]; then echo "PII scan PASS: no personal info in tracked files"; \
else echo ""; echo "FAIL: Personal information detected. Clean before release."; exit 1; fi
@echo ""
@echo "── [6/8] Frontend (typecheck + test + build) ───────────────"
cd $(FRONTEND_DIR) && \
[ -s $$HOME/.nvm/nvm.sh ] && . $$HOME/.nvm/nvm.sh; \
nvm use --silent 2>/dev/null || true; \
npm run type-check && npm test -- --passWithNoTests && npm run build
@echo ""
@echo "── [7/8] Formal verification (TLA+ vertical validation) ────"
cargo run -p dsm_vertical_validation -- tla-check
@echo ""
@echo "── [8/8] SBOM generation ───────────────────────────────────"
bash scripts/generate-sbom.sh --run-id preflight-$$(date +%Y%m%d)
@echo ""
@echo "╔══════════════════════════════════════════════════════════╗"
@echo "║ ✓ All gates passed — ready to tag ║"
@echo "╚══════════════════════════════════════════════════════════╝"
# ---------------------------------------------------------------------------
# CLEAN
# ---------------------------------------------------------------------------
.PHONY: clean
clean: ## Remove all build artifacts
cargo clean
rm -rf $(FRONTEND_DIR)/dist $(FRONTEND_DIR)/build
cd $(ANDROID_DIR) && ./gradlew clean --no-daemon --console=plain
@echo "==> Clean complete."