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
2 changes: 1 addition & 1 deletion .github/workflows/benchmarks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
cache-bin: false

- name: Set up just
uses: ./.github/actions/setup-just
uses: ./.github/actions/setup-just # zizmor: ignore[self-repository] actionlint 1.7.12 does not accept $/...

- name: Resolve cargo-nextest version
id: cargo_nextest_version
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ jobs:
# toolchain, components, etc. are specified in rust-toolchain.toml

- name: Set up just
uses: ./.github/actions/setup-just
uses: ./.github/actions/setup-just # zizmor: ignore[self-repository] actionlint 1.7.12 does not accept $/...

- name: Export tool versions
id: tool_versions
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
cache-bin: false

- name: Set up just
uses: ./.github/actions/setup-just
uses: ./.github/actions/setup-just # zizmor: ignore[self-repository] actionlint 1.7.12 does not accept $/...

- name: Export coverage tool versions
id: tool_versions
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-benchmarks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
cache: false

- name: Set up just
uses: ./.github/actions/setup-just
uses: ./.github/actions/setup-just # zizmor: ignore[self-repository] actionlint 1.7.12 does not accept $/...

- name: Resolve cargo-nextest version
id: cargo_nextest_version
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/rust-clippy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
cache-bin: false

- name: Set up just
uses: ./.github/actions/setup-just
uses: ./.github/actions/setup-just # zizmor: ignore[self-repository] actionlint 1.7.12 does not accept $/...

- name: Export tool versions
id: tool_versions
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/semgrep-sarif.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
persist-credentials: false

- name: Set up just
uses: ./.github/actions/setup-just
uses: ./.github/actions/setup-just # zizmor: ignore[self-repository] actionlint 1.7.12 does not accept $/...

- name: Resolve uv version
id: uv_version
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ just ci # Full CI simulation (checks + tests + examples + bench co
just test # Lib + doc tests (fast)
just test-all # All tests (Rust, benchmark inputs, and Python)
just examples # Run all examples
just update # Update dependency requirements, locks, and repository-owned Cargo tools
just update # Update dependency requirements, locks, and repository-owned tool pins
just update-version vX.Y.Z # Update release metadata without upgrading dependencies
```

Expand Down
14 changes: 7 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions docs/RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ just setup
```

Refresh Cargo dependency requirements, exact Python development-tool pins,
lockfiles, and repository-owned Cargo tool pins before creating the release
branch:
lockfiles, repository-owned Cargo tool pins, and the active uv pin before
creating the release branch:

```bash
just update
Expand Down
64 changes: 43 additions & 21 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ cargo_machete_version := "0.9.2"
cargo_nextest_version := "0.9.143"
cargo_update_version := "22.1.1"
clippy_sarif_version := "0.8.0"
dprint_version := "0.56.1"
dprint_version := "0.57.0"
git_cliff_version := "2.13.1"
just_version := "1.58.0"
rumdl_version := "0.2.60"
rumdl_version := "0.2.62"
sarif_fmt_version := "0.8.0"
taplo_version := "0.10.0"
typos_version := "1.49.0"
uv_version := "0.12.5"
zizmor_version := "1.29.0"
typos_version := "1.50.0"
uv_version := "0.12.7"
zizmor_version := "1.30.0"

# Internal helpers: ensure external tooling is installed
_ensure-actionlint: _ensure-uv
Expand All @@ -52,6 +52,15 @@ _ensure-cargo-edit:
exit 1
fi

_ensure-cargo-install-update:
#!/usr/bin/env bash
set -euo pipefail
command -v cargo-install-update >/dev/null || {
echo "❌ 'cargo-install-update' not found. Run 'just setup-tools' or install it with:"
echo " cargo install --locked cargo-update"
exit 1
}

_ensure-cargo-llvm-cov:
#!/usr/bin/env bash
set -euo pipefail
Expand Down Expand Up @@ -177,7 +186,7 @@ _ensure-typos:
exit 1
fi

_ensure-uv:
_ensure-uv: _ensure-uv-available
#!/usr/bin/env bash
set -euo pipefail
resolved="$(command -v uv 2>/dev/null || true)"
Expand All @@ -188,6 +197,24 @@ _ensure-uv:
exit 1
fi

_ensure-uv-available:
#!/usr/bin/env bash
set -euo pipefail
command -v uv >/dev/null || {
echo "❌ 'uv' not found. Install it from https://github.com/astral-sh/uv" >&2
exit 1
}
uv --version >/dev/null

_ensure-stable-uv-version: _ensure-uv-available
#!/usr/bin/env bash
set -euo pipefail
version_output="$(uv --version 2>&1)"
if [[ ! "$version_output" =~ ^uv[[:space:]]+([0-9]+\.[0-9]+\.[0-9]+)([[:space:]]|$) ]]; then
echo "❌ 'uv --version' must report a stable X.Y.Z version; got: $version_output" >&2
exit 1
fi

_ensure-yamllint: _ensure-uv
#!/usr/bin/env bash
set -euo pipefail
Expand Down Expand Up @@ -500,7 +527,7 @@ help-workflows:
@echo "Setup:"
@echo " just setup # Setup project environment (depends on setup-tools)"
@echo " just setup-tools # Install/verify external tooling"
@echo " just update # Update dependencies and repository-owned Cargo tools"
@echo " just update # Update dependencies and repository-owned tool pins"
@echo ""
@echo "Testing:"
@echo " just coverage # Generate coverage report (HTML)"
Expand Down Expand Up @@ -1059,22 +1086,16 @@ toml-parse-check: python-sync
unused-deps: _ensure-cargo-machete
cargo machete

# Update dependency requirements, locks, and locally installed Cargo tools owned by this repository.
update: update-dependencies update-cargo-tools
# Update dependency requirements, locks, managed Cargo tools, and the active uv pin.
update: _ensure-cargo-install-update _ensure-stable-uv-version update-dependencies update-cargo-tools
@echo "✅ Repository dependencies and tools updated."

# Update locally installed Cargo CLI tools owned by `setup-tools` and reconcile their pins.
[doc('Update Cargo CLI tools owned by setup-tools and reconcile their root justfile pins.')]
update-cargo-tools: _ensure-uv
# Update locally installed Cargo CLI tools and reconcile their pins plus the active uv version.
[doc('Update managed Cargo CLI tools and reconcile all root justfile tool pins.')]
update-cargo-tools: _ensure-stable-uv-version _ensure-cargo-install-update
#!/usr/bin/env bash
set -euo pipefail

if ! command -v cargo-install-update >/dev/null 2>&1; then
echo "❌ 'cargo-install-update' not found. Install it with:"
echo " cargo install --locked cargo-update"
exit 1
fi

packages=(
cargo-edit
cargo-llvm-cov
Expand All @@ -1094,17 +1115,18 @@ update-cargo-tools: _ensure-uv

# Advance Cargo and exact Python development requirements plus their lockfiles.
[doc('Update Cargo and Python development requirements plus all Cargo/uv locked dependencies.')]
update-dependencies: update-cargo-dependencies update-python-dependencies
update-dependencies: _ensure-cargo-edit _ensure-uv-available update-cargo-dependencies update-python-dependencies

# Advance Cargo dependency declarations and lockfile entries.
[doc('Update Cargo.toml dependency requirements and Cargo.lock.')]
update-cargo-dependencies: _ensure-cargo-edit
cargo upgrade
# num-bigint and num-rational share public types and must advance together.
cargo upgrade --incompatible allow --exclude num-bigint --exclude num-rational
cargo update

# Resolve latest Python development tools, retain exact pins, and sync the environment.
[doc('Update exact dependency-groups.dev pins and uv.lock through uv.')]
update-python-dependencies: _ensure-uv
update-python-dependencies: _ensure-uv-available
uv run --locked update-python-dev-pins
uv lock --upgrade
uv sync --locked --group dev
Expand Down
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,10 @@ package = true
dev = [
"actionlint-py==1.7.12.24",
"pytest==9.1.1",
"ruff==0.16.4",
"semgrep==1.174.0",
"ruff==0.16.5",
"semgrep==1.175.0",
"shellcheck-py==0.11.0.1",
"shfmt-py==4.0.0",
"ty==0.0.74",
"shfmt-py==4.1.0",
"ty==0.0.77",
"yamllint==1.38.0",
]
15 changes: 10 additions & 5 deletions scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,15 @@ uv sync --locked --group dev

Run `just update` for the deliberate maintenance workflow. It updates Cargo and
exact Python development-tool declarations and their locks, upgrades only the
Cargo CLI packages owned by `setup-tools`, and then reconciles the installed
package versions with the root `justfile` atomically. The Python updater asks
uv to resolve one cross-platform tool set before applying all changed exact
pins together; it does not change runtime or build-system requirements.
Cargo CLI packages owned by `setup-tools`, and then reconciles their installed
versions plus the active uv version with the root `justfile` atomically. All
required update tools are checked before the first dependency write, and the
maintenance workflow accepts a newer active uv so it can become the new pin.
The coupled `num-bigint` and `num-rational` requirements are excluded from
independent incompatible upgrades and must be advanced together. The Python
updater asks uv to resolve one cross-platform tool set before applying all
changed exact pins together; it does not change runtime or build-system
requirements.

## How to use it

Expand Down Expand Up @@ -283,7 +288,7 @@ validates SemVer, and handles GitHub's 125KB tag-annotation size limit.
| `tag_release.py` | Create annotated git tags from CHANGELOG.md sections |
| `postprocess_changelog.py` | Normalize and reflow generated git-cliff Markdown safely |
| `subprocess_utils.py` | Safe subprocess wrappers for git commands |
| `update_cargo_tool_pins.py` | Reconcile repository-owned Cargo tool pins with installed versions |
| `update_cargo_tool_pins.py` | Reconcile repository-owned Cargo and active uv tool pins with installed versions |
| `update_python_dev_pins.py` | Resolve and advance exact Python development-tool pins through uv |
| `update_release_version.py` | Transactionally update deterministic release-version metadata |

Expand Down
53 changes: 43 additions & 10 deletions scripts/tests/test_justfile_discoverability.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def test_uv_backed_helpers_reuse_pinned_guard() -> None:

assert "uv --version" in ensure_uv_body
assert "uv_version" in ensure_uv_body
assert [dependency["recipe"] for dependency in recipes["_ensure-uv"]["dependencies"]] == ["_ensure-uv-available"]
assert "verify_tool_version uv" in setup_tools_body
for name in ("_ensure-actionlint", "_ensure-shellcheck", "_ensure-shfmt", "_ensure-yamllint"):
dependencies = {dependency["recipe"] for dependency in recipes[name]["dependencies"]}
Expand All @@ -73,29 +74,61 @@ def test_uv_guard_reports_expected_and_actual_versions(tmp_path: Path) -> None:
assert f"version '9.9.9', expected '{expected}'" in result.stderr


def test_stable_uv_preflight_rejects_nonstable_or_embedded_versions(tmp_path: Path) -> None:
"""Update preflights should reject uv versions the pin reconciler cannot store."""
fake_bin = tmp_path / "bin"
fake_bin.mkdir()
fake_uv = fake_bin / "uv"
environment = os.environ.copy()
environment["PATH"] = f"{fake_bin}{os.pathsep}{environment['PATH']}"

for output in ("uv 9.9.9-beta.1", "uv 9.9.9.1", "uv release-9.9.9"):
fake_uv.write_text(f"#!/bin/sh\nprintf '%s\\n' '{output}'\n", encoding="utf-8")
fake_uv.chmod(fake_uv.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
result = run_just("_ensure-stable-uv-version", check=False, env=environment)

assert result.returncode != 0
assert "must report a stable X.Y.Z version" in result.stderr


def test_update_workflow_composes_scoped_dependency_and_tool_updates() -> None:
"""Update recipes should cover repo state without touching unrelated global tools."""
recipes = just_recipes()
update_dependencies = {dependency["recipe"] for dependency in recipes["update"]["dependencies"]}
dependency_updates = {dependency["recipe"] for dependency in recipes["update-dependencies"]["dependencies"]}
update_dependencies = [dependency["recipe"] for dependency in recipes["update"]["dependencies"]]

assert update_dependencies == [
"_ensure-cargo-install-update",
"_ensure-stable-uv-version",
"update-dependencies",
"update-cargo-tools",
]

assert update_dependencies == {"update-cargo-tools", "update-dependencies"}
assert dependency_updates == {"update-cargo-dependencies", "update-python-dependencies"}
aggregate_result = run_just("--dry-run", "update")
aggregate_update = aggregate_result.stdout + aggregate_result.stderr
cargo_upgrade = "cargo upgrade --incompatible allow --exclude num-bigint --exclude num-rational"
assert aggregate_update.index("command -v cargo-install-update") < aggregate_update.index(cargo_upgrade)
assert aggregate_update.index("must report a stable X.Y.Z version") < aggregate_update.index(cargo_upgrade)

dependency_result = run_just("--dry-run", "update-dependencies")
dependency_update = dependency_result.stdout + dependency_result.stderr
assert "cargo upgrade" in dependency_update
assert "cargo upgrade --incompatible allow" not in dependency_update
dependency_preflights = [dependency["recipe"] for dependency in recipes["update-dependencies"]["dependencies"]]
assert dependency_preflights[:2] == ["_ensure-cargo-edit", "_ensure-uv-available"]
assert dependency_update.index("cargo upgrade --version") < dependency_update.index(cargo_upgrade)
assert dependency_update.index("uv --version") < dependency_update.index(cargo_upgrade)
assert cargo_upgrade in dependency_update
assert "cargo update" in dependency_update
assert "update-python-dev-pins" in dependency_update
assert "uv run --locked update-python-dev-pins" in dependency_update
assert "uv lock --upgrade" in dependency_update
assert dependency_update.index("uv run --locked update-python-dev-pins") < dependency_update.index("uv lock --upgrade")
assert "uv sync --locked --group dev" in dependency_update
assert "cargo install-update --all" not in dependency_update
assert "uv tool upgrade" not in dependency_update

tool_result = run_just("--dry-run", "update-cargo-tools")
tool_update = tool_result.stdout + tool_result.stderr
assert "command -v cargo-install-update" in tool_update
assert "cargo install-update --locked" in tool_update
assert tool_update.index("must report a stable X.Y.Z version") < tool_update.index("cargo install-update --locked")
assert "update-cargo-tool-pins" in tool_update
assert "cargo install-update --all" not in tool_update
assert "uv tool upgrade" not in tool_update
Expand All @@ -113,9 +146,9 @@ def test_setup_tools_installs_and_verifies_cargo_update_provider() -> None:
assert "verify_tool_version cargo-install-update" in body


def test_managed_cargo_tool_pins_exist_once_in_root_justfile() -> None:
"""Every managed Cargo package should map to one real root Just pin."""
def test_managed_tool_pins_exist_once_in_root_justfile() -> None:
"""Every managed Cargo package and uv should map to one root Just pin."""
justfile_text = (REPO_ROOT / "justfile").read_text(encoding="utf-8")

for pin in update_cargo_tool_pins.PIN_TO_PACKAGE:
for pin in update_cargo_tool_pins.PIN_TO_TOOL:
assert len(re.findall(rf'(?m)^{re.escape(pin)}\s*:=\s*"[^"]+"\s*$', justfile_text)) == 1
Loading
Loading