Don't prompt if there's nothing to do (#293)

Fixes #291
This commit is contained in:
Félix Saparelli 2022-08-09 21:09:21 +12:00 committed by GitHub
parent 4500e4af63
commit 763d4610e5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 205 additions and 95 deletions

View file

@ -5,18 +5,15 @@
`binstall` works by fetching the crate information from `crates.io`, then searching the linked `repository` for matching releases and artifacts, with fallbacks to [quickinstall](https://github.com/alsuren/cargo-quickinstall) and finally `cargo install` if these are not found. `binstall` works by fetching the crate information from `crates.io`, then searching the linked `repository` for matching releases and artifacts, with fallbacks to [quickinstall](https://github.com/alsuren/cargo-quickinstall) and finally `cargo install` if these are not found.
To support `binstall` maintainers must add configuration values to `Cargo.toml` to allow the tool to locate the appropriate binary package for a given version and target. See [SUPPORT.md](./SUPPORT.md) for more detail. To support `binstall` maintainers must add configuration values to `Cargo.toml` to allow the tool to locate the appropriate binary package for a given version and target. See [SUPPORT.md](./SUPPORT.md) for more detail.
## Status ## Status
![Build](https://github.com/ryankurte/cargo-binstall/workflows/Rust/badge.svg) ![Build](https://github.com/cargo-bins/cargo-binstall/workflows/Rust/badge.svg)
[![GitHub tag](https://img.shields.io/github/tag/ryankurte/cargo-binstall.svg)](https://github.com/ryankurte/cargo-binstall) [![GitHub tag](https://img.shields.io/github/tag/cargo-bins/cargo-binstall.svg)](https://github.com/cargo-bins/cargo-binstall)
[![Crates.io](https://img.shields.io/crates/v/cargo-binstall.svg)](https://crates.io/crates/cargo-binstall) [![Crates.io](https://img.shields.io/crates/v/cargo-binstall.svg)](https://crates.io/crates/cargo-binstall)
[![Docs.rs](https://docs.rs/cargo-binstall/badge.svg)](https://docs.rs/cargo-binstall)
## Installation ## Installation
To get started _using_ `cargo-binstall` first install the binary (either via `cargo install cargo-binstall` or by downloading a pre-compiled [release](https://github.com/ryankurte/cargo-binstall/releases)). To get started _using_ `cargo-binstall` first install the binary (either via `cargo install cargo-binstall` or by downloading a pre-compiled [release](https://github.com/cargo-bins/cargo-binstall/releases)).
| OS | Arch | URL | | OS | Arch | URL |
| ------- | ------- | ------------------------------------------------------------ | | ------- | ------- | ------------------------------------------------------------ |
@ -27,7 +24,7 @@ To get started _using_ `cargo-binstall` first install the binary (either via `ca
| macos | m1 | https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-aarch64-apple-darwin.zip | | macos | m1 | https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-aarch64-apple-darwin.zip |
| windows | x86\_64 | https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-pc-windows-msvc.zip | | windows | x86\_64 | https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-pc-windows-msvc.zip |
To upgrade, use `cargo binstall cargo-binstall`!
## Usage ## Usage
@ -37,16 +34,15 @@ Package versions and targets may be specified using the `--version` and `--targe
``` ```
[garry] ➜ ~ cargo binstall radio-sx128x --version 0.14.1-alpha.5 [garry] ➜ ~ cargo binstall radio-sx128x --version 0.14.1-alpha.5
21:14:09 [INFO] Installing package: 'radio-sx128x' 21:14:15 [INFO] Resolving package: 'radio-sx128x'
21:14:13 [INFO] Downloading package from: 'https://github.com/rust-iot/rust-radio-sx128x/releases/download/v0.14.1-alpha.5/sx128x-util-x86_64-apple-darwin.tgz'
21:14:18 [INFO] This will install the following binaries: 21:14:18 [INFO] This will install the following binaries:
21:14:18 [INFO] - sx128x-util (sx128x-util-x86_64-apple-darwin -> /Users/ryankurte/.cargo/bin/sx128x-util-v0.14.1-alpha.5) 21:14:18 [INFO] - sx128x-util (sx128x-util-x86_64-apple-darwin -> /Users/ryankurte/.cargo/bin/sx128x-util-v0.14.1-alpha.5)
21:14:18 [INFO] And create (or update) the following symlinks: 21:14:18 [INFO] And create (or update) the following symlinks:
21:14:18 [INFO] - sx128x-util (/Users/ryankurte/.cargo/bin/sx128x-util-v0.14.1-alpha.5 -> /Users/ryankurte/.cargo/bin/sx128x-util) 21:14:18 [INFO] - sx128x-util (/Users/ryankurte/.cargo/bin/sx128x-util-v0.14.1-alpha.5 -> /Users/ryankurte/.cargo/bin/sx128x-util)
21:14:18 [INFO] Do you wish to continue? yes/no 21:14:18 [INFO] Do you wish to continue? yes/[no]
yes ? yes
21:15:30 [INFO] Installing binaries... 21:14:20 [INFO] Installing binaries...
21:15:30 [INFO] Installation complete! 21:14:21 [INFO] Done in 6.212736s
``` ```
### Unsupported crates ### Unsupported crates
@ -60,7 +56,6 @@ $ binstall \
--pkg-fmt="txz" crate_name --pkg-fmt="txz" crate_name
``` ```
## FAQ ## FAQ
- Why use this? - Why use this?

View file

@ -48,11 +48,12 @@ cargo binstall --help >/dev/null
cargo binstall --help >/dev/null cargo binstall --help >/dev/null
# Test skip when installed # Test skip when installed
"./$1" binstall --no-confirm cargo-binstall | grep -q 'package cargo-binstall is already installed' "./$1" binstall --no-confirm --force cargo-binstall@0.11.1
"./$1" binstall --no-confirm cargo-binstall@0.11.1 | grep -q 'package cargo-binstall@=0.11.1 is already installed' "./$1" binstall --no-confirm cargo-binstall@0.11.1 | grep -q 'cargo-binstall v0.11.1 is already installed'
"./$1" binstall --no-confirm cargo-binstall@0.10.0 | grep -q -v 'package cargo-binstall@=0.10.0 is already installed' "./$1" binstall --no-confirm cargo-binstall@0.10.0 | grep -q -v 'cargo-binstall v0.10.0 is already installed'
## Test When 0.11.0 is installed but can be upgraded. ## Test When 0.11.0 is installed but can be upgraded.
"./$1" binstall --force --log-level debug --no-confirm cargo-binstall@0.11.0 "./$1" binstall --no-confirm cargo-binstall@0.11.0
"./$1" binstall --no-confirm cargo-binstall@^0.11.0 | grep -q -v 'package cargo-binstall@^0.11.0 is already installed' "./$1" binstall --no-confirm cargo-binstall@0.11.0 | grep -q 'cargo-binstall v0.11.0 is already installed'
"./$1" binstall --no-confirm cargo-binstall@^0.11.0 | grep -q -v 'cargo-binstall v0.11.0 is already installed'

View file

@ -3,17 +3,16 @@ use std::{path::PathBuf, process, sync::Arc};
use cargo_toml::Package; use cargo_toml::Package;
use compact_str::CompactString; use compact_str::CompactString;
use log::{debug, error, info}; use log::{debug, error, info};
use miette::{miette, IntoDiagnostic, Result, WrapErr};
use tokio::{process::Command, task::block_in_place}; use tokio::{process::Command, task::block_in_place};
use super::{MetaData, Options, Resolution}; use super::{MetaData, Options, Resolution};
use crate::{bins, fetchers::Fetcher, metafiles::binstall_v1::Source, *}; use crate::{bins, fetchers::Fetcher, metafiles::binstall_v1::Source, BinstallError, *};
pub async fn install( pub async fn install(
resolution: Resolution, resolution: Resolution,
opts: Arc<Options>, opts: Arc<Options>,
jobserver_client: LazyJobserverClient, jobserver_client: LazyJobserverClient,
) -> Result<Option<MetaData>> { ) -> Result<Option<MetaData>, BinstallError> {
match resolution { match resolution {
Resolution::AlreadyUpToDate => Ok(None), Resolution::AlreadyUpToDate => Ok(None),
Resolution::Fetch { Resolution::Fetch {
@ -24,7 +23,14 @@ pub async fn install(
bin_path, bin_path,
bin_files, bin_files,
} => { } => {
let current_version = package.version.parse().into_diagnostic()?; let current_version =
package
.version
.parse()
.map_err(|err| BinstallError::VersionParse {
v: package.version,
err,
})?;
let target = fetcher.target().into(); let target = fetcher.target().into();
install_from_package(fetcher, opts, bin_path, bin_files) install_from_package(fetcher, opts, bin_path, bin_files)
@ -45,7 +51,7 @@ pub async fn install(
let desired_targets = opts.desired_targets.get().await; let desired_targets = opts.desired_targets.get().await;
let target = desired_targets let target = desired_targets
.first() .first()
.ok_or_else(|| miette!("No viable targets found, try with `--targets`"))?; .ok_or(BinstallError::NoViableTargets)?;
if !opts.dry_run { if !opts.dry_run {
install_from_source(package, target, jobserver_client, opts.quiet, opts.force) install_from_source(package, target, jobserver_client, opts.quiet, opts.force)
@ -67,7 +73,7 @@ async fn install_from_package(
opts: Arc<Options>, opts: Arc<Options>,
bin_path: PathBuf, bin_path: PathBuf,
bin_files: Vec<bins::BinFile>, bin_files: Vec<bins::BinFile>,
) -> Result<Option<Vec<CompactString>>> { ) -> Result<Option<Vec<CompactString>>, BinstallError> {
// Download package // Download package
if opts.dry_run { if opts.dry_run {
info!("Dry run, not downloading package"); info!("Dry run, not downloading package");
@ -129,7 +135,7 @@ async fn install_from_source(
lazy_jobserver_client: LazyJobserverClient, lazy_jobserver_client: LazyJobserverClient,
quiet: bool, quiet: bool,
force: bool, force: bool,
) -> Result<()> { ) -> Result<(), BinstallError> {
let jobserver_client = lazy_jobserver_client.get().await?; let jobserver_client = lazy_jobserver_client.get().await?;
debug!( debug!(
@ -156,22 +162,20 @@ async fn install_from_source(
cmd.arg("--force"); cmd.arg("--force");
} }
let mut child = cmd let command_string = format!("{:?}", cmd);
.spawn()
.into_diagnostic() let mut child = cmd.spawn()?;
.wrap_err("Spawning cargo install failed.")?;
debug!("Spawned command pid={:?}", child.id()); debug!("Spawned command pid={:?}", child.id());
let status = child let status = child.wait().await?;
.wait()
.await
.into_diagnostic()
.wrap_err("Running cargo install failed.")?;
if status.success() { if status.success() {
info!("Cargo finished successfully"); info!("Cargo finished successfully");
Ok(()) Ok(())
} else { } else {
error!("Cargo errored! {status:?}"); error!("Cargo errored! {status:?}");
Err(miette!("Cargo install error")) Err(BinstallError::SubProcess {
command: command_string,
status,
})
} }
} }

View file

@ -5,8 +5,7 @@ use std::{
use cargo_toml::{Package, Product}; use cargo_toml::{Package, Product};
use compact_str::{CompactString, ToCompactString}; use compact_str::{CompactString, ToCompactString};
use log::{debug, error, info, warn}; use log::{debug, info, warn};
use miette::{miette, Result};
use reqwest::Client; use reqwest::Client;
use semver::{Version, VersionReq}; use semver::{Version, VersionReq};
@ -14,7 +13,7 @@ use super::Options;
use crate::{ use crate::{
bins, bins,
fetchers::{Data, Fetcher, GhCrateMeta, MultiFetcher, QuickInstall}, fetchers::{Data, Fetcher, GhCrateMeta, MultiFetcher, QuickInstall},
*, BinstallError, *,
}; };
pub enum Resolution { pub enum Resolution {
@ -84,8 +83,31 @@ pub async fn resolve(
install_path: Arc<Path>, install_path: Arc<Path>,
client: Client, client: Client,
crates_io_api_client: crates_io_api::AsyncClient, crates_io_api_client: crates_io_api::AsyncClient,
) -> Result<Resolution> { ) -> Result<Resolution, BinstallError> {
info!("Installing package: '{}'", crate_name); let crate_name_name = crate_name.name.clone();
resolve_inner(
opts,
crate_name,
curr_version,
temp_dir,
install_path,
client,
crates_io_api_client,
)
.await
.map_err(|err| err.crate_context(crate_name_name))
}
async fn resolve_inner(
opts: Arc<Options>,
crate_name: CrateName,
curr_version: Option<Version>,
temp_dir: Arc<Path>,
install_path: Arc<Path>,
client: Client,
crates_io_api_client: crates_io_api::AsyncClient,
) -> Result<Resolution, BinstallError> {
info!("Resolving package: '{}'", crate_name);
let version_req: VersionReq = match (&crate_name.version_req, &opts.version_req) { let version_req: VersionReq = match (&crate_name.version_req, &opts.version_req) {
(Some(version), None) => version.clone(), (Some(version), None) => version.clone(),
@ -120,7 +142,10 @@ pub async fn resolve(
})?; })?;
if new_version == curr_version { if new_version == curr_version {
info!("package {crate_name} is already up to date {curr_version}"); info!(
"{} v{curr_version} is already installed, use --force to override",
crate_name.name
);
return Ok(Resolution::AlreadyUpToDate); return Ok(Resolution::AlreadyUpToDate);
} }
} }
@ -208,7 +233,7 @@ fn collect_bin_files(
binaries: Vec<Product>, binaries: Vec<Product>,
bin_path: PathBuf, bin_path: PathBuf,
install_path: PathBuf, install_path: PathBuf,
) -> Result<Vec<bins::BinFile>> { ) -> Result<Vec<bins::BinFile>, BinstallError> {
// Update meta // Update meta
if fetcher.source_name() == "QuickInstall" { if fetcher.source_name() == "QuickInstall" {
// TODO: less of a hack? // TODO: less of a hack?
@ -217,10 +242,7 @@ fn collect_bin_files(
// Check binaries // Check binaries
if binaries.is_empty() { if binaries.is_empty() {
error!("No binaries specified (or inferred from file system)"); return Err(BinstallError::UnspecifiedBinaries);
return Err(miette!(
"No binaries specified (or inferred from file system)"
));
} }
// List files to be installed // List files to be installed

View file

@ -1,13 +1,13 @@
use std::process::{ExitCode, Termination}; use std::process::{ExitCode, ExitStatus, Termination};
use compact_str::CompactString;
use log::{error, warn}; use log::{error, warn};
use miette::{Diagnostic, Report}; use miette::{Diagnostic, Report};
use thiserror::Error; use thiserror::Error;
use tokio::task; use tokio::task;
/// Errors emitted by cargo-binstall. /// Error kinds emitted by cargo-binstall.
#[derive(Error, Diagnostic, Debug)] #[derive(Error, Diagnostic, Debug)]
#[diagnostic(url(docsrs))]
#[non_exhaustive] #[non_exhaustive]
pub enum BinstallError { pub enum BinstallError {
/// Internal: a task could not be joined. /// Internal: a task could not be joined.
@ -71,7 +71,7 @@ pub enum BinstallError {
/// ///
/// - Code: `binstall::http` /// - Code: `binstall::http`
/// - Exit: 69 /// - Exit: 69
#[error("could not {method} {url}: {err}")] #[error("could not {method} {url}")]
#[diagnostic(severity(error), code(binstall::http))] #[diagnostic(severity(error), code(binstall::http))]
Http { Http {
method: reqwest::Method, method: reqwest::Method,
@ -80,6 +80,16 @@ pub enum BinstallError {
err: reqwest::Error, err: reqwest::Error,
}, },
/// A subprocess failed.
///
/// This is often about cargo-install calls.
///
/// - Code: `binstall::subprocess`
/// - Exit: 70
#[error("subprocess {command} errored with {status}")]
#[diagnostic(severity(error), code(binstall::subprocess))]
SubProcess { command: String, status: ExitStatus },
/// A generic I/O error. /// A generic I/O error.
/// ///
/// - Code: `binstall::io` /// - Code: `binstall::io`
@ -94,7 +104,7 @@ pub enum BinstallError {
/// ///
/// - Code: `binstall::crates_io_api` /// - Code: `binstall::crates_io_api`
/// - Exit: 76 /// - Exit: 76
#[error("crates.io api error fetching crate information for '{crate_name}': {err}")] #[error("crates.io API error")]
#[diagnostic( #[diagnostic(
severity(error), severity(error),
code(binstall::crates_io_api), code(binstall::crates_io_api),
@ -137,7 +147,7 @@ pub enum BinstallError {
/// ///
/// - Code: `binstall::version::parse` /// - Code: `binstall::version::parse`
/// - Exit: 80 /// - Exit: 80
#[error("version string '{v}' is not semver: {err}")] #[error("version string '{v}' is not semver")]
#[diagnostic(severity(error), code(binstall::version::parse))] #[diagnostic(severity(error), code(binstall::version::parse))]
VersionParse { VersionParse {
v: String, v: String,
@ -154,7 +164,7 @@ pub enum BinstallError {
/// ///
/// - Code: `binstall::version::requirement` /// - Code: `binstall::version::requirement`
/// - Exit: 81 /// - Exit: 81
#[error("version requirement '{req}' is not semver: {err}")] #[error("version requirement '{req}' is not semver")]
#[diagnostic(severity(error), code(binstall::version::requirement))] #[diagnostic(severity(error), code(binstall::version::requirement))]
VersionReq { VersionReq {
req: String, req: String,
@ -220,17 +230,52 @@ pub enum BinstallError {
help("You cannot use --{option} and specify multiple packages at the same time. Do one or the other.") help("You cannot use --{option} and specify multiple packages at the same time. Do one or the other.")
)] )]
OverrideOptionUsedWithMultiInstall { option: &'static str }, OverrideOptionUsedWithMultiInstall { option: &'static str },
/// No binaries were found for the crate.
///
/// When installing, either the binaries are specified in the crate's Cargo.toml, or they're
/// inferred from the crate layout (e.g. src/main.rs or src/bins/name.rs). If no binaries are
/// found through these methods, we can't know what to install!
///
/// - Code: `binstall::resolve::binaries`
/// - Exit: 86
#[error("no binaries specified nor inferred")]
#[diagnostic(
severity(error),
code(binstall::resolve::binaries),
help("This crate doesn't specify any binaries, so there's nothing to install.")
)]
UnspecifiedBinaries,
/// No viable targets were found.
///
/// When installing, we attempt to find which targets the host (your computer) supports, and
/// discover builds for these targets from the remote binary source. This error occurs when we
/// fail to discover the host's target.
///
/// You should in this case specify --target manually.
///
/// - Code: `binstall::targets::none_host`
/// - Exit: 87
#[error("failed to discovered a viable target from the host")]
#[diagnostic(
severity(error),
code(binstall::targets::none_host),
help("Try to specify --target")
)]
NoViableTargets,
/// A wrapped error providing the context of which crate the error is about.
#[error("for crate {crate_name}")]
CrateContext {
#[source]
error: Box<BinstallError>,
crate_name: CompactString,
},
} }
impl BinstallError { impl BinstallError {
/// The recommended exit code for this error. fn exit_number(&self) -> u8 {
///
/// This will never output:
/// - 0 (success)
/// - 1 and 2 (catchall and shell)
/// - 16 (binstall errors not handled here)
/// - 64 (generic error)
pub fn exit_code(&self) -> ExitCode {
use BinstallError::*; use BinstallError::*;
let code: u8 = match self { let code: u8 = match self {
TaskJoinError(_) => 17, TaskJoinError(_) => 17,
@ -240,6 +285,7 @@ impl BinstallError {
Template(_) => 67, Template(_) => 67,
Reqwest(_) => 68, Reqwest(_) => 68,
Http { .. } => 69, Http { .. } => 69,
SubProcess { .. } => 70,
Io(_) => 74, Io(_) => 74,
CratesIoApi { .. } => 76, CratesIoApi { .. } => 76,
CargoManifestPath => 77, CargoManifestPath => 77,
@ -250,12 +296,34 @@ impl BinstallError {
VersionUnavailable { .. } => 83, VersionUnavailable { .. } => 83,
SuperfluousVersionOption => 84, SuperfluousVersionOption => 84,
OverrideOptionUsedWithMultiInstall { .. } => 85, OverrideOptionUsedWithMultiInstall { .. } => 85,
UnspecifiedBinaries => 86,
NoViableTargets => 87,
CrateContext { error, .. } => error.exit_number(),
}; };
// reserved codes // reserved codes
debug_assert!(code != 64 && code != 16 && code != 1 && code != 2 && code != 0); debug_assert!(code != 64 && code != 16 && code != 1 && code != 2 && code != 0);
code.into() code
}
/// The recommended exit code for this error.
///
/// This will never output:
/// - 0 (success)
/// - 1 and 2 (catchall and shell)
/// - 16 (binstall errors not handled here)
/// - 64 (generic error)
pub fn exit_code(&self) -> ExitCode {
self.exit_number().into()
}
/// Add crate context to the error
pub fn crate_context(self, crate_name: impl Into<CompactString>) -> Self {
Self::CrateContext {
error: Box::new(self),
crate_name: crate_name.into(),
}
} }
} }

View file

@ -2,7 +2,7 @@ use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use compact_str::{CompactString, ToCompactString}; use compact_str::{CompactString, ToCompactString};
use log::{debug, info, warn}; use log::{debug, warn};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use reqwest::Client; use reqwest::Client;
use reqwest::Method; use reqwest::Method;
@ -43,7 +43,7 @@ impl super::Fetcher for GhCrateMeta {
let client = self.client.clone(); let client = self.client.clone();
AutoAbortJoinHandle::spawn(async move { AutoAbortJoinHandle::spawn(async move {
let url = url?; let url = url?;
info!("Checking for package at: '{url}'"); debug!("Checking for package at: '{url}'");
remote_exists(client, url.clone(), Method::HEAD) remote_exists(client, url.clone(), Method::HEAD)
.await .await
.map(|exists| (url.clone(), exists)) .map(|exists| (url.clone(), exists))
@ -61,7 +61,7 @@ impl super::Fetcher for GhCrateMeta {
); );
} }
info!("Winning URL is {url}"); debug!("Winning URL is {url}");
self.url.set(url).unwrap(); // find() is called first self.url.set(url).unwrap(); // find() is called first
return Ok(true); return Ok(true);
} }
@ -72,7 +72,7 @@ impl super::Fetcher for GhCrateMeta {
async fn fetch_and_extract(&self, dst: &Path) -> Result<(), BinstallError> { async fn fetch_and_extract(&self, dst: &Path) -> Result<(), BinstallError> {
let url = self.url.get().unwrap(); // find() is called first let url = self.url.get().unwrap(); // find() is called first
info!("Downloading package from: '{url}'"); debug!("Downloading package from: '{url}'");
download_and_extract(&self.client, url, self.pkg_fmt(), dst).await download_and_extract(&self.client, url, self.pkg_fmt(), dst).await
} }

View file

@ -2,7 +2,7 @@ use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use compact_str::CompactString; use compact_str::CompactString;
use log::{debug, info}; use log::debug;
use reqwest::Client; use reqwest::Client;
use reqwest::Method; use reqwest::Method;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
@ -36,13 +36,13 @@ impl super::Fetcher for QuickInstall {
async fn find(&self) -> Result<bool, BinstallError> { async fn find(&self) -> Result<bool, BinstallError> {
let url = self.package_url(); let url = self.package_url();
self.report(); self.report();
info!("Checking for package at: '{url}'"); debug!("Checking for package at: '{url}'");
remote_exists(self.client.clone(), Url::parse(&url)?, Method::HEAD).await remote_exists(self.client.clone(), Url::parse(&url)?, Method::HEAD).await
} }
async fn fetch_and_extract(&self, dst: &Path) -> Result<(), BinstallError> { async fn fetch_and_extract(&self, dst: &Path) -> Result<(), BinstallError> {
let url = self.package_url(); let url = self.package_url();
info!("Downloading package from: '{url}'"); debug!("Downloading package from: '{url}'");
download_and_extract(&self.client, &Url::parse(&url)?, self.pkg_fmt(), dst).await download_and_extract(&self.client, &Url::parse(&url)?, self.pkg_fmt(), dst).await
} }

View file

@ -36,7 +36,11 @@ struct Options {
/// ///
/// If duplicate names are provided, the last one (and their version requirement) /// If duplicate names are provided, the last one (and their version requirement)
/// is kept. /// is kept.
#[clap(help_heading = "Package selection", value_name = "crate[@version]")] #[clap(
help_heading = "Package selection",
value_name = "crate[@version]",
required_unless_present_any = ["version", "help"],
)]
crate_names: Vec<CrateName>, crate_names: Vec<CrateName>,
/// Package version to install. /// Package version to install.
@ -202,7 +206,7 @@ impl Termination for MainExit {
fn report(self) -> ExitCode { fn report(self) -> ExitCode {
match self { match self {
Self::Success(spent) => { Self::Success(spent) => {
info!("Installation completed in {spent:?}"); info!("Done in {spent:?}");
ExitCode::SUCCESS ExitCode::SUCCESS
} }
Self::Error(err) => err.report(), Self::Error(err) => err.report(),
@ -354,31 +358,39 @@ async fn entry(jobserver_client: LazyJobserverClient) -> Result<()> {
})?; })?;
// Remove installed crates // Remove installed crates
let crate_names = CrateName::dedup(crate_names).filter_map(|crate_name| { let crate_names = CrateName::dedup(crate_names)
if opts.force { .filter_map(|crate_name| {
Some((crate_name, None)) match (
} else if let Some(records) = &metadata { opts.force,
if let Some(metadata) = records.get(&crate_name.name) { metadata.as_ref().and_then(|records| records.get(&crate_name.name)),
if let Some(version_req) = &crate_name.version_req { &crate_name.version_req,
if version_req.is_latest_compatible(&metadata.current_version) { ) {
info!( (false, Some(metadata), Some(version_req))
"package {crate_name} is already installed and cannot be upgraded, use --force to override" if version_req.is_latest_compatible(&metadata.current_version) =>
); {
None debug!("Bailing out early because we can assume wanted is already installed from metafile");
} else { info!(
Some((crate_name, Some(metadata.current_version.clone()))) "{} v{} is already installed, use --force to override",
} crate_name.name, metadata.current_version
} else { );
info!("package {crate_name} is already installed, use --force to override");
None None
} }
} else {
Some((crate_name, None)) // we have to assume that the version req could be *,
// and therefore a remote upgraded version could exist
(false, Some(metadata), _) => {
Some((crate_name, Some(metadata.current_version.clone())))
}
_ => Some((crate_name, None)),
} }
} else { })
Some((crate_name, None)) .collect::<Vec<_>>();
}
}); if crate_names.is_empty() {
debug!("Nothing to do");
return Ok(());
}
let temp_dir_path: Arc<Path> = Arc::from(temp_dir.path()); let temp_dir_path: Arc<Path> = Arc::from(temp_dir.path());
@ -414,7 +426,15 @@ async fn entry(jobserver_client: LazyJobserverClient) -> Result<()> {
// Confirm // Confirm
let mut resolutions = Vec::with_capacity(tasks.len()); let mut resolutions = Vec::with_capacity(tasks.len());
for task in tasks { for task in tasks {
resolutions.push(task.await??); match task.await?? {
binstall::Resolution::AlreadyUpToDate => {}
res => resolutions.push(res),
}
}
if resolutions.is_empty() {
debug!("Nothing to do");
return Ok(());
} }
uithread.confirm().await?; uithread.confirm().await?;