Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add features options when searching or verifying MSRV #834

Merged
merged 6 commits into from
Nov 17, 2023
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
2 changes: 1 addition & 1 deletion .github/workflows/msrv.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
with:
tool: cargo-binstall
- name: install_cargo_msrv_bin
run: cargo binstall --version 0.16.0-beta.15 --no-confirm cargo-msrv
run: cargo binstall --version 0.16.0-beta.17 --no-confirm cargo-msrv
- name: version_of_cargo_msrv
run: cargo msrv --version
- name: run_cargo_msrv
Expand Down
16 changes: 6 additions & 10 deletions src/check/rustup_toolchain_check.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::check::Check;
use crate::command::RustupCommand;
use crate::command::cargo_command::CargoCommand;
use crate::command::rustup_command::RustupCommand;
use crate::context::EnvironmentContext;
use crate::download::{DownloadToolchain, ToolchainDownloader};
use crate::error::{IoError, IoErrorSource};
Expand Down Expand Up @@ -214,15 +215,10 @@ pub struct RunCommand {
}

impl RunCommand {
pub fn default(target: impl ToString) -> Self {
let command = vec![
"cargo".to_string(),
"check".to_string(),
"--target".to_string(),
target.to_string(),
];

Self { command }
pub fn default(cargo_command: CargoCommand) -> Self {
Self {
command: cargo_command.into_args(),
}
}

pub fn custom(command: Vec<String>) -> Self {
Expand Down
4 changes: 2 additions & 2 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::cli::custom_check_opts::CustomCheckOpts;
use crate::cli::custom_check_opts::CheckCommandOpts;
use crate::cli::find_opts::FindOpts;
use crate::cli::rust_releases_opts::RustReleasesOpts;
use crate::cli::shared_opts::SharedOpts;
Expand Down Expand Up @@ -150,7 +150,7 @@ pub struct VerifyOpts {
pub toolchain_opts: ToolchainOpts,

#[command(flatten)]
pub custom_check: CustomCheckOpts,
pub cargo_check_opts: CheckCommandOpts,

/// The Rust version, to check against for toolchain compatibility
///
Expand Down
23 changes: 22 additions & 1 deletion src/cli/custom_check_opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,28 @@

#[derive(Debug, Args)]
#[command(next_help_heading = "Custom check options")]
pub struct CustomCheckOpts {
pub struct CheckCommandOpts {
/// Forwards the provided features to cargo, when running cargo-msrv with the default compatibility
/// check command.
///
/// If a custom a custom compatibility check command is used, this option is ignored.
#[arg(long)]
pub features: Option<Vec<String>>,

Check warning on line 11 in src/cli/custom_check_opts.rs

View check run for this annotation

Codecov / codecov/patch

src/cli/custom_check_opts.rs#L11

Added line #L11 was not covered by tests

/// Forwards the --all-features flag to cargo, when running cargo-msrv with the default compatibility
/// check command.
///
/// If a custom a custom compatibility check command is used, this option is ignored.
#[arg(long, value_delimiter = ' ')]
pub all_features: bool,

Check warning on line 18 in src/cli/custom_check_opts.rs

View check run for this annotation

Codecov / codecov/patch

src/cli/custom_check_opts.rs#L18

Added line #L18 was not covered by tests

/// Forwards the --no-default-features flag to cargo, when running cargo-msrv with the default compatibility
/// check command.
///
/// If a custom a custom compatibility check command is used, this option is ignored.
#[arg(long)]
pub no_default_features: bool,

Check warning on line 25 in src/cli/custom_check_opts.rs

View check run for this annotation

Codecov / codecov/patch

src/cli/custom_check_opts.rs#L25

Added line #L25 was not covered by tests

/// Supply a custom `check` command to be used by cargo msrv
#[arg(last = true)]
pub custom_check_command: Option<Vec<String>>,
Expand Down
4 changes: 2 additions & 2 deletions src/cli/find_opts.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::cli::custom_check_opts::CustomCheckOpts;
use crate::cli::custom_check_opts::CheckCommandOpts;
use crate::cli::rust_releases_opts::RustReleasesOpts;
use crate::cli::toolchain_opts::ToolchainOpts;
use clap::Args;
Expand Down Expand Up @@ -59,5 +59,5 @@ pub struct FindOpts {
pub toolchain_opts: ToolchainOpts,

#[command(flatten)]
pub custom_check_opts: CustomCheckOpts,
pub custom_check_opts: CheckCommandOpts,
}
122 changes: 2 additions & 120 deletions src/command.rs
Original file line number Diff line number Diff line change
@@ -1,120 +1,2 @@
use std::ffi::{OsStr, OsString};
use std::path::Path;
use std::process::{Command, Stdio};

use crate::error::{IoError, IoErrorSource, TResult};

pub struct RustupCommand {
command: Command,
args: Vec<OsString>,
stdout: Stdio,
stderr: Stdio,
}

impl RustupCommand {
pub fn new() -> Self {
Self {
command: Command::new("rustup"),
args: Vec::new(),
stdout: Stdio::null(),
stderr: Stdio::null(),
}
}

pub fn with_dir(mut self, path: impl AsRef<Path>) -> Self {
let _ = self.command.current_dir(path);
self
}

pub fn with_args<T: Into<OsString>>(mut self, args: impl IntoIterator<Item = T>) -> Self {
self.args.extend(args.into_iter().map(Into::into));
self
}

pub fn with_stdout(mut self) -> Self {
self.stdout = Stdio::piped();
self
}

pub fn with_stderr(mut self) -> Self {
self.stderr = Stdio::piped();
self
}

/// Execute `rustup run [...]`
pub fn run(self) -> TResult<RustupOutput> {
self.execute(OsStr::new("run"))
}

/// Execute `rustup install [...]`
pub fn install(self) -> TResult<RustupOutput> {
self.execute(OsStr::new("install"))
}

/// Execute `rustup show [...]`
pub fn show(self) -> TResult<RustupOutput> {
self.execute(OsStr::new("show"))
}

pub fn target(self) -> TResult<RustupOutput> {
self.execute(OsStr::new("target"))
}

/// Execute a given `rustup` command.
///
/// See also:
/// * [RustupCommand::run](RustupCommand::run)
/// * [RustupCommand::install](RustupCommand::install)
/// * [RustupCommand::show](RustupCommand::show)
pub fn execute(mut self, cmd: &OsStr) -> TResult<RustupOutput> {
debug!(
cmd = ?cmd,
args = ?self.args.as_slice()
);

self.command.arg(cmd);
self.command.args(self.args);

self.command.stdout(self.stdout);
self.command.stderr(self.stderr);

let child = self.command.spawn().map_err(|error| IoError {
error,
source: IoErrorSource::SpawnProcess(cmd.to_owned()),
})?;
let output = child.wait_with_output().map_err(|error| IoError {
error,
source: IoErrorSource::WaitForProcessAndCollectOutput(cmd.to_owned()),
})?;

Ok(RustupOutput {
output,
stdout: once_cell::sync::OnceCell::new(),
stderr: once_cell::sync::OnceCell::new(),
})
}
}

pub struct RustupOutput {
output: std::process::Output,
stdout: once_cell::sync::OnceCell<String>,
stderr: once_cell::sync::OnceCell<String>,
}

impl RustupOutput {
pub fn stdout(&self) -> &str {
self.stdout
.get_or_init(|| String::from_utf8_lossy(&self.output.stdout).into_owned())
.as_str()
}

pub fn stderr(&self) -> &str {
self.stderr
.get_or_init(|| String::from_utf8_lossy(&self.output.stderr).into_owned())
.as_str()
}

pub fn exit_status(&self) -> std::process::ExitStatus {
self.output.status
}
}
pub mod cargo_command;
pub mod rustup_command;
166 changes: 166 additions & 0 deletions src/command/cargo_command.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
#[derive(Debug, Default)]
pub struct CargoCommand {
features: Option<Vec<String>>,
all_features: bool,
no_default_features: bool,
target: Option<String>,
}

impl CargoCommand {
/// Set the features to be forwarded as `cargo <cmd> --features`
pub fn features(mut self, features: Option<Vec<String>>) -> Self {
self.features = features;
self
}

/// Set the `all features` flag to be forwarded as `cargo <cmd> --all-features`
pub fn all_features(mut self, value: bool) -> Self {
self.all_features = value;
self
}

/// Set the `no default features` flag to be forwarded as `cargo <cmd> --no-default-features`
pub fn no_default_features(mut self, value: bool) -> Self {
self.no_default_features = value;
self
}

/// Set the target flag to be forwarded as `cargo <cmd> --target
pub fn target(mut self, target: Option<impl ToString>) -> Self {
self.target = target.map(|t| t.to_string());
self
}

/// Intended to be used in conjunction with [`RunCommand`] and/or [`RustupCommand`].
///
/// [`RunCommand`]: crate::check::RunCommand
/// [`RustupCommand`]: crate::command::rustup_command::RustupCommand
// Currently we don't invoke it from here directly, but we might eventually, if
// we want to also provide some nicer structs around parsing. However compared to
// some other cargo subcommand crates, we also (currently) need rustup, so the invocation
// would need to supply everything we supply to rustup.
pub fn into_args(self) -> Vec<String> {
// Eventually we should also add support for CARGO env var
let mut args = Vec::<String>::with_capacity(8);

// Currently only `cargo check` is used by cargo msrv.
// Alternatives can be set when using cargo msrv -- custom cmd
// This value does open the path to use cargo build for Rust < 1.16
args.extend_from_slice(&["cargo".to_string(), "check".to_string()]);

if let Some(features) = self.features {
let features = features.join(",");

args.extend_from_slice(&["--features".to_string(), features]);
}

// probably unnecessary to supply both this and --features, if both have a value, but
// by adding both to the command separately, we can optimally invoke cargo's own behaviour
if self.all_features {
args.push("--all-features".to_string());
}

if self.no_default_features {
args.push("--no-default-features".to_string());
}

if let Some(target) = self.target {
args.push("--target".to_string());
args.push(target);
}

args
}
}

#[cfg(test)]
mod tests {
use crate::command::cargo_command::CargoCommand;

#[test]
fn set_features_none() {
let cargo_command = CargoCommand::default();
let cargo_command = cargo_command.features(None);
assert_eq!(
cargo_command.into_args().join(" "),
"cargo check".to_string()
);
}

#[test]
fn set_features_one() {
let cargo_command = CargoCommand::default();
let cargo_command = cargo_command.features(Some(vec!["pika".to_string()]));
assert_eq!(
cargo_command.into_args().join(" "),
"cargo check --features pika".to_string()
);
}

#[test]
fn set_features_two() {
let cargo_command = CargoCommand::default();
let cargo_command =
cargo_command.features(Some(vec!["chu".to_string(), "chris".to_string()]));
assert_eq!(
cargo_command.into_args().join(" "),
"cargo check --features chu,chris".to_string()
);
}

#[test]
fn set_no_default_features() {
let cargo_command = CargoCommand::default();
let cargo_command = cargo_command.no_default_features(true);
assert_eq!(
cargo_command.into_args().join(" "),
"cargo check --no-default-features".to_string()
);
}

#[test]
fn set_all_features() {
let cargo_command = CargoCommand::default();
let cargo_command = cargo_command.all_features(true);
assert_eq!(
cargo_command.into_args().join(" "),
"cargo check --all-features".to_string()
);
}

#[test]
fn set_target_none() {
let cargo_command = CargoCommand::default();
let cargo_command = cargo_command.target(None::<String>);
assert_eq!(
cargo_command.into_args().join(" "),
"cargo check".to_string()
);
}

#[test]
fn set_target_some() {
let cargo_command = CargoCommand::default();
let cargo_command = cargo_command.target(Some("some"));
assert_eq!(
cargo_command.into_args().join(" "),
"cargo check --target some".to_string()
);
}

#[test]
fn combination_of_everything() {
let cargo_command = CargoCommand::default();
let cargo_command = cargo_command
.features(Some(vec!["pika".to_string(), "chu".to_string()]))
.all_features(true)
.no_default_features(true)
.target(Some("pickme"));

let cmd = cargo_command.into_args().join(" ");
assert!(cmd.contains("--all-features"));
assert!(cmd.contains("--features pika,chu"));
assert!(cmd.contains("--no-default-features"));
assert!(cmd.contains("--target pickme"));
}
}
Loading
Loading