-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMakefile
More file actions
485 lines (446 loc) · 24.9 KB
/
Copy pathMakefile
File metadata and controls
485 lines (446 loc) · 24.9 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
.PHONY: help all clean test test-doc coverage build release lint fmt check-fmt markdownlint nixie publish-check typecheck install-smoke installer-msrv-check release-installer-dry-run package-lints workflow-test workflow-test-deps test-workflow-contracts test-markdown-format test-glibc-baseline test-sccache-health verus kani verus-clone-detector kani-clone-detector spelling skill-frontmatter-lint skill-manifest-validate skill-metadata-check skill-manifest-check test-skill-metadata-check
# Make chooses the recipe shell itself, so the `shell: bash` default in the
# workflows does not reach recipes; the `/bin/sh` it would otherwise use
# rejects the `pipefail` that the validation targets require.
SHELL := bash
# Appended only on targets that invoke binaries commonly installed under these
# prefixes (cargo/bun/user-local), so the default recipe environment stays
# aligned with the caller's PATH.
TOOL_PATH_SUFFIX = $$HOME/.cargo/bin:$$HOME/.bun/bin:$$HOME/.local/bin
APP ?= whitaker-installer
PATH := $(HOME)/.cargo/bin:$(HOME)/.bun/bin:$(PATH)
CARGO ?= $(or $(shell command -v cargo 2>/dev/null),$(shell [ -x "$(HOME)/.cargo/bin/cargo" ] && echo "$(HOME)/.cargo/bin/cargo"))
CARGO_LOCKED ?=
BUILD_JOBS ?=
CARGO_FLAGS ?= --workspace --all-targets --all-features
TEST_EXCLUDES ?= --exclude rustc_ast --exclude rustc_attr_data_structures --exclude rustc_hir --exclude rustc_lint --exclude rustc_middle --exclude rustc_session --exclude rustc_span --exclude whitaker --exclude function_attrs_follow_docs --exclude module_max_lines --exclude no_expect_outside_tests
TEST_CARGO_FLAGS ?= $(CARGO_FLAGS) $(TEST_EXCLUDES)
# Doctests cannot run for a crate that links `rustc_private`. A doctest is
# compiled as its own crate without `#![feature(rustc_private)]`, so every
# lint crate, the `rustc_*` proxy shims, `clippy_utils`, the `whitaker` root,
# and the suite fail at `E0658` before a single assertion runs. The remaining
# four packages hold the documented public API and are the doctest lane.
DOCTEST_EXCLUDES ?= --exclude bumpy_road_function --exclude clippy_utils \
--exclude conditional_max_n_branches --exclude function_attrs_follow_docs \
--exclude module_max_lines --exclude module_must_have_inner_docs \
--exclude no_expect_outside_tests --exclude no_std_fs_operations \
--exclude no_unwrap_or_else_panic --exclude rstest_helper_should_be_fixture \
--exclude rustc_ast --exclude rustc_attr_data_structures --exclude rustc_hir \
--exclude rustc_lint --exclude rustc_middle --exclude rustc_session \
--exclude rustc_span --exclude test_must_not_have_example \
--exclude whitaker --exclude whitaker_suite
DOCTEST_CARGO_FLAGS ?= --workspace --all-features $(DOCTEST_EXCLUDES)
NEXTEST_PROFILE ?=
# The cargo test driver. `test` runs `cargo nextest run`; `coverage`
# overrides this with `cargo llvm-cov nextest ...` so instrumentation runs
# over the exact same crate subset (TEST_CARGO_FLAGS) and RUSTFLAGS.
TEST_RUNNER ?= nextest run
COVERAGE_OUTPUT ?= lcov.info
# Both the outer coverage driver and nested Cargo invocations must use this
# exact directory. cargo-llvm-cov otherwise passes its target only as a
# Nextest argument, which Dylint's nested Cargo build cannot observe.
COVERAGE_TARGET_DIR ?= $(CURDIR)/target/llvm-cov-target
RUST_FLAGS ?= -D warnings
# The installer MSRV check and the publish check each build in a scratch
# tree they discard afterwards. The trees live at fixed paths rather than
# under `mktemp`, because sccache keys a compilation on its absolute paths: a
# directory named afresh on every run made every one of those compilations a
# miss on every run, 340 Rust misses a run in `linux-full`.
#
# They stay outside the workspace, as the `mktemp` directories were: Cargo
# walks up from a package it installs, and a packaged crate extracted under
# this checkout finds the root `Cargo.toml` and refuses to build.
#
# The root is the user's own cache directory rather than a shared temporary
# directory, so no other user can pre-create or swap a tree these recipes
# clear. Each name ends in the first 16 hex digits of the SHA-256 of this
# checkout's path: two checkouts on one host never share a tree, however
# their paths are spelled, one checkout gets the same tree on every run, and
# the name stays short however deep the checkout sits.
SCRATCH_ROOT ?= $(or $(XDG_CACHE_HOME),$(HOME)/.cache)/whitaker/scratch
SCRATCH_ID := $(shell printf '%s' '$(CURDIR)' | { sha256sum 2>/dev/null || shasum -a 256; } | cut -c1-16)
INSTALLER_MSRV_DIR ?= $(SCRATCH_ROOT)/installer-msrv-$(SCRATCH_ID)
PUBLISH_CHECK_DIR ?= $(SCRATCH_ROOT)/publish-check-$(SCRATCH_ID)
# Run before either recipe clears its tree. `XDG_CACHE_HOME` can point
# anywhere, so the root's location alone proves nothing: it is created
# private, and then it must belong to this user and be writable by nobody
# else, or the recipe stops before `rm -rf` touches anything.
SCRATCH_ROOT_CHECK = mkdir -p -m 700 "$(SCRATCH_ROOT)"; \
if [ -z "$$(find "$(SCRATCH_ROOT)" -maxdepth 0 -user "$$(id -u)" ! -perm -g=w ! -perm -o=w)" ]; then \
echo "$(SCRATCH_ROOT) must belong to $$(id -un) and be writable by no one else" >&2; \
exit 1; \
fi
RUSTDOC_FLAGS ?= --cfg docsrs -D warnings
MDLINT ?= $(shell command -v markdownlint-cli2 2>/dev/null || printf '%s' "$$HOME/.bun/bin/markdownlint-cli2")
# `make fmt` and `make check-fmt` call mdtablefix directly. `--git` selects the
# Markdown files Git tracks and `--include-untracked` adds the untracked files
# Git does not ignore, so a new document is formatted before it is staged.
# Both modes need mdtablefix 0.6.0 or later; CI pins the version at the
# install-mdtablefix step.
MDTABLEFIX_SELECT = --git --include-untracked
MDTABLEFIX_RULES = --wrap --renumber --breaks --ellipsis --fences
MDTABLEFIX ?= mdtablefix
NIXIE ?= nixie
WHITAKER_REPO ?= $(CURDIR)
WHITAKER_REV ?= HEAD
PUBLISH_PACKAGES ?=
UV ?= uv
UV_ENV = UV_CACHE_DIR=.uv-cache UV_TOOL_DIR=.uv-tools
TYPOS_CONFIG_BUILDER_VERSION ?= v0.1.3
TYPOS_CONFIG_BUILDER = $(UV_ENV) $(UV) tool run --python 3.14 --from \
"git+https://github.com/leynos/typos-config-builder.git@$(TYPOS_CONFIG_BUILDER_VERSION)" \
typos-config-builder
# Agent Skills manifest validation. `lint` depends on `skill-manifest-check`,
# so a malformed manifest fails the standard gate rather than shipping. The
# pins live here rather than in a project manifest because this repository has
# no Python project; they run through uv like the other pins.
SKILL_DIRS ?= $(sort $(dir $(wildcard skills/*/SKILL.md)))
YAMLLINT_VERSION ?= 1.38.0
# Mirrors the inline `pyyaml` pin in `scripts/check_skill_metadata.py`; the
# unit tests resolve this one with `uv run --with`.
PYYAML_VERSION ?= 6.0.2
SKILLS_REF_COMMIT := 69ef37e9424c0a7ea9dd2293b559e43ec8176379
SKILLS_REF_SOURCE := git+https://github.com/agentskills/agentskills.git@$(SKILLS_REF_COMMIT)\#subdirectory=skills-ref
YAMLLINT := $(UV_ENV) $(UV) tool run yamllint@$(YAMLLINT_VERSION)
SKILLS_REF := $(UV_ENV) $(UV) tool run --python 3.14 \
--from "$(SKILLS_REF_SOURCE)" skills-ref
# A manifest `description` is legitimately one long line, so the default
# `line-length` rule is disabled rather than wrapping the frontmatter.
SKILL_YAMLLINT_CONFIG := {extends: default, rules: {line-length: disable}}
WORKFLOW_TEST_VENV ?= .venv
LINT_CRATES ?= bumpy_road_function conditional_max_n_branches function_attrs_follow_docs module_max_lines module_must_have_inner_docs no_expect_outside_tests test_must_not_have_example no_std_fs_operations no_unwrap_or_else_panic whitaker_suite
CARGO_DYLINT_VERSION ?= 6.0.1
DYLINT_LINK_VERSION ?= 6.0.1
# The pinned host tools are downloaded once into a durable directory rather
# than a temporary root, so CI can cache them under one owner and a warm run
# observes a hit instead of repeating the download.
DYLINT_TOOLS_DIR ?= $(HOME)/.cache/whitaker-dylint-tools
# Retained only so the provisioning script's argument contract is unchanged.
# The host tools are now downloaded as verified prebuilt binaries, so no
# toolchain participates in their installation.
DYLINT_TOOLS_TOOLCHAIN ?= stable
WHITAKER_SCRIPT ?= $(HOME)/.local/bin/whitaker
# Markdown sources checked by `check-fmt`. Keep generated output and local tool
# caches out of this list; the checker itself never writes to these sources.
MD_FILES_FIND = find . \
\( -type d \( -name .git -o -name target -o -name .venv -o \
-name .uv-cache -o -name .uv-tools -o -name .pytest_cache -o \
-name .vtcode -o -name memories -o -name node_modules \) -prune \) -o \
\( -type f -name '*.md' -print0 \)
build: target/debug/$(APP) ## Build debug binary
release: target/release/$(APP) ## Build release binary
all: release ## Default target builds release binary
clean: ## Remove build artefacts
$(CARGO) clean
rm -rf .uv-cache .uv-tools
test: ## Run tests with warnings treated as errors
@export PATH="$$PATH:$(TOOL_PATH_SUFFIX)"; command -v cargo-nextest >/dev/null || { echo "Install cargo-nextest (cargo install cargo-nextest)"; exit 1; }
@# Prefer dynamic linking during local `cargo test` runs to avoid rustc_private
@# linkage pitfalls when building cdylib-based lints; `publish-check` omits
@# this flag to exercise production-like linking behaviour.
@# Run tests with backup/restore safeguard in a single shell with trap
@# to ensure cleanup runs even when tests fail.
@set -eu; \
export PATH="$$PATH:$(TOOL_PATH_SUFFIX)"; \
WHITAKER_BACKUP=""; \
HAD_WHITAKER=false; \
cleanup() { \
EXIT_CODE=$$?; \
if [ -n "$$WHITAKER_BACKUP" ] && [ -f "$$WHITAKER_BACKUP" ]; then \
if [ "$$HAD_WHITAKER" = "true" ]; then \
if [ ! -f "$(WHITAKER_SCRIPT)" ] || ! diff -q "$(WHITAKER_SCRIPT)" "$$WHITAKER_BACKUP" >/dev/null 2>&1; then \
echo "ERROR: Tests modified $(WHITAKER_SCRIPT) - restoring backup"; \
cp "$$WHITAKER_BACKUP" "$(WHITAKER_SCRIPT)"; \
rm -f "$$WHITAKER_BACKUP"; \
exit 1; \
fi; \
fi; \
rm -f "$$WHITAKER_BACKUP"; \
elif [ "$$HAD_WHITAKER" = "false" ] && [ -f "$(WHITAKER_SCRIPT)" ]; then \
echo "ERROR: Tests created $(WHITAKER_SCRIPT) (file did not exist before tests)"; \
if [ -n "$${CI:-}" ] || [ -n "$${WHITAKER_TEST_STRICT:-}" ]; then \
echo "Cleaning up $(WHITAKER_SCRIPT) because strict test mode is enabled (CI/WHITAKER_TEST_STRICT)"; \
rm -f "$(WHITAKER_SCRIPT)"; \
else \
echo "Leaving $(WHITAKER_SCRIPT) in place (not running under CI; set WHITAKER_TEST_STRICT=1 to enforce cleanup)"; \
fi; \
exit 1; \
fi; \
exit $$EXIT_CODE; \
}; \
trap cleanup EXIT; \
WHITAKER_BACKUP=$$(mktemp "$${TMPDIR:-/tmp}/.whitaker-test-backup-XXXXXX"); \
if cp "$(WHITAKER_SCRIPT)" "$$WHITAKER_BACKUP" 2>/dev/null; then \
HAD_WHITAKER=true; \
else \
rm -f "$$WHITAKER_BACKUP"; \
WHITAKER_BACKUP=""; \
fi; \
RUSTFLAGS="-C prefer-dynamic -Z force-unstable-if-unmarked $(RUST_FLAGS)" $(CARGO) $(TEST_RUNNER) $(CARGO_LOCKED) $(TEST_CARGO_FLAGS) $(BUILD_JOBS) $(if $(NEXTEST_PROFILE),--profile $(NEXTEST_PROFILE)); \
if [ "$${ACT_WORKFLOW_TESTS:-0}" = "1" ]; then \
$(MAKE) workflow-test; \
fi
coverage: ## Generate LCOV coverage over the CI-tested crate subset
@# Reuse the `test` recipe verbatim (same TEST_CARGO_FLAGS excludes,
@# same prefer-dynamic RUSTFLAGS, same WHITAKER_SCRIPT safeguard) but
@# swap the driver to `cargo llvm-cov nextest`. This keeps the
@# instrumented run in lockstep with the plain test run: the 11
@# CI-excluded crates (rustc_* proxy shims, the whitaker root, and the
@# three lint crates whose dylint UI tests are excluded) stay excluded,
@# so coverage never attempts a bare `--workspace` build the suite
@# cannot support.
@export PATH="$$PATH:$(TOOL_PATH_SUFFIX)"; command -v cargo-llvm-cov >/dev/null || { echo "Install cargo-llvm-cov (cargo install cargo-llvm-cov)"; exit 1; }
@CARGO_LLVM_COV_TARGET_DIR="$(COVERAGE_TARGET_DIR)" \
CARGO_TARGET_DIR="$(COVERAGE_TARGET_DIR)" \
$(MAKE) test TEST_RUNNER="llvm-cov nextest --lcov --output-path $(COVERAGE_OUTPUT)"
test-doc: ## Run the workspace doctests, which no other lane executes
@# `cargo llvm-cov nextest` runs no doctests and `--all-targets` excludes
@# them, so this target is the only place they run. The RUSTFLAGS here
@# deliberately omit the `-C prefer-dynamic -Z force-unstable-if-unmarked`
@# pair the test lane uses: a doctest is compiled as a separate crate, and
@# `force-unstable-if-unmarked` then makes every one of them fail to load
@# its own library at `E0658`.
RUSTFLAGS="$(RUST_FLAGS)" $(CARGO) test --doc $(CARGO_LOCKED) $(DOCTEST_CARGO_FLAGS) $(BUILD_JOBS)
workflow-test: workflow-test-deps ## Run opt-in GitHub workflow smoke tests with act + pytest
@export PATH="$$PATH:$(TOOL_PATH_SUFFIX)"; command -v act >/dev/null || { echo "Install act to run workflow tests"; exit 1; }
@export PATH="$$PATH:$(TOOL_PATH_SUFFIX)"; command -v $(UV) >/dev/null || { echo "uv is required for workflow tests"; exit 1; }
@test -x "$(WORKFLOW_TEST_VENV)/bin/python" || { \
echo "workflow-test virtualenv is missing or invalid:"; \
echo " expected: $(WORKFLOW_TEST_VENV)/bin/python"; \
echo "Run 'make workflow-test-deps' to create or refresh the virtualenv."; \
exit 1; \
}
@ACT_WORKFLOW_TESTS=1 $(WORKFLOW_TEST_VENV)/bin/python -m pytest tests/workflows
test-workflow-contracts: ## Validate the mutation-testing caller contract
@export PATH="$$PATH:$(TOOL_PATH_SUFFIX)"; command -v $(UV) >/dev/null || { echo "uv is required for workflow contract tests"; exit 1; }
@export PATH="$$PATH:$(TOOL_PATH_SUFFIX)"; $(UV) run --with 'pytest>=8' --with 'pyyaml>=6' --with 'hypothesis>=6' pytest tests/workflow_contracts --doctest-modules -q
test-markdown-format: ## Validate the Markdown formatter checker
@PYTHONPATH=scripts $(UV_ENV) $(UV) run --no-project --python 3.14 \
--with pytest==9.0.2 --with hypothesis==6.151.9 \
python -m pytest scripts/tests/test_check_markdown_format.py -c /dev/null \
--rootdir=. -p no:cacheprovider
test-glibc-baseline: ## Validate the Linux release glibc-baseline checker
@$(UV_ENV) $(UV) run --no-project --python 3.14 \
--with pytest==9.0.2 --with hypothesis==6.151.9 \
python -m pytest scripts/tests/test_check_glibc_baseline.py -c /dev/null \
--rootdir=. -p no:cacheprovider
test-sccache-health: ## Validate the sccache health checker the gha lanes run
@$(UV_ENV) $(UV) run --no-project --python 3.14 \
--with pytest==9.0.2 --with hypothesis==6.151.9 \
python -m pytest scripts/tests/test_check_sccache_health.py -c /dev/null \
--rootdir=. -p no:cacheprovider --doctest-modules \
scripts/check_sccache_health.py
test-skill-metadata-check: ## Validate the Agent Skills metadata checker
@$(UV_ENV) $(UV) run --no-project --python 3.14 \
--with pyyaml==$(PYYAML_VERSION) --with pytest==9.0.2 \
python -m pytest scripts/tests/test_check_skill_metadata.py -c /dev/null \
--rootdir=. -p no:cacheprovider
workflow-test-deps: ## Install Python dependencies for workflow tests
@export PATH="$$PATH:$(TOOL_PATH_SUFFIX)"; command -v $(UV) >/dev/null || { echo "uv is required for workflow tests"; exit 1; }
@export PATH="$$PATH:$(TOOL_PATH_SUFFIX)"; $(UV) venv --allow-existing $(WORKFLOW_TEST_VENV)
@export PATH="$$PATH:$(TOOL_PATH_SUFFIX)"; $(UV) pip install --python $(WORKFLOW_TEST_VENV)/bin/python -r tests/workflows/requirements.txt
target/%/$(APP): ## Build binary in debug or release mode
manifest=$$(grep -l whitaker-installer */Cargo.toml crates/*/Cargo.toml); \
$(CARGO) build $(CARGO_LOCKED) $(BUILD_JOBS) $(if $(findstring release,$(@)),--release) --bin $(APP) --manifest-path "$$manifest"
skill-frontmatter-lint: ## Lint the YAML frontmatter of every skill manifest
@# `errexit` and `pipefail` fail the target on the first offending manifest.
@# Without them the loop exits with the status of its final iteration, so a
@# conformant trailing skill masks a malformed earlier one, and an unreadable
@# manifest is reported only by the pipeline.
@set -euo pipefail; for skill_dir in $(SKILL_DIRS); do \
skill_file="$${skill_dir%/}/SKILL.md"; \
echo "yamllint $$skill_file frontmatter"; \
awk 'NR == 1 { if ($$0 != "---") exit 1; print; next } $$0 == "---" { found = 1; print; exit } { print } END { if (!found) exit 1 }' "$$skill_file" | $(YAMLLINT) -d '$(SKILL_YAMLLINT_CONFIG)' -; \
done
skill-manifest-validate: ## Validate every skill directory against the Agent Skills schema
@set -eu; for skill_dir in $(SKILL_DIRS); do \
echo "skills-ref validate $$skill_dir"; \
$(SKILLS_REF) validate "$$skill_dir"; \
done
skill-metadata-check: ## Reject skill metadata that is not a mapping of strings
@# `set -e` stops the loop on the first offending manifest; without it the
@# loop exits with the status of its final iteration, so a conformant
@# trailing skill masks a malformed earlier one.
@set -eu; $(UV_ENV) $(UV) run --no-project --python 3.14 \
scripts/check_skill_metadata.py $(SKILL_DIRS)
skill-manifest-check: skill-frontmatter-lint skill-manifest-validate skill-metadata-check ## Validate every shipped skill manifest
lint: skill-manifest-check ## Run Clippy with warnings denied
RUSTDOCFLAGS="$(RUSTDOC_FLAGS)" $(CARGO) doc $(CARGO_LOCKED) --workspace --no-deps
$(CARGO) clippy $(CARGO_LOCKED) $(CARGO_FLAGS) -- $(RUST_FLAGS)
fmt: ## Format Rust and Markdown sources
$(CARGO) fmt --all
export PATH="$$PATH:$(TOOL_PATH_SUFFIX)"; $(MDTABLEFIX) --in-place $(MDTABLEFIX_SELECT) $(MDTABLEFIX_RULES)
export PATH="$$PATH:$(TOOL_PATH_SUFFIX)"; $(MDLINT) --fix "**/*.md"
check-fmt: ## Verify formatting
$(CARGO) fmt --all -- --check
$(MDTABLEFIX) --check $(MDTABLEFIX_SELECT) $(MDTABLEFIX_RULES)
markdownlint: spelling ## Lint Markdown files and enforce spelling
export PATH="$$PATH:$(TOOL_PATH_SUFFIX)"; $(MDLINT) '**/*.md' '!**/.uv-cache/**' '!**/.uv-tools/**'
spelling: ## Enforce en-GB-oxendict in tracked text
$(TYPOS_CONFIG_BUILDER) gate --repository . --scope all
nixie:
# CI currently requires --no-sandbox; remove once nixie supports
# environment variable control for this option
export PATH="$$PATH:$(TOOL_PATH_SUFFIX)"; $(NIXIE) --no-sandbox
typecheck:
RUSTFLAGS="-C prefer-dynamic -Z force-unstable-if-unmarked $(RUST_FLAGS)" $(CARGO) check $(CARGO_LOCKED) $(CARGO_FLAGS)
verus: ## Run the pinned Verus proof sidecar
./scripts/run-verus.sh
verus-clone-detector: ## Run clone-detector Verus proofs
./scripts/run-verus.sh clone-detector
kani: ## Run practical Kani sidecar harnesses
./scripts/run-kani.sh
kani-clone-detector: ## Run clone-detector Kani harnesses
./scripts/run-kani.sh clone-detector
install-smoke: ## Install whitaker-installer and verify basic functionality
set -eu; \
TMP_DIR=$$(mktemp -d); \
trap 'rm -rf "$$TMP_DIR"' 0 INT TERM HUP; \
$(CARGO) install --path installer --root "$$TMP_DIR" --locked; \
export PATH="$$TMP_DIR/bin:$$PATH"; \
SYSROOT=$$(rustc --print sysroot); \
HOST_TRIPLE=$$(rustc -vV | awk -F ': ' '/host:/ {print $$2}'); \
RUSTLIB_DIR="$$SYSROOT/lib/rustlib/$$HOST_TRIPLE/lib"; \
export LD_LIBRARY_PATH="$$RUSTLIB_DIR:$${LD_LIBRARY_PATH:-}"; \
command -v whitaker-installer >/dev/null; \
whitaker-installer --help >/dev/null; \
whitaker-installer --version >/dev/null
installer-msrv-check: ## Install whitaker-installer with its declared MSRV
set -eu; \
$(SCRATCH_ROOT_CHECK); \
TMP_DIR="$(INSTALLER_MSRV_DIR)"; \
rm -rf -- "$$TMP_DIR"; \
mkdir -p "$$TMP_DIR"; \
trap 'rm -rf -- "$$TMP_DIR"' EXIT INT TERM HUP; \
CARGO_TARGET_DIR="$$TMP_DIR/target" $(CARGO) +1.85.0 package --locked -p whitaker-installer --allow-dirty; \
set -- "$$TMP_DIR"/target/package/whitaker-installer-*.crate; \
if [ "$$#" -ne 1 ] || [ ! -f "$$1" ]; then \
echo "Expected exactly one packaged whitaker-installer crate"; \
exit 1; \
fi; \
PACKAGE_ARCHIVE="$$1"; \
PACKAGE_SOURCE_DIR="$$TMP_DIR/package-source"; \
mkdir -p "$$PACKAGE_SOURCE_DIR"; \
tar -xzf "$$PACKAGE_ARCHIVE" -C "$$PACKAGE_SOURCE_DIR"; \
PACKAGE_ROOT=$$(find "$$PACKAGE_SOURCE_DIR" -mindepth 1 -maxdepth 1 -type d -name 'whitaker-installer-*' -print -quit); \
if [ -z "$$PACKAGE_ROOT" ]; then \
echo "Packaged whitaker-installer crate did not contain a source directory"; \
exit 1; \
fi; \
$(CARGO) +1.85.0 install --locked --path "$$PACKAGE_ROOT" --root "$$TMP_DIR"; \
"$$TMP_DIR/bin/whitaker-installer" --version >/dev/null
release-installer-dry-run: ## Build and package the host-platform installer archive
set -eu; \
[ -n "$(CARGO)" ] || { echo "Install cargo to run release-installer-dry-run"; exit 1; }; \
for tool in awk jq mktemp rustc uv; do \
command -v "$$tool" >/dev/null || { echo "Install $$tool to run release-installer-dry-run"; exit 1; }; \
done; \
TMP_DIR=$$(mktemp -d); \
trap 'rm -rf "$$TMP_DIR"' 0 INT TERM HUP; \
HOST_TRIPLE=$$(rustc -vV | awk -F ': ' '/host:/ {print $$2}'); \
VERSION=$$($(CARGO) metadata $(CARGO_LOCKED) --manifest-path installer/Cargo.toml --no-deps --format-version 1 | jq -r '.packages[] | select(.name == "whitaker-installer") | .version'); \
if [ -z "$$VERSION" ]; then \
echo "Failed to extract whitaker-installer version from Cargo metadata"; \
exit 1; \
fi; \
$(CARGO) build $(CARGO_LOCKED) $(BUILD_JOBS) -p whitaker-installer --release --target "$$HOST_TRIPLE"; \
$(CARGO) build $(CARGO_LOCKED) $(BUILD_JOBS) --release -p whitaker-installer --bin whitaker-package-installer --target "$$HOST_TRIPLE"; \
DIST_DIR="$$TMP_DIR/dist"; \
mkdir -p "$$DIST_DIR"; \
case "$$HOST_TRIPLE" in \
*windows*) \
INSTALLER_BIN="target/$$HOST_TRIPLE/release/whitaker-installer.exe"; \
PACKAGER="./target/$$HOST_TRIPLE/release/whitaker-package-installer.exe"; \
ARCHIVE_GLOB="$$DIST_DIR/*.zip"; \
;; \
*) \
INSTALLER_BIN="target/$$HOST_TRIPLE/release/whitaker-installer"; \
PACKAGER="./target/$$HOST_TRIPLE/release/whitaker-package-installer"; \
ARCHIVE_GLOB="$$DIST_DIR/*.tgz"; \
;; \
esac; \
"$$PACKAGER" \
--crate-version "$$VERSION" \
--target "$$HOST_TRIPLE" \
--binary-path "$$INSTALLER_BIN" \
--output-dir "$$DIST_DIR"; \
scripts/generate_checksums.py "$$DIST_DIR"; \
found_archive=false; \
for archive in $$ARCHIVE_GLOB; do \
if [ -f "$$archive" ]; then \
found_archive=true; \
break; \
fi; \
done; \
if [ "$$found_archive" != "true" ]; then \
echo "Expected installer archive matching $$ARCHIVE_GLOB"; \
exit 1; \
fi; \
found_checksum=false; \
for checksum in "$$DIST_DIR"/*.sha256; do \
if [ -f "$$checksum" ]; then \
found_checksum=true; \
break; \
fi; \
done; \
if [ "$$found_checksum" != "true" ]; then \
echo "Expected installer checksum in $$DIST_DIR"; \
exit 1; \
fi
# The Rust suite is executed once per pull request, by the coverage lane.
# This target therefore builds and validates the packages without re-running
# it; see "One gate per executed test set" in the developers' guide.
publish-check: ## Build and validate packages before publishing
set -eu; \
export PATH="$$PATH:$(TOOL_PATH_SUFFIX)"; \
PINNED_TOOLCHAIN=$$(awk -F '\"' '/^channel/ {print $$2}' rust-toolchain.toml); \
TOOLCHAIN="$$PINNED_TOOLCHAIN"; \
ORIG_DIR="$(CURDIR)"; \
rustup component add --toolchain "$$TOOLCHAIN" rust-src rustc-dev llvm-tools-preview; \
RUSTFLAGS="$(RUST_FLAGS)" $(CARGO) build $(CARGO_LOCKED) --workspace --all-features $(BUILD_JOBS); \
$(SCRATCH_ROOT_CHECK); \
TMP_DIR="$(PUBLISH_CHECK_DIR)"; \
rm -rf -- "$$TMP_DIR"; \
mkdir -p "$$TMP_DIR"; \
trap 'rm -rf "$$TMP_DIR"' 0 INT TERM HUP; \
DYLINT_TOOLS_DIR="$(DYLINT_TOOLS_DIR)"; \
mkdir -p "$$DYLINT_TOOLS_DIR/bin"; \
export PATH="$$DYLINT_TOOLS_DIR/bin:$$PATH"; \
scripts/install-dylint-tools.sh "$$DYLINT_TOOLS_DIR" "$(CARGO_DYLINT_VERSION)" "$(DYLINT_LINK_VERSION)" "$(CARGO)" "$(DYLINT_TOOLS_TOOLCHAIN)"; \
TARGET_DIR="$$TMP_DIR/target"; \
git clone "$(WHITAKER_REPO)" "$$TMP_DIR/whitaker-src"; \
cd "$$TMP_DIR/whitaker-src" || exit 1; \
{ \
CLONE_HEAD=$$(git rev-parse HEAD); \
TARGET_REV=$${GIT_TAG:-$${WHITAKER_REV:-$$CLONE_HEAD}}; \
git checkout "$$TARGET_REV"; \
for lint in $(LINT_CRATES); do \
CARGO_TARGET_DIR="$$TARGET_DIR" RUSTFLAGS="$(RUST_FLAGS)" $(CARGO) +$$TOOLCHAIN build $(CARGO_LOCKED) --release --features dylint-driver -p $$lint; \
mkdir -p "$$TARGET_DIR/dylint/libraries/$$TOOLCHAIN/release"; \
cp "$$TARGET_DIR/release/lib$$lint.so" "$$TARGET_DIR/dylint/libraries/$$TOOLCHAIN/release/lib$$lint@$$TOOLCHAIN.so"; \
done; \
DYLINT_LIBRARY_PATH="$$TARGET_DIR/dylint/libraries/$$TOOLCHAIN/release" CARGO_TARGET_DIR="$$TARGET_DIR" $(CARGO) +$$TOOLCHAIN dylint list --no-metadata --no-build; \
}; \
cd "$$ORIG_DIR"; \
for crate in $(PUBLISH_PACKAGES); do \
$(CARGO) package $(CARGO_LOCKED) -p $$crate --allow-dirty; \
done
package-lints: ## Build lint crates and package as .tar.zst archives
set -eu; \
TOOLCHAIN=$$(awk -F '"' '/^channel/ {print $$2}' rust-toolchain.toml); \
HOST_TRIPLE=$$(rustc -vV | awk -F ': ' '/host:/ {print $$2}'); \
SHA=$$(git rev-parse --short HEAD); \
DIST_DIR="$(CURDIR)/dist"; \
mkdir -p "$$DIST_DIR"; \
for lint in $(LINT_CRATES); do \
RUSTFLAGS="$(RUST_FLAGS)" $(CARGO) +$$TOOLCHAIN build --release --features dylint-driver -p $$lint; \
done; \
$(CARGO) run -p whitaker-installer --bin whitaker-package-lints -- \
--git-sha "$$SHA" \
--toolchain "$$TOOLCHAIN" \
--target "$$HOST_TRIPLE" \
--output-dir "$$DIST_DIR" \
--release-dir target/release
help: ## Show available targets
@grep -E '^[a-zA-Z_-]+:.*?##' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS=":"; printf "Available targets:\n"} {printf " %-20s %s\n", $$1, $$2}'