mirror of
https://github.com/cargo-bins/cargo-binstall.git
synced 2025-04-22 05:28:42 +00:00

If then default is too long, then users would still have to wait a long time if they hit rate limit. Signed-off-by: Jiahao XU <30436523+NobodyXu@users.noreply.github.com>
685 lines
23 KiB
Rust
685 lines
23 KiB
Rust
use std::{
|
||
env,
|
||
ffi::OsString,
|
||
fmt, mem,
|
||
num::{NonZeroU16, NonZeroU64, ParseIntError},
|
||
path::PathBuf,
|
||
str::FromStr,
|
||
};
|
||
|
||
use binstalk::{
|
||
helpers::remote,
|
||
manifests::cargo_toml_binstall::PkgFmt,
|
||
ops::resolve::{CrateName, VersionReqExt},
|
||
registry::Registry,
|
||
};
|
||
use binstalk_manifests::cargo_toml_binstall::{PkgOverride, Strategy};
|
||
use clap::{builder::PossibleValue, error::ErrorKind, CommandFactory, Parser, ValueEnum};
|
||
use compact_str::CompactString;
|
||
use log::LevelFilter;
|
||
use semver::VersionReq;
|
||
use strum::EnumCount;
|
||
use zeroize::Zeroizing;
|
||
|
||
#[derive(Debug, Parser)]
|
||
#[clap(
|
||
version,
|
||
about = "Install a Rust binary... from binaries!",
|
||
after_long_help =
|
||
"License: GPLv3. Source available at https://github.com/cargo-bins/cargo-binstall\n\n\
|
||
Some crate installation strategies may collect anonymized usage statistics by default. \
|
||
If you prefer not to participate on such data collection, you can opt out by using the \
|
||
`--disable-telemetry` flag or its associated environment variable. For more details \
|
||
about this data collection, please refer to the mentioned flag or the project's README \
|
||
file",
|
||
arg_required_else_help(true),
|
||
// Avoid conflict with version_req
|
||
disable_version_flag(true),
|
||
)]
|
||
pub struct Args {
|
||
/// Packages to install.
|
||
///
|
||
/// Syntax: `crate[@version]`
|
||
///
|
||
/// Each value is either a crate name alone, or a crate name followed by @ and the version to
|
||
/// install. The version syntax is as with the --version option.
|
||
///
|
||
/// When multiple names are provided, the --version option and override option
|
||
/// `--manifest-path` and `--git` are unavailable due to ambiguity.
|
||
///
|
||
/// If duplicate names are provided, the last one (and their version requirement)
|
||
/// is kept.
|
||
#[clap(
|
||
help_heading = "Package selection",
|
||
value_name = "crate[@version]",
|
||
required_unless_present_any = ["version", "help"],
|
||
)]
|
||
pub(crate) crate_names: Vec<CrateName>,
|
||
|
||
/// Package version to install.
|
||
///
|
||
/// Takes either an exact semver version or a semver version requirement expression, which will
|
||
/// be resolved to the highest matching version available.
|
||
///
|
||
/// Cannot be used when multiple packages are installed at once, use the attached version
|
||
/// syntax in that case.
|
||
#[clap(
|
||
help_heading = "Package selection",
|
||
long = "version",
|
||
value_parser(VersionReq::parse_from_cli)
|
||
)]
|
||
pub(crate) version_req: Option<VersionReq>,
|
||
|
||
/// Override binary target set.
|
||
///
|
||
/// Binstall is able to look for binaries for several targets, installing the first one it finds
|
||
/// in the order the targets were given. For example, on a 64-bit glibc Linux distribution, the
|
||
/// default is to look first for a `x86_64-unknown-linux-gnu` binary, then for a
|
||
/// `x86_64-unknown-linux-musl` binary. However, on a musl system, the gnu version will not be
|
||
/// considered.
|
||
///
|
||
/// This option takes a comma-separated list of target triples, which will be tried in order.
|
||
/// They override the default list, which is detected automatically from the current platform.
|
||
///
|
||
/// If falling back to installing from source, the first target will be used.
|
||
#[clap(
|
||
help_heading = "Package selection",
|
||
alias = "target",
|
||
long,
|
||
value_name = "TRIPLE"
|
||
)]
|
||
pub(crate) targets: Option<Vec<String>>,
|
||
|
||
/// Override Cargo.toml package manifest path.
|
||
///
|
||
/// This skips searching crates.io for a manifest and uses the specified path directly, useful
|
||
/// for debugging and when adding Binstall support. This may be either the path to the folder
|
||
/// containing a Cargo.toml file, or the Cargo.toml file itself.
|
||
///
|
||
/// This option cannot be used with `--git`.
|
||
#[clap(help_heading = "Overrides", long)]
|
||
pub(crate) manifest_path: Option<PathBuf>,
|
||
|
||
#[cfg(feature = "git")]
|
||
/// Override how to fetch Cargo.toml package manifest.
|
||
///
|
||
/// This skip searching crates.io and instead clone the repository specified and
|
||
/// runs as if `--manifest-path $cloned_repo` is passed to binstall.
|
||
///
|
||
/// This option cannot be used with `--manifest-path`.
|
||
#[clap(help_heading = "Overrides", long, conflicts_with("manifest_path"))]
|
||
pub(crate) git: Option<binstalk::registry::GitUrl>,
|
||
|
||
/// Override Cargo.toml package manifest bin-dir.
|
||
#[clap(help_heading = "Overrides", long)]
|
||
pub(crate) bin_dir: Option<String>,
|
||
|
||
/// Override Cargo.toml package manifest pkg-fmt.
|
||
///
|
||
/// The available package formats are:
|
||
///
|
||
/// - tar: download format is TAR (uncompressed)
|
||
///
|
||
/// - tbz2: Download format is TAR + Bzip2
|
||
///
|
||
/// - tgz: Download format is TGZ (TAR + GZip)
|
||
///
|
||
/// - txz: Download format is TAR + XZ
|
||
///
|
||
/// - tzstd: Download format is TAR + Zstd
|
||
///
|
||
/// - zip: Download format is Zip
|
||
///
|
||
/// - bin: Download format is raw / binary
|
||
#[clap(help_heading = "Overrides", long, value_name = "PKG_FMT")]
|
||
pub(crate) pkg_fmt: Option<PkgFmt>,
|
||
|
||
/// Override Cargo.toml package manifest pkg-url.
|
||
#[clap(help_heading = "Overrides", long)]
|
||
pub(crate) pkg_url: Option<String>,
|
||
|
||
/// Override the rate limit duration.
|
||
///
|
||
/// By default, cargo-binstall allows one request per 10 ms.
|
||
///
|
||
/// Example:
|
||
///
|
||
/// - `6`: Set the duration to 6ms, allows one request per 6 ms.
|
||
///
|
||
/// - `6/2`: Set the duration to 6ms and request_count to 2,
|
||
/// allows 2 requests per 6ms.
|
||
///
|
||
/// Both duration and request count must not be 0.
|
||
#[clap(
|
||
help_heading = "Overrides",
|
||
long,
|
||
default_value_t = RateLimit::default(),
|
||
env = "BINSTALL_RATE_LIMIT"
|
||
)]
|
||
pub(crate) rate_limit: RateLimit,
|
||
|
||
/// Specify the strategies to be used,
|
||
/// binstall will run the strategies specified in order.
|
||
///
|
||
/// If this option is specified, then cargo-binstall will ignore
|
||
/// `disabled-strategies` in `package.metadata` in the cargo manifest
|
||
/// of the installed packages.
|
||
///
|
||
/// Default value is "crate-meta-data,quick-install,compile".
|
||
#[clap(
|
||
help_heading = "Overrides",
|
||
long,
|
||
value_delimiter(','),
|
||
env = "BINSTALL_STRATEGIES"
|
||
)]
|
||
pub(crate) strategies: Vec<StrategyWrapped>,
|
||
|
||
/// Disable the strategies specified.
|
||
/// If a strategy is specified in `--strategies` and `--disable-strategies`,
|
||
/// then it will be removed.
|
||
///
|
||
/// If `--strategies` is not specified, then the strategies specified in this
|
||
/// option will be merged with the disabled-strategies` in `package.metadata`
|
||
/// in the cargo manifest of the installed packages.
|
||
#[clap(
|
||
help_heading = "Overrides",
|
||
long,
|
||
value_delimiter(','),
|
||
env = "BINSTALL_DISABLE_STRATEGIES"
|
||
)]
|
||
pub(crate) disable_strategies: Vec<StrategyWrapped>,
|
||
|
||
/// If `--github-token` or environment variable `GITHUB_TOKEN`/`GH_TOKEN`
|
||
/// is not specified, then cargo-binstall will try to extract github token from
|
||
/// `$HOME/.git-credentials` or `$HOME/.config/gh/hosts.yml` by default.
|
||
///
|
||
/// This option can be used to disable that behavior.
|
||
#[clap(
|
||
help_heading = "Overrides",
|
||
long,
|
||
env = "BINSTALL_NO_DISCOVER_GITHUB_TOKEN"
|
||
)]
|
||
pub(crate) no_discover_github_token: bool,
|
||
|
||
/// Maximum time each resolution (one for each possible target and each strategy), in seconds.
|
||
#[clap(
|
||
help_heading = "Overrides",
|
||
long,
|
||
env = "BINSTALL_MAXIMUM_RESOLUTION_TIMEOUT",
|
||
default_value_t = NonZeroU16::new(15).unwrap(),
|
||
)]
|
||
pub(crate) maximum_resolution_timeout: NonZeroU16,
|
||
|
||
/// This flag is now enabled by default thus a no-op.
|
||
///
|
||
/// By default, Binstall will install a binary as-is in the install path.
|
||
#[clap(help_heading = "Options", long, default_value_t = true)]
|
||
pub(crate) no_symlinks: bool,
|
||
|
||
/// Dry run, fetch and show changes without installing binaries.
|
||
#[clap(help_heading = "Options", long)]
|
||
pub(crate) dry_run: bool,
|
||
|
||
/// Disable interactive mode / confirmation prompts.
|
||
#[clap(help_heading = "Options", short = 'y', long)]
|
||
pub(crate) no_confirm: bool,
|
||
|
||
/// Do not cleanup temporary files.
|
||
#[clap(help_heading = "Options", long)]
|
||
pub(crate) no_cleanup: bool,
|
||
|
||
/// Continue installing other crates even if one of the crate failed to install.
|
||
#[clap(help_heading = "Options", long)]
|
||
pub(crate) continue_on_failure: bool,
|
||
|
||
/// By default, binstall keeps track of the installed packages with metadata files
|
||
/// stored in the installation root directory.
|
||
///
|
||
/// This flag tells binstall not to use or create that file.
|
||
///
|
||
/// With this flag, binstall will refuse to overwrite any existing files unless the
|
||
/// `--force` flag is used.
|
||
///
|
||
/// This also disables binstall’s ability to protect against multiple concurrent
|
||
/// invocations of binstall installing at the same time.
|
||
///
|
||
/// This flag will also be passed to `cargo-install` if it is invoked.
|
||
#[clap(help_heading = "Options", long)]
|
||
pub(crate) no_track: bool,
|
||
|
||
/// Disable statistics collection on popular crates.
|
||
///
|
||
/// Strategy quick-install (can be disabled via --disable-strategies) collects
|
||
/// statistics of popular crates by default, by sending the crate, version and
|
||
/// target to https://warehouse-clerk-tmp.vercel.app/api/crate
|
||
#[clap(help_heading = "Options", long, env = "BINSTALL_DISABLE_TELEMETRY")]
|
||
pub(crate) disable_telemetry: bool,
|
||
|
||
/// Install binaries in a custom location.
|
||
///
|
||
/// By default, binaries are installed to the global location `$CARGO_HOME/bin`, and global
|
||
/// metadata files are updated with the package information. Specifying another path here
|
||
/// switches over to a "local" install, where binaries are installed at the path given, and the
|
||
/// global metadata files are not updated.
|
||
#[clap(help_heading = "Options", long)]
|
||
pub(crate) install_path: Option<PathBuf>,
|
||
|
||
/// Install binaries with a custom cargo root.
|
||
///
|
||
/// By default, we use `$CARGO_INSTALL_ROOT` or `$CARGO_HOME` as the
|
||
/// cargo root and global metadata files are updated with the
|
||
/// package information.
|
||
///
|
||
/// Specifying another path here would install the binaries and update
|
||
/// the metadata files inside the path you specified.
|
||
///
|
||
/// NOTE that `--install-path` takes precedence over this option.
|
||
#[clap(help_heading = "Options", long, alias = "roots")]
|
||
pub(crate) root: Option<PathBuf>,
|
||
|
||
/// The URL of the registry index to use.
|
||
///
|
||
/// Cannot be used with `--registry`.
|
||
#[clap(help_heading = "Options", long)]
|
||
pub(crate) index: Option<Registry>,
|
||
|
||
/// Name of the registry to use. Registry names are defined in Cargo config
|
||
/// files <https://doc.rust-lang.org/cargo/reference/config.html>.
|
||
///
|
||
/// If not specified in cmdline or via environment variable, the default
|
||
/// registry is used, which is defined by the
|
||
/// `registry.default` config key in `.cargo/config.toml` which defaults
|
||
/// to crates-io.
|
||
///
|
||
/// If it is set, then it will try to read environment variable
|
||
/// `CARGO_REGISTRIES_{registry_name}_INDEX` for index url and fallback to
|
||
/// reading from `registries.<name>.index`.
|
||
///
|
||
/// Cannot be used with `--index`.
|
||
#[clap(
|
||
help_heading = "Options",
|
||
long,
|
||
env = "CARGO_REGISTRY_DEFAULT",
|
||
conflicts_with("index")
|
||
)]
|
||
pub(crate) registry: Option<CompactString>,
|
||
|
||
/// This option will be passed through to all `cargo-install` invocations.
|
||
///
|
||
/// It will require `Cargo.lock` to be up to date.
|
||
#[clap(help_heading = "Options", long)]
|
||
pub(crate) locked: bool,
|
||
|
||
/// Deprecated, here for back-compat only. Secure is now on by default.
|
||
#[clap(hide(true), long)]
|
||
pub(crate) secure: bool,
|
||
|
||
/// Force a crate to be installed even if it is already installed.
|
||
#[clap(help_heading = "Options", long)]
|
||
pub(crate) force: bool,
|
||
|
||
/// Require a minimum TLS version from remote endpoints.
|
||
///
|
||
/// The default is not to require any minimum TLS version, and use the negotiated highest
|
||
/// version available to both this client and the remote server.
|
||
#[clap(help_heading = "Options", long, value_enum, value_name = "VERSION")]
|
||
pub(crate) min_tls_version: Option<TLSVersion>,
|
||
|
||
/// Specify the root certificates to use for https connnections,
|
||
/// in addition to default system-wide ones.
|
||
#[clap(help_heading = "Options", long, env = "BINSTALL_HTTPS_ROOT_CERTS")]
|
||
pub(crate) root_certificates: Vec<PathBuf>,
|
||
|
||
/// Print logs in json format to be parsable.
|
||
#[clap(help_heading = "Options", long)]
|
||
pub json_output: bool,
|
||
|
||
/// Provide the github token for accessing the restful API of api.github.com
|
||
///
|
||
/// Fallback to environment variable `GITHUB_TOKEN` if this option is not
|
||
/// specified (which is also shown by clap's auto generated doc below), or
|
||
/// try environment variable `GH_TOKEN`, which is also used by `gh` cli.
|
||
///
|
||
/// If none of them is present, then binstall will try to extract github
|
||
/// token from `$HOME/.git-credentials` or `$HOME/.config/gh/hosts.yml`
|
||
/// unless `--no-discover-github-token` is specified.
|
||
#[clap(help_heading = "Options", long, env = "GITHUB_TOKEN")]
|
||
pub(crate) github_token: Option<GithubToken>,
|
||
|
||
/// Only install packages that are signed
|
||
///
|
||
/// The default is to verify signatures if they are available, but to allow
|
||
/// unsigned packages as well.
|
||
#[clap(help_heading = "Options", long)]
|
||
pub(crate) only_signed: bool,
|
||
|
||
/// Don't check any signatures
|
||
///
|
||
/// The default is to verify signatures if they are available. This option
|
||
/// disables that behaviour entirely, which will also stop downloading
|
||
/// signature files in the first place.
|
||
///
|
||
/// Note that this is insecure and not recommended outside of testing.
|
||
#[clap(help_heading = "Options", long, conflicts_with = "only_signed")]
|
||
pub(crate) skip_signatures: bool,
|
||
|
||
/// Print version information
|
||
#[clap(help_heading = "Meta", short = 'V')]
|
||
pub version: bool,
|
||
|
||
/// Utility log level
|
||
///
|
||
/// Set to `trace` to print very low priority, often extremely
|
||
/// verbose information.
|
||
///
|
||
/// Set to `debug` when submitting a bug report.
|
||
///
|
||
/// Set to `info` to only print useful information.
|
||
///
|
||
/// Set to `warn` to only print on hazardous situations.
|
||
///
|
||
/// Set to `error` to only print serious errors.
|
||
///
|
||
/// Set to `off` to disable logging completely, this will also
|
||
/// disable output from `cargo-install`.
|
||
///
|
||
/// If `--log-level` is not specified on cmdline, then cargo-binstall
|
||
/// will try to read environment variable `BINSTALL_LOG_LEVEL` and
|
||
/// interpret it as a log-level.
|
||
#[clap(help_heading = "Meta", long, value_name = "LEVEL")]
|
||
pub log_level: Option<LevelFilter>,
|
||
|
||
/// Implies `--log-level debug` and it can also be used with `--version`
|
||
/// to print out verbose information,
|
||
#[clap(help_heading = "Meta", short, long)]
|
||
pub verbose: bool,
|
||
|
||
/// Equivalent to setting `log_level` to `off`.
|
||
///
|
||
/// This would override the `log_level`.
|
||
#[clap(help_heading = "Meta", short, long, conflicts_with("verbose"))]
|
||
pub(crate) quiet: bool,
|
||
}
|
||
|
||
#[derive(Debug, Clone)]
|
||
pub(crate) struct GithubToken(pub(crate) Zeroizing<Box<str>>);
|
||
|
||
impl From<&str> for GithubToken {
|
||
fn from(s: &str) -> Self {
|
||
Self(Zeroizing::new(s.into()))
|
||
}
|
||
}
|
||
|
||
#[derive(Debug, Copy, Clone, ValueEnum)]
|
||
pub(crate) enum TLSVersion {
|
||
#[clap(name = "1.2")]
|
||
Tls1_2,
|
||
#[clap(name = "1.3")]
|
||
Tls1_3,
|
||
}
|
||
|
||
impl From<TLSVersion> for remote::TLSVersion {
|
||
fn from(ver: TLSVersion) -> Self {
|
||
match ver {
|
||
TLSVersion::Tls1_2 => remote::TLSVersion::TLS_1_2,
|
||
TLSVersion::Tls1_3 => remote::TLSVersion::TLS_1_3,
|
||
}
|
||
}
|
||
}
|
||
|
||
#[derive(Copy, Clone, Debug)]
|
||
pub(crate) struct RateLimit {
|
||
pub(crate) duration: NonZeroU16,
|
||
pub(crate) request_count: NonZeroU64,
|
||
}
|
||
|
||
impl fmt::Display for RateLimit {
|
||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
write!(f, "{}/{}", self.duration, self.request_count)
|
||
}
|
||
}
|
||
|
||
impl FromStr for RateLimit {
|
||
type Err = ParseIntError;
|
||
|
||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||
Ok(if let Some((first, second)) = s.split_once('/') {
|
||
Self {
|
||
duration: first.parse()?,
|
||
request_count: second.parse()?,
|
||
}
|
||
} else {
|
||
Self {
|
||
duration: s.parse()?,
|
||
..Default::default()
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
impl Default for RateLimit {
|
||
fn default() -> Self {
|
||
Self {
|
||
duration: NonZeroU16::new(10).unwrap(),
|
||
request_count: NonZeroU64::new(1).unwrap(),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Strategy for installing the package
|
||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||
pub(crate) struct StrategyWrapped(pub(crate) Strategy);
|
||
|
||
impl StrategyWrapped {
|
||
const VARIANTS: &'static [Self; 3] = &[
|
||
Self(Strategy::CrateMetaData),
|
||
Self(Strategy::QuickInstall),
|
||
Self(Strategy::Compile),
|
||
];
|
||
}
|
||
|
||
impl ValueEnum for StrategyWrapped {
|
||
fn value_variants<'a>() -> &'a [Self] {
|
||
Self::VARIANTS
|
||
}
|
||
fn to_possible_value(&self) -> Option<PossibleValue> {
|
||
Some(PossibleValue::new(self.0.to_str()))
|
||
}
|
||
}
|
||
|
||
pub fn parse() -> (Args, PkgOverride) {
|
||
// Filter extraneous arg when invoked by cargo
|
||
// `cargo run -- --help` gives ["target/debug/cargo-binstall", "--help"]
|
||
// `cargo binstall --help` gives ["/home/ryan/.cargo/bin/cargo-binstall", "binstall", "--help"]
|
||
let mut args: Vec<OsString> = env::args_os().collect();
|
||
let args = if args.get(1).map(|arg| arg == "binstall").unwrap_or_default() {
|
||
// Equivalent to
|
||
//
|
||
// args.remove(1);
|
||
//
|
||
// But is O(1)
|
||
args.swap(0, 1);
|
||
let mut args = args.into_iter();
|
||
drop(args.next().unwrap());
|
||
|
||
args
|
||
} else {
|
||
args.into_iter()
|
||
};
|
||
|
||
// Load options
|
||
let mut opts = Args::parse_from(args);
|
||
|
||
if opts.log_level.is_none() {
|
||
if let Some(log) = env::var("BINSTALL_LOG_LEVEL")
|
||
.ok()
|
||
.and_then(|s| s.parse().ok())
|
||
{
|
||
opts.log_level = Some(log);
|
||
} else if opts.quiet {
|
||
opts.log_level = Some(LevelFilter::Off);
|
||
} else if opts.verbose {
|
||
opts.log_level = Some(LevelFilter::Debug);
|
||
}
|
||
}
|
||
|
||
// Ensure no conflict
|
||
let mut command = Args::command();
|
||
|
||
if opts.crate_names.len() > 1 {
|
||
let option = if opts.version_req.is_some() {
|
||
"version"
|
||
} else if opts.manifest_path.is_some() {
|
||
"manifest-path"
|
||
} else {
|
||
#[cfg(not(feature = "git"))]
|
||
{
|
||
""
|
||
}
|
||
|
||
#[cfg(feature = "git")]
|
||
if opts.git.is_some() {
|
||
"git"
|
||
} else {
|
||
""
|
||
}
|
||
};
|
||
|
||
if !option.is_empty() {
|
||
command
|
||
.error(
|
||
ErrorKind::ArgumentConflict,
|
||
format_args!(
|
||
r#"override option used with multi package syntax.
|
||
You cannot use --{option} and specify multiple packages at the same time. Do one or the other."#
|
||
),
|
||
)
|
||
.exit();
|
||
}
|
||
}
|
||
|
||
// Check strategies for duplicates
|
||
let mut new_dup_strategy_err = || {
|
||
command.error(
|
||
ErrorKind::TooManyValues,
|
||
"--strategies should not contain duplicate strategy",
|
||
)
|
||
};
|
||
|
||
if opts.strategies.len() > Strategy::COUNT {
|
||
// If len of strategies is larger than number of variants of Strategy,
|
||
// then there must be duplicates by pigeon hole principle.
|
||
new_dup_strategy_err().exit()
|
||
}
|
||
|
||
// Whether specific variant of Strategy is present
|
||
let mut is_variant_present = [false; Strategy::COUNT];
|
||
|
||
for strategy in &opts.strategies {
|
||
let index = strategy.0 as u8 as usize;
|
||
if is_variant_present[index] {
|
||
new_dup_strategy_err().exit()
|
||
} else {
|
||
is_variant_present[index] = true;
|
||
}
|
||
}
|
||
|
||
let ignore_disabled_strategies = !opts.strategies.is_empty();
|
||
|
||
// Default strategies if empty
|
||
if opts.strategies.is_empty() {
|
||
opts.strategies = vec![
|
||
StrategyWrapped(Strategy::CrateMetaData),
|
||
StrategyWrapped(Strategy::QuickInstall),
|
||
StrategyWrapped(Strategy::Compile),
|
||
];
|
||
}
|
||
|
||
// Filter out all disabled strategies
|
||
if !opts.disable_strategies.is_empty() {
|
||
// Since order doesn't matter, we can sort it and remove all duplicates
|
||
// to speedup checking.
|
||
opts.disable_strategies.sort_unstable();
|
||
opts.disable_strategies.dedup();
|
||
|
||
// disable_strategies.len() <= Strategy::COUNT, of which is faster
|
||
// to just use [Strategy]::contains rather than
|
||
// [Strategy]::binary_search
|
||
opts.strategies
|
||
.retain(|strategy| !opts.disable_strategies.contains(strategy));
|
||
|
||
if opts.strategies.is_empty() {
|
||
command
|
||
.error(ErrorKind::TooFewValues, "You have disabled all strategies")
|
||
.exit()
|
||
}
|
||
}
|
||
|
||
// Ensure that Strategy::Compile is specified as the last strategy
|
||
if opts.strategies[..(opts.strategies.len() - 1)].contains(&StrategyWrapped(Strategy::Compile))
|
||
{
|
||
command
|
||
.error(
|
||
ErrorKind::InvalidValue,
|
||
"Compile strategy must be the last one",
|
||
)
|
||
.exit()
|
||
}
|
||
|
||
if opts.github_token.is_none() {
|
||
if let Ok(github_token) = env::var("GH_TOKEN") {
|
||
opts.github_token = Some(GithubToken(Zeroizing::new(github_token.into())));
|
||
}
|
||
}
|
||
match opts.github_token.as_ref() {
|
||
Some(token) if token.0.len() < 10 => opts.github_token = None,
|
||
_ => (),
|
||
}
|
||
|
||
let cli_overrides = PkgOverride {
|
||
pkg_url: opts.pkg_url.take(),
|
||
pkg_fmt: opts.pkg_fmt.take(),
|
||
bin_dir: opts.bin_dir.take(),
|
||
disabled_strategies: Some(
|
||
mem::take(&mut opts.disable_strategies)
|
||
.into_iter()
|
||
.map(|strategy| strategy.0)
|
||
.collect::<Vec<_>>()
|
||
.into_boxed_slice(),
|
||
),
|
||
ignore_disabled_strategies,
|
||
signing: None,
|
||
};
|
||
|
||
(opts, cli_overrides)
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod test {
|
||
use strum::VariantArray;
|
||
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn verify_cli() {
|
||
Args::command().debug_assert()
|
||
}
|
||
|
||
#[test]
|
||
fn quickinstall_url_matches() {
|
||
let long_help = Args::command()
|
||
.get_opts()
|
||
.find(|opt| opt.get_long() == Some("disable-telemetry"))
|
||
.unwrap()
|
||
.get_long_help()
|
||
.unwrap()
|
||
.to_string();
|
||
assert!(
|
||
long_help.ends_with(binstalk::QUICK_INSTALL_STATS_URL),
|
||
"{}",
|
||
long_help
|
||
);
|
||
}
|
||
|
||
const _: () = assert!(Strategy::VARIANTS.len() == StrategyWrapped::VARIANTS.len());
|
||
}
|