use std::collections::BTreeMap;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::{Arc, Mutex};
use anyhow::{bail, Context, Result};
use cargo_metadata::MetadataCommand;
use semver::Version;
use crate::lockfile::Digest;
#[derive(Debug, Clone)]
pub(crate) struct Cargo {
path: PathBuf,
rustc_path: PathBuf,
full_version: Arc<Mutex<Option<String>>>,
cargo_home: Option<PathBuf>,
}
impl Cargo {
pub(crate) fn new(path: PathBuf, rustc: PathBuf) -> Cargo {
Cargo {
path,
rustc_path: rustc,
full_version: Arc::new(Mutex::new(None)),
cargo_home: None,
}
}
pub(crate) fn command(&self) -> Result<Command> {
let mut command = Command::new(&self.path);
command.envs(self.env()?);
if self.is_nightly()? {
command.arg("-Zbindeps");
}
Ok(command)
}
pub(crate) fn metadata_command_with_options(
&self,
manifest_path: &Path,
other_options: Vec<String>,
) -> Result<MetadataCommand> {
let mut command = MetadataCommand::new();
command.cargo_path(&self.path);
for (k, v) in self.env()? {
command.env(k, v);
}
command.manifest_path(manifest_path);
let manifest_dir = manifest_path
.parent()
.ok_or_else(|| anyhow::anyhow!("manifest_path {:?} must have parent", manifest_path))?;
command.current_dir(manifest_dir);
let mut other_options = other_options;
if self.is_nightly()? {
other_options.push("-Zbindeps".to_owned());
}
command.other_options(other_options);
Ok(command)
}
pub(crate) fn full_version(&self) -> Result<String> {
let mut full_version = self.full_version.lock().unwrap();
if full_version.is_none() {
let observed_version = Digest::bin_version(&self.path)?;
*full_version = Some(observed_version);
}
Ok(full_version.clone().unwrap())
}
pub(crate) fn is_nightly(&self) -> Result<bool> {
let full_version = self.full_version()?;
let version_str = full_version.split(' ').nth(1);
if let Some(version_str) = version_str {
let version = Version::parse(version_str).context("Failed to parse cargo version")?;
return Ok(version.pre.as_str() == "nightly");
}
bail!("Couldn't parse cargo version");
}
pub(crate) fn use_sparse_registries_for_crates_io(&self) -> Result<bool> {
let full_version = self.full_version()?;
let version_str = full_version.split(' ').nth(1);
if let Some(version_str) = version_str {
let version = Version::parse(version_str).context("Failed to parse cargo version")?;
return Ok(version.major >= 1 && version.minor >= 68);
}
bail!("Couldn't parse cargo version");
}
#[cfg(test)]
pub(crate) fn uses_new_package_id_format(&self) -> Result<bool> {
let full_version = self.full_version()?;
let version_str = full_version.split(' ').nth(1);
if let Some(version_str) = version_str {
let version = Version::parse(version_str).context("Failed to parse cargo version")?;
return Ok(version.major >= 1 && version.minor >= 77);
}
bail!("Couldn't parse cargo version");
}
pub(crate) fn uses_stable_registry_hash(&self) -> Result<bool> {
let full_version = self.full_version()?;
let version_str = full_version.split(' ').nth(1);
if let Some(version_str) = version_str {
let version = Version::parse(version_str).context("Failed to parse cargo version")?;
return Ok(version.major >= 1 && version.minor >= 85);
}
bail!("Couldn't parse cargo version");
}
fn env(&self) -> Result<BTreeMap<String, OsString>> {
let mut map = BTreeMap::new();
map.insert("RUSTC".into(), self.rustc_path.as_os_str().to_owned());
if self.use_sparse_registries_for_crates_io()? {
map.insert(
"CARGO_REGISTRIES_CRATES_IO_PROTOCOL".into(),
"sparse".into(),
);
}
if let Some(cargo_home) = &self.cargo_home {
map.insert("CARGO_HOME".into(), cargo_home.as_os_str().to_owned());
}
Ok(map)
}
}