Skip to main content

cargo_install/
error.rs

1use crate::utils::{extract_backticked_after, extract_backticked_before};
2use std::process::ExitStatus;
3use thiserror::Error;
4
5#[derive(Debug, Error)]
6/// Errors returned when executing `cargo install`.
7pub enum CargoInstallError {
8    /// The `cargo` executable could not be found in `PATH`.
9    #[error("`cargo` command is not installed or not found in PATH")]
10    CargoNotInstalled,
11    /// Spawning or waiting on the `cargo` process failed for an I/O reason.
12    #[error("failed to execute `cargo install`: {0}")]
13    Io(#[from] std::io::Error),
14    /// Cargo reported that the package is already installed.
15    ///
16    /// `stderr` contains the full original cargo output.
17    #[error("package is already installed: {package}")]
18    AlreadyInstalled { package: String, stderr: String },
19    /// Cargo reported that a binary with the same name already exists.
20    ///
21    /// `stderr` contains the full original cargo output.
22    #[error("binary already exists in destination: {binary}")]
23    BinaryAlreadyExists { binary: String, stderr: String },
24    /// Cargo reported that the selected package has no installable binaries or examples.
25    ///
26    /// `stderr` contains the full original cargo output.
27    #[error("package has no installable binaries or examples")]
28    NoInstallableTargets { stderr: String },
29    /// Cargo failed while compiling the selected package.
30    ///
31    /// `stderr` contains the full original cargo output.
32    #[error("failed to compile package: {package}")]
33    CompileFailed { package: String, stderr: String },
34    /// Cargo exited with a non-success status that this crate did not recognize.
35    ///
36    /// `stderr` contains the full original cargo output.
37    #[error("unknown `cargo install` error with status {status}: {stderr}")]
38    UnknownCargoError { status: ExitStatus, stderr: String },
39}
40
41impl CargoInstallError {
42    pub(crate) fn from_spawn_error(error: std::io::Error) -> Self {
43        if error.kind() == std::io::ErrorKind::NotFound {
44            Self::CargoNotInstalled
45        } else {
46            Self::Io(error)
47        }
48    }
49
50    pub(crate) fn from_output(status: ExitStatus, stderr: Vec<u8>) -> Self {
51        let stderr = String::from_utf8_lossy(&stderr).trim().to_owned();
52
53        if let Some(package) = extract_backticked_before(&stderr, "is already installed") {
54            return Self::AlreadyInstalled { package, stderr };
55        }
56
57        if let Some(binary) = extract_backticked_after(&stderr, "binary `") {
58            return Self::BinaryAlreadyExists { binary, stderr };
59        }
60
61        if stderr.contains("specified package has no binaries")
62            || stderr.contains("no packages found with binaries or examples")
63        {
64            return Self::NoInstallableTargets { stderr };
65        }
66
67        if let Some(package) = extract_backticked_after(&stderr, "failed to compile `") {
68            return Self::CompileFailed { package, stderr };
69        }
70
71        Self::UnknownCargoError { status, stderr }
72    }
73}