Skip to content

Commit

Permalink
[ci] Separately track MSRV and MWRV (#810)
Browse files Browse the repository at this point in the history
MWRV stands for "minimum working Rust version" (a term we invent here).
It is the minimum version of the toolchain that we test with in CI. We
maintain (and test) the invariant that MSRV >= MWRV so that we know at
all times that our code is compatible with our published MSRV.

The reason for doing this is so that we can make progress towards
lowering our MSRV without painting ourselves into a corner. We treat
MSRV bumps as breaking changes, so if we were to publish a particular
MSRV and then later discover a problem, we wouldn't be able to revert to
a previously-published, higher MSRV. By tracking MWRV separately, we can
ensure that we're making progress while still leaving ourselves wiggle
room to revert changes so long as those reversions are compatible with
our published MSRV. Once we have let a particular MWRV bake long enough,
we can lower the published MSRV to match.

Closes #807
  • Loading branch information
joshlf committed Jan 23, 2024
1 parent 7f4a1bf commit 8c38c06
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 17 deletions.
87 changes: 75 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
matrix:
# See `INTERNAL.md` for an explanation of these pinned toolchain
# versions.
toolchain: [ "msrv", "stable", "nightly" ]
toolchain: [ "mwrv", "stable", "nightly" ]
target: [
"i686-unknown-linux-gnu",
"x86_64-unknown-linux-gnu",
Expand All @@ -55,7 +55,7 @@ jobs:
exclude:
# Exclude any combination which uses a non-nightly toolchain but
# enables nightly features.
- toolchain: "msrv"
- toolchain: "mwrv"
features: "--all-features"
- toolchain: "stable"
features: "--all-features"
Expand Down Expand Up @@ -83,7 +83,7 @@ jobs:
run: |
set -eo pipefail
# We use toolchain descriptors ("msrv", "stable", and "nightly") in the
# We use toolchain descriptors ("mwrv", "stable", and "nightly") in the
# matrix. This step converts the current descriptor to a particular
# toolchain version by looking up the corresponding key in `Cargo.toml`. It
# sets the `ZC_TOOLCHAIN` environment variable for use in the next step
Expand Down Expand Up @@ -310,6 +310,70 @@ jobs:
diff <(./generate-readme.sh) README.md
exit $?
check_mwrv:
needs: generate_cache
runs-on: ubuntu-latest
name: Check MWRV <= MSRV
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

- uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
with:
path: |
~/.cargo/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}

# Make sure that the rust-version key in zerocopy's `Cargo.toml` file
# (MSRV) is at least as high as the
# `metadata.ci."minimum-working-rust-version"` key. This latter key (known
# as the MWRV) is the one we actually test with in CI; if MSRV < MWRV,
# then our CI wouldn't actually be verifying that our code works correctly
# on the published MSRV.
#
# Note that we also verify the same inequality for zerocopy-derive's MSRV.
# This is technically unnecessary since, in a separate CI job, we check to
# make sure that zerocopy's and zerocopy-derive's MSRVs are the same.
# However, it's trivial to support this as well, and it is more robust
# against a future in which we remove that other CI job.
- name: Check MWRV <= MSRV
run: |
set -eo pipefail
# Usage: mwrv <crate-name>
function mwrv {
cargo metadata --format-version 1 | jq -r ".packages[] | select(.name == \"$1\").metadata.ci.\"minimum-working-rust-version\""
}
# Usage: msrv <crate-name>
function msrv {
cargo metadata --format-version 1 | jq -r ".packages[] | select(.name == \"$1\").rust_version"
}
mwrv=$(mwrv zerocopy)
msrv_zerocopy=$(msrv zerocopy)
msrv_zerocopy_derive=$(msrv zerocopy-derive)
# Usage: semver_lteq <a> <b>
function semver_lteq {
[ "$1" = "`echo -e "$1\n$2" | sort -V | head -n1`" ]
}
if semver_lteq "$mwrv" "$msrv_zerocopy"; then
echo "MWRV ($mwrv) <= zerocopy MSRV ($msrv_zerocopy)." | tee -a $GITHUB_STEP_SUMMARY
exit 0
else
echo "MWRV ($mwrv) > zerocopy MSRV ($msrv_zerocopy)." | tee -a $GITHUB_STEP_SUMMARY >&2
exit 1
fi
if semver_lteq "$mwrv" "$msrv_zerocopy_derive"; then
echo "MWRV ($mwrv) <= zerocopy-derive MSRV ($msrv_zerocopy_derive)." | tee -a $GITHUB_STEP_SUMMARY
exit 0
else
echo "MWRV ($mwrv) > zerocopy-derive MSRV ($msrv_zerocopy_derive)." | tee -a $GITHUB_STEP_SUMMARY >&2
exit 1
fi
check_msrv:
needs: generate_cache
runs-on: ubuntu-latest
Expand All @@ -324,14 +388,13 @@ jobs:
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}

# Make sure that the MSRV in zerocopy's and zerocopy-derive's `Cargo.toml`
# files are the same. In CI, we test with a single MSRV (the one indicated
# in zerocopy's `Cargo.toml`), so it's important that:
# - zerocopy-derive's MSRV is not lower than zerocopy's (we don't test with
# a lower MSRV in CI, so we couldn't guarantee that zerocopy-derive
# actually built and ran on a lower MSRV)
# - zerocopy-derive's MSRV is not higher than zerocopy's (this would mean
# that compiling zerocopy with the `derive` feature enabled would fail
# on its own published MSRV)
# files are the same. In CI, we test with a single minimum working Rust
# version (MWRV), and validate that it is at least as low as both MSRVs,
# so technically this check is unnecessary. However, we logically treat
# zerocopy and zerocopy-derive as part of a single monolith, so they
# should have equal MSRVs. To do otherwise would be surprising to users
# (and, if zerocopy-derive's MSRV were higher, it would mean that
# zerocopy's `derive` feature would not respect its own MSRV).
- name: Check MSRVs match
run: |
set -eo pipefail
Expand Down Expand Up @@ -510,7 +573,7 @@ jobs:
# https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks#handling-skipped-but-required-checks
if: failure()
runs-on: ubuntu-latest
needs: [build_test, kani, check_fmt, check_readme, check_msrv, check_versions, generate_cache, check-job-dependencies]
needs: [build_test, kani, check_fmt, check_readme, check_msrv, check_mwrv, check_versions, generate_cache, check-job-dependencies]
steps:
- name: Mark the job as failed
run: exit 1
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ rustdoc-args = ["--cfg", "doc_cfg", "--generate-link-to-definition"]
# The versions of the stable and nightly compiler toolchains to use in CI.
pinned-stable = "1.75.0"
pinned-nightly = "nightly-2024-01-11"
minimum-working-rust-version = "1.60.0"

[package.metadata.playground]
features = ["__internal_use_only_features_that_work_on_stable"]
Expand Down
10 changes: 5 additions & 5 deletions cargo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ function pkg-meta {
function lookup-version {
VERSION="$1"
case "$VERSION" in
msrv)
pkg-meta rust_version
mwrv)
pkg-meta 'metadata.ci."minimum-working-rust-version"'
;;
stable)
pkg-meta 'metadata.ci."pinned-stable"'
Expand All @@ -60,7 +60,7 @@ function lookup-version {
pkg-meta 'metadata.ci."pinned-nightly"'
;;
*)
echo "Unrecognized toolchain name: '$VERSION' (options are 'msrv', 'stable', 'nightly')" >&2
echo "Unrecognized toolchain name: '$VERSION' (options are 'mwrv', 'stable', 'nightly')" >&2
return 1
;;
esac
Expand Down Expand Up @@ -91,8 +91,8 @@ case "$1" in
;;
# cargo.sh +all [...]
+all)
echo "[cargo.sh] warning: running the same command for each toolchain (msrv, stable, nightly)" >&2
for toolchain in msrv stable nightly; do
echo "[cargo.sh] warning: running the same command for each toolchain (mwrv, stable, nightly)" >&2
for toolchain in mwrv stable nightly; do
echo "[cargo.sh] running with toolchain: $toolchain" >&2
$0 "+$toolchain" ${@:2}
done
Expand Down

0 comments on commit 8c38c06

Please sign in to comment.