Skip to content

Commit

Permalink
Merge #102
Browse files Browse the repository at this point in the history
102: Add --version-range option r=taiki-e a=taiki-e

Closes #93
> This may be useful for catching issues like BurntSushi/termcolor#35, rust-lang/regex#685, rayon-rs/rayon#761 (comment), rust-lang/rust-clippy#6324.

```text
--version-range <START>..[END]
    Perform commands on a specified (inclusive) range of Rust versions.

    If the given range is unclosed, the latest stable compiler is treated as the upper bound.

    Note that ranges are always inclusive ranges.

--version-step <NUM>
    Specify the version interval of --version-range.
```


Note: Ranges are always **inclusive** ranges. (`start..=end`)

```console
$ cargo hack check --version-range 1.46..1.47
info: running `cargo +1.46 check` on cargo-hack (1/2)
    Finished dev [unoptimized + debuginfo] target(s) in 0.28s
info: running `cargo +1.47 check` on cargo-hack (2/2)
    Finished dev [unoptimized + debuginfo] target(s) in 0.23s
```

If the given range is unclosed, the latest stable compiler is treated as the upper bound.

```console
$ cargo hack check --version-range 1.46..
info: running `cargo +1.46 check` on cargo-hack (1/3)
    Finished dev [unoptimized + debuginfo] target(s) in 0.28s
info: running `cargo +1.47 check` on cargo-hack (2/3)
    Finished dev [unoptimized + debuginfo] target(s) in 0.23s
info: running `cargo +1.48 check` on cargo-hack (3/3)
    Finished dev [unoptimized + debuginfo] target(s) in 0.28
```

You can also specify the version interval by using `--version-step`. (`(start..=end).step_by(step)`)

```console
$ cargo hack check --version-range 1.45.. --version-step 2
info: running `cargo +1.45 check` on cargo-hack (1/2)
    Finished dev [unoptimized + debuginfo] target(s) in 0.29s
info: running `cargo +1.47 check` on cargo-hack (2/2)
    Finished dev [unoptimized + debuginfo] target(s) in 0.25s
```

Co-authored-by: Taiki Endo <[email protected]>
  • Loading branch information
bors[bot] and taiki-e authored Dec 5, 2020
2 parents d1e5f20 + 0658021 commit 65b3653
Show file tree
Hide file tree
Showing 11 changed files with 289 additions and 34 deletions.
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,22 @@ jobs:
- if: startsWith(matrix.rust, 'nightly') && matrix.target == ''
run: bash scripts/check-minimal-versions.sh

build:
strategy:
matrix:
range:
# This is the minimum supported Rust version of this crate.
# When updating this, the reminder to update the minimum supported Rust version in README.md.
- 1.36..1.40
- 1.41..1.45
- 1.46..
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# TODO: replace this with `cargo install cargo-hack` when cargo-hack 0.5.0 released
- run: cargo +stable install --path .
- run: cargo hack build --all --ignore-private --no-dev-deps --version-range ${{ matrix.range }}

run:
strategy:
matrix:
Expand Down
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,32 @@ cargo-hack is usually runnable with Cargo versions older than the Rust version r

Remove artifacts for that package before running the command.

This also works as a workaround for [rust-lang/rust-clippy#4612].

* **`--version-range`**

Perform commands on a specified (inclusive) range of Rust versions.

```console
$ cargo hack check --version-range 1.46..1.47
info: running `cargo +1.46 check` on cargo-hack (1/2)
...
info: running `cargo +1.47 check` on cargo-hack (2/2)
...
```

If the given range is unclosed, the latest stable compiler is treated as the upper bound.

This might be useful for catching issues like [BurntSushi/termcolor#35], [rust-lang/regex#685], [rust-lang/rust-clippy#6324].

[BurntSushi/termcolor#35]: https://github.com/BurntSushi/termcolor/issues/35
[rust-lang/regex#685]: https://github.com/rust-lang/regex/issues/685
[rust-lang/rust-clippy#6324]: https://github.com/rust-lang/rust-clippy/issues/6324.

* **`--version-step`**

Specify the version interval of `--version-range`.

The following flags can be used with `--each-feature` and `--feature-powerset`.

* **`--optional-deps`**
Expand Down Expand Up @@ -141,6 +167,7 @@ The following flags can be used with `--each-feature` and `--feature-powerset`.
[rust-lang/cargo#5015]: https://github.com/rust-lang/cargo/issues/4463
[rust-lang/cargo#5364]: https://github.com/rust-lang/cargo/issues/5364
[rust-lang/cargo#6195]: https://github.com/rust-lang/cargo/issues/6195
[rust-lang/rust-clippy#4612]: https://github.com/rust-lang/cargo/issues/4612
[cargo-metadata]: https://doc.rust-lang.org/cargo/commands/cargo-metadata.html

## License
Expand Down
8 changes: 5 additions & 3 deletions src/cargo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ impl Cargo {

// If failed to determine cargo version, assign 0 to skip all version-dependent decisions.
let version = minor_version(&mut ProcessBuilder::new(&path))
.map_err(|e| warn!("unable to determine cargo version: {}", e))
.map_err(|e| warn!("unable to determine cargo version: {:#}", e))
.unwrap_or(0);

Self { path, version }
Expand All @@ -38,7 +38,9 @@ pub(crate) fn minor_version(cmd: &mut ProcessBuilder<'_>) -> Result<u32> {
.lines()
.find(|line| line.starts_with("release: "))
.map(|line| &line["release: ".len()..])
.ok_or_else(|| format_err!("could not find rustc release from output of {}", cmd))?;
.ok_or_else(|| {
format_err!("could not find rustc release from output of {}: {}", cmd, output)
})?;

// Split the version and channel info.
let mut version_channel = release.split('-');
Expand All @@ -47,7 +49,7 @@ pub(crate) fn minor_version(cmd: &mut ProcessBuilder<'_>) -> Result<u32> {

let version = parse_version(version)?;
if version.major != 1 || version.patch.is_none() {
bail!("unexpected output from {}", cmd);
bail!("unexpected output from {}: {}", cmd, output);
}

Ok(version.minor)
Expand Down
30 changes: 28 additions & 2 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use anyhow::{bail, format_err, Error};
use std::{env, fmt, mem};

use crate::{term, Cargo, Feature, Result};
use crate::{rustup, term, Cargo, Feature, Result, Rustup};

fn print_version() {
println!("{0} {1}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"))
Expand Down Expand Up @@ -81,6 +81,16 @@ const HELP: &[(&str, &str, &str, &[&str])] = &[
"If used this flag with --workspace, --each-feature, or --feature-powerset, artifacts will be removed before each run.",
"Note that dependencies artifacts will be preserved.",
]),
(
"",
"--version-range <START>..[END]",
"Perform commands on a specified (inclusive) range of Rust versions",
&[
"If the given range is unclosed, the latest stable compiler is treated as the upper bound.",
"Note that ranges are always inclusive ranges.",
],
),
("", "--version-step <NUM>", "Specify the version interval of --version-range", &[]),
("-v", "--verbose", "Use verbose output", &[]),
("", "--color <WHEN>", "Coloring: auto, always, never", &[
"This flag will be propagated to cargo.",
Expand Down Expand Up @@ -208,6 +218,8 @@ pub(crate) struct Args<'a> {
pub(crate) ignore_unknown_features: bool,
/// --clean-per-run
pub(crate) clean_per_run: bool,
/// --version-range and --version-step
pub(crate) version_range: Option<Vec<String>>,

// options for --each-feature and --feature-powerset
/// --optional-deps [DEPS]...
Expand Down Expand Up @@ -250,7 +262,7 @@ pub(crate) fn raw() -> RawArgs {

pub(crate) struct RawArgs(Vec<String>);

pub(crate) fn parse_args<'a>(raw: &'a RawArgs, cargo: &Cargo) -> Result<Args<'a>> {
pub(crate) fn parse_args<'a>(raw: &'a RawArgs, cargo: &Cargo, rustup: &Rustup) -> Result<Args<'a>> {
let mut iter = raw.0.iter();
let mut args = iter.by_ref().map(String::as_str).peekable();
match args.next() {
Expand Down Expand Up @@ -282,6 +294,8 @@ pub(crate) fn parse_args<'a>(raw: &'a RawArgs, cargo: &Cargo) -> Result<Args<'a>
let mut ignore_private = false;
let mut ignore_unknown_features = false;
let mut clean_per_run = false;
let mut version_range = None;
let mut version_step = None;

let mut optional_deps = None;
let mut include_features = Vec::new();
Expand Down Expand Up @@ -397,6 +411,8 @@ pub(crate) fn parse_args<'a>(raw: &'a RawArgs, cargo: &Cargo) -> Result<Args<'a>
parse_opt!(manifest_path, false, "--manifest-path", "--manifest-path <PATH>");
parse_opt!(depth, false, "--depth", "--depth <NUM>");
parse_opt!(color, true, "--color", "--color <WHEN>");
parse_opt!(version_range, false, "--version-range", "--version-range <START>..[END]");
parse_opt!(version_step, false, "--version-step", "--version-step <NUM>");

parse_multi_opt!(package, false, true, "--package", "--package <SPEC>...");
parse_multi_opt!(package, false, true, "-p", "--package <SPEC>...");
Expand Down Expand Up @@ -544,6 +560,10 @@ pub(crate) fn parse_args<'a>(raw: &'a RawArgs, cargo: &Cargo) -> Result<Args<'a>
bail!("--group-features can only be used together with --feature-powerset");
}
}
if version_range.is_none() && version_step.is_some() {
bail!("--version-step can only be used together with --version-range");
}

let depth = depth.map(str::parse::<usize>).transpose()?;
let group_features =
group_features.iter().try_fold(Vec::with_capacity(group_features.len()), |mut v, g| {
Expand All @@ -560,6 +580,8 @@ pub(crate) fn parse_args<'a>(raw: &'a RawArgs, cargo: &Cargo) -> Result<Args<'a>
v.push(Feature::group(g));
Ok(v)
})?;
let version_range =
version_range.map(|range| rustup::version_range(range, version_step)).transpose()?;

if let Some(subcommand) = subcommand {
if subcommand == "test" || subcommand == "bench" {
Expand Down Expand Up @@ -629,6 +651,9 @@ pub(crate) fn parse_args<'a>(raw: &'a RawArgs, cargo: &Cargo) -> Result<Args<'a>
if cargo.version < 41 && include_deps_features {
bail!("--include-deps-features requires Cargo 1.41 or leter");
}
if rustup.version < 23 && version_range.is_some() {
bail!("--version-range requires rustup 1.23 or leter");
}

if subcommand.is_none() {
if leading.contains(&"-h") {
Expand Down Expand Up @@ -682,6 +707,7 @@ pub(crate) fn parse_args<'a>(raw: &'a RawArgs, cargo: &Cargo) -> Result<Args<'a>
clean_per_run,
include_features: include_features.into_iter().map(Into::into).collect(),
include_deps_features,
version_range,

depth,
group_features,
Expand Down
7 changes: 4 additions & 3 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{
features::Features,
manifest::Manifest,
metadata::{Metadata, Package, PackageId},
Cargo, ProcessBuilder, Result,
Cargo, ProcessBuilder, Result, Rustup,
};

pub(crate) struct Context<'a> {
Expand All @@ -26,9 +26,10 @@ pub(crate) struct Context<'a> {
impl<'a> Context<'a> {
pub(crate) fn new(args: &'a RawArgs) -> Result<Self> {
let cargo = Cargo::new();
let rustup = Rustup::new();
let current_dir = env::current_dir()?;

let args = cli::parse_args(args, &cargo)?;
let args = cli::parse_args(args, &cargo, &rustup)?;
assert!(
args.subcommand.is_some() || args.remove_dev_deps,
"no subcommand or valid flag specified"
Expand Down Expand Up @@ -112,7 +113,7 @@ impl<'a> Context<'a> {
(self.cargo.version < 39 && self.ignore_private) || self.no_dev_deps || self.remove_dev_deps
}

pub(crate) fn process(&self) -> ProcessBuilder<'_> {
pub(crate) fn cargo(&self) -> ProcessBuilder<'_> {
let mut cmd = self.cargo.process();
if self.verbose {
cmd.display_manifest_path();
Expand Down
35 changes: 27 additions & 8 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ mod metadata;
mod process;
mod remove_dev_deps;
mod restore;
mod rustup;
mod version;

use anyhow::{bail, Context as _};
use std::{fmt::Write, fs};

use crate::{
cargo::Cargo, context::Context, features::Feature, metadata::PackageId,
process::ProcessBuilder, restore::Restore,
process::ProcessBuilder, restore::Restore, rustup::Rustup,
};

type Result<T, E = anyhow::Error> = std::result::Result<T, E>;
Expand Down Expand Up @@ -58,13 +59,31 @@ fn exec_on_workspace(cx: &Context<'_>) -> Result<()> {
// )
// }

let line = cx.process().with_args(cx);

let restore = Restore::new(cx);
let mut progress = Progress::default();
determine_package_list(cx, &mut progress)?
.iter()
.try_for_each(|(id, kind)| exec_on_package(cx, id, kind, &line, &restore, &mut progress))
let packages = determine_package_list(cx, &mut progress)?;
let restore = Restore::new(cx);
if let Some(range) = &cx.version_range {
progress.total *= range.len();
let mut line = ProcessBuilder::new("cargo".as_ref());
if cx.verbose {
line.display_manifest_path();
}
range.iter().try_for_each(|toolchain| {
rustup::install_toolchain(&toolchain[1..])?;
let mut line = line.clone();
line.leading_arg(toolchain);
line.with_args(cx);
packages.iter().try_for_each(|(id, kind)| {
exec_on_package(cx, id, kind, &line, &restore, &mut progress)
})
})
} else {
let mut line = cx.cargo();
line.with_args(cx);
packages.iter().try_for_each(|(id, kind)| {
exec_on_package(cx, id, kind, &line, &restore, &mut progress)
})
}
}

#[derive(Default)]
Expand Down Expand Up @@ -344,7 +363,7 @@ fn exec_cargo(
}

fn cargo_clean(cx: &Context<'_>, id: &PackageId) -> Result<()> {
let mut line = cx.process();
let mut line = cx.cargo();
line.arg("clean");
line.arg("--package");
line.arg(&cx.packages(id).name);
Expand Down
Loading

0 comments on commit 65b3653

Please sign in to comment.