LCOV - code coverage report
Current view: top level - internal/src - cargo.rs (source / functions) Hit Total Coverage
Test: unnamed Lines: 108 117 92.3 %
Date: 2024-10-23 19:20:50 Functions: 18 24 75.0 %

          Line data    Source code
       1             : use crate::CommandExt;
       2             : use ansi_term::Style;
       3             : use anyhow::{anyhow, ensure, Result};
       4             : use bitflags::bitflags;
       5             : use cargo_metadata::{Metadata, MetadataCommand, Package, PackageId};
       6             : use is_terminal::IsTerminal;
       7             : use once_cell::sync::Lazy;
       8             : use std::{
       9             :     io::Write,
      10             :     path::{Path, PathBuf},
      11             :     process::{Command, Stdio},
      12             : };
      13             : 
      14             : #[allow(clippy::module_name_repetitions)]
      15             : pub use home::cargo_home;
      16             : 
      17           6 : static STABLE_CARGO: Lazy<PathBuf> = Lazy::new(|| {
      18           6 :     let mut command = Command::new("rustup");
      19           6 :     command.args(["+stable", "which", "cargo"]);
      20           6 :     let output = command.logged_output(true).unwrap();
      21           6 :     assert!(output.status.success());
      22           6 :     let stdout = String::from_utf8(output.stdout).unwrap();
      23           6 :     PathBuf::from(stdout.trim_end())
      24           6 : });
      25             : 
      26             : bitflags! {
      27             :     pub struct Quiet: u8 {
      28             :         const MESSAGE = 1 << 0;
      29             :         const STDERR = 1 << 1;
      30             :     }
      31             : }
      32             : 
      33             : impl From<bool> for Quiet {
      34          38 :     fn from(value: bool) -> Self {
      35          38 :         if value {
      36           9 :             Self::all()
      37             :         } else {
      38          29 :             Self::empty()
      39             :         }
      40          38 :     }
      41             : }
      42             : 
      43             : /// A `cargo` command builder
      44             : ///
      45             : /// Note that [`std::process::Command`]is itself a builder. So technically that makes this a
      46             : /// "builder builder".
      47             : pub struct Builder {
      48             :     subcommand: String,
      49             :     verb: String,
      50             :     description: String,
      51             :     quiet: Quiet,
      52             :     stable: bool,
      53             : }
      54             : 
      55             : #[must_use]
      56          57 : pub fn build(description: &str) -> Builder {
      57          57 :     Builder::new("build", "Building", description)
      58          57 : }
      59             : 
      60             : #[must_use]
      61          21 : pub fn check(description: &str) -> Builder {
      62          21 :     Builder::new("check", "Checking", description)
      63          21 : }
      64             : 
      65             : #[must_use]
      66           9 : pub fn fetch(description: &str) -> Builder {
      67           9 :     Builder::new("fetch", "Fetching", description)
      68           9 : }
      69             : 
      70             : #[must_use]
      71           1 : pub fn fix(description: &str) -> Builder {
      72           1 :     Builder::new("fix", "Fixing", description)
      73           1 : }
      74             : 
      75             : #[must_use]
      76          18 : pub fn init(description: &str) -> Builder {
      77          18 :     Builder::new("init", "Initializing", description)
      78          18 : }
      79             : 
      80             : #[must_use]
      81           0 : pub fn run(description: &str) -> Builder {
      82           0 :     Builder::new("run", "Running", description)
      83           0 : }
      84             : 
      85             : #[must_use]
      86           3 : pub fn test(description: &str) -> Builder {
      87           3 :     Builder::new("test", "Testing", description)
      88           3 : }
      89             : 
      90             : #[must_use]
      91           0 : pub fn update(description: &str) -> Builder {
      92           0 :     Builder::new("update", "Updating", description)
      93           0 : }
      94             : 
      95             : impl Builder {
      96         109 :     fn new(subcommand: &str, verb: &str, description: &str) -> Self {
      97         109 :         Self {
      98         109 :             subcommand: subcommand.to_owned(),
      99         109 :             verb: verb.to_owned(),
     100         109 :             description: description.to_owned(),
     101         109 :             quiet: Quiet::empty(),
     102         109 :             stable: false,
     103         109 :         }
     104         109 :     }
     105             : 
     106             :     /// Whether to allow the command to write to standard error.
     107          47 :     pub fn quiet(&mut self, value: impl Into<Quiet>) -> &mut Self {
     108          47 :         let value = value.into();
     109          47 :         // smoelius: `cargo check` and `cargo fix` are never silenced.
     110          47 :         if !value.is_empty() {
     111          18 :             assert!(!matches!(self.subcommand.as_str(), "check" | "fix"));
     112          29 :         }
     113          47 :         self.quiet = value;
     114          47 :         self
     115          47 :     }
     116             : 
     117             :     /// Whether to use a cached path to stable `cargo`. Using the cached path avoids repeated calls
     118             :     /// to `rustup`.
     119          18 :     pub fn stable(&mut self, value: bool) -> &mut Self {
     120          18 :         self.stable = value;
     121          18 :         self
     122          18 :     }
     123             : 
     124             :     /// Consumes the builder and returns a [`std::process::Command`].
     125             :     #[allow(clippy::needless_pass_by_ref_mut)]
     126         109 :     pub fn build(&mut self) -> Command {
     127         109 :         if !self.quiet.contains(Quiet::MESSAGE) {
     128             :             // smoelius: Writing directly to `stderr` prevents capture by `libtest`.
     129          91 :             let message = format!("{} {}", self.verb, self.description);
     130          91 :             std::io::stderr()
     131          91 :                 .write_fmt(format_args!(
     132          91 :                     "{}\n",
     133          91 :                     if std::io::stderr().is_terminal() {
     134           0 :                         Style::new().bold()
     135             :                     } else {
     136          91 :                         Style::new()
     137             :                     }
     138          91 :                     .paint(message)
     139          91 :                 ))
     140          91 :                 .expect("Could not write to stderr");
     141          18 :         }
     142         109 :         let mut command = if self.stable {
     143          18 :             Command::new(&*STABLE_CARGO)
     144             :         } else {
     145          91 :             Command::new("cargo")
     146             :         };
     147             :         #[cfg(windows)]
     148             :         {
     149             :             // smoelius: Work around: https://github.com/rust-lang/rustup/pull/2978
     150             :             let cargo_home = cargo_home().unwrap();
     151             :             let new_path = crate::prepend_path(Path::new(&cargo_home).join("bin")).unwrap();
     152             :             command.envs(vec![(crate::env::PATH, new_path)]);
     153             :         }
     154         109 :         command.args([&self.subcommand]);
     155         109 :         if self.quiet.contains(Quiet::STDERR) {
     156           9 :             command.stderr(Stdio::null());
     157         100 :         }
     158         109 :         command
     159         109 :     }
     160             : }
     161             : 
     162             : /// Get metadata based on the current directory.
     163          11 : pub fn current_metadata() -> Result<Metadata> {
     164          11 :     MetadataCommand::new().no_deps().exec().map_err(Into::into)
     165          11 : }
     166             : 
     167          30 : pub fn package_with_root(metadata: &Metadata, package_root: &Path) -> Result<Package> {
     168          30 :     let packages = metadata
     169          30 :         .packages
     170          30 :         .iter()
     171         288 :         .map(|package| {
     172         288 :             let path = package
     173         288 :                 .manifest_path
     174         288 :                 .parent()
     175         288 :                 .ok_or_else(|| anyhow!("Could not get parent directory"))?;
     176         288 :             Ok(if path == package_root {
     177          30 :                 Some(package)
     178             :             } else {
     179         258 :                 None
     180             :             })
     181         288 :         })
     182          30 :         .filter_map(Result::transpose)
     183          30 :         .collect::<Result<Vec<_>>>()?;
     184             : 
     185          30 :     ensure!(
     186          30 :         packages.len() <= 1,
     187           0 :         "Found multiple packages in `{}`",
     188           0 :         package_root.to_string_lossy()
     189             :     );
     190             : 
     191          30 :     packages
     192          30 :         .into_iter()
     193          30 :         .next()
     194          30 :         .cloned()
     195          30 :         .ok_or_else(|| anyhow!("Found no packages in `{}`", package_root.to_string_lossy()))
     196          30 : }
     197             : 
     198          63 : pub fn package(metadata: &Metadata, package_id: &PackageId) -> Result<Package> {
     199          63 :     metadata
     200          63 :         .packages
     201          63 :         .iter()
     202         252 :         .find(|package| package.id == *package_id)
     203          63 :         .cloned()
     204          63 :         .ok_or_else(|| anyhow!("Could not find package"))
     205          63 : }

Generated by: LCOV version 1.14