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
48 changes: 48 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,54 @@ jobs:
name: termlens-report-ci-${{ matrix.os }}
cli-version: "0.10.1" # checked against the manifest by check-skill-version.sh

# The public API against the last published release.
#
# `--release-type` is FORCED, never inferred, and the reason is measured
# rather than assumed. Removing `Config::kernel` from launchbound-space
# while bumping the manifest to 3.0.0 in the same PR:
#
# inferred -> "0 checks: 0 pass, 254 skip", exit 0
# --release-type patch -> "1 major and 0 minor checks failed", exit 100
#
# cargo-semver-checks compares the manifest version to the baseline and
# runs only the lints that bump would not already excuse. Declare the major
# bump in the same commit as the break and it has nothing left to say. That
# is the hole: the gate agrees with whatever the PR claims about itself.
#
# `patch` means "no API change of any kind is excused", so every lint
# fires. It does NOT forbid additions -- cargo-semver-checks only reports
# breaking changes, so a new `pub fn` passes (verified: 223 checks, 223
# pass, with a `pub use` added).
#
# A deliberate break carries the `breaking` label, which switches this to
# `major`. Three of the eleven published crates are binary-only and have no
# library API; the eight that do are all covered by --workspace.
#
# `baseline-version` is a literal, moved in the release PR after publish --
# left at the old release it would compare against a version nobody can
# install, and moved before publish it would compare against one that does
# not exist yet.
semver:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pinned toolchain from rust-toolchain.toml
run: rustup show
# A prebuilt binary, downloaded every run: never built from source, and
# never restored from a cache. A gate that reports on whatever a cache
# held the day it was filled is not reporting on this PR.
- uses: taiki-e/install-action@v2
with:
tool: cargo-semver-checks
- name: Compare the public API against the published baseline
env:
RELEASE_TYPE: ${{ contains(github.event.pull_request.labels.*.name, 'breaking') && 'major' || 'patch' }}
run: |
echo "release-type: $RELEASE_TYPE"
cargo semver-checks --workspace \
--baseline-version 2.1.0 \
--release-type "$RELEASE_TYPE"

# MSRV applies to the crates that do not need the pinned nightly
# (CONTRIBUTING.md); checked against the committed lockfile.
msrv:
Expand Down
21 changes: 13 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,19 @@ keywords = ["cuda", "gpu", "autotuner", "convergence", "kernel"]
categories = ["development-tools", "development-tools::profiling"]

[workspace.dependencies]
launchbound-space = { path = "crates/launchbound-space", version = "2.0.0" }
launchbound-prune = { path = "crates/launchbound-prune", version = "2.0.0" }
launchbound-build = { path = "crates/launchbound-build", version = "2.0.0" }
launchbound-bench = { path = "crates/launchbound-bench", version = "2.0.0" }
launchbound-report = { path = "crates/launchbound-report", version = "2.0.0" }
launchbound-search = { path = "crates/launchbound-search", version = "2.0.0" }
launchbound-model = { path = "crates/launchbound-model", version = "2.0.0" }
launchbound-metal = { path = "crates/launchbound-metal", version = "2.0.0" }
# These `version =` pins must equal `workspace.package.version` above. They
# are what a crates.io consumer resolves against, so a stale pin says a
# published crate is compatible with a release it was never built against --
# harmless for a path build, wrong as a record. They sat at 2.0.0 through the
# whole 2.1.0 line; `just versions` (and a test) now refuse the drift.
launchbound-space = { path = "crates/launchbound-space", version = "2.1.0" }
launchbound-prune = { path = "crates/launchbound-prune", version = "2.1.0" }
launchbound-build = { path = "crates/launchbound-build", version = "2.1.0" }
launchbound-bench = { path = "crates/launchbound-bench", version = "2.1.0" }
launchbound-report = { path = "crates/launchbound-report", version = "2.1.0" }
launchbound-search = { path = "crates/launchbound-search", version = "2.1.0" }
launchbound-model = { path = "crates/launchbound-model", version = "2.1.0" }
launchbound-metal = { path = "crates/launchbound-metal", version = "2.1.0" }
anyhow = "1"
clap = { version = "4", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
Expand Down
22 changes: 20 additions & 2 deletions docs/RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,13 @@ maintainer moving between them is not relearning the process.
gh workflow run stress.yml -f iterations=100
gh run watch # ten shards, both OSes

# 1. Bump the version. It appears once per crate plus the workspace pins.
$EDITOR Cargo.toml # version = "X.Y.Z"
# 1. Bump the version. `workspace.package.version` AND the internal
# `version =` pins in [workspace.dependencies] -- both, or `just
# versions` fails. They are not cosmetic: pins left behind a major
# bump make `cargo metadata` refuse to resolve the workspace at all.
$EDITOR Cargo.toml # version = "X.Y.Z", and the eight pins
cargo check --workspace # refreshes Cargo.lock
just versions # the two agree

# 2. Move the CHANGELOG section: [Unreleased] -> [X.Y.Z] - YYYY-MM-DD,
# leaving an empty [Unreleased] above it.
Expand Down Expand Up @@ -70,6 +74,20 @@ tag had to be pushed by hand.
```sh
gh workflow run install.yml
```
- **Move the semver baseline, in a PR of its own after the publish.**
`baseline-version` in `ci.yml`'s `semver` job is a literal. Left at the old
release it compares every PR against a version nobody can install any more,
and it would also carry this release's own breaks forward as if they were
new. Moved *before* the publish it names a version that does not exist yet
and the job cannot fetch it. So: publish, confirm the index has it, then
bump the literal.

```sh
$EDITOR .github/workflows/ci.yml # baseline-version: X.Y.Z
```
- **A break needs the `breaking` label on its PR**, which switches the semver
job from `patch` to `major`. Without it the job fails, which is the point;
with it, the release notes owe the reader a migration note.

## What a version number means here

Expand Down
9 changes: 8 additions & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ default: ci
# "all required jobs green" aggregator job in ci.yml — this recipe is the
# aggregator, and the `ci` job runs it verbatim on both OSes — so a new gate
# becomes required by being listed here.
ci: fmt-check clippy test deny schemas pins skill
ci: fmt-check clippy test deny schemas pins versions skill

# Cargo errors on a memberless virtual workspace, so the cargo recipes no-op
# until the first crate lands in S1. `grep -c` prints 1 when packages is empty.
Expand Down Expand Up @@ -48,6 +48,13 @@ prune cc="8.6":
pins:
./scripts/check-pins.sh

# The workspace version, every crate's version, and the internal `version =`
# pins in [workspace.dependencies] agree. The pins sat at 2.0.0 through the
# whole 2.1.0 line: harmless for a path build, wrong as a record, and fatal
# to the next major bump (`cargo metadata` refuses to resolve).
versions:
./scripts/check-versions.sh

# Golden + JSON Schema validation of report documents (S4).
schemas:
cargo test -p launchbound-report --test schema_and_golden
Expand Down
67 changes: 67 additions & 0 deletions scripts/check-versions.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env bash
# CI gate: the workspace version, every crate's version, and the internal
# `version =` pins in [workspace.dependencies] all agree.
#
# The pins are what a crates.io consumer resolves against. `launchbound-prune`
# 2.1.0 depending on `launchbound-space = "^2.0.0"` says it works with a
# release it was never built or tested against -- harmless for a path build,
# which is why it went unnoticed through the whole 2.1.0 line, and wrong as a
# record. It is not only cosmetic: a `3.0.0` workspace bump with the pins left
# at `2.0.0` fails `cargo metadata` outright ("candidate versions found which
# didn't match: 3.0.0"), so the drift breaks the next major release rather
# than the current one.
#
# Portability: macOS ships bash 3.2 -- no `declare -A`, no `local -n`, no
# GNU-only sed. (scripts/check-pins.sh, which learned this the hard way.)
set -euo pipefail

ROOT=$(cd "$(dirname "$0")/.." && pwd)
cd "$ROOT"

status=0

# `workspace.package.version` -- the one everything else must match. Anchored
# to the section so a dependency's `version =` cannot be read by mistake.
workspace_version=$(
sed -n '/^\[workspace\.package\]/,/^\[/s/^version = "\(.*\)"/\1/p' Cargo.toml | head -1
)
if [ -z "$workspace_version" ]; then
echo "VERSION GATE: could not read workspace.package.version from Cargo.toml" >&2
exit 1
fi
echo "workspace.package.version: $workspace_version"

# The internal path pins.
pins=$(sed -n 's/^\(launchbound-[a-z-]*\) = { path = "[^"]*", version = "\([^"]*\)".*/\1 \2/p' Cargo.toml)
if [ -z "$pins" ]; then
echo "VERSION GATE: no internal version pins found -- has [workspace.dependencies] changed shape?" >&2
exit 1
fi

count=0
while read -r name pin; do
[ -z "$name" ] && continue
count=$((count + 1))
if [ "$pin" != "$workspace_version" ]; then
echo "VERSION GATE: $name is pinned at $pin, workspace is $workspace_version" >&2
status=1
fi
done <<EOF
$pins
EOF
echo " internal pins checked: $count"

# Every member crate inherits the workspace version rather than setting its
# own; a literal here would drift silently.
for manifest in crates/*/Cargo.toml; do
own=$(sed -n '/^\[package\]/,/^\[/s/^version = "\(.*\)"/\1/p' "$manifest" | head -1)
if [ -n "$own" ]; then
echo "VERSION GATE: $manifest sets version = \"$own\" instead of version.workspace = true" >&2
status=1
fi
done

if [ "$status" -eq 0 ]; then
echo "every recorded version agrees"
fi
exit "$status"
Loading