Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/actions/setup-dev-fast/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
name: Set up Linux dev-fast prerequisites
description: Install the pinned Cranelift component and checksum-verified mold binary.
runs:
using: composite
steps:
- name: Install the pinned backend and linker
shell: bash
run: |
set -euo pipefail
toolchain='nightly-2026-08-23'
component='rustc-codegen-cranelift'
version="$(tr -d '\r\n' < tools/mold/VERSION)"
case "$(uname -m)" in
x86_64|amd64) architecture=x86_64 ;;
aarch64|arm64) architecture=aarch64 ;;
*) echo "unsupported mold architecture: $(uname -m)" >&2; exit 1 ;;
esac
archive="mold-${version}-${architecture}-linux.tar.gz"
expected="$(awk -v archive="${archive}" '$2 == archive { print $1 }' tools/mold/SHA256SUMS)"
test "$(printf '%s\n' "${expected}" | sed '/^$/d' | wc -l)" -eq 1
rustup toolchain install "${toolchain}" --profile minimal
rustup component add "${component}" clippy --toolchain "${toolchain}"
curl --fail --location --silent --show-error \
--output "${RUNNER_TEMP}/${archive}" \
"https://github.com/rui314/mold/releases/download/v${version}/${archive}"
printf '%s %s\n' "${expected}" "${RUNNER_TEMP}/${archive}" | sha256sum --check --status
mkdir --parents "${HOME}/.local"
tar --extract --gzip --strip-components=1 --directory "${HOME}/.local" \
--file "${RUNNER_TEMP}/${archive}"
echo "${HOME}/.local/bin" >> "${GITHUB_PATH}"
13 changes: 13 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,11 @@ jobs:
cache-provider: external
use-sccache: 'false'

# Only the Linux debug route selects Cranelift and mold. The stable
# toolchain remains selected for Whitaker and every other lane.
- name: Install Linux dev-fast prerequisites
uses: ./.github/actions/setup-dev-fast

- name: Install Whitaker
uses: leynos/shared-actions/.github/actions/install-whitaker@c5a54701c8603a0fa756a6b34c49bc2af75a6c11
with:
Expand Down Expand Up @@ -290,6 +295,11 @@ jobs:
- name: Run lint, including Skylos dead-code detection
run: /usr/bin/make ACTIONLINT="$GITHUB_WORKSPACE/actionlint" lint

# The accelerated lint pass uses a maintenance nightly, so retain a
# separate fragment-free compilation check on the published Rust MSRV.
- name: Verify Rust MSRV
run: make msrv-check

# The Windows wheel build compiles this arm but without `-D warnings`,
# so it catches a Windows arm that fails to build and nothing else. This
# step is what holds the `#[cfg(windows)]` and `#[cfg(unix)]` branches to
Expand Down Expand Up @@ -716,6 +726,9 @@ jobs:
use-sccache: 'false'
install-binstall: 'false'

- name: Install Linux dev-fast prerequisites
uses: ./.github/actions/setup-dev-fast

- name: Prepare Cargo dependencies
run: cargo fetch --locked --manifest-path rust/cuprum-rust/Cargo.toml

Expand Down
11 changes: 9 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,15 @@ working on the Rust portions of the project:
- The project toolchain stays pinned to Rust `1.85.0` and declares `rustfmt`,
`clippy`, and `rust-analyzer`. CI also provisions `nightly-2026-05-28` for
the maintenance formatter, then restores the project toolchain. The current
`fmt` and `check-fmt` recipes still use the project toolchain; the nightly is
not a build, test, lint, or documentation toolchain.
`fmt` and `check-fmt` recipes still use the project toolchain. Linux-only
debug Rust work additionally uses the separately pinned dev-fast nightly
through `tools/dev-fast/config.toml`; run `make dev-fast-check` first, since
it requires both the `rustc-codegen-cranelift` and `clippy` components. The
stable toolchain remains mandatory for release, coverage, Whitaker, the
explicit `make msrv-check` verification target, and the macOS and Windows
platform alternatives. All Rust packages inherit Cargo's
`rust-version = "1.85.0"` contract, which also keeps MSRV-aware Clippy
findings valid when debug linting uses the dev-fast nightly.
- Clippy warnings MUST be disallowed.
- Fix any warnings emitted during tests in code instead of silencing them.
- Where a function is too long, extract meaningfully named helper functions
Expand Down
70 changes: 63 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,46 @@ TOOLS = $(MDLINT) $(YAMLLINT) $(ACTIONLINT) uv
VENV_TOOLS = pytest ruff
RUST_DIR ?= rust
CARGO ?= cargo
# Rust 1.85.0 remains Cuprum's published compatibility toolchain. This
# separately pinned nightly is selected only for compatible Linux debug work.
MSRV_TOOLCHAIN ?= 1.85.0
DEV_FAST_TOOLCHAIN ?= nightly-2026-08-23
DEV_FAST_CRANELIFT_COMPONENT ?= rustc-codegen-cranelift
# Clippy is a separate rustup component from the backend, but `rust-lint` runs
# it through this same nightly, so the prerequisite gate must require both.
DEV_FAST_LINT_COMPONENT ?= clippy
DEV_FAST_REQUIRED_COMPONENTS ?= $(DEV_FAST_CRANELIFT_COMPONENT) $(DEV_FAST_LINT_COMPONENT)
# The selected Cargo fragment is repository-fixed state, not a caller input.
# `override` is load-bearing, not decoration: a plain or `?=` definition still
# yields to an ordinary environment variable, and any non-override definition
# yields to a command-line variable. With either, `make dev-build
# DEV_FAST_RUST_CONFIG=/tmp/other.toml` would run the accelerated route under a
# configuration the project never approved while `dev-fast-check` still passed.
# Both names need it: the second because a caller could otherwise replace the
# derived value directly. Anything needing a different Cargo configuration is
# outside this route and does not use it.
override DEV_FAST_CONFIG_RELATIVE := tools/dev-fast/config.toml
override DEV_FAST_RUST_CONFIG := ../$(DEV_FAST_CONFIG_RELATIVE)
DEV_FAST_MOLD_VERSION_FILE ?= tools/mold/VERSION
DEV_FAST_MOLD_VERSION := $(strip $(shell tr -d '\r\n' < $(DEV_FAST_MOLD_VERSION_FILE)))
DEV_FAST_MOLD_VERSION_PATTERN = $(subst .,\.,$(DEV_FAST_MOLD_VERSION))
DEV_FAST_CARGO_BRIDGE ?= tools/dev-fast/cargo
DEV_FAST_HOST_IS_LINUX := $(if $(filter Linux,$(shell uname -s)),yes)
# `CARGO` remains a caller-injectable single executable. Maturin can select
# that executable but cannot pass Cargo's configuration-file `--config` form.
DEV_FAST_CARGO_COMMAND = RUSTUP_TOOLCHAIN=$(DEV_FAST_TOOLCHAIN) $(CARGO) --config $(DEV_FAST_RUST_CONFIG)
MSRV_CARGO_COMMAND = RUSTUP_TOOLCHAIN=$(MSRV_TOOLCHAIN) $(CARGO)
RUST_DEBUG_CARGO = $(if $(DEV_FAST_HOST_IS_LINUX),$(DEV_FAST_CARGO_COMMAND),$(CARGO))
RUST_DEBUG_PREREQUISITE = $(if $(DEV_FAST_HOST_IS_LINUX),dev-fast-check)
# Parse release spellings without re-parsing caller-provided flags in a shell.
maturin_release_profile = $(strip $(if $(1),$(if $(and $(filter --profile,$(firstword $(1))),$(filter release,$(word 2,$(1)))),yes,$(call maturin_release_profile,$(wordlist 2,$(words $(1)),$(1))))))
MATURIN_DEVELOP_IS_RELEASE := $(strip $(filter --release -r --profile=release,$(MATURIN_DEVELOP_FLAGS)) $(call maturin_release_profile,$(MATURIN_DEVELOP_FLAGS)))
DEVELOP_DEV_FAST_ENABLED := $(if $(DEV_FAST_HOST_IS_LINUX),$(if $(MATURIN_DEVELOP_IS_RELEASE),,yes))
DEVELOP_DEV_FAST_PREREQUISITE = $(if $(DEVELOP_DEV_FAST_ENABLED),dev-fast-check)
DEVELOP_DEV_FAST_ENV = $(if $(DEVELOP_DEV_FAST_ENABLED),RUSTUP_TOOLCHAIN=$(DEV_FAST_TOOLCHAIN) DEV_FAST_CARGO=$(CARGO) CARGO=$(DEV_FAST_CARGO_BRIDGE))
DEV_FAST_CHECK_COMMAND = test "$(DEV_FAST_HOST_IS_LINUX)" = yes || { printf '%s\n' 'dev-fast is supported only on Linux; use the stable backend on this host' >&2; exit 1; }; test -f "$(DEV_FAST_CONFIG_RELATIVE)" || { printf 'dev-fast configuration is missing: %s\n' "$(DEV_FAST_CONFIG_RELATIVE)" >&2; exit 1; }; command -v mold >/dev/null 2>&1 || { printf 'mold %s is required for Linux dev-fast builds\n' "$(DEV_FAST_MOLD_VERSION)" >&2; exit 1; }; mold --version | grep -q '^mold $(DEV_FAST_MOLD_VERSION_PATTERN)\($$\|[[:space:]]\)' || { printf 'mold %s is required for Linux dev-fast builds\n' "$(DEV_FAST_MOLD_VERSION)" >&2; exit 1; }; components="$$(rustup component list --installed --toolchain "$(DEV_FAST_TOOLCHAIN)")" || { printf 'cannot inspect the components installed for %s\n' "$(DEV_FAST_TOOLCHAIN)" >&2; exit 1; }; for component in $(DEV_FAST_REQUIRED_COMPONENTS); do printf '%s\n' "$$components" | grep -q "^$$component" || { printf 'install %s for %s before using dev-fast\n' "$$component" "$(DEV_FAST_TOOLCHAIN)" >&2; exit 1; }; done
DEV_FAST_TEST_RUSTFLAGS = $(TEST_RUSTFLAGS) $(if $(DEV_FAST_HOST_IS_LINUX),-Clink-arg=-fuse-ld=mold)
DEV_FAST_TEST_COMMAND = if $(LOCAL_TOOL_ENV) command -v cargo-nextest >/dev/null 2>&1; then cd $(RUST_DIR) && CARGO_BUILD_JOBS="$(TEST_CARGO_BUILD_JOBS)" RUSTFLAGS="$(DEV_FAST_TEST_RUSTFLAGS)" $(DEV_FAST_CARGO_COMMAND) nextest run $(TEST_FLAGS) $(BUILD_JOBS); else echo "cargo-nextest not found; falling back to cargo test." >&2; cd $(RUST_DIR) && CARGO_BUILD_JOBS="$(TEST_CARGO_BUILD_JOBS)" RUSTFLAGS="$(DEV_FAST_TEST_RUSTFLAGS)" $(DEV_FAST_CARGO_COMMAND) test $(TEST_FLAGS) $(BUILD_JOBS); fi
WHITAKER ?= whitaker
BUILD_JOBS ?=
RUST_FLAGS ?= -D warnings
Expand Down Expand Up @@ -160,6 +200,7 @@ MDLINT_CHECK_COMMAND = unset FORCE_COLOR; $(LOCAL_TOOL_ENV) xargs -0 -r $(MDLINT
lint-windows fmt check-fmt \
markdownlint spelling nixie test test-python test-rust typecheck \
test-extension test-markdown-format develop makeutil skylos-allow \
test-dev-fast-contract dev-fast-check dev-build dev-test msrv-check \
benchmark-micro benchmark-e2e \
$(TOOLS) $(VENV_TOOLS)
.NOTPARALLEL: lint
Expand All @@ -176,9 +217,9 @@ build: uv .venv ## Build virtual-env and install deps

# Why this exists and why `ensurepip` comes first: see "Building the
# extension for tests" in docs/developers-guide.md.
develop: build ## Build the native extension into the dev virtual-env
develop: build $(DEVELOP_DEV_FAST_PREREQUISITE) ## Build the native extension into the dev virtual-env
$(UV_RUN_ENV) uv run $(UV_RUN_FLAGS) python -m ensurepip --upgrade
$(UV_RUN_ENV) uv run $(UV_RUN_FLAGS) maturin develop $(MATURIN_DEVELOP_FLAGS) --manifest-path $(RUST_DIR)/cuprum-rust/Cargo.toml
$(DEVELOP_DEV_FAST_ENV) $(UV_RUN_ENV) uv run $(UV_RUN_FLAGS) maturin develop $(MATURIN_DEVELOP_FLAGS) --manifest-path $(RUST_DIR)/cuprum-rust/Cargo.toml

build-release: ## Build artefacts (sdist & wheel)
python -m build --sdist --wheel
Expand Down Expand Up @@ -254,8 +295,8 @@ python-lint: ruff uv ## Run Ruff, interrogate, pylint, df12-python-lints, and am
$(AMBRLEAKS) cuprum/unittests scripts/tests tests
$(SKYLOS) $(SKYLOS_PRODUCTION_TARGETS) --exclude $(SKYLOS_EXCLUDE_FOLDERS) --category dead_code --gate --format concise --no-upload --no-provenance --no-grep-verify

rust-lint: ## Run Rust documentation, Clippy, Whitaker, and spelling checks
cd $(RUST_DIR) && RUSTDOCFLAGS="$(RUSTDOC_FLAGS)" $(CARGO) doc --no-deps $(DOC_FLAGS) && $(CARGO) clippy $(CLIPPY_FLAGS)
rust-lint: $(RUST_DEBUG_PREREQUISITE) ## Run Rust documentation, Clippy, Whitaker, and spelling checks
cd $(RUST_DIR) && RUSTDOCFLAGS="$(RUSTDOC_FLAGS)" $(RUST_DEBUG_CARGO) doc --no-deps $(DOC_FLAGS) && $(RUST_DEBUG_CARGO) clippy $(CLIPPY_FLAGS)
@if ! $(LOCAL_TOOL_ENV) command -v $(WHITAKER) >/dev/null 2>&1; then echo "whitaker is required for linting. Install it before running this target." >&2; exit 1; fi
cd $(RUST_DIR) && $(LOCAL_TOOL_ENV) RUSTFLAGS="$(WHITAKER_RUSTFLAGS)" $(WHITAKER) --all -- $(WHITAKER_CARGO_FLAGS)
+$(MAKE) spelling
Expand Down Expand Up @@ -311,19 +352,34 @@ test-python: build uv $(VENV_TOOLS) makeutil ## Run the Python suite
CARGO_BUILD_JOBS="$(PYTEST_CARGO_BUILD_JOBS)" RUSTFLAGS="$(PYTEST_RUSTFLAGS)" $(PYTEST) -v -n $(PYTEST_WORKERS) "$$@" || exit $$?; \
done

test-rust: ## Run the Rust suite
test-rust: $(RUST_DEBUG_PREREQUISITE) ## Run the Rust suite
@if $(LOCAL_TOOL_ENV) command -v cargo-nextest >/dev/null 2>&1; then \
cd $(RUST_DIR) && CARGO_BUILD_JOBS="$(TEST_CARGO_BUILD_JOBS)" RUSTFLAGS="$(TEST_RUSTFLAGS)" $(CARGO) nextest run $(TEST_FLAGS) $(BUILD_JOBS); \
cd $(RUST_DIR) && CARGO_BUILD_JOBS="$(TEST_CARGO_BUILD_JOBS)" RUSTFLAGS="$(DEV_FAST_TEST_RUSTFLAGS)" $(RUST_DEBUG_CARGO) nextest run $(TEST_FLAGS) $(BUILD_JOBS); \
else \
echo "cargo-nextest not found; falling back to cargo test." >&2; \
cd $(RUST_DIR) && CARGO_BUILD_JOBS="$(TEST_CARGO_BUILD_JOBS)" RUSTFLAGS="$(TEST_RUSTFLAGS)" $(CARGO) test $(TEST_FLAGS) $(BUILD_JOBS); \
cd $(RUST_DIR) && CARGO_BUILD_JOBS="$(TEST_CARGO_BUILD_JOBS)" RUSTFLAGS="$(DEV_FAST_TEST_RUSTFLAGS)" $(RUST_DEBUG_CARGO) test $(TEST_FLAGS) $(BUILD_JOBS); \
fi

msrv-check: ## Verify every Rust target compiles on the published MSRV
cd $(RUST_DIR) && $(MSRV_CARGO_COMMAND) check --workspace --all-targets --all-features

dev-fast-check: ## Verify Linux dev-fast prerequisites
@$(DEV_FAST_CHECK_COMMAND)

dev-build: dev-fast-check ## Build Rust debug targets through the accelerated route
cd $(RUST_DIR) && $(DEV_FAST_CARGO_COMMAND) build $(CARGO_FLAGS)

dev-test: dev-fast-check ## Test Rust through the accelerated route
@$(DEV_FAST_TEST_COMMAND)

# Run `make develop` first. Without the extension the guard fails the run with
# a message naming that command, which is the intended diagnostic.
test-extension: build uv $(VENV_TOOLS) ## Run the extension-gated tests, requiring the extension
CUPRUM_REQUIRE_RUST_EXTENSION=1 $(PYTEST) -v $(EXTENSION_TEST_TARGETS)

test-dev-fast-contract: build uv $(VENV_TOOLS) ## Validate dev-fast routing and adapter contracts
$(PYTEST) -v cuprum/unittests/test_dev_fast_contract.py cuprum/unittests/test_dev_fast_adapter.py cuprum/unittests/test_dev_fast_prerequisites.py tests/test_dev_fast_action.py

benchmark-micro: build uv ## Run pytest-benchmark microbenchmarks
mkdir -p dist/benchmarks
$(UV_RUN_ENV) CUPRUM_RUN_BENCHMARKS=1 uv run pytest -q \
Expand Down
Loading
Loading