Merge pull request from passcod/clap3

This commit is contained in:
Félix Saparelli 2022-06-08 16:37:53 +12:00 committed by GitHub
commit adfc260d58
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 205 additions and 131 deletions

92
Cargo.lock generated
View file

@ -69,7 +69,7 @@ dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"clap",
"clap 2.34.0",
"env_logger",
"lazy_static",
"lazycell",
@ -144,6 +144,7 @@ dependencies = [
"async-trait",
"cargo_metadata",
"cargo_toml",
"clap 3.1.18",
"crates_io_api",
"dirs",
"env_logger",
@ -155,7 +156,6 @@ dependencies = [
"semver",
"serde",
"simplelog",
"structopt",
"strum",
"strum_macros",
"tar",
@ -257,12 +257,51 @@ dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim",
"strsim 0.8.0",
"textwrap 0.11.0",
"unicode-width",
"vec_map",
]
[[package]]
name = "clap"
version = "3.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b"
dependencies = [
"atty",
"bitflags",
"clap_derive",
"clap_lex",
"indexmap",
"lazy_static",
"strsim 0.10.0",
"termcolor",
"textwrap 0.15.0",
]
[[package]]
name = "clap_derive"
version = "3.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "cmake"
version = "0.1.48"
@ -568,15 +607,6 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "heck"
version = "0.4.0"
@ -922,6 +952,12 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225"
[[package]]
name = "os_str_bytes"
version = "6.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa"
[[package]]
name = "owo-colors"
version = "3.4.0"
@ -1261,28 +1297,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "structopt"
version = "0.3.26"
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10"
dependencies = [
"clap",
"lazy_static",
"structopt-derive",
]
[[package]]
name = "structopt-derive"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0"
dependencies = [
"heck 0.3.3",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "strum"
@ -1296,7 +1314,7 @@ version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef"
dependencies = [
"heck 0.4.0",
"heck",
"proc-macro2",
"quote",
"rustversion",
@ -1595,12 +1613,6 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-segmentation"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
[[package]]
name = "unicode-width"
version = "0.1.9"

View file

@ -22,6 +22,7 @@ pkg-fmt = "zip"
async-trait = "0.1.56"
cargo_metadata = "0.14.2"
cargo_toml = "0.11.4"
clap = { version = "3.1.18", features = ["derive"] }
crates_io_api = { version = "0.8.0", default-features = false, features = ["rustls"] }
dirs = "4.0.0"
flate2 = { version = "1.0.24", features = ["zlib-ng"], default-features = false }
@ -31,7 +32,6 @@ reqwest = { version = "0.11.10", features = [ "rustls-tls" ], default-features =
semver = "1.0.7"
serde = { version = "1.0.136", features = [ "derive" ] }
simplelog = "0.12.0"
structopt = "0.3.26"
strum = "0.24.0"
strum_macros = "0.24.0"
tar = "0.4.38"

View file

@ -1,7 +1,7 @@
use std::path::Path;
use std::sync::Arc;
use log::info;
use log::{debug, info};
use reqwest::Method;
use url::Url;
@ -77,8 +77,14 @@ impl QuickInstall {
}
pub async fn report(&self) -> Result<(), BinstallError> {
info!("Sending installation report to quickinstall (anonymous)");
if cfg!(debug_assertions) {
debug!("Not sending quickinstall report in debug mode");
return Ok(());
}
let url = Url::parse(&self.stats_url())?;
debug!("Sending installation report to quickinstall ({url})");
reqwest::Client::builder()
.user_agent(USER_AGENT)
.build()?

View file

@ -7,10 +7,10 @@ use std::{
};
use cargo_toml::{Package, Product};
use clap::Parser;
use log::{debug, error, info, warn, LevelFilter};
use miette::{miette, IntoDiagnostic, Result, WrapErr};
use simplelog::{ColorChoice, ConfigBuilder, TermLogger, TerminalMode};
use structopt::StructOpt;
use tempfile::TempDir;
use tokio::{process::Command, runtime::Runtime, task::JoinError};
@ -20,64 +20,94 @@ use cargo_binstall::{
*,
};
#[derive(Debug, StructOpt)]
#[derive(Debug, Parser)]
#[clap(version, about = "Install a Rust binary... from binaries!")]
struct Options {
/// Package name or URL for installation
/// This must be either a crates.io package name or github or gitlab url
#[structopt()]
/// Package name for installation.
///
/// This must be a crates.io package name.
#[clap(value_name = "crate")]
name: String,
/// Filter for package version to install, in Cargo.toml format.
/// Use `=1.2.3` to install a specific version.
#[structopt(long, default_value = "*")]
/// Semver filter to select the package version to install.
///
/// This is in Cargo.toml dependencies format: `--version 1.2.3` is equivalent to
/// `--version "^1.2.3"`. Use `=1.2.3` to install a specific version.
#[clap(long, default_value = "*")]
version: String,
/// Override binary target, ignoring compiled version
#[structopt(long, default_value = TARGET)]
target: String,
/// 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 = "OVERRIDES",
alias = "target",
long,
value_name = "TRIPLE"
)]
targets: Option<String>,
/// Override install path for downloaded binary.
///
/// Defaults to `$HOME/.cargo/bin`
#[structopt(long)]
#[clap(help_heading = "OVERRIDES", long)]
install_path: Option<String>,
/// Disable symlinking / versioned updates
#[structopt(long)]
/// Disable symlinking / versioned updates.
///
/// By default, Binstall will install a binary named `<name>-<version>` in the install path, and
/// either symlink or copy it to (depending on platform) the plain binary name. This makes it
/// possible to have multiple versions of the same binary, for example for testing or rollback.
///
/// Pass this flag to disable this behavior.
#[clap(long)]
no_symlinks: bool,
/// Dry run, fetch and show changes without installing binaries
#[structopt(long)]
/// Dry run, fetch and show changes without installing binaries.
#[clap(long)]
dry_run: bool,
/// Disable interactive mode / confirmation
#[structopt(long)]
/// Disable interactive mode / confirmation prompts.
#[clap(long)]
no_confirm: bool,
/// Do not cleanup temporary files on success
#[structopt(long)]
/// Do not cleanup temporary files.
#[clap(long)]
no_cleanup: bool,
/// Override manifest source.
/// This skips searching crates.io for a manifest and uses
/// the specified path directly, useful for debugging and
/// when adding `binstall` support.
#[structopt(long)]
///
/// This skips searching crates.io for a manifest and uses the specified path directly, useful
/// for debugging and when adding Binstall support. This must be the path to the folder
/// containing a Cargo.toml file, not the Cargo.toml file itself.
#[clap(help_heading = "OVERRIDES", long)]
manifest_path: Option<PathBuf>,
/// Utility log level
#[structopt(long, default_value = "info")]
///
/// Set to `debug` when submitting a bug report.
#[clap(long, default_value = "info", value_name = "LEVEL")]
log_level: LevelFilter,
/// Override Cargo.toml package manifest bin-dir.
#[structopt(long)]
#[clap(help_heading = "OVERRIDES", long)]
bin_dir: Option<String>,
/// Override Cargo.toml package manifest pkg-fmt.
#[structopt(long)]
#[clap(help_heading = "OVERRIDES", long)]
pkg_fmt: Option<PkgFmt>,
/// Override Cargo.toml package manifest pkg-url.
#[structopt(long)]
#[clap(help_heading = "OVERRIDES", long)]
pkg_url: Option<String>,
}
@ -140,7 +170,7 @@ async fn entry() -> Result<()> {
}
// Load options
let mut opts = Options::from_iter(args.iter());
let mut opts = Options::parse_from(args);
let cli_overrides = PkgOverride {
pkg_url: opts.pkg_url.take(),
pkg_fmt: opts.pkg_fmt.take(),
@ -151,6 +181,7 @@ async fn entry() -> Result<()> {
let mut log_config = ConfigBuilder::new();
log_config.add_filter_ignore("hyper".to_string());
log_config.add_filter_ignore("reqwest".to_string());
log_config.add_filter_ignore("rustls".to_string());
log_config.set_location_level(LevelFilter::Off);
TermLogger::init(
opts.log_level,
@ -160,6 +191,13 @@ async fn entry() -> Result<()> {
)
.unwrap();
// Compute install directory
let install_path = get_install_path(opts.install_path.as_deref()).ok_or_else(|| {
error!("No viable install path found of specified, try `--install-path`");
miette!("No install path found or specified")
})?;
debug!("Using install path: {}", install_path.display());
// Create a temporary directory for downloads etc.
let temp_dir = TempDir::new()
.map_err(BinstallError::from)
@ -204,51 +242,68 @@ async fn entry() -> Result<()> {
manifest.bin,
);
// Merge any overrides
if let Some(o) = meta.overrides.remove(&opts.target) {
meta.merge(&o);
}
let desired_targets = {
let from_opts = opts
.targets
.as_ref()
.map(|ts| ts.split(',').map(|t| t.to_string()).collect());
meta.merge(&cli_overrides);
debug!("Found metadata: {:?}", meta);
if let Some(ts) = from_opts {
ts
} else {
detect_targets().await
}
};
// Compute install directory
let install_path = get_install_path(opts.install_path.as_deref()).ok_or_else(|| {
error!("No viable install path found of specified, try `--install-path`");
miette!("No install path found or specified")
})?;
debug!("Using install path: {}", install_path.display());
let mut fetchers = MultiFetcher::default();
// Compute temporary directory for downloads
let pkg_path = temp_dir
.path()
.join(format!("pkg-{}.{}", opts.name, meta.pkg_fmt));
debug!("Using temporary download path: {}", pkg_path.display());
for target in &desired_targets {
debug!("Building metadata for target: {target}");
let mut target_meta = meta.clone();
let fetcher_data: Vec<_> = detect_targets()
.await
.into_iter()
.map(|target| Data {
// Merge any overrides
if let Some(o) = target_meta.overrides.get(target).cloned() {
target_meta.merge(&o);
}
target_meta.merge(&cli_overrides);
debug!("Found metadata: {target_meta:?}");
let fetcher_data = Data {
name: package.name.clone(),
target: target.into(),
target: target.clone(),
version: package.version.clone(),
repo: package.repository.clone(),
meta: meta.clone(),
})
.collect();
meta: target_meta,
};
// Try github releases, then quickinstall
let mut fetchers = MultiFetcher::default();
for data in &fetcher_data {
fetchers.add(GhCrateMeta::new(data).await);
fetchers.add(QuickInstall::new(data).await);
fetchers.add(GhCrateMeta::new(&fetcher_data).await);
fetchers.add(QuickInstall::new(&fetcher_data).await);
}
match fetchers.first_available().await {
Some(fetcher) => {
// Build final metadata
let fetcher_target = fetcher.target();
if let Some(o) = meta.overrides.get(&fetcher_target.to_owned()).cloned() {
meta.merge(&o);
}
meta.merge(&cli_overrides);
debug!(
"Found a binary install source: {} ({fetcher_target})",
fetcher.source_name()
);
// Compute temporary directory for downloads
let pkg_path = temp_dir
.path()
.join(format!("pkg-{}.{}", opts.name, meta.pkg_fmt));
debug!("Using temporary download path: {}", pkg_path.display());
install_from_package(
binaries,
&*fetcher,
fetcher.as_ref(),
install_path,
meta,
opts,
@ -259,10 +314,17 @@ async fn entry() -> Result<()> {
.await
}
None => {
temp_dir.close().unwrap_or_else(|err| {
warn!("Failed to clean up some resources: {err}");
});
install_from_source(opts, package).await
if !opts.no_cleanup {
temp_dir.close().unwrap_or_else(|err| {
warn!("Failed to clean up some resources: {err}");
});
}
let target = desired_targets
.first()
.ok_or_else(|| miette!("No viable targets found, try with `--targets`"))?;
install_from_source(opts, package, target).await
}
}
}
@ -407,7 +469,7 @@ async fn install_from_package(
Ok(())
}
async fn install_from_source(opts: Options, package: Package<Meta>) -> Result<()> {
async fn install_from_source(opts: Options, package: Package<Meta>, target: &str) -> Result<()> {
// Prompt user for source install
warn!("The package will be installed from source (with cargo)",);
if !opts.no_confirm && !opts.dry_run {
@ -416,14 +478,14 @@ async fn install_from_source(opts: Options, package: Package<Meta>) -> Result<()
if opts.dry_run {
info!(
"Dry-run: running `cargo install {} --version {} --target {}`",
package.name, package.version, opts.target
"Dry-run: running `cargo install {} --version {} --target {target}`",
package.name, package.version
);
Ok(())
} else {
debug!(
"Running `cargo install {} --version {} --target {}`",
package.name, package.version, opts.target
"Running `cargo install {} --version {} --target {target}`",
package.name, package.version
);
let mut child = Command::new("cargo")
.arg("install")
@ -431,7 +493,7 @@ async fn install_from_source(opts: Options, package: Package<Meta>) -> Result<()
.arg("--version")
.arg(package.version)
.arg("--target")
.arg(opts.target)
.arg(target)
.spawn()
.into_diagnostic()
.wrap_err("Spawning cargo install failed.")?;

View file

@ -18,13 +18,13 @@ pub const TARGET: &str = env!("TARGET");
///
/// Check [this issue](https://github.com/ryankurte/cargo-binstall/issues/155)
/// for more information.
pub async fn detect_targets() -> Vec<Box<str>> {
pub async fn detect_targets() -> Vec<String> {
if let Some(target) = get_target_from_rustc().await {
let mut v = vec![target];
#[cfg(target_os = "linux")]
if v[0].contains("gnu") {
v.push(v[0].replace("gnu", "musl").into_boxed_str());
v.push(v[0].replace("gnu", "musl"));
}
#[cfg(target_os = "macos")]
@ -51,7 +51,7 @@ pub async fn detect_targets() -> Vec<Box<str>> {
/// Figure out what the host target is using `rustc`.
/// If `rustc` is absent, then it would return `None`.
async fn get_target_from_rustc() -> Option<Box<str>> {
async fn get_target_from_rustc() -> Option<String> {
let Output { status, stdout, .. } = Command::new("rustc").arg("-vV").output().await.ok()?;
if !status.success() {
return None;
@ -60,17 +60,14 @@ async fn get_target_from_rustc() -> Option<Box<str>> {
Cursor::new(stdout)
.lines()
.filter_map(|line| line.ok())
.find_map(|line| {
line.strip_prefix("host: ")
.map(|host| host.to_owned().into_boxed_str())
})
.find_map(|line| line.strip_prefix("host: ").map(|host| host.to_owned()))
}
#[cfg(target_os = "linux")]
mod linux {
use super::{Command, Output, TARGET};
pub(super) async fn detect_targets_linux() -> Vec<Box<str>> {
pub(super) async fn detect_targets_linux() -> Vec<String> {
let abi = parse_abi();
if let Ok(Output {
@ -123,16 +120,13 @@ mod linux {
}
}
fn create_target_str(libc_version: &str, abi: &str) -> Box<str> {
let prefix = TARGET.rsplit_once('-').unwrap().0;
fn create_target_str(libc_version: &str, abi: &str) -> String {
let prefix = TARGET
.rsplit_once('-')
.expect("unwrap: TARGET always has a -")
.0;
let mut target = String::with_capacity(prefix.len() + 1 + libc_version.len() + abi.len());
target.push_str(prefix);
target.push('-');
target.push_str(libc_version);
target.push_str(abi);
target.into_boxed_str()
format!("{prefix}-{libc_version}{abi}")
}
}
@ -143,7 +137,7 @@ mod macos {
pub(super) const AARCH64: &str = "aarch64-apple-darwin";
pub(super) const X86: &str = "x86_64-apple-darwin";
pub(super) fn detect_targets_macos() -> Vec<Box<str>> {
pub(super) fn detect_targets_macos() -> Vec<String> {
if guess_host_triple() == Some(AARCH64) {
vec![AARCH64.into(), X86.into()]
} else {