use crate::{CommandExt, home};
use anstyle::Style;
use anyhow::{Result, anyhow, ensure};
use bitflags::bitflags;
use cargo_metadata::{Metadata, MetadataCommand, Package, PackageId};
use std::{
io::{IsTerminal, Write},
path::{Path, PathBuf},
process::{Command, Stdio},
sync::LazyLock,
};
pub use home::cargo_home;
static STABLE_CARGO: LazyLock<PathBuf> = LazyLock::new(|| {
let mut command = Command::new("rustup");
command.env_remove("RUSTUP_TOOLCHAIN");
command.args(["+stable", "which", "cargo"]);
let output = command.logged_output(true).unwrap();
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
PathBuf::from(stdout.trim_end())
});
bitflags! {
pub struct Quiet: u8 {
const MESSAGE = 1 << 0;
const STDERR = 1 << 1;
}
}
impl From<bool> for Quiet {
fn from(value: bool) -> Self {
if value { Self::all() } else { Self::empty() }
}
}
pub struct Builder {
subcommand: String,
verb: String,
description: String,
quiet: Quiet,
stable: bool,
}
#[must_use]
pub fn build(description: &str) -> Builder {
Builder::new("build", "Building", description)
}
#[must_use]
pub fn check(description: &str) -> Builder {
Builder::new("check", "Checking", description)
}
#[must_use]
pub fn fetch(description: &str) -> Builder {
Builder::new("fetch", "Fetching", description)
}
#[must_use]
pub fn fix(description: &str) -> Builder {
Builder::new("fix", "Fixing", description)
}
#[must_use]
pub fn init(description: &str) -> Builder {
Builder::new("init", "Initializing", description)
}
#[must_use]
pub fn run(description: &str) -> Builder {
Builder::new("run", "Running", description)
}
#[must_use]
pub fn test(description: &str) -> Builder {
Builder::new("test", "Testing", description)
}
#[must_use]
pub fn update(description: &str) -> Builder {
Builder::new("update", "Updating", description)
}
impl Builder {
fn new(subcommand: &str, verb: &str, description: &str) -> Self {
Self {
subcommand: subcommand.to_owned(),
verb: verb.to_owned(),
description: description.to_owned(),
quiet: Quiet::empty(),
stable: false,
}
}
pub fn quiet(&mut self, value: impl Into<Quiet>) -> &mut Self {
let value = value.into();
if !value.is_empty() {
assert!(!matches!(self.subcommand.as_str(), "check" | "fix"));
}
self.quiet = value;
self
}
#[allow(clippy::missing_const_for_fn)]
pub fn stable(&mut self, value: bool) -> &mut Self {
self.stable = value;
self
}
#[allow(clippy::needless_pass_by_ref_mut)]
pub fn build(&mut self) -> Command {
if !self.quiet.contains(Quiet::MESSAGE) {
let style = if std::io::stderr().is_terminal() {
Style::new().bold()
} else {
Style::new()
};
let message = format!("{} {}", self.verb, self.description);
writeln!(std::io::stderr(), "{style}{message}{style:#}")
.unwrap_or_else(|error| panic!("Could not write to stderr: {error}"));
}
let mut command = if self.stable {
Command::new(&*STABLE_CARGO)
} else {
Command::new("cargo")
};
#[cfg(windows)]
{
let cargo_home = cargo_home().unwrap();
let new_path = crate::prepend_path(Path::new(&cargo_home).join("bin")).unwrap();
command.envs(vec![(crate::env::PATH, new_path)]);
}
command.args([&self.subcommand]);
if self.quiet.contains(Quiet::STDERR) {
command.stderr(Stdio::null());
}
command
}
}
pub fn current_metadata() -> Result<Metadata> {
MetadataCommand::new().no_deps().exec().map_err(Into::into)
}
pub fn package_with_root(metadata: &Metadata, package_root: &Path) -> Result<Package> {
let mut packages = Vec::new();
for package in &metadata.packages {
let path = package
.manifest_path
.parent()
.ok_or_else(|| anyhow!("Could not get parent directory"))?;
if path == package_root {
packages.push(package);
}
}
ensure!(
packages.len() <= 1,
"Found multiple packages in `{}`",
package_root.to_string_lossy()
);
packages
.into_iter()
.next()
.cloned()
.ok_or_else(|| anyhow!("Found no packages in `{}`", package_root.to_string_lossy()))
}
pub fn package(metadata: &Metadata, package_id: &PackageId) -> Result<Package> {
metadata
.packages
.iter()
.find(|package| package.id == *package_id)
.cloned()
.ok_or_else(|| anyhow!("Could not find package"))
}
#[must_use]
pub fn stable_cargo_path() -> &'static Path {
&STABLE_CARGO
}