-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathMakefile
More file actions
446 lines (382 loc) Β· 17.6 KB
/
Copy pathMakefile
File metadata and controls
446 lines (382 loc) Β· 17.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
# CPEX β Rust workspace Makefile
# =============================================================================
# Targets mirror CI (.github/workflows/) so a green `make ci` locally means a
# green pipeline. The CPEX Python package now lives on the `0.1.x` branch.
SHELL := /bin/bash
.SHELLFLAGS := -eu -o pipefail -c
CARGO ?= cargo
GO ?= go
GO_DIR = go/cpex
GO_EXAMPLES_DIR = examples/go-demo
HUGO ?= hugo
DOCS_DIR = docs
DOCS_PORT ?= 1313
GOLANGCI_LINT ?= golangci-lint
# =============================================================================
# Help
# =============================================================================
.PHONY: help
help:
@echo "CPEX (Rust) β Makefile"
@echo ""
@echo "Build:"
@echo " build Build the workspace (debug)"
@echo " build-release Build the workspace (release, size-optimized)"
@echo " check cargo check the workspace"
@echo " clean Remove the target/ directory"
@echo ""
@echo "Lint & format:"
@echo " fmt Format Rust code (cargo fmt --all)"
@echo " lint CI lint gate: fmt --check + clippy -D warnings"
@echo " clippy Run clippy on the workspace (-D warnings)"
@echo " lint-fix Auto-fix: cargo fmt + clippy --fix"
@echo " machete Report unused dependencies (advisory)"
@echo ""
@echo "Test:"
@echo " test Run all workspace tests"
@echo " test-ffi Run only the cpex-ffi crate tests"
@echo " test-all Rust tests + Go tests (with -race)"
@echo " test-python-e2e Python host e2e (#[ignore]d); needs CPEX_PYTHON_SOURCE."
@echo " Skips FAIL here β this lane must really run."
@echo ""
@echo "Supply chain & coverage:"
@echo " audit cargo deny check (advisories, licenses, bans, sources)"
@echo " coverage Line/region coverage summary (cargo-llvm-cov; report only)"
@echo ""
@echo "Docs:"
@echo " doc Build API docs (rustdoc, -D warnings)"
@echo " docs Build the Hugo documentation site"
@echo " docs-serve Hugo dev server with live reload"
@echo " docs-clean Remove generated documentation artifacts"
@echo ""
@echo "Go bindings (go/cpex):"
@echo " go-build go-test go-test-race go-fmt go-vet go-lint-check go-lint-fix"
@echo ""
@echo "Python bindings (bindings/python β requires maturin):"
@echo " bindings-python-build Build and install Python bindings (debug)"
@echo " bindings-python-build-release Build Python bindings wheel (release)"
@echo " bindings-python-test Build + run Python binding tests"
@echo ""
@echo "Examples:"
@echo " examples-build Build all Rust + Go examples (catches stale APIs)"
@echo " examples-run Run all examples end-to-end"
@echo ""
@echo "End-to-end:"
@echo " ci Lint + tests + examples-build (CI gate)"
@echo ""
@echo "Release (version bump + tag locally; CI publishes on tag push):"
@echo " release-dry Preview the release (no changes)"
@echo " release-version Set the version everywhere (no commit/tag)"
@echo " release Bump + commit + tag (then: git push origin vX.Y.Z)"
@echo " publish-dry Local packaging dry-run (mirrors CI dry-run)"
@echo " Pass LEVEL=alpha|patch|minor|major|rc|release or VERSION=X.Y.Z"
@echo " tag Tag + push all 3 release tags: make tag VERSION=X.Y.Z"
# =============================================================================
# Build
# =============================================================================
.PHONY: build
build:
@$(CARGO) build --workspace
.PHONY: build-release
build-release:
@$(CARGO) build --release --workspace
.PHONY: check
check:
@$(CARGO) check --workspace
.PHONY: clean
clean:
@$(CARGO) clean
# =============================================================================
# Lint & format
# =============================================================================
.PHONY: fmt
fmt:
@$(CARGO) fmt --all
.PHONY: clippy
clippy:
@$(CARGO) clippy --workspace --all-targets -- -D warnings
# CI-safe gate: read-only fmt check + clippy. Lint levels come from the
# [workspace.lints] wall in Cargo.toml.
.PHONY: lint
lint:
@echo "π¦ fmt --check + clippy -D warnings ..."
@$(CARGO) fmt --all -- --check
@$(CARGO) clippy --workspace --all-targets -- -D warnings
@echo "β
lint passed"
# Developer convenience: format, then apply clippy's machine-applicable fixes.
.PHONY: lint-fix
lint-fix:
@$(CARGO) fmt --all
@$(CARGO) clippy --workspace --all-targets --fix --allow-dirty --allow-staged -- -D warnings
# Advisory: cargo-machete static analysis false-positives on macro/derive-only
# crates, so this is not part of the blocking `lint` gate.
.PHONY: machete
machete:
@command -v cargo-machete >/dev/null 2>&1 || $(CARGO) install cargo-machete --locked
@cargo machete || true
# =============================================================================
# Test
# =============================================================================
.PHONY: test
test:
@$(CARGO) test --workspace
.PHONY: test-ffi
test-ffi:
@$(CARGO) test -p cpex-ffi --lib
# The Python host's end-to-end tests. They are `#[ignore]`d because they need a
# python3 and a checkout of the cpex Python side, so `make test` reports them as
# ignored rather than passing a body that never ran.
#
# CPEX_REQUIRE_PYTHON_E2E=1 turns every in-test skip into a panic: this target
# is the lane that is supposed to have the environment, so a skip here is a
# broken lane, not an absent dependency. That is what stops the suite reporting
# safety it has not verified.
#
# CPEX_PYTHON_SOURCE must point at a cpex Python checkout carrying
# cpex/framework/isolated/worker.py (PyPI's is behind this branch).
PYTHON_E2E_TESTS = credential_e2e isolated_venv_e2e extensions_merge_e2e
.PHONY: test-python-e2e
test-python-e2e:
@command -v python3 >/dev/null 2>&1 || { \
echo "β python3 not found β the Python host e2e tests need an interpreter"; exit 1; }
@test -n "$(CPEX_PYTHON_SOURCE)" || { \
echo "β CPEX_PYTHON_SOURCE is unset. Point it at a cpex Python checkout containing"; \
echo " cpex/framework/isolated/worker.py, e.g.:"; \
echo " make test-python-e2e CPEX_PYTHON_SOURCE=../cpex-python"; exit 1; }
@test -f "$(CPEX_PYTHON_SOURCE)/cpex/framework/isolated/worker.py" || { \
echo "β $(CPEX_PYTHON_SOURCE) has no cpex/framework/isolated/worker.py"; exit 1; }
@echo "π Python host e2e (skips fail here) ..."
@for t in $(PYTHON_E2E_TESTS); do \
echo "β $$t"; \
CPEX_REQUIRE_PYTHON_E2E=1 CPEX_PYTHON_SOURCE="$(CPEX_PYTHON_SOURCE)" \
$(CARGO) test -p cpex-hosts-python --test $$t \
-- --ignored --nocapture || exit 1; \
done
@echo "β
Python host e2e passed (no skips)"
# Rust workspace tests + Go tests under the race detector.
.PHONY: test-all
test-all: test go-test-race
# =============================================================================
# Supply chain & coverage
# =============================================================================
# Single supply-chain gate (advisories + licenses + bans + sources). Policy
# lives in deny.toml.
.PHONY: audit
audit:
@command -v cargo-deny >/dev/null 2>&1 || $(CARGO) install cargo-deny --locked
@cargo deny check
# Report-only: prints a coverage summary, does NOT enforce a threshold.
# Add `--fail-under-lines N` here and in coverage.yaml to turn on a gate.
.PHONY: coverage
coverage:
@command -v cargo-llvm-cov >/dev/null 2>&1 || $(CARGO) install cargo-llvm-cov --locked
@cargo llvm-cov --workspace --summary-only
# =============================================================================
# Docs
# =============================================================================
.PHONY: doc
doc:
@RUSTDOCFLAGS="-D warnings" $(CARGO) doc --workspace --no-deps
.PHONY: docs
docs:
@command -v $(HUGO) >/dev/null 2>&1 || { echo "β Hugo not found. Install with: brew install hugo"; exit 1; }
@cd $(DOCS_DIR) && $(HUGO)
.PHONY: docs-serve
docs-serve:
@command -v $(HUGO) >/dev/null 2>&1 || { echo "β Hugo not found. Install with: brew install hugo"; exit 1; }
@cd $(DOCS_DIR) && $(HUGO) server --buildDrafts --port $(DOCS_PORT)
.PHONY: docs-clean
docs-clean:
@rm -rf $(DOCS_DIR)/public $(DOCS_DIR)/resources
# =============================================================================
# Python bindings (bindings/python)
# =============================================================================
#
# cpex-python is built via maturin, not plain cargo. The targets below
# require maturin to be installed (`pip install maturin`). The crate is
# excluded from the pure-Rust `rust-build` / `rust-test` targets so those
# paths stay libpython-independent (KD3).
PYTHON_BINDINGS_DIR = bindings/python
VENV_BIN = .venv/bin
MATURIN ?= maturin
.PHONY: bindings-python-build
bindings-python-build:
@echo "π Building Python bindings (debug)..."
@cd $(PYTHON_BINDINGS_DIR) && python -m venv .venv && source .venv/bin/activate && pip install maturin pytest pytest-asyncio && $(MATURIN) develop
@echo "β
Python bindings built (debug)"
.PHONY: bindings-python-build-release
bindings-python-build-release:
@echo "π Building Python bindings (release)..."
@cd $(PYTHON_BINDINGS_DIR) && python -m venv .venv && source .venv/bin/activate && pip install maturin pytest pytest-asyncio && $(MATURIN) build --release
@echo "β
Python bindings built (release)"
.PHONY: bindings-python-test
bindings-python-test: bindings-python-build
@echo "π§ͺ Running Python binding tests..."
@cd $(PYTHON_BINDINGS_DIR) && $(VENV_BIN)/pytest tests/ -v
@echo "β
Python binding tests passed"
# =============================================================================
# Go bindings (go/cpex)
# =============================================================================
#
# go/cpex links against the cpex-ffi cdylib at target/release. Go targets
# ensure the release build is current first β Go's linker errors on a missing
# libcpex_ffi are easy to misread.
.PHONY: go-build
go-build: build-release
@cd $(GO_DIR) && $(GO) build ./...
.PHONY: go-test
go-test: build-release
@cd $(GO_DIR) && $(GO) test -count=1 ./...
.PHONY: go-test-race
go-test-race: build-release
@cd $(GO_DIR) && $(GO) test -count=1 -race ./...
.PHONY: go-vet
go-vet: build-release
@cd $(GO_DIR) && $(GO) vet ./...
.PHONY: go-fmt
go-fmt:
@cd $(GO_DIR) && $(GO) fmt ./...
.PHONY: go-lint-fix
go-lint-fix: build-release
@command -v $(GOLANGCI_LINT) >/dev/null 2>&1 || { \
echo "β golangci-lint not found (brew install golangci-lint)"; exit 1; }
@cd $(GO_DIR) && $(GO) fmt ./... && $(GO) vet ./... && $(GOLANGCI_LINT) run --fix ./...
.PHONY: go-lint-check
go-lint-check: build-release
@command -v $(GOLANGCI_LINT) >/dev/null 2>&1 || { \
echo "β golangci-lint not found (brew install golangci-lint)"; exit 1; }
@cd $(GO_DIR) && unformatted=$$(gofmt -l .); \
if [ -n "$$unformatted" ]; then echo "β Files need formatting:"; echo "$$unformatted"; exit 1; fi
@cd $(GO_DIR) && $(GO) vet ./... && $(GOLANGCI_LINT) run ./...
# =============================================================================
# Examples
# =============================================================================
#
# Building examples is the cheapest way to catch stale public-API usage: cargo
# test / go test only build code reachable from tests, so an example using a
# renamed function compiles fine in isolation but breaks at example-build time.
.PHONY: rust-examples-build
rust-examples-build:
@$(CARGO) build --examples --workspace
.PHONY: go-examples-build
go-examples-build: build-release
@cd $(GO_EXAMPLES_DIR) && $(GO) build ./...
.PHONY: examples-build
examples-build: rust-examples-build go-examples-build
@echo "β
All examples built"
.PHONY: examples-run
examples-run: examples-build tutorial-check-local
@$(CARGO) run --example plugin_demo -p cpex-core --quiet >/dev/null
@$(CARGO) run --example cmf_capabilities_demo -p cpex-core --quiet >/dev/null
@cd $(GO_EXAMPLES_DIR) && $(GO) run . >/dev/null
@cd $(GO_EXAMPLES_DIR) && $(GO) run ./cmd/cmf-demo >/dev/null
@echo "β
All examples ran successfully"
# =============================================================================
# Tutorial (examples/tutorial)
# =============================================================================
#
# The tutorial ships one runnable binary per module, each with a `--check`
# mode that asserts its scripted scenario. `tutorial-check-local` runs the
# modules that need no infrastructure; `tutorial-check` additionally brings
# up the tutorial Keycloak (docker compose) and runs the IdP-backed modules,
# tearing the stack down afterward. CI runs `tutorial-check`.
TUTORIAL_IDP_COMPOSE = examples/tutorial/idp/docker-compose.yml
TUTORIAL_NOIDP_MODULES = m01_hello m03_shaping m04_effects m09_custom_plugin m10_testing
TUTORIAL_IDP_MODULES = m02_identity m05_pdp m06_delegation m07_tainting m08_elicitation capstone
.PHONY: tutorial-check-local
tutorial-check-local:
@for m in $(TUTORIAL_NOIDP_MODULES); do \
echo "β tutorial $$m --check"; \
$(CARGO) run -q -p cpex-tutorial --example $$m -- --check >/dev/null || exit 1; \
done
@echo "β
Tutorial (no-IdP) checks passed"
.PHONY: tutorial-check
tutorial-check: tutorial-check-local
@echo "β starting tutorial IdP"
@docker compose -f $(TUTORIAL_IDP_COMPOSE) up -d
@echo "β waiting for Keycloak realm to be ready"
@$(CARGO) run -q -p cpex-tutorial --example wait_for_idp || { \
docker compose -f $(TUTORIAL_IDP_COMPOSE) down; exit 1; }
@for m in $(TUTORIAL_IDP_MODULES); do \
echo "β tutorial $$m --check"; \
$(CARGO) run -q -p cpex-tutorial --example $$m -- --check || { \
docker compose -f $(TUTORIAL_IDP_COMPOSE) down; exit 1; }; \
done
@docker compose -f $(TUTORIAL_IDP_COMPOSE) down
@echo "β
Tutorial checks passed (incl. IdP-backed modules)"
.PHONY: tutorial-recordings
tutorial-recordings:
@examples/tutorial/recordings/record.sh
@echo "Upload each cast to asciinema.org, then embed per examples/tutorial/recordings/README.md"
# =============================================================================
# CI gate
# =============================================================================
#
# Canonical local gate: read-only lint, full test suite, example builds. If
# this passes locally, the same checks pass in CI.
.PHONY: ci
ci: lint test examples-build bindings-python-build-release bindings-python-test
@echo "β
CI gate passed (lint + tests + examples + bindings/python)"
# =============================================================================
# Release
# =============================================================================
#
# This workspace versions and releases every publishable crate together. The
# version lives in ONE place β `[workspace.package] version` plus the
# `[workspace.dependencies]` table in the root Cargo.toml β and cargo-release
# keeps both in sync. Config (shared-version, tag name, publish=false) lives in
# release.toml; the actual crates.io publish runs in CI on the pushed tag.
#
# Bump level (LEVEL) or explicit VERSION:
# make release-dry # preview, no changes (default LEVEL=alpha)
# make release LEVEL=patch # 0.2.0 -> 0.2.1
# make release VERSION=0.2.0 # drop the pre-release suffix
# git push origin "v$(...)" # push the tag to let CI publish
LEVEL ?= alpha
VERSION ?=
# Explicit VERSION wins over LEVEL when set.
RELEASE_ARG = $(if $(VERSION),$(VERSION),$(LEVEL))
.PHONY: release-tool
release-tool:
@command -v cargo-release >/dev/null 2>&1 || $(CARGO) install cargo-release --locked
# Preview only β cargo-release makes NO changes without --execute.
.PHONY: release-dry
release-dry: release-tool
@$(CARGO) release $(RELEASE_ARG) --workspace
# Rewrite the version in [workspace.package] + [workspace.dependencies] only;
# no commit, no tag. Useful for a manual, reviewed bump.
.PHONY: release-version
release-version: release-tool
@$(CARGO) release version $(RELEASE_ARG) --workspace --execute --no-confirm
# Bump + commit + tag, then stop. --no-publish/--no-push enforce the
# "CI publishes on tag push" model at the CLI level too (release.toml already
# sets publish=false/push=false; this makes the guarantee not depend on config
# parsing). Afterwards: `git push origin vX.Y.Z` to trigger the CI publish.
.PHONY: release
release: release-tool
@$(CARGO) release $(RELEASE_ARG) --workspace --no-publish --no-push --execute
# Build + verify a .crate for every crates.io-published member without
# uploading β the same check the release workflow's dry-run runs. The two
# `publish = false` FFI crates are excluded (cpex-ffi ships as signed prebuilt
# artifacts; cpex-demo-ffi is an example). CI runs this on a clean checkout;
# --allow-dirty lets you run it locally with work in progress.
.PHONY: publish-dry
publish-dry:
@$(CARGO) package --workspace --locked --allow-dirty --exclude cpex-ffi --exclude cpex-demo-ffi
# Tag the current commit across the three namespaces the project releases on,
# then push all three. The `v<version>` tag is what the CI release workflow
# triggers on; `go/cpex/v<version>` is the Go module tag; the bare `<version>`
# is the crates.io-style tag. VERSION must be semver (e.g. 0.2.0 or
# 0.2.0-alpha.5), with no leading `v`.
# make tag VERSION=0.2.0-alpha.5
.PHONY: tag
tag:
@test -n "$(VERSION)" || { echo "usage: make tag VERSION=X.Y.Z[-prerelease]"; exit 1; }
@echo "$(VERSION)" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$$' \
|| { echo "error: VERSION '$(VERSION)' is not semver (e.g. 0.2.0 or 0.2.0-alpha.5; no leading 'v')"; exit 1; }
git tag v$(VERSION)
git tag go/cpex/v$(VERSION)
git tag $(VERSION)
git push origin v$(VERSION)
git push origin go/cpex/v$(VERSION)
git push origin $(VERSION)