cargo-msrv 0.11.0

Find your minimum supported Rust version (MSRV)!
Documentation
use crate::fetch::is_target_available;
use clap::{App, AppSettings, Arg, SubCommand};

pub mod id {
    pub const SUB_COMMAND_MSRV: &str = "msrv";
    pub const ARG_SEEK_PATH: &str = "seek_path";
    pub const ARG_SEEK_CUSTOM_TARGET: &str = "seek_target";
    pub const ARG_CUSTOM_CHECK: &str = "custom_check";
    pub const ARG_INCLUDE_ALL_PATCH_RELEASES: &str = "include_all_patch";
    pub const ARG_MIN: &str = "min";
    pub const ARG_MAX: &str = "max";
    pub const ARG_BISECT: &str = "bisect";
    pub const ARG_TOOLCHAIN_FILE: &str = "toolchain_file";
    pub const ARG_IGNORE_LOCKFILE: &str = "lockfile";
    pub const ARG_OUTPUT_FORMAT: &str = "output_format";
    pub const ARG_VERIFY: &str = "verify_msrv";
    pub const ARG_RELEASE_SOURCE: &str = "release_source";
    pub const ARG_NO_LOG: &str = "no_log";
    pub const ARG_NO_READ_MIN_EDITION: &str = "no_read_min_edition";
}

pub fn cli() -> App<'static, 'static> {
    App::new("cargo-msrv")
        .bin_name("cargo")
        .version(env!("CARGO_PKG_VERSION"))
        .author("Martijn Gribnau <garm@ilumeo.com>")
        .global_setting(AppSettings::NextLineHelp)
        .global_setting(AppSettings::ColoredHelp)
        .global_setting(AppSettings::ColorAuto)
        .global_setting(AppSettings::DontCollapseArgsInUsage)
        .global_setting(AppSettings::UnifiedHelpMessage)
        .setting(AppSettings::SubcommandRequiredElseHelp)
        .max_term_width(120)
        .subcommand(
            SubCommand::with_name(id::SUB_COMMAND_MSRV)
                .version(env!("CARGO_PKG_VERSION"))
                .usage("cargo msrv [OPTIONS]")
                .about("Helps with finding the Minimal Supported Rust Version (MSRV)")
                .after_help("\
An argument provided after two dashes (`--`), will be interpreted as a custom command `check` command, \
used to validate whether a Rust toolchain version is compatible. The default `check` command is \
\"cargo build\". A custom `check` command should be runnable by rustup, as they will be passed on to \
rustup like so: `rustup run <toolchain> <COMMAND...>`. You'll only need to provide the <COMMAND...> part.")
                .arg(
                    Arg::with_name(id::ARG_SEEK_PATH)
                        .long("path")
                        .help("Path to the cargo project directory")
                        .takes_value(true)
                        .value_name("DIR")
                        .validator(|value| {
                            std::fs::metadata(&value)
                                .map_err(|_| "Path doesn't exist.".to_string())
                                .and_then(|m| {
                                    if m.is_dir() {
                                        Ok(())
                                    } else {
                                        Err("Not a directory.".to_string())
                                    }
                                })
                        }),
                )
                .arg(
                    Arg::with_name(id::ARG_SEEK_CUSTOM_TARGET)
                        .long("target")
                        .help("Check against a custom target (instead of the rustup default)")
                        .takes_value(true)
                        .value_name("TARGET")
                        .validator(|value| {
                            is_target_available(&value).map_err(|_| {
                                "The provided target is not available. Use `rustup target list` to \
                                 review the available targets."
                                    .to_string()
                            })
                        }),
                )
                .arg(Arg::with_name(id::ARG_INCLUDE_ALL_PATCH_RELEASES)
                    .long("include-all-patch-releases")
                    .help("Include all patch releases, instead of only the last")
                    .takes_value(false)
                )
                .arg(Arg::with_name(id::ARG_MIN)
                    .long("min")
                    .visible_alias("minimum")
                    .help("Earliest version to take into account")
                    .long_help("Earliest (least recent) version to take into account. \
                     Version must match a valid Rust toolchain, and be semver compatible. Edition aliases may also be used.")
                    .takes_value(true)
                )
                .arg(Arg::with_name(id::ARG_MAX)
                    .long("max")
                    .visible_alias("maximum")
                    .help("Latest version to take into account")
                    .long_help("Latest (most recent) version to take into account.\
                     Version must match a valid Rust toolchain, and be semver compatible.")
                    .takes_value(true)
                )
                .arg(Arg::with_name(id::ARG_BISECT)
                    .long("bisect")
                    .help("Use a binary search to find the MSRV instead of a linear search")
                    .takes_value(false)
                )
                .arg(Arg::with_name(id::ARG_TOOLCHAIN_FILE)
                    .long("toolchain-file")
                    .help("Output a rust-toolchain file with the MSRV as toolchain")
                    .long_help("Output a rust-toolchain file with the MSRV as toolchain. \
                    The toolchain file will pin the Rust version for this crate. \
                    See https://rust-lang.github.io/rustup/overrides.html#the-toolchain-file for more.")
                )
                .arg(Arg::with_name(id::ARG_IGNORE_LOCKFILE)
                    .long("ignore-lockfile")
                    .help("Temporarily removes the lockfile, so it will not interfere with the building process")
                    .long_help("Temporarily removes the lockfile, so it will not interfere with the building process. \
                    This is important when testing against Rust versions prior to 1.38.0, for which Cargo does not recognize the new v2 lockfile.")
                )
                .arg(Arg::with_name(id::ARG_OUTPUT_FORMAT)
                    .long("output-format")
                    .help("Output status messages in machine-readable format")
                    .takes_value(true)
                    .possible_values(&["json"])
                    .long_help("Output status messages in machine-readable format. \
                Machine-readable status updates will be printed in the requested format to stdout.")
                )
                .arg(Arg::with_name(id::ARG_VERIFY)
                    .long("verify")
                    .help("Verify the MSRV defined in the 'package.rust-version' or the 'package.metadata.msrv' key in Cargo.toml")
                    .long_help("Verify the MSRV defined in the 'package.rust-version' or the 'package.metadata.msrv' key in Cargo.toml. \
                    When this flag is present, cargo-msrv will not attempt to determine the true MSRV. \
                    Instead it attempts to verify whether for the specified MSRV, the `check` command passes. This is similar to \
                    how we determine whether a Rust toolchain version is compatible for your crate or not.")
                    .takes_value(false)
                )
                .arg(Arg::with_name(id::ARG_RELEASE_SOURCE)
                    .long("release-source")
                    .help("Select the rust-releases source to use as the release index")
                    .takes_value(true)
                    .possible_values(&["rust-changelog", "rust-dist"])
                    .default_value("rust-changelog")
                )
                .arg(Arg::with_name(id::ARG_NO_LOG)
                    .long("no-log")
                    .help("Disable logging")
                    .takes_value(false)
                )
                .arg(Arg::with_name(id::ARG_NO_READ_MIN_EDITION)
                    .long("no-read-min-edition")
                    .help("If provided, the 'package.edition' value in the Cargo.toml will not \
                    be used to reduce search space.")
                    .takes_value(false)
                )
                .arg(
                    Arg::with_name(id::ARG_CUSTOM_CHECK)
                        .value_name("COMMAND")
                        .help("If given, this command is used to validate if a Rust version is \
                        compatible. Should be available to rustup, i.e. the command should work like \
                        so: `rustup run <toolchain> <COMMAND>`. \
                        The default check action is `cargo check --all`.")
                        .multiple(true)
                        .last(true)

                )
        )
}