use std::{
borrow::Cow,
collections::HashMap,
env,
ffi::OsString,
ops,
path::{Path, PathBuf},
};
use anyhow::{Context as _, Result, bail};
use crate::{
ProcessBuilder, cargo,
cli::Args,
features::Features,
manifest::Manifest,
metadata::{Metadata, Package, PackageId},
restore, term,
};
pub(crate) struct Context {
args: Args,
pub(crate) metadata: Metadata,
manifests: HashMap<PackageId, Manifest>,
pkg_features: HashMap<PackageId, Features>,
cargo: PathBuf,
pub(crate) cargo_version: u32,
pub(crate) restore: restore::Manager,
pub(crate) current_dir: PathBuf,
pub(crate) current_package: Option<PackageId>,
}
impl Context {
pub(crate) fn new(args: Args, cargo: OsString) -> Result<Self> {
assert!(
args.subcommand.is_some() || args.remove_dev_deps,
"no subcommand or valid flag specified"
);
let cargo_version = cargo::version(cmd!(&cargo))
.map_err(|e| warn!("unable to determine cargo version: {e:#}"))
.map_or(0, |v| v.minor);
let mut restore = restore::Manager::new(!args.remove_dev_deps);
let metadata = Metadata::new(
args.manifest_path.as_deref(),
&cargo,
cargo_version,
&args,
&mut restore,
)?;
if metadata.cargo_version < 41 && args.include_deps_features {
bail!("--include-deps-features requires Cargo 1.41 or later");
}
let mut manifests = HashMap::with_capacity(metadata.workspace_members.len());
let mut pkg_features = HashMap::with_capacity(metadata.workspace_members.len());
for &id in &metadata.workspace_members {
let manifest_path = &metadata[id].manifest_path;
let manifest = Manifest::new(manifest_path, metadata.cargo_version)?;
let features = Features::new(&metadata, &manifest, id, args.include_deps_features);
manifests.insert(id, manifest);
pkg_features.insert(id, features);
}
let mut cmd = cmd!(&cargo, "locate-project");
if let Some(manifest_path) = &args.manifest_path {
cmd.arg("--manifest-path");
cmd.arg(manifest_path);
}
let locate_project: serde_json::Map<String, serde_json::Value> =
serde_json::from_str(&cmd.read()?)
.with_context(|| format!("failed to parse output from {cmd}"))?;
let locate_project = Path::new(locate_project["root"].as_str().unwrap());
let mut current_package = None;
for &id in &metadata.workspace_members {
let manifest_path = &*metadata[id].manifest_path;
if locate_project == manifest_path {
current_package = Some(id);
break;
}
}
let this = Self {
args,
metadata,
manifests,
pkg_features,
cargo: cargo.into(),
cargo_version,
restore,
current_dir: env::current_dir()?,
current_package,
};
Ok(this)
}
pub(crate) fn packages(&self, id: PackageId) -> &Package {
&self.metadata[id]
}
pub(crate) fn workspace_members(&self) -> impl ExactSizeIterator<Item = &PackageId> {
self.metadata.workspace_members.iter()
}
pub(crate) fn current_package(&self) -> Option<PackageId> {
self.current_package
}
pub(crate) fn workspace_root(&self) -> &Path {
&self.metadata.workspace_root
}
pub(crate) fn manifests(&self, id: PackageId) -> &Manifest {
&self.manifests[&id]
}
pub(crate) fn pkg_features(&self, id: PackageId) -> &Features {
&self.pkg_features[&id]
}
pub(crate) fn is_private(&self, id: PackageId) -> bool {
if self.metadata.cargo_version >= 39 {
!self.packages(id).publish
} else {
!self.manifests(id).package.publish.unwrap()
}
}
pub(crate) fn rust_version(&self, id: PackageId) -> Option<&str> {
if self.metadata.cargo_version >= 58 {
self.packages(id).rust_version.as_deref()
} else {
self.manifests(id).package.rust_version.as_ref().unwrap().as_deref()
}
}
pub(crate) fn name_verbose(&self, id: PackageId) -> Cow<'_, str> {
let package = self.packages(id);
if term::verbose() {
Cow::Owned(format!(
"{} ({})",
package.name,
package.manifest_path.parent().unwrap().display()
))
} else {
Cow::Borrowed(&package.name)
}
}
pub(crate) fn cargo(&self) -> ProcessBuilder<'_> {
cmd!(&self.cargo)
}
}
impl ops::Deref for Context {
type Target = Args;
fn deref(&self) -> &Self::Target {
&self.args
}
}