mirror of
https://github.com/cargo-bins/cargo-binstall.git
synced 2025-04-20 20:48:43 +00:00
Merge pull request #151 from passcod/rich-errors
This commit is contained in:
commit
a1fa3a47e5
12 changed files with 555 additions and 166 deletions
129
Cargo.lock
generated
129
Cargo.lock
generated
|
@ -26,12 +26,6 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.53"
|
||||
|
@ -147,7 +141,6 @@ dependencies = [
|
|||
name = "cargo-binstall"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"cargo_metadata",
|
||||
"cargo_toml",
|
||||
|
@ -156,6 +149,7 @@ dependencies = [
|
|||
"env_logger",
|
||||
"flate2",
|
||||
"log",
|
||||
"miette",
|
||||
"reqwest",
|
||||
"semver",
|
||||
"serde",
|
||||
|
@ -165,6 +159,7 @@ dependencies = [
|
|||
"strum_macros",
|
||||
"tar",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"tinytemplate",
|
||||
"tokio",
|
||||
"url",
|
||||
|
@ -262,7 +257,7 @@ dependencies = [
|
|||
"atty",
|
||||
"bitflags",
|
||||
"strsim",
|
||||
"textwrap",
|
||||
"textwrap 0.11.0",
|
||||
"unicode-width",
|
||||
"vec_map",
|
||||
]
|
||||
|
@ -676,6 +671,12 @@ version = "2.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b"
|
||||
|
||||
[[package]]
|
||||
name = "is_ci"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.2"
|
||||
|
@ -770,6 +771,36 @@ version = "2.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "miette"
|
||||
version = "4.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c90329e44f9208b55f45711f9558cec15d7ef8295cc65ecd6d4188ae8edc58c"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"miette-derive",
|
||||
"once_cell",
|
||||
"owo-colors",
|
||||
"supports-color",
|
||||
"supports-hyperlinks",
|
||||
"supports-unicode",
|
||||
"terminal_size",
|
||||
"textwrap 0.15.0",
|
||||
"thiserror",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miette-derive"
|
||||
version = "4.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b5bc45b761bcf1b5e6e6c4128cd93b84c218721a8d9b894aa0aff4ed180174c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.16"
|
||||
|
@ -857,6 +888,12 @@ version = "1.12.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225"
|
||||
|
||||
[[package]]
|
||||
name = "owo-colors"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b"
|
||||
|
||||
[[package]]
|
||||
name = "peeking_take_while"
|
||||
version = "0.1.2"
|
||||
|
@ -1161,6 +1198,12 @@ version = "0.4.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32"
|
||||
|
||||
[[package]]
|
||||
name = "smawk"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.4"
|
||||
|
@ -1226,6 +1269,34 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "supports-color"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4872ced36b91d47bae8a214a683fe54e7078875b399dfa251df346c9b547d1f9"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"is_ci",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "supports-hyperlinks"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "590b34f7c5f01ecc9d78dba4b3f445f31df750a67621cf31626f3b7441ce6406"
|
||||
dependencies = [
|
||||
"atty",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "supports-unicode"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8b945e45b417b125a8ec51f1b7df2f8df7920367700d1f98aedd21e5735f8b2"
|
||||
dependencies = [
|
||||
"atty",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.95"
|
||||
|
@ -1271,6 +1342,16 @@ dependencies = [
|
|||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
|
@ -1280,6 +1361,17 @@ dependencies = [
|
|||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||
dependencies = [
|
||||
"smawk",
|
||||
"unicode-linebreak",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.31"
|
||||
|
@ -1358,21 +1450,9 @@ dependencies = [
|
|||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.23.4"
|
||||
|
@ -1463,6 +1543,15 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-linebreak"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f"
|
||||
dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.19"
|
||||
|
|
|
@ -19,7 +19,6 @@ pkg-fmt = "zip"
|
|||
pkg-fmt = "zip"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.57"
|
||||
async-trait = "0.1.52"
|
||||
cargo_metadata = "0.14.2"
|
||||
cargo_toml = "0.11.4"
|
||||
|
@ -27,6 +26,7 @@ crates_io_api = { version = "0.8.0", default-features = false, features = ["rust
|
|||
dirs = "4.0.0"
|
||||
flate2 = { version = "1.0.24", features = ["zlib-ng"], default-features = false }
|
||||
log = "0.4.14"
|
||||
miette = { version = "4.7.1", features = ["fancy-no-backtrace"] }
|
||||
reqwest = { version = "0.11.10", features = [ "rustls-tls" ], default-features = false }
|
||||
semver = "1.0.7"
|
||||
serde = { version = "1.0.136", features = [ "derive" ] }
|
||||
|
@ -36,13 +36,16 @@ strum = "0.24.0"
|
|||
strum_macros = "0.24.0"
|
||||
tar = "0.4.38"
|
||||
tempfile = "3.3.0"
|
||||
thiserror = "1.0.31"
|
||||
tinytemplate = "1.2.1"
|
||||
|
||||
# This crate uses features rt-multi-thread and macros in `#[tokio::main]` and
|
||||
# uses feature process to create process.
|
||||
tokio = { version = "1.18.0", features = [ "rt-multi-thread", "macros", "process" ], default-features = false }
|
||||
tokio = { version = "1.18.0", features = [ "rt-multi-thread", "process" ], default-features = false }
|
||||
|
||||
url = "2.2.2"
|
||||
xz2 = "0.1.6"
|
||||
|
||||
# Disable all features of zip except for features of compression algorithms:
|
||||
# Disable features include:
|
||||
# - aes-crypto: Enables decryption of files which were encrypted with AES, absolutely zero use for
|
||||
|
@ -50,6 +53,7 @@ xz2 = "0.1.6"
|
|||
# - time: Enables features using the [time](https://github.com/time-rs/time) crate,
|
||||
# which is not used by this crate.
|
||||
zip = { version = "0.6.2", default-features = false, features = [ "deflate", "bzip2", "zstd" ] }
|
||||
|
||||
# zstd is also depended by zip.
|
||||
# Since zip 0.6.2 depends on zstd 0.10.0, we also have to use 0.10.0 here,
|
||||
# otherwise there will be a link conflict.
|
||||
|
|
|
@ -157,7 +157,7 @@ If your package already uses this approach, you shouldn't need to set anything.
|
|||
|
||||
### Examples
|
||||
|
||||
For example, the default configuration (as shown above) for a crate called `radio-sx128x` (version: `v0.14.1-alpha.5` on x86_64 linux) would be interpolated to:
|
||||
For example, the default configuration (as shown above) for a crate called `radio-sx128x` (version: `v0.14.1-alpha.5` on x86\_64 linux) would be interpolated to:
|
||||
|
||||
- A download URL of `https://github.com/rust-iot/rust-radio-sx128x/releases/download/v0.14.1-alpha.5/rust-radio-sx128x-x86_64-unknown-linux-gnu-v0.14.1-alpha.5.tgz`
|
||||
- Containing a single binary file `rust-radio-sx128x-x86_64-unknown-linux-gnu-v0.14.1-alpha.5/rust-radio-x86_64-unknown-linux-gnu`
|
||||
|
@ -199,6 +199,8 @@ Which provides a binary path of: `sx128x-util-x86_64-unknown-linux-gnu[.exe]`. I
|
|||
- Yes and also no? We're not (yet? #1) doing anything to verify the CI binaries are produced by the right person / organisation.
|
||||
However, we're pulling data from crates.io and the cargo manifest, both of which are _already_ trusted entities, and this is
|
||||
functionally a replacement for `curl ... | bash` or `wget`-ing the same files, so, things can be improved but it's also sorta moot
|
||||
- What do the error codes mean?
|
||||
- You can find a full description of errors including exit codes here: <https://docs.rs/cargo-binstall/latest/cargo_binstall/errors/enum.BinstallError.html>
|
||||
|
||||
---
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ use cargo_toml::Product;
|
|||
use log::debug;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{PkgFmt, PkgMeta, Template};
|
||||
use crate::{BinstallError, PkgFmt, PkgMeta, Template};
|
||||
|
||||
pub struct BinFile {
|
||||
pub base_name: String,
|
||||
|
@ -14,7 +14,7 @@ pub struct BinFile {
|
|||
}
|
||||
|
||||
impl BinFile {
|
||||
pub fn from_product(data: &Data, product: &Product) -> Result<Self, anyhow::Error> {
|
||||
pub fn from_product(data: &Data, product: &Product) -> Result<Self, BinstallError> {
|
||||
let base_name = product.name.clone().unwrap();
|
||||
|
||||
let binary_ext = if data.target.contains("windows") {
|
||||
|
@ -77,7 +77,7 @@ impl BinFile {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn install_bin(&self) -> Result<(), anyhow::Error> {
|
||||
pub fn install_bin(&self) -> Result<(), BinstallError> {
|
||||
// TODO: check if file already exists
|
||||
debug!(
|
||||
"Copy file from '{}' to '{}'",
|
||||
|
@ -96,7 +96,7 @@ impl BinFile {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn install_link(&self) -> Result<(), anyhow::Error> {
|
||||
pub fn install_link(&self) -> Result<(), BinstallError> {
|
||||
// Remove existing symlink
|
||||
// TODO: check if existing symlink is correct
|
||||
if self.link.exists() {
|
||||
|
|
|
@ -1,25 +1,26 @@
|
|||
use std::collections::BTreeSet;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use crates_io_api::AsyncClient;
|
||||
use log::debug;
|
||||
use semver::{Version, VersionReq};
|
||||
|
||||
use crates_io_api::AsyncClient;
|
||||
|
||||
use crate::helpers::*;
|
||||
use crate::PkgFmt;
|
||||
use crate::{helpers::*, BinstallError, PkgFmt};
|
||||
|
||||
fn find_version<'a, V: Iterator<Item = &'a String>>(
|
||||
requirement: &str,
|
||||
version_iter: V,
|
||||
) -> Result<String, anyhow::Error> {
|
||||
) -> Result<Version, BinstallError> {
|
||||
// Parse version requirement
|
||||
let version_req = VersionReq::parse(requirement)?;
|
||||
let version_req = VersionReq::parse(requirement).map_err(|err| BinstallError::VersionReq {
|
||||
req: requirement.into(),
|
||||
err,
|
||||
})?;
|
||||
|
||||
// Filter for matching versions
|
||||
let mut filtered: Vec<_> = version_iter
|
||||
.filter(|v| {
|
||||
let filtered: BTreeSet<_> = version_iter
|
||||
.filter_map(|v| {
|
||||
// Remove leading `v` for git tags
|
||||
let ver_str = match v.strip_prefix("s") {
|
||||
Some(v) => v,
|
||||
|
@ -27,36 +28,26 @@ fn find_version<'a, V: Iterator<Item = &'a String>>(
|
|||
};
|
||||
|
||||
// Parse out version
|
||||
let ver = match Version::parse(ver_str) {
|
||||
Ok(sv) => sv,
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
let ver = Version::parse(ver_str).ok()?;
|
||||
debug!("Version: {:?}", ver);
|
||||
|
||||
// Filter by version match
|
||||
version_req.matches(&ver)
|
||||
if version_req.matches(&ver) {
|
||||
Some(ver)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Sort by highest matching version
|
||||
filtered.sort_by(|a, b| {
|
||||
let a = Version::parse(a).unwrap();
|
||||
let b = Version::parse(b).unwrap();
|
||||
|
||||
b.partial_cmp(&a).unwrap()
|
||||
});
|
||||
|
||||
debug!("Filtered: {:?}", filtered);
|
||||
|
||||
// Return highest version
|
||||
match filtered.get(0) {
|
||||
Some(v) => Ok(v.to_string()),
|
||||
None => Err(anyhow!(
|
||||
"No matching version for requirement: '{}'",
|
||||
version_req
|
||||
)),
|
||||
}
|
||||
filtered
|
||||
.iter()
|
||||
.max()
|
||||
.cloned()
|
||||
.ok_or_else(|| BinstallError::VersionMismatch { req: version_req })
|
||||
}
|
||||
|
||||
/// Fetch a crate by name and version from crates.io
|
||||
|
@ -64,7 +55,7 @@ pub async fn fetch_crate_cratesio(
|
|||
name: &str,
|
||||
version_req: &str,
|
||||
temp_dir: &Path,
|
||||
) -> Result<PathBuf, anyhow::Error> {
|
||||
) -> Result<PathBuf, BinstallError> {
|
||||
// Fetch / update index
|
||||
debug!("Looking up crate information");
|
||||
|
||||
|
@ -72,23 +63,18 @@ pub async fn fetch_crate_cratesio(
|
|||
let api_client = AsyncClient::new(
|
||||
"cargo-binstall (https://github.com/ryankurte/cargo-binstall)",
|
||||
Duration::from_millis(100),
|
||||
)?;
|
||||
)
|
||||
.expect("bug: invalid user agent");
|
||||
|
||||
// Fetch online crate information
|
||||
let crate_info = api_client
|
||||
let base_info =
|
||||
api_client
|
||||
.get_crate(name.as_ref())
|
||||
.await
|
||||
.context("Error fetching crate information");
|
||||
|
||||
let base_info = match crate_info {
|
||||
Ok(i) => i,
|
||||
Err(_) => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Error fetching information for crate {}",
|
||||
name
|
||||
));
|
||||
}
|
||||
};
|
||||
.map_err(|err| BinstallError::CratesIoApi {
|
||||
crate_name: name.into(),
|
||||
err,
|
||||
})?;
|
||||
|
||||
// Locate matching version
|
||||
let version_iter =
|
||||
|
@ -99,16 +85,14 @@ pub async fn fetch_crate_cratesio(
|
|||
let version_name = find_version(version_req, version_iter)?;
|
||||
|
||||
// Fetch information for the filtered version
|
||||
let version = match base_info.versions.iter().find(|v| v.num == version_name) {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"No information found for crate: '{}' version: '{}'",
|
||||
name,
|
||||
version_name
|
||||
));
|
||||
}
|
||||
};
|
||||
let version = base_info
|
||||
.versions
|
||||
.iter()
|
||||
.find(|v| v.num == version_name.to_string())
|
||||
.ok_or_else(|| BinstallError::VersionUnavailable {
|
||||
crate_name: name.into(),
|
||||
v: version_name.clone(),
|
||||
})?;
|
||||
|
||||
debug!("Found information for crate version: '{}'", version.num);
|
||||
|
||||
|
@ -136,6 +120,6 @@ pub async fn fetch_crate_gh_releases(
|
|||
_name: &str,
|
||||
_version: Option<&str>,
|
||||
_temp_dir: &Path,
|
||||
) -> Result<PathBuf, anyhow::Error> {
|
||||
) -> Result<PathBuf, BinstallError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
|
233
src/errors.rs
Normal file
233
src/errors.rs
Normal file
|
@ -0,0 +1,233 @@
|
|||
use std::process::{ExitCode, Termination};
|
||||
|
||||
use log::{error, warn};
|
||||
use miette::{Diagnostic, Report};
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors emitted by the library portion of cargo-binstall.
|
||||
#[derive(Error, Diagnostic, Debug)]
|
||||
#[diagnostic(url(docsrs))]
|
||||
#[non_exhaustive]
|
||||
pub enum BinstallError {
|
||||
/// The installation was cancelled by a user at a confirmation prompt.
|
||||
///
|
||||
/// - Code: `binstall::user_abort`
|
||||
/// - Exit: 32
|
||||
#[error("installation cancelled by user")]
|
||||
#[diagnostic(severity(info), code(binstall::user_abort))]
|
||||
UserAbort,
|
||||
|
||||
/// A URL is invalid.
|
||||
///
|
||||
/// This may be the result of a template in a Cargo manifest.
|
||||
///
|
||||
/// - Code: `binstall::url_parse`
|
||||
/// - Exit: 65
|
||||
#[error(transparent)]
|
||||
#[diagnostic(severity(error), code(binstall::url_parse))]
|
||||
UrlParse(#[from] url::ParseError),
|
||||
|
||||
/// An error while unzipping a file.
|
||||
///
|
||||
/// - Code: `binstall::unzip`
|
||||
/// - Exit: 66
|
||||
#[error(transparent)]
|
||||
#[diagnostic(severity(error), code(binstall::unzip))]
|
||||
Unzip(#[from] zip::result::ZipError),
|
||||
|
||||
/// A rendering error in a template.
|
||||
///
|
||||
/// - Code: `binstall::template`
|
||||
/// - Exit: 67
|
||||
#[error(transparent)]
|
||||
#[diagnostic(severity(error), code(binstall::template))]
|
||||
Template(#[from] tinytemplate::error::Error),
|
||||
|
||||
/// A generic error from our HTTP client, reqwest.
|
||||
///
|
||||
/// Errors resulting from HTTP fetches are handled with [`BinstallError::Http`] instead.
|
||||
///
|
||||
/// - Code: `binstall::reqwest`
|
||||
/// - Exit: 68
|
||||
#[error(transparent)]
|
||||
#[diagnostic(severity(error), code(binstall::reqwest))]
|
||||
Reqwest(#[from] reqwest::Error),
|
||||
|
||||
/// An HTTP request failed.
|
||||
///
|
||||
/// This includes both connection/transport failures and when the HTTP status of the response
|
||||
/// is not as expected.
|
||||
///
|
||||
/// - Code: `binstall::http`
|
||||
/// - Exit: 69
|
||||
#[error("could not {method} {url}: {err}")]
|
||||
#[diagnostic(severity(error), code(binstall::http))]
|
||||
Http {
|
||||
method: reqwest::Method,
|
||||
url: url::Url,
|
||||
#[source]
|
||||
err: reqwest::Error,
|
||||
},
|
||||
|
||||
/// A generic I/O error.
|
||||
///
|
||||
/// - Code: `binstall::io`
|
||||
/// - Exit: 74
|
||||
#[error(transparent)]
|
||||
#[diagnostic(severity(error), code(binstall::io))]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
/// An error interacting with the crates.io API.
|
||||
///
|
||||
/// This could either be a "not found" or a server/transport error.
|
||||
///
|
||||
/// - Code: `binstall::crates_io_api`
|
||||
/// - Exit: 76
|
||||
#[error("crates.io api error fetching crate information for '{crate_name}': {err}")]
|
||||
#[diagnostic(
|
||||
severity(error),
|
||||
code(binstall::crates_io_api),
|
||||
help("Check that the crate name you provided is correct.\nYou can also search for a matching crate at: https://lib.rs/search?q={crate_name}")
|
||||
)]
|
||||
CratesIoApi {
|
||||
crate_name: String,
|
||||
#[source]
|
||||
err: crates_io_api::Error,
|
||||
},
|
||||
|
||||
/// A parsing or validation error in a cargo manifest.
|
||||
///
|
||||
/// This should be rare, as manifests are generally fetched from crates.io, which does its own
|
||||
/// validation upstream. The most common failure will therefore be for direct repository access
|
||||
/// and with the `--manifest-path` option.
|
||||
///
|
||||
/// - Code: `binstall::cargo_manifest`
|
||||
/// - Exit: 78
|
||||
#[error(transparent)]
|
||||
#[diagnostic(
|
||||
severity(error),
|
||||
code(binstall::cargo_manifest),
|
||||
help("If you used --manifest-path, check the Cargo.toml syntax.")
|
||||
)]
|
||||
CargoManifest(#[from] cargo_toml::Error),
|
||||
|
||||
/// A version is not valid semver.
|
||||
///
|
||||
/// Note that we use the [`semver`] crate, which parses Cargo version syntax; this may be
|
||||
/// somewhat stricter or very slightly different from other semver implementations.
|
||||
///
|
||||
/// - Code: `binstall::version::parse`
|
||||
/// - Exit: 80
|
||||
#[error("version string '{v}' is not semver: {err}")]
|
||||
#[diagnostic(severity(error), code(binstall::version::parse))]
|
||||
VersionParse {
|
||||
v: String,
|
||||
#[source]
|
||||
err: semver::Error,
|
||||
},
|
||||
|
||||
/// A version requirement is not valid.
|
||||
///
|
||||
/// This is usually provided via the `--version` option.
|
||||
///
|
||||
/// Note that we use the [`semver`] crate, which parses Cargo version requirement syntax; they
|
||||
/// may be slightly different from other semver requirements expressions implementations.
|
||||
///
|
||||
/// - Code: `binstall::version::requirement`
|
||||
/// - Exit: 81
|
||||
#[error("version requirement '{req}' is not semver: {err}")]
|
||||
#[diagnostic(severity(error), code(binstall::version::requirement))]
|
||||
VersionReq {
|
||||
req: String,
|
||||
#[source]
|
||||
err: semver::Error,
|
||||
},
|
||||
|
||||
/// No available version matches the requirements.
|
||||
///
|
||||
/// This may be the case when using the `--version` option.
|
||||
///
|
||||
/// Note that using `--version 1.2.3` is interpreted as the requirement `^1.2.3` as per
|
||||
/// Cargo.toml rules. If you want the exact version 1.2.3, use `--version '=1.2.3'`.
|
||||
///
|
||||
/// - Code: `binstall::version::mismatch`
|
||||
/// - Exit: 82
|
||||
#[error("no version matching requirement '{req}'")]
|
||||
#[diagnostic(severity(error), code(binstall::version::mismatch))]
|
||||
VersionMismatch { req: semver::VersionReq },
|
||||
|
||||
/// The crates.io API doesn't have manifest metadata for the given version.
|
||||
///
|
||||
/// - Code: `binstall::version::unavailable`
|
||||
/// - Exit: 83
|
||||
#[error("no crate information available for '{crate_name}' version '{v}'")]
|
||||
#[diagnostic(severity(error), code(binstall::version::unavailable))]
|
||||
VersionUnavailable {
|
||||
crate_name: String,
|
||||
v: semver::Version,
|
||||
},
|
||||
|
||||
/// Warning: The resolved version may not be what was meant.
|
||||
///
|
||||
/// This occurs when using the `--version` option with a bare version, like `--version 1.2.3`.
|
||||
/// That is parsed as the semver requirement `^1.2.3`, but the user may have expected that to
|
||||
/// be an exact version (which should be specified with `--version '=1.2.3'`.
|
||||
///
|
||||
/// - Code: `binstall::version::warning`
|
||||
/// - Exit: none (runtime warning only)
|
||||
#[error("version semantic mismatch: {ver} <> {req}")]
|
||||
#[diagnostic(
|
||||
severity(warning),
|
||||
code(binstall::version::warning),
|
||||
help("You specified `--version {req}` but the package resolved that to '{ver}'.\nUse `--version '={req}'` if you want an exact match.")
|
||||
)]
|
||||
VersionWarning { ver: String, req: String },
|
||||
}
|
||||
|
||||
impl BinstallError {
|
||||
/// 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 {
|
||||
use BinstallError::*;
|
||||
let code: u8 = match self {
|
||||
UserAbort => 32,
|
||||
UrlParse(_) => 65,
|
||||
Unzip(_) => 66,
|
||||
Template(_) => 67,
|
||||
Reqwest(_) => 68,
|
||||
Http { .. } => 69,
|
||||
Io(_) => 74,
|
||||
CratesIoApi { .. } => 76,
|
||||
CargoManifest { .. } => 78,
|
||||
VersionParse { .. } => 80,
|
||||
VersionReq { .. } => 81,
|
||||
VersionMismatch { .. } => 82,
|
||||
VersionUnavailable { .. } => 83,
|
||||
VersionWarning { .. } => unimplemented!("BUG: warnings do not terminate"),
|
||||
};
|
||||
|
||||
// reserved codes
|
||||
debug_assert!(code != 64 && code != 16 && code != 1 && code != 2 && code != 0);
|
||||
|
||||
code.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Termination for BinstallError {
|
||||
fn report(self) -> ExitCode {
|
||||
let code = self.exit_code();
|
||||
if let BinstallError::UserAbort = self {
|
||||
warn!("Installation cancelled");
|
||||
} else {
|
||||
error!("Fatal error:");
|
||||
eprintln!("{:?}", Report::new(self));
|
||||
}
|
||||
|
||||
code
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ pub use gh_crate_meta::*;
|
|||
pub use log::debug;
|
||||
pub use quickinstall::*;
|
||||
|
||||
use crate::{PkgFmt, PkgMeta};
|
||||
use crate::{BinstallError, PkgFmt, PkgMeta};
|
||||
|
||||
mod gh_crate_meta;
|
||||
mod quickinstall;
|
||||
|
@ -17,10 +17,10 @@ pub trait Fetcher {
|
|||
Self: Sized;
|
||||
|
||||
/// Fetch a package
|
||||
async fn fetch(&self, dst: &Path) -> Result<(), anyhow::Error>;
|
||||
async fn fetch(&self, dst: &Path) -> Result<(), BinstallError>;
|
||||
|
||||
/// Check if a package is available for download
|
||||
async fn check(&self) -> Result<bool, anyhow::Error>;
|
||||
async fn check(&self) -> Result<bool, BinstallError>;
|
||||
|
||||
/// Return the package format
|
||||
fn pkg_fmt(&self) -> PkgFmt;
|
||||
|
|
|
@ -6,14 +6,14 @@ use serde::Serialize;
|
|||
use url::Url;
|
||||
|
||||
use super::Data;
|
||||
use crate::{download, remote_exists, PkgFmt, Template};
|
||||
use crate::{download, remote_exists, BinstallError, PkgFmt, Template};
|
||||
|
||||
pub struct GhCrateMeta {
|
||||
data: Data,
|
||||
}
|
||||
|
||||
impl GhCrateMeta {
|
||||
fn url(&self) -> Result<Url, anyhow::Error> {
|
||||
fn url(&self) -> Result<Url, BinstallError> {
|
||||
let ctx = Context::from_data(&self.data);
|
||||
debug!("Using context: {:?}", ctx);
|
||||
Ok(ctx.render_url(&self.data.meta.pkg_url)?)
|
||||
|
@ -26,13 +26,13 @@ impl super::Fetcher for GhCrateMeta {
|
|||
Box::new(Self { data: data.clone() })
|
||||
}
|
||||
|
||||
async fn check(&self) -> Result<bool, anyhow::Error> {
|
||||
async fn check(&self) -> Result<bool, BinstallError> {
|
||||
let url = self.url()?;
|
||||
info!("Checking for package at: '{url}'");
|
||||
remote_exists(url.as_str(), Method::HEAD).await
|
||||
}
|
||||
|
||||
async fn fetch(&self, dst: &Path) -> Result<(), anyhow::Error> {
|
||||
async fn fetch(&self, dst: &Path) -> Result<(), BinstallError> {
|
||||
let url = self.url()?;
|
||||
info!("Downloading package from: '{url}'");
|
||||
download(url.as_str(), dst).await
|
||||
|
@ -101,7 +101,7 @@ impl<'c> Context<'c> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(self) fn render_url(&self, template: &str) -> Result<Url, anyhow::Error> {
|
||||
pub(self) fn render_url(&self, template: &str) -> Result<Url, BinstallError> {
|
||||
Ok(Url::parse(&self.render(template)?)?)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,10 @@ use std::path::Path;
|
|||
|
||||
use log::info;
|
||||
use reqwest::Method;
|
||||
use url::Url;
|
||||
|
||||
use super::Data;
|
||||
use crate::{download, remote_exists, PkgFmt};
|
||||
use crate::{download, remote_exists, BinstallError, PkgFmt};
|
||||
|
||||
const BASE_URL: &str = "https://github.com/alsuren/cargo-quickinstall/releases/download";
|
||||
const STATS_URL: &str = "https://warehouse-clerk-tmp.vercel.app/api/crate";
|
||||
|
@ -25,14 +26,14 @@ impl super::Fetcher for QuickInstall {
|
|||
})
|
||||
}
|
||||
|
||||
async fn check(&self) -> Result<bool, anyhow::Error> {
|
||||
async fn check(&self) -> Result<bool, BinstallError> {
|
||||
let url = self.package_url();
|
||||
self.report().await?;
|
||||
info!("Checking for package at: '{url}'");
|
||||
remote_exists(&url, Method::HEAD).await
|
||||
}
|
||||
|
||||
async fn fetch(&self, dst: &Path) -> Result<(), anyhow::Error> {
|
||||
async fn fetch(&self, dst: &Path) -> Result<(), BinstallError> {
|
||||
let url = self.package_url();
|
||||
info!("Downloading package from: '{url}'");
|
||||
download(&url, &dst).await
|
||||
|
@ -68,14 +69,20 @@ impl QuickInstall {
|
|||
)
|
||||
}
|
||||
|
||||
pub async fn report(&self) -> Result<(), anyhow::Error> {
|
||||
pub async fn report(&self) -> Result<(), BinstallError> {
|
||||
info!("Sending installation report to quickinstall (anonymous)");
|
||||
let url = Url::parse(&self.stats_url())?;
|
||||
reqwest::Client::builder()
|
||||
.user_agent(USER_AGENT)
|
||||
.build()?
|
||||
.request(Method::HEAD, &self.stats_url())
|
||||
.request(Method::HEAD, url.clone())
|
||||
.send()
|
||||
.await?;
|
||||
.await
|
||||
.map_err(|err| BinstallError::Http {
|
||||
method: Method::HEAD,
|
||||
url,
|
||||
err,
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,24 +1,28 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::{
|
||||
fs,
|
||||
io::{stderr, stdin, Write},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use log::{debug, error, info};
|
||||
use log::{debug, info};
|
||||
|
||||
use cargo_toml::Manifest;
|
||||
use flate2::read::GzDecoder;
|
||||
use reqwest::Method;
|
||||
use serde::Serialize;
|
||||
use tar::Archive;
|
||||
use tinytemplate::TinyTemplate;
|
||||
use url::Url;
|
||||
use xz2::read::XzDecoder;
|
||||
use zip::read::ZipArchive;
|
||||
use zstd::stream::Decoder as ZstdDecoder;
|
||||
|
||||
use crate::Meta;
|
||||
|
||||
use super::PkgFmt;
|
||||
use crate::{BinstallError, Meta, PkgFmt};
|
||||
|
||||
/// Load binstall metadata from the crate `Cargo.toml` at the provided path
|
||||
pub fn load_manifest_path<P: AsRef<Path>>(
|
||||
manifest_path: P,
|
||||
) -> Result<Manifest<Meta>, anyhow::Error> {
|
||||
) -> Result<Manifest<Meta>, BinstallError> {
|
||||
debug!("Reading manifest: {}", manifest_path.as_ref().display());
|
||||
|
||||
// Load and parse manifest (this checks file system for binary output names)
|
||||
|
@ -28,29 +32,37 @@ pub fn load_manifest_path<P: AsRef<Path>>(
|
|||
Ok(manifest)
|
||||
}
|
||||
|
||||
pub async fn remote_exists(url: &str, method: reqwest::Method) -> Result<bool, anyhow::Error> {
|
||||
let req = reqwest::Client::new().request(method, url).send().await?;
|
||||
pub async fn remote_exists(url: &str, method: Method) -> Result<bool, BinstallError> {
|
||||
let url = Url::parse(url)?;
|
||||
let req = reqwest::Client::new()
|
||||
.request(method.clone(), url.clone())
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| BinstallError::Http { method, url, err })?;
|
||||
Ok(req.status().is_success())
|
||||
}
|
||||
|
||||
/// Download a file from the provided URL to the provided path
|
||||
pub async fn download<P: AsRef<Path>>(url: &str, path: P) -> Result<(), anyhow::Error> {
|
||||
pub async fn download<P: AsRef<Path>>(url: &str, path: P) -> Result<(), BinstallError> {
|
||||
let url = Url::parse(url)?;
|
||||
debug!("Downloading from: '{}'", url);
|
||||
|
||||
let resp = reqwest::get(url).await?;
|
||||
|
||||
if !resp.status().is_success() {
|
||||
error!("Download error: {}", resp.status());
|
||||
return Err(anyhow::anyhow!(resp.status()));
|
||||
}
|
||||
let resp = reqwest::get(url.clone())
|
||||
.await
|
||||
.and_then(|r| r.error_for_status())
|
||||
.map_err(|err| BinstallError::Http {
|
||||
method: Method::GET,
|
||||
url,
|
||||
err,
|
||||
})?;
|
||||
|
||||
let bytes = resp.bytes().await?;
|
||||
|
||||
let path = path.as_ref();
|
||||
debug!("Download OK, writing to file: '{}'", path.display());
|
||||
|
||||
std::fs::create_dir_all(path.parent().unwrap())?;
|
||||
std::fs::write(&path, bytes)?;
|
||||
fs::create_dir_all(path.parent().unwrap())?;
|
||||
fs::write(&path, bytes)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -60,7 +72,7 @@ pub fn extract<S: AsRef<Path>, P: AsRef<Path>>(
|
|||
source: S,
|
||||
fmt: PkgFmt,
|
||||
path: P,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
) -> Result<(), BinstallError> {
|
||||
match fmt {
|
||||
PkgFmt::Tar => {
|
||||
// Extract to install dir
|
||||
|
@ -70,7 +82,7 @@ pub fn extract<S: AsRef<Path>, P: AsRef<Path>>(
|
|||
path.as_ref()
|
||||
);
|
||||
|
||||
let dat = std::fs::File::open(source)?;
|
||||
let dat = fs::File::open(source)?;
|
||||
let mut tar = Archive::new(dat);
|
||||
|
||||
tar.unpack(path)?;
|
||||
|
@ -83,7 +95,7 @@ pub fn extract<S: AsRef<Path>, P: AsRef<Path>>(
|
|||
path.as_ref()
|
||||
);
|
||||
|
||||
let dat = std::fs::File::open(source)?;
|
||||
let dat = fs::File::open(source)?;
|
||||
let tar = GzDecoder::new(dat);
|
||||
let mut tgz = Archive::new(tar);
|
||||
|
||||
|
@ -97,7 +109,7 @@ pub fn extract<S: AsRef<Path>, P: AsRef<Path>>(
|
|||
path.as_ref()
|
||||
);
|
||||
|
||||
let dat = std::fs::File::open(source)?;
|
||||
let dat = fs::File::open(source)?;
|
||||
let tar = XzDecoder::new(dat);
|
||||
let mut txz = Archive::new(tar);
|
||||
|
||||
|
@ -130,7 +142,7 @@ pub fn extract<S: AsRef<Path>, P: AsRef<Path>>(
|
|||
path.as_ref()
|
||||
);
|
||||
|
||||
let dat = std::fs::File::open(source)?;
|
||||
let dat = fs::File::open(source)?;
|
||||
let mut zip = ZipArchive::new(dat)?;
|
||||
|
||||
zip.extract(path)?;
|
||||
|
@ -142,7 +154,7 @@ pub fn extract<S: AsRef<Path>, P: AsRef<Path>>(
|
|||
path.as_ref()
|
||||
);
|
||||
// Copy to install dir
|
||||
std::fs::copy(source, path)?;
|
||||
fs::copy(source, path)?;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -150,7 +162,7 @@ pub fn extract<S: AsRef<Path>, P: AsRef<Path>>(
|
|||
}
|
||||
|
||||
/// Fetch install path from environment
|
||||
/// roughly follows https://doc.rust-lang.org/cargo/commands/cargo-install.html#description
|
||||
/// roughly follows <https://doc.rust-lang.org/cargo/commands/cargo-install.html#description>
|
||||
pub fn get_install_path<P: AsRef<Path>>(install_path: Option<P>) -> Option<PathBuf> {
|
||||
// Command line override first first
|
||||
if let Some(p) = install_path {
|
||||
|
@ -188,23 +200,25 @@ pub fn get_install_path<P: AsRef<Path>>(install_path: Option<P>) -> Option<PathB
|
|||
None
|
||||
}
|
||||
|
||||
pub fn confirm() -> Result<bool, anyhow::Error> {
|
||||
info!("Do you wish to continue? yes/no");
|
||||
pub fn confirm() -> Result<(), BinstallError> {
|
||||
loop {
|
||||
info!("Do you wish to continue? yes/[no]");
|
||||
eprint!("? ");
|
||||
stderr().flush().ok();
|
||||
|
||||
let mut input = String::new();
|
||||
std::io::stdin().read_line(&mut input)?;
|
||||
stdin().read_line(&mut input).unwrap();
|
||||
|
||||
match input.as_str().trim() {
|
||||
"yes" => Ok(true),
|
||||
"no" => Ok(false),
|
||||
_ => Err(anyhow::anyhow!(
|
||||
"Valid options are 'yes', 'no', please try again"
|
||||
)),
|
||||
"yes" | "y" | "YES" | "Y" => break Ok(()),
|
||||
"no" | "n" | "NO" | "N" | "" => break Err(BinstallError::UserAbort),
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Template: Serialize {
|
||||
fn render(&self, template: &str) -> Result<String, anyhow::Error>
|
||||
fn render(&self, template: &str) -> Result<String, BinstallError>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
|
|
@ -3,12 +3,15 @@ use std::collections::HashMap;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use strum_macros::{Display, EnumString, EnumVariantNames};
|
||||
|
||||
pub mod helpers;
|
||||
pub use helpers::*;
|
||||
|
||||
pub mod drivers;
|
||||
pub use drivers::*;
|
||||
|
||||
pub mod errors;
|
||||
pub use errors::*;
|
||||
|
||||
pub mod helpers;
|
||||
pub use helpers::*;
|
||||
|
||||
pub mod bins;
|
||||
pub mod fetchers;
|
||||
|
||||
|
|
113
src/main.rs
113
src/main.rs
|
@ -1,11 +1,17 @@
|
|||
use std::{path::PathBuf, str::FromStr};
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
process::{ExitCode, Termination},
|
||||
str::FromStr,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use cargo_toml::{Package, Product};
|
||||
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;
|
||||
use tokio::{process::Command, runtime::Runtime};
|
||||
|
||||
use cargo_binstall::{
|
||||
bins,
|
||||
|
@ -74,8 +80,49 @@ struct Options {
|
|||
pkg_url: Option<String>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), anyhow::Error> {
|
||||
enum MainExit {
|
||||
Success(Duration),
|
||||
Error(BinstallError),
|
||||
Report(miette::Report),
|
||||
}
|
||||
|
||||
impl Termination for MainExit {
|
||||
fn report(self) -> ExitCode {
|
||||
match self {
|
||||
Self::Success(spent) => {
|
||||
info!("Installation completed in {spent:?}");
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
Self::Error(err) => err.report(),
|
||||
Self::Report(err) => {
|
||||
error!("Fatal error:");
|
||||
eprintln!("{err:?}");
|
||||
ExitCode::from(16)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> MainExit {
|
||||
let start = Instant::now();
|
||||
|
||||
let rt = Runtime::new().unwrap();
|
||||
let result = rt.block_on(entry());
|
||||
drop(rt);
|
||||
|
||||
let done = start.elapsed();
|
||||
debug!("run time: {done:?}");
|
||||
|
||||
result
|
||||
.map(|_| MainExit::Success(done))
|
||||
.unwrap_or_else(|err| {
|
||||
err.downcast::<BinstallError>()
|
||||
.map(MainExit::Error)
|
||||
.unwrap_or_else(MainExit::Report)
|
||||
})
|
||||
}
|
||||
|
||||
async fn entry() -> Result<()> {
|
||||
// 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"]
|
||||
|
@ -106,7 +153,9 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||
.unwrap();
|
||||
|
||||
// Create a temporary directory for downloads etc.
|
||||
let temp_dir = TempDir::new()?;
|
||||
let temp_dir = TempDir::new()
|
||||
.map_err(BinstallError::from)
|
||||
.wrap_err("Creating a temporary directory failed.")?;
|
||||
|
||||
info!("Installing package: '{}'", opts.name);
|
||||
|
||||
|
@ -124,14 +173,17 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||
|
||||
let is_plain_version = semver::Version::from_str(&opts.version).is_ok();
|
||||
if is_plain_version && package.version != opts.version {
|
||||
warn!(
|
||||
"You specified `--version {o}` but the package resolved that to '{p}', use `={o}` if you want an exact match",
|
||||
o=opts.version, p=package.version
|
||||
warn!("Warning!");
|
||||
eprintln!(
|
||||
"{:?}",
|
||||
miette::Report::new(BinstallError::VersionWarning {
|
||||
ver: package.version.clone(),
|
||||
req: opts.version.clone()
|
||||
})
|
||||
);
|
||||
|
||||
if opts.no_confirm || opts.dry_run || !confirm()? {
|
||||
warn!("Installation cancelled");
|
||||
return Ok(());
|
||||
if !opts.no_confirm && !opts.dry_run {
|
||||
confirm()?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,7 +208,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||
// 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`");
|
||||
anyhow::anyhow!("No install path found or specified")
|
||||
miette!("No install path found or specified")
|
||||
})?;
|
||||
debug!("Using install path: {}", install_path.display());
|
||||
|
||||
|
@ -211,16 +263,15 @@ async fn install_from_package(
|
|||
package: Package<Meta>,
|
||||
pkg_path: PathBuf,
|
||||
temp_dir: TempDir,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
) -> Result<()> {
|
||||
// Prompt user for third-party source
|
||||
if fetcher.is_third_party() {
|
||||
warn!(
|
||||
"The package will be downloaded from third-party source {}",
|
||||
fetcher.source_name()
|
||||
);
|
||||
if !opts.no_confirm && !opts.dry_run && !confirm()? {
|
||||
warn!("Installation cancelled");
|
||||
return Ok(());
|
||||
if !opts.no_confirm && !opts.dry_run {
|
||||
confirm()?;
|
||||
}
|
||||
} else {
|
||||
info!(
|
||||
|
@ -274,7 +325,7 @@ async fn install_from_package(
|
|||
|
||||
if binaries.is_empty() {
|
||||
error!("No binaries specified (or inferred from file system)");
|
||||
return Err(anyhow::anyhow!(
|
||||
return Err(miette!(
|
||||
"No binaries specified (or inferred from file system)"
|
||||
));
|
||||
}
|
||||
|
@ -295,7 +346,7 @@ async fn install_from_package(
|
|||
let bin_files = binaries
|
||||
.iter()
|
||||
.map(|p| bins::BinFile::from_product(&bin_data, p))
|
||||
.collect::<Result<Vec<_>, anyhow::Error>>()?;
|
||||
.collect::<Result<Vec<_>, BinstallError>>()?;
|
||||
|
||||
// Prompt user for confirmation
|
||||
info!("This will install the following binaries:");
|
||||
|
@ -315,9 +366,8 @@ async fn install_from_package(
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
if !opts.no_confirm && !confirm()? {
|
||||
warn!("Installation cancelled");
|
||||
return Ok(());
|
||||
if !opts.no_confirm {
|
||||
confirm()?;
|
||||
}
|
||||
|
||||
info!("Installing binaries...");
|
||||
|
@ -332,8 +382,6 @@ async fn install_from_package(
|
|||
}
|
||||
}
|
||||
|
||||
info!("Installation complete!");
|
||||
|
||||
if opts.no_cleanup {
|
||||
let _ = temp_dir.into_path();
|
||||
} else {
|
||||
|
@ -345,12 +393,11 @@ async fn install_from_package(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn install_from_source(opts: Options, package: Package<Meta>) -> Result<(), anyhow::Error> {
|
||||
async fn install_from_source(opts: Options, package: Package<Meta>) -> Result<()> {
|
||||
// Prompt user for source install
|
||||
warn!("The package will be installed from source (with cargo)",);
|
||||
if !opts.no_confirm && !opts.dry_run && !confirm()? {
|
||||
warn!("Installation cancelled");
|
||||
return Ok(());
|
||||
if !opts.no_confirm && !opts.dry_run {
|
||||
confirm()?;
|
||||
}
|
||||
|
||||
if opts.dry_run {
|
||||
|
@ -371,16 +418,22 @@ async fn install_from_source(opts: Options, package: Package<Meta>) -> Result<()
|
|||
.arg(package.version)
|
||||
.arg("--target")
|
||||
.arg(opts.target)
|
||||
.spawn()?;
|
||||
.spawn()
|
||||
.into_diagnostic()
|
||||
.wrap_err("Spawning cargo install failed.")?;
|
||||
debug!("Spawned command pid={:?}", child.id());
|
||||
|
||||
let status = child.wait().await?;
|
||||
let status = child
|
||||
.wait()
|
||||
.await
|
||||
.into_diagnostic()
|
||||
.wrap_err("Running cargo install failed.")?;
|
||||
if status.success() {
|
||||
info!("Cargo finished successfully");
|
||||
Ok(())
|
||||
} else {
|
||||
error!("Cargo errored! {:?}", status);
|
||||
Err(anyhow::anyhow!("Cargo install error"))
|
||||
Err(miette!("Cargo install error"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue