Skip to content

Commit

Permalink
Merge pull request #834 from foresterre/feat/gh-832
Browse files Browse the repository at this point in the history
  • Loading branch information
foresterre authored Nov 17, 2023
2 parents 8debb89 + f7b23c1 commit 2dcd61e
Show file tree
Hide file tree
Showing 22 changed files with 504 additions and 151 deletions.
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 @@ use clap::Args;

#[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>>,

/// 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,

/// 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,

/// 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

0 comments on commit 2dcd61e

Please sign in to comment.