Skip to content

Commit 6b607d3

Browse files
epagetaiki-e
authored andcommitted
feat: Support mixed MSRV in --version-range
This expands on the approach taken in #212 bucketing packages into rust-versions to run. If we skipped the MSRV (due to `--version-step`), we automatically inject it. If a package's MSRV isn't within the range, we skip it. Benefits - Relatively simple to implement and to explain - We keep the number of runs to a minimum by walking in lock-step the `--version-step`, independent of what each package' MSRV I did have to specialize `--rust-version` vs `--version-range` to avoid `--rust-version` range users walking more than they needed. To keep the progress total accurate, I shifted the calculating of the total from `determine_package_list` to after we have bucketed everything. To make this feasible, I saved off the how many iterations a package will have without the version range being taken into account. As a byproduct, this fixes a bug in #212 where it didn't take the rust-version into account when determining the total. Fixes #199
1 parent af30888 commit 6b607d3

File tree

3 files changed

+101
-71
lines changed

3 files changed

+101
-71
lines changed

src/main.rs

+59-55
Original file line numberDiff line numberDiff line change
@@ -66,68 +66,70 @@ fn try_main() -> Result<()> {
6666
let mut progress = Progress::default();
6767
let mut keep_going = KeepGoing::default();
6868
if let Some(range) = cx.version_range {
69-
if range == VersionRange::msrv() {
70-
let total = packages.iter().map(|p| p.feature_count).sum();
71-
progress.total = total;
72-
73-
let mut versions = BTreeMap::new();
74-
for pkg in packages {
75-
let v = cx
76-
.rust_version(pkg.id)
77-
.map(str::parse::<Version>)
78-
.transpose()?
79-
.ok_or_else(|| {
80-
format_err!("no rust-version field in Cargo.toml is specified")
81-
})?;
82-
versions.entry(v.strip_patch()).or_insert_with(Vec::new).push(pkg);
83-
}
84-
85-
// First, generate the lockfile using the oldest cargo specified.
86-
// https://github.com/taiki-e/cargo-hack/issues/105
87-
let mut generate_lockfile = true;
88-
// Workaround for spurious "failed to select a version" error.
89-
// (This does not work around the underlying cargo bug: https://github.com/rust-lang/cargo/issues/10623)
90-
let mut regenerate_lockfile_on_51_or_up = false;
91-
for (cargo_version, packages) in versions {
92-
versioned_cargo_exec_on_packages(
93-
cx,
94-
&packages,
95-
cargo_version.minor,
96-
&mut progress,
97-
&mut keep_going,
98-
&mut generate_lockfile,
99-
&mut regenerate_lockfile_on_51_or_up,
100-
)?;
69+
let mut versions = BTreeMap::new();
70+
let steps = rustup::version_range(range, cx.version_step, &packages, cx)?;
71+
for pkg in packages {
72+
let msrv = cx
73+
.rust_version(pkg.id)
74+
.map(str::parse::<Version>)
75+
.transpose()?
76+
.map(Version::strip_patch);
77+
if range == VersionRange::msrv() {
78+
let msrv = msrv.ok_or_else(|| {
79+
format_err!("no rust-version field in Cargo.toml is specified")
80+
})?;
81+
versions.entry(msrv).or_insert_with(Vec::new).push(pkg);
82+
} else {
83+
let mut seen = false;
84+
for cargo_version in &steps {
85+
if msrv.is_some() && Some(*cargo_version) < msrv {
86+
continue;
87+
}
88+
if !seen {
89+
if Some(*cargo_version) != msrv {
90+
if let Some(msrv) = msrv {
91+
versions.entry(msrv).or_insert_with(Vec::new).push(pkg.clone());
92+
}
93+
}
94+
seen = true;
95+
}
96+
versions.entry(*cargo_version).or_insert_with(Vec::new).push(pkg.clone());
97+
}
98+
if !seen {
99+
let package = cx.packages(pkg.id);
100+
let name = &package.name;
101+
let msrv = msrv.expect("always `seen` if no msrv");
102+
warn!("skipping {name}, rust-version ({msrv}) is not in specified range ({range})");
103+
}
101104
}
102-
} else {
103-
let range = rustup::version_range(range, cx.version_step, &packages, cx)?;
105+
}
104106

105-
let total: usize = packages.iter().map(|p| p.feature_count).sum();
106-
for cargo_version in &range {
107+
for (cargo_version, packages) in &versions {
108+
for package in packages {
107109
if cx.target.is_empty() || cargo_version.minor >= 64 {
108-
progress.total += total;
110+
progress.total += package.feature_count;
109111
} else {
110-
progress.total += total * cx.target.len();
112+
progress.total += package.feature_count * cx.target.len();
111113
}
112114
}
115+
}
113116

114-
// First, generate the lockfile using the oldest cargo specified.
115-
// https://github.com/taiki-e/cargo-hack/issues/105
116-
let mut generate_lockfile = true;
117-
// Workaround for spurious "failed to select a version" error.
118-
// (This does not work around the underlying cargo bug: https://github.com/rust-lang/cargo/issues/10623)
119-
let mut regenerate_lockfile_on_51_or_up = false;
120-
for cargo_version in range {
121-
versioned_cargo_exec_on_packages(
122-
cx,
123-
&packages,
124-
cargo_version.minor,
125-
&mut progress,
126-
&mut keep_going,
127-
&mut generate_lockfile,
128-
&mut regenerate_lockfile_on_51_or_up,
129-
)?;
130-
}
117+
// First, generate the lockfile using the oldest cargo specified.
118+
// https://github.com/taiki-e/cargo-hack/issues/105
119+
let mut generate_lockfile = true;
120+
// Workaround for spurious "failed to select a version" error.
121+
// (This does not work around the underlying cargo bug: https://github.com/rust-lang/cargo/issues/10623)
122+
let mut regenerate_lockfile_on_51_or_up = false;
123+
for (cargo_version, packages) in versions {
124+
versioned_cargo_exec_on_packages(
125+
cx,
126+
&packages,
127+
cargo_version.minor,
128+
&mut progress,
129+
&mut keep_going,
130+
&mut generate_lockfile,
131+
&mut regenerate_lockfile_on_51_or_up,
132+
)?;
131133
}
132134
} else {
133135
let total = packages.iter().map(|p| p.feature_count).sum();
@@ -148,6 +150,7 @@ struct Progress {
148150
count: usize,
149151
}
150152

153+
#[derive(Clone)]
151154
enum Kind<'a> {
152155
Normal,
153156
Each { features: Vec<&'a Feature> },
@@ -263,6 +266,7 @@ fn determine_kind<'a>(
263266
}
264267
}
265268

269+
#[derive(Clone)]
266270
struct PackageRuns<'a> {
267271
id: &'a PackageId,
268272
kind: Kind<'a>,

src/rustup.rs

+21-14
Original file line numberDiff line numberDiff line change
@@ -60,23 +60,30 @@ pub(crate) fn version_range(
6060
if let Some(rust_version) = rust_version {
6161
Ok(rust_version)
6262
} else {
63-
let mut version = None;
63+
let mut lowest_msrv = None;
6464
for pkg in packages {
65-
let v = cx.rust_version(pkg.id);
66-
if v.is_none() || v == version {
67-
// no-op
68-
} else if version.is_none() {
69-
version = v;
70-
} else {
71-
bail!("automatic detection of the lower bound of the version range is not yet supported when the minimum supported Rust version of the crates in the workspace do not match")
72-
}
65+
let pkg_msrv = cx
66+
.rust_version(pkg.id)
67+
.map(str::parse::<Version>)
68+
.transpose()?
69+
.map(Version::strip_patch);
70+
lowest_msrv = match (lowest_msrv, pkg_msrv) {
71+
(Some(workspace), Some(pkg)) => {
72+
if workspace < pkg {
73+
Some(workspace)
74+
} else {
75+
Some(pkg)
76+
}
77+
}
78+
(Some(msrv), None) | (None, Some(msrv)) => Some(msrv),
79+
(None, None) => None,
80+
};
7381
}
74-
let version = match version {
75-
Some(v) => v.parse()?,
76-
None => bail!("no rust-version field in Cargo.toml is specified"),
82+
let Some(lowest_msrv) = lowest_msrv else {
83+
bail!("no rust-version field in Cargo.toml is specified")
7784
};
78-
rust_version = Some(version);
79-
Ok(version)
85+
rust_version = Some(lowest_msrv);
86+
Ok(lowest_msrv)
8087
}
8188
};
8289

tests/test.rs

+21-2
Original file line numberDiff line numberDiff line change
@@ -1353,11 +1353,30 @@ fn version_range() {
13531353
running `rustup run 1.64 cargo check` on member2 (4/4)
13541354
",
13551355
);
1356+
// Skips `real` because it isn't in range
13561357
cargo_hack(["check", "--version-range", "..=1.64", "--workspace"])
1357-
.assert_failure("rust-version")
1358+
.assert_failure("rust-version") // warn
13581359
.stderr_contains(
13591360
"
1360-
automatic detection of the lower bound of the version range is not yet supported when the minimum supported Rust version of the crates in the workspace do not match
1361+
warning: skipping real, rust-version (1.65) is not in specified range (..=1.64)
1362+
running `rustup run 1.63 cargo check` on member1 (1/5)
1363+
running `rustup run 1.63 cargo check` on member2 (2/5)
1364+
running `rustup run 1.64 cargo check` on member1 (3/5)
1365+
running `rustup run 1.64 cargo check` on member2 (4/5)
1366+
running `rustup run 1.64 cargo check` on member3 (5/5)
1367+
",
1368+
);
1369+
cargo_hack(["check", "--version-range", "..=1.66", "--version-step", "2", "--workspace"])
1370+
.assert_success("rust-version")
1371+
.stderr_contains(
1372+
"
1373+
running `rustup run 1.63 cargo check` on member1 (1/7)
1374+
running `rustup run 1.63 cargo check` on member2 (2/7)
1375+
running `rustup run 1.64 cargo check` on member3 (3/7)
1376+
running `rustup run 1.65 cargo check` on member1 (4/7)
1377+
running `rustup run 1.65 cargo check` on member2 (5/7)
1378+
running `rustup run 1.65 cargo check` on member3 (6/7)
1379+
running `rustup run 1.65 cargo check` on real (7/7)
13611380
",
13621381
);
13631382
}

0 commit comments

Comments
 (0)