Skip to main content

krypt_pkg/
cargo.rs

1//! `cargo install` as a package source for Rust crates.
2//!
3//! Not a system package manager: it is never auto-detected and has no field
4//! of its own in a `[[deps]]` group. A group routes an entry here by writing
5//! it as `cargo:<crate>` in any manager's list, so each manager decides per
6//! package whether to use its own package or build the crate.
7
8use crate::manager::{PackageError, PackageManager, RunOutcome, Runner};
9
10/// Prefix that routes a `[[deps]]` entry to [`Cargo`].
11pub const PREFIX: &str = "cargo:";
12
13/// Installs crates with `cargo install --locked` into Cargo's own bin
14/// directory (`$CARGO_HOME/bin`); no root privileges.
15pub struct Cargo;
16
17impl Cargo {
18    /// The crate name of a `name` or `name@version` spec.
19    fn crate_name(spec: &str) -> &str {
20        spec.split_once('@').map_or(spec, |(name, _)| name)
21    }
22}
23
24impl PackageManager for Cargo {
25    fn name(&self) -> &'static str {
26        "cargo"
27    }
28
29    fn is_available(&self) -> bool {
30        which::which("cargo").is_ok()
31    }
32
33    /// `cargo info` queries the registry and fails for an unknown crate.
34    fn exists(&self, runner: &dyn Runner, pkg: &str) -> Result<bool, PackageError> {
35        let RunOutcome { status, .. } = runner.run("cargo", &["info", pkg])?;
36        Ok(status == 0)
37    }
38
39    /// Looks for the crate in `cargo install --list`, whose crate lines read
40    /// `name v1.2.3:` and whose binary lines are indented.
41    fn is_installed(&self, runner: &dyn Runner, pkg: &str) -> Result<bool, PackageError> {
42        let RunOutcome {
43            status,
44            stdout,
45            stderr,
46        } = runner.run("cargo", &["install", "--list"])?;
47        if status != 0 {
48            return Err(PackageError::ExitFailure { status, stderr });
49        }
50        let name = Self::crate_name(pkg);
51        Ok(stdout
52            .lines()
53            .filter(|line| !line.starts_with(char::is_whitespace))
54            .any(|line| line.split_whitespace().next() == Some(name)))
55    }
56
57    fn install(&self, runner: &dyn Runner, packages: &[String]) -> Result<(), PackageError> {
58        let mut args = vec!["install", "--locked"];
59        args.extend(packages.iter().map(String::as_str));
60        let RunOutcome { status, stderr, .. } = runner.run("cargo", &args)?;
61        if status != 0 {
62            return Err(PackageError::ExitFailure { status, stderr });
63        }
64        Ok(())
65    }
66}