Repository navigation
Expand file tree
/
Copy pathMakefile
More file actions
267 lines (234 loc) · 12.2 KB
/
Copy pathMakefile
File metadata and controls
267 lines (234 loc) · 12.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
# Single entry point for developer checks.
#
# The uv-managed CPython's dylib install-name breaks `cargo test` linking,
# so Rust tests link against the Homebrew Python instead.
HOMEBREW_PYTHON := /opt/homebrew/opt/python@3.13/bin/python3.13
PYO3_PYTHON ?= $(if $(wildcard $(HOMEBREW_PYTHON)),$(HOMEBREW_PYTHON),python3.13)
export PYO3_PYTHON
COOLDOWN_DAYS := 14
QUALIFICATION_DIR := target/qualification
PYTEST_ARGS ?=
# Command seams keep the normal targets readable and let tests prove that every
# qualification component fails closed without running or corrupting a real
# release build. Production and CI never override these defaults.
TEST_RUST ?= cargo test
TEST_PYTHON ?= uv run --no-sync pytest $(PYTEST_ARGS)
CHECK_LINT ?= $(MAKE) lint
CHECK_TYPECHECK ?= $(MAKE) typecheck
CHECK_TEST ?= $(MAKE) test
QUALIFY_PREPARE ?= rm -rf "$(QUALIFICATION_DIR)" && mkdir -p "$(QUALIFICATION_DIR)"
QUALIFY_SYNC ?= uv sync --all-groups --locked
QUALIFY_BUILD ?= $(MAKE) build
QUALIFY_CHECK ?= $(MAKE) check PYTEST_ARGS="--junitxml=$(QUALIFICATION_DIR)/pytest.xml"
QUALIFY_CONFORMANCE ?= $(MAKE) conformance
QUALIFY_G2 ?= $(MAKE) g2
QUALIFY_REPORT ?= uv run --no-sync python scripts/release-report.py --check-changelog
QUALIFY_COPY_EVIDENCE ?= cp conformance/conformance-results.json "$(QUALIFICATION_DIR)/conformance-results.json" && cp conformance/allocation-proof.json "$(QUALIFICATION_DIR)/allocation-proof.json"
QUALIFY_SUMMARY ?= uv run --no-sync python scripts/qualification-summary.py --junit "$(QUALIFICATION_DIR)/pytest.xml" --output "$(QUALIFICATION_DIR)/summary.json"
RELEASE_BENCH_VENV ?= .venv-release
RELEASE_BENCH_PYTHON := $(RELEASE_BENCH_VENV)/bin/python
RELEASE_PERF_VERIFY ?= $(RELEASE_BENCH_PYTHON) -I scripts/release_artifacts.py verify-release-wheel --directory release/dist --manifest release/verified-release.json --python-abi cp313-abi3 --output release/benchmark-wheel.verified.json
RELEASE_PERF_GUARD ?= UV_PROJECT_ENVIRONMENT=$(RELEASE_BENCH_VENV) $(MAKE) guard
RELEASE_PERF_AB ?= $(RELEASE_BENCH_PYTHON) -I benches/ab.py --current-venv $(RELEASE_BENCH_VENV)
RELEASE_PERF_REPORT ?= $(RELEASE_BENCH_PYTHON) -I benches/collect_report.py --python $(RELEASE_BENCH_PYTHON)
RELEASE_PERF_CHECK ?= $(RELEASE_BENCH_PYTHON) -I scripts/release-report.py --check-performance-evidence
# Opt-in build against the proposed msgspec Struct C API. The normal project
# environment and dependency pin stay untouched; this profile owns a separate
# source checkout, wheel directory, and virtual environment.
MSGSPEC_FASTPATH_COMMIT := 10c9ac4a8d0a9aacb7854a71f5cf479b47594736
MSGSPEC_FASTPATH_REPO := https://github.com/jcrist/msgspec.git
MSGSPEC_FASTPATH_PATCH := $(CURDIR)/patches/msgspec-0.21.1-struct-c-api.patch
FASTPATH_ROOT := target/fastpath
FASTPATH_MSGSPEC_SRC := $(FASTPATH_ROOT)/msgspec
FASTPATH_MSGSPEC_WHEELS := $(FASTPATH_ROOT)/msgspec-wheels
FASTPATH_TOON_WHEELS := $(FASTPATH_ROOT)/toon-wheels
FASTPATH_VENV := .venv-fastpath
FASTPATH_PYTHON := $(FASTPATH_VENV)/bin/python
# Two baselines, two jobs. STORY is what the optimization round bought — it is
# reported and never gated, so the ledger keeps its reference point. GUARD is
# the latest release; it is the only thing the gate compares against, because a
# distant baseline cannot detect a regression: measured, a 24% slowdown against
# STORY read as "+2.2%, no significant difference", since the current build led
# that baseline by 15-20%. Re-cut GUARD at every release or it decays into the
# same blind spot.
BASELINE_TAG := v0.1.0-conformant
# The public-release guard is explicit because this development repository also
# carries internal milestone tags that are not PyPI releases. The release
# process advances this one-line pointer; `benches/ab.py` reads the same file
# and refuses a guard built from any other tag.
GUARD_TAG := $(shell tr -d '[:space:]' < benches/GUARD_TAG)
.PHONY: lint typecheck test check build conformance qualify bench benchmark-env report public-report release-performance audit relock baseline guard ab ab-focused ab-exploratory ab-story g2 efficiency \
fastpath-source fastpath-build fastpath-check fastpath-bench fastpath-gates fastpath-clean
lint:
uv run --no-sync ruff check .
uv run --no-sync ruff format --check .
cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo clippy --all-targets --features alloc-stats -- -D warnings
typecheck:
uv run --no-sync mypy python/msgspec_toon
test:
$(TEST_RUST)
$(TEST_PYTHON)
# Offline-capable by design: the network-dependent cooldown audit is a
# separate target, run in CI and before releases.
check:
$(CHECK_LINT)
$(CHECK_TYPECHECK)
$(CHECK_TEST)
# Build the release extension into the environment that is actually imported.
# `.venv` installs this project editable, so `uv run` resolves
# `python/msgspec_toon/_native.abi3.so` and a separately pip-installed wheel is
# shadowed (and undone by the next re-sync). `maturin develop --release` writes
# the release abi3 build to that path, so what runs is what was measured.
# `benches/build_freshness.py` enforces it rather than trusting it.
build:
rm -f target/wheels/*.whl
uv run --no-sync maturin develop --release
conformance:
uv run --no-sync python conformance/run.py
# One release qualification definition. CI and release workflows invoke this
# target instead of maintaining smaller copies of the command list.
qualify:
$(QUALIFY_PREPARE)
$(QUALIFY_SYNC)
$(QUALIFY_BUILD)
$(QUALIFY_CHECK)
$(QUALIFY_CONFORMANCE)
$(QUALIFY_G2)
$(QUALIFY_REPORT)
$(QUALIFY_COPY_EVIDENCE)
$(QUALIFY_SUMMARY)
benchmark-env:
uv sync --group bench --locked
bench: benchmark-env build
uv run --no-sync python benches/collect_report.py
report: benchmark-env build
uv run --no-sync python benches/collect_report.py
uv run --no-sync python scripts/release-report.py
# R and its plotting packages are host tools, not project dependencies. This
# target consumes the generated JSON evidence and writes public SVG/PNG/Markdown.
public-report: report
Rscript .github/reporting/render_benchmarks.R conformance/report.json
# Canonical release-performance path for a candidate wheel already installed in
# RELEASE_BENCH_VENV. It builds the declared previous-release guard, runs the
# R-owned non-inferiority panel, then collects the vectorized absolute report.
# The GitHub release workflow invokes this exact target against its verified wheel.
release-performance:
@test -x "$(RELEASE_BENCH_PYTHON)"
$(RELEASE_PERF_VERIFY)
$(RELEASE_PERF_GUARD)
$(RELEASE_PERF_AB)
$(RELEASE_PERF_REPORT)
$(RELEASE_PERF_CHECK)
# Reproduce the upstream capsule experiment without changing `.venv` or the
# publishable `msgspec==0.21.1` dependency. The source is hash-pinned, the patch
# is repository-owned, and both packages are installed as release wheels.
fastpath-source:
mkdir -p $(FASTPATH_ROOT)
@if test ! -d "$(FASTPATH_MSGSPEC_SRC)/.git"; then \
git init "$(FASTPATH_MSGSPEC_SRC)"; \
git -C "$(FASTPATH_MSGSPEC_SRC)" remote add origin "$(MSGSPEC_FASTPATH_REPO)"; \
fi
@if ! git -C "$(FASTPATH_MSGSPEC_SRC)" cat-file -e "$(MSGSPEC_FASTPATH_COMMIT)^{commit}" 2>/dev/null; then \
git -C "$(FASTPATH_MSGSPEC_SRC)" fetch --depth 1 origin "$(MSGSPEC_FASTPATH_COMMIT)"; \
fi
git -C "$(FASTPATH_MSGSPEC_SRC)" checkout --detach "$(MSGSPEC_FASTPATH_COMMIT)"
@if git -C "$(FASTPATH_MSGSPEC_SRC)" apply --reverse --check "$(MSGSPEC_FASTPATH_PATCH)" 2>/dev/null; then \
echo "msgspec Struct C API patch already applied"; \
else \
git -C "$(FASTPATH_MSGSPEC_SRC)" apply "$(MSGSPEC_FASTPATH_PATCH)"; \
fi
@test "$$(git -C "$(FASTPATH_MSGSPEC_SRC)" rev-parse HEAD)" = "$(MSGSPEC_FASTPATH_COMMIT)"
git -C "$(FASTPATH_MSGSPEC_SRC)" diff --check
fastpath-build: fastpath-source
SETUPTOOLS_SCM_PRETEND_VERSION=0.21.1 uv build --wheel --clear \
--out-dir "$(FASTPATH_MSGSPEC_WHEELS)" "$(FASTPATH_MSGSPEC_SRC)"
mkdir -p "$(FASTPATH_TOON_WHEELS)"
find "$(FASTPATH_TOON_WHEELS)" -maxdepth 1 -type f -name '*.whl' -delete
uv run --no-sync maturin build --release --locked \
--features experimental-struct-offset-capi \
-o "$(FASTPATH_TOON_WHEELS)"
UV_PROJECT_ENVIRONMENT="$(FASTPATH_VENV)" uv sync --python 3.13 --all-groups --locked
uv pip install --python "$(FASTPATH_PYTHON)" --reinstall --no-deps \
$(FASTPATH_MSGSPEC_WHEELS)/*.whl $(FASTPATH_TOON_WHEELS)/*.whl
$(FASTPATH_PYTHON) -c "import msgspec, msgspec_toon; assert msgspec.__version__ == '0.21.1'; assert msgspec_toon.Encoder()._native._struct_access == 'capsule'; print('fast path active: msgspec 0.21.1 + capsule')"
fastpath-check: fastpath-build
$(MAKE) lint typecheck
cargo test
$(FASTPATH_PYTHON) -m pytest
$(FASTPATH_PYTHON) conformance/run.py
fastpath-bench: fastpath-build
$(FASTPATH_PYTHON) benches/bench_struct_capi.py
fastpath-gates: fastpath-build
$(FASTPATH_PYTHON) benches/bench_typed.py
fastpath-clean:
rm -rf "$(FASTPATH_VENV)" "$(FASTPATH_ROOT)"
# Build the frozen-baseline wheel ($(BASELINE_TAG)) into .venv-baseline so
# benches/ab.py can run same-session before/after comparisons.
baseline:
git worktree remove --force .baseline-src 2>/dev/null || true
rm -rf .baseline-src .venv-baseline target/baseline-wheels
git worktree add --detach .baseline-src $(BASELINE_TAG)
uv venv .venv-baseline --python 3.13
uv run --no-sync maturin build --release \
-m .baseline-src/Cargo.toml -o target/baseline-wheels
uv pip install --python .venv-baseline/bin/python target/baseline-wheels/*.whl \
python-toon==0.1.3 toons pytest
git worktree remove --force .baseline-src
# Build the guard wheel ($(GUARD_TAG)) into .venv-guard. This is what the gate
# measures against, so it must track the latest release.
guard:
git worktree remove --force .guard-src 2>/dev/null || true
rm -rf .guard-src .venv-guard target/guard-wheels
git worktree add --detach .guard-src $(GUARD_TAG)
uv venv .venv-guard --python 3.13
uv run --no-sync maturin build --release \
-m .guard-src/Cargo.toml -o target/guard-wheels
uv pip install --python .venv-guard/bin/python target/guard-wheels/*.whl \
python-toon==0.1.3 toons pytest
git worktree remove --force .guard-src
echo "$(GUARD_TAG)" > .venv-guard/GUARD_TAG
@echo "guard built from $(GUARD_TAG)"
# The preregistered release guard. Python writes raw timings; R makes the
# family-adjusted non-inferiority decision. No data-dependent confirmation.
ab:
uv run --no-sync python benches/ab.py
# The seconds-scale inner loop for the current distinct-key hypothesis and its
# two ordinary-object controls. The family is fixed in performance-manifest.json.
ab-focused:
uv run --no-sync python benches/ab.py --family distinct-key-hotfix
# The historical 100-cell grid is exploratory and cannot support a claim.
ab-exploratory:
uv run --no-sync python benches/ab.py --family full-exploratory --no-gate
# The story: what the optimization round bought, measured against the frozen
# tag. Reported, never gated — a distant baseline cannot police a regression.
ab-story:
uv run --no-sync python benches/ab.py --baseline-venv .venv-baseline --no-gate
# The efficiency lock: what canonical output costs. Deterministic and offline,
# so it also runs inside `make check` as an ordinary test.
efficiency:
uv run --no-sync python scripts/efficiency-lock.py
# The G2 proof runs against an instrumented wheel in its own environment, so
# the release wheel every benchmark measures stays free of counters. Same
# separation as `make baseline`: a second venv, never the working one.
g2:
rm -rf .venv-g2 target/g2-wheels
UV_PROJECT_ENVIRONMENT=.venv-g2 uv sync --python 3.13 --locked --no-install-project
uv run --no-sync maturin build --release --features alloc-stats -o target/g2-wheels
uv pip install --python .venv-g2/bin/python --reinstall --no-deps target/g2-wheels/*.whl
.venv-g2/bin/python -m pytest tests/test_typed_allocations.py -q
.venv-g2/bin/python scripts/allocation-proof.py
# Python needs no date plumbing: [tool.uv] exclude-newer = "14 days" makes
# every uv resolution rolling-compliant on its own. This target just
# re-resolves both ecosystems and re-audits.
relock:
uv lock
cargo update
$(MAKE) audit
# Fail if any resolved version in Cargo.lock, fuzz/Cargo.lock, or uv.lock is younger than
# $(COOLDOWN_DAYS) days (queries crates.io / PyPI; network required).
# This is the enforcement layer Cargo lacks natively, and a belt-and-
# suspenders double check on the uv side.
audit:
uv run --no-sync python scripts/check-package-ages.py --days $(COOLDOWN_DAYS)