CI: an MSRV that could not build, and a gate that floated
First remote CI run went red twice, neither from the change that triggered it. The declared MSRV was false. Cargo.toml claimed 1.77 and all ten crates inherited it, but the committed lockfile carries blake3 1.8.5, which ships edition 2024 and cannot be parsed by any Cargo before 1.85. The job died in fifteen seconds, before compiling a line. The claim had simply never been tested -- there was no remote to test it. Raised to 1.85, which is measured rather than picked: over the Linux-target dependency closure minus the GUI crate, the highest declared rust-version is constant_time_eq at 1.85.0, pulled in by blake3 itself. The comment on the field says so, and says not to raise it again to make a build pass. Raising it woke six clippy lints that MSRV-gating had suppressed: map_or(true, f) is is_none_or(f) as of 1.82. Rewritten. Five of the six sites are covered -- verified by flipping each to is_some_and and confirming a named test dies. The sixth, invariants.rs:1335, survives the flip: nothing constructs a Constant tempo segment with end_tempo None, so that branch has no test that can see it. The rewrite is safe by the standard library's own equivalence, but the gap is real and is not created by this commit. The blocking jobs no longer float on stable. CI ran 1.97.1; this machine has 1.95.0 with no rustup, so the second failure -- float_literal_f32_fallback on a 2.0 literal in the GUI crate -- was invisible and unreproducible here. With -D warnings and a floating toolchain, a Rust release turns the gate red with no repo change, and a gate that fails for reasons the developer cannot reproduce stops being a gate. All four blocking jobs now pin PINNED_STABLE; the nightly soak keeps floating, so drift surfaces on a schedule instead of in an unrelated push. The GUI literal is suffixed anyway, so the eventual deliberate bump is clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5e465a1067
commit
e6a118e1c6
|
|
@ -16,6 +16,16 @@ on:
|
|||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTFLAGS: -D warnings
|
||||
# Every blocking job pins an exact toolchain. With `-D warnings` and a
|
||||
# floating `stable`, a Rust release turns this gate red with no repo change —
|
||||
# which is exactly how it first failed, eight days after 1.97.1 added
|
||||
# `float_literal_f32_fallback`. Worse, the developer machine cannot install
|
||||
# arbitrary toolchains, so such a failure is unreproducible locally and the
|
||||
# local gate stops meaning anything. Pinned here to the version developed
|
||||
# against; bump it deliberately, as its own commit, with the lints it
|
||||
# introduces fixed in that commit. The nightly soak floats on `stable` and is
|
||||
# the drift detector.
|
||||
PINNED_STABLE: "1.95.0"
|
||||
|
||||
jobs:
|
||||
check:
|
||||
|
|
@ -27,7 +37,7 @@ jobs:
|
|||
- name: Install Rust (pinned MSRV)
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: "1.77"
|
||||
toolchain: "1.85"
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Cache cargo
|
||||
|
|
@ -56,18 +66,20 @@ jobs:
|
|||
RUSTDOCFLAGS: -D warnings
|
||||
run: cargo doc --workspace --exclude epiphany-editor-gui --no-deps
|
||||
|
||||
# The demo GUI crate, on current stable (its eframe/resvg tree requires a newer
|
||||
# toolchain than the workspace MSRV). Separate so a GUI build break is its own
|
||||
# attributable signal, not a regression in the core crates.
|
||||
# The demo GUI crate, on the pinned stable rather than the MSRV: its
|
||||
# eframe/resvg tree pulls `image`, which declares 1.88 — three releases above
|
||||
# the workspace floor. Separate so a GUI build break is its own attributable
|
||||
# signal, not a regression in the core crates.
|
||||
editor-gui:
|
||||
name: editor GUI (current stable)
|
||||
name: editor GUI (pinned stable)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust (current stable)
|
||||
- name: Install Rust (pinned stable)
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.PINNED_STABLE }}
|
||||
components: clippy
|
||||
|
||||
- name: Install GUI build dependencies
|
||||
|
|
@ -94,6 +106,8 @@ jobs:
|
|||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.PINNED_STABLE }}
|
||||
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
|
@ -131,6 +145,8 @@ jobs:
|
|||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.PINNED_STABLE }}
|
||||
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
|
@ -139,6 +155,10 @@ jobs:
|
|||
run: cargo test -p epiphany-testkit --test prepass
|
||||
|
||||
# A longer soak, scheduled nightly, exploring a wider seed space at scale.
|
||||
# This job deliberately floats on `stable` while every blocking job is pinned:
|
||||
# it is where toolchain drift surfaces, on a schedule, instead of in a push
|
||||
# that has nothing to do with it. A red soak against a green push means Rust
|
||||
# moved, not that the change did.
|
||||
soak:
|
||||
name: conformance soak (nightly)
|
||||
if: github.event_name == 'schedule'
|
||||
|
|
|
|||
|
|
@ -26,7 +26,12 @@ members = [
|
|||
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
rust-version = "1.77"
|
||||
# 1.85 is the floor the dependency graph actually imposes, not a preference:
|
||||
# blake3 and its `constant_time_eq` both ship edition 2024, which no Cargo
|
||||
# before 1.85 can parse. The previous 1.77 claim was untestable until CI
|
||||
# existed and was false when it ran — raise this only against a measured
|
||||
# closure (`cargo metadata --filter-platform`), never to make a build pass.
|
||||
rust-version = "1.85"
|
||||
authors = ["Epiphany contributors"]
|
||||
repository = "https://github.com/epiphany-notation/epiphany"
|
||||
|
||||
|
|
|
|||
|
|
@ -1332,7 +1332,7 @@ impl<'a> GraphIndex<'a> {
|
|||
TempoShape::Constant => flag(
|
||||
seg.end_tempo
|
||||
.as_ref()
|
||||
.map_or(true, |et| et == &seg.start_tempo),
|
||||
.is_none_or(|et| et == &seg.start_tempo),
|
||||
"constant tempo segment has end_tempo != start_tempo".to_string(),
|
||||
),
|
||||
TempoShape::Linear | TempoShape::Exponential | TempoShape::Curve => flag(
|
||||
|
|
|
|||
|
|
@ -484,7 +484,9 @@ impl EditorApp {
|
|||
ui.painter().rect_stroke(
|
||||
shape_rect(®ion.shape, &vm),
|
||||
0.0,
|
||||
egui::Stroke::new(2.0, egui::Color32::from_rgb(0, 120, 215)),
|
||||
// Suffixed: `float_literal_f32_fallback` (Rust 1.97) rejects
|
||||
// inferring `f32` here, and it becomes a hard error later.
|
||||
egui::Stroke::new(2.0_f32, egui::Color32::from_rgb(0, 120, 215)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1516,7 +1516,7 @@ fn stroke_fate(
|
|||
} else {
|
||||
0.0
|
||||
};
|
||||
if best.map_or(true, |(_, d)| distance < d) {
|
||||
if best.is_none_or(|(_, d)| distance < d) {
|
||||
best = Some((r, distance));
|
||||
}
|
||||
}
|
||||
|
|
@ -1595,7 +1595,7 @@ fn curve_fate(
|
|||
for (r, span) in region_spans.iter().enumerate() {
|
||||
let Some((rlo, rhi)) = span else { continue };
|
||||
let distance = interval_distance(lo, hi, (*rlo, *rhi));
|
||||
if best.map_or(true, |(_, d)| distance < d) {
|
||||
if best.is_none_or(|(_, d)| distance < d) {
|
||||
best = Some((r, distance));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -824,7 +824,7 @@ fn tempo_segment_shape_well_formed(segment: &TempoSegment) -> bool {
|
|||
TempoShape::Constant => segment
|
||||
.end_tempo
|
||||
.as_ref()
|
||||
.map_or(true, |end| end == &segment.start_tempo),
|
||||
.is_none_or(|end| end == &segment.start_tempo),
|
||||
TempoShape::Linear | TempoShape::Exponential | TempoShape::Curve => {
|
||||
segment.end_tempo.is_some() && segment.end.is_some()
|
||||
}
|
||||
|
|
@ -6620,7 +6620,7 @@ impl<'a> Reducer<'a> {
|
|||
(0u8, signed)
|
||||
};
|
||||
let key = (rank, distance, direction, *event);
|
||||
if best.as_ref().map_or(true, |current| key < *current) {
|
||||
if best.as_ref().is_none_or(|current| key < *current) {
|
||||
best = Some(key);
|
||||
}
|
||||
}
|
||||
|
|
@ -7425,7 +7425,7 @@ fn first_missing_vector_predecessor(
|
|||
|
||||
if !complete {
|
||||
let candidate = OperationId::new(replica, expected);
|
||||
if first_missing.map_or(true, |current| candidate < current) {
|
||||
if first_missing.is_none_or(|current| candidate < current) {
|
||||
first_missing = Some(candidate);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue