mod crate_spec;
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::collections::VecDeque;
use std::fmt::Write;
use std::path::Path;
use std::str::FromStr;
use anyhow::Context as _;
use cargo_util::paths;
use cargo_util_schemas::core::PartialVersion;
use cargo_util_schemas::manifest::RustVersion;
use indexmap::IndexSet;
use itertools::Itertools;
use toml_edit::Item as TomlItem;
use crate::core::dependency::DepKind;
use crate::core::registry::PackageRegistry;
use crate::core::FeatureValue;
use crate::core::Package;
use crate::core::Registry;
use crate::core::Shell;
use crate::core::Summary;
use crate::core::Workspace;
use crate::sources::source::QueryKind;
use crate::util::cache_lock::CacheLockMode;
use crate::util::style;
use crate::util::toml_mut::dependency::Dependency;
use crate::util::toml_mut::dependency::GitSource;
use crate::util::toml_mut::dependency::MaybeWorkspace;
use crate::util::toml_mut::dependency::PathSource;
use crate::util::toml_mut::dependency::Source;
use crate::util::toml_mut::dependency::WorkspaceSource;
use crate::util::toml_mut::is_sorted;
use crate::util::toml_mut::manifest::DepTable;
use crate::util::toml_mut::manifest::LocalManifest;
use crate::CargoResult;
use crate::GlobalContext;
use crate_spec::CrateSpec;
#[derive(Clone, Debug)]
pub struct AddOptions<'a> {
pub gctx: &'a GlobalContext,
pub spec: &'a Package,
pub dependencies: Vec<DepOp>,
pub section: DepTable,
pub dry_run: bool,
pub honor_rust_version: Option<bool>,
}
pub fn add(workspace: &Workspace<'_>, options: &AddOptions<'_>) -> CargoResult<()> {
let dep_table = options
.section
.to_table()
.into_iter()
.map(String::from)
.collect::<Vec<_>>();
let manifest_path = options.spec.manifest_path().to_path_buf();
let mut manifest = LocalManifest::try_new(&manifest_path)?;
let original_raw_manifest = manifest.to_string();
let legacy = manifest.get_legacy_sections();
if !legacy.is_empty() {
anyhow::bail!(
"Deprecated dependency sections are unsupported: {}",
legacy.join(", ")
);
}
let mut registry = workspace.package_registry()?;
let deps = {
let _lock = options
.gctx
.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?;
registry.lock_patches();
options
.dependencies
.iter()
.map(|raw| {
resolve_dependency(
&manifest,
raw,
workspace,
&options.spec,
&options.section,
options.honor_rust_version,
options.gctx,
&mut registry,
)
})
.collect::<CargoResult<Vec<_>>>()?
};
let was_sorted = manifest
.get_table(&dep_table)
.map(TomlItem::as_table)
.map_or(true, |table_option| {
table_option.map_or(true, |table| {
is_sorted(table.get_values().iter_mut().map(|(key, _)| {
key.remove(0)
}))
})
});
for dep in deps {
print_action_msg(&mut options.gctx.shell(), &dep, &dep_table)?;
if let Some(Source::Path(src)) = dep.source() {
if src.path == manifest.path.parent().unwrap_or_else(|| Path::new("")) {
anyhow::bail!(
"cannot add `{}` as a dependency to itself",
manifest.package_name()?
)
}
}
let available_features = dep
.available_features
.keys()
.map(|s| s.as_ref())
.collect::<BTreeSet<&str>>();
let mut unknown_features: Vec<&str> = Vec::new();
if let Some(req_feats) = dep.features.as_ref() {
let req_feats: BTreeSet<_> = req_feats.iter().map(|s| s.as_str()).collect();
unknown_features.extend(req_feats.difference(&available_features).copied());
}
if let Some(inherited_features) = dep.inherited_features.as_ref() {
let inherited_features: BTreeSet<_> =
inherited_features.iter().map(|s| s.as_str()).collect();
unknown_features.extend(inherited_features.difference(&available_features).copied());
}
unknown_features.sort();
if !unknown_features.is_empty() {
let (mut activated, mut deactivated) = dep.features();
deactivated.retain(|f| !unknown_features.contains(f));
activated.retain(|f| !unknown_features.contains(f));
let mut message = format!(
"unrecognized feature{} for crate {}: {}\n",
if unknown_features.len() == 1 { "" } else { "s" },
dep.name,
unknown_features.iter().format(", "),
);
if activated.is_empty() && deactivated.is_empty() {
write!(message, "no features available for crate {}", dep.name)?;
} else {
if !deactivated.is_empty() {
writeln!(
message,
"disabled features:\n {}",
deactivated
.iter()
.map(|s| s.to_string())
.coalesce(|x, y| if x.len() + y.len() < 78 {
Ok(format!("{x}, {y}"))
} else {
Err((x, y))
})
.into_iter()
.format("\n ")
)?
}
if !activated.is_empty() {
writeln!(
message,
"enabled features:\n {}",
activated
.iter()
.map(|s| s.to_string())
.coalesce(|x, y| if x.len() + y.len() < 78 {
Ok(format!("{x}, {y}"))
} else {
Err((x, y))
})
.into_iter()
.format("\n ")
)?
}
}
anyhow::bail!(message.trim().to_owned());
}
print_dep_table_msg(&mut options.gctx.shell(), &dep)?;
manifest.insert_into_table(&dep_table, &dep)?;
if dep.optional == Some(true) {
let is_namespaced_features_supported =
check_rust_version_for_optional_dependency(options.spec.rust_version())?;
if is_namespaced_features_supported {
let dep_key = dep.toml_key();
if !manifest.is_explicit_dep_activation(dep_key) {
let table = manifest.get_table_mut(&[String::from("features")])?;
let dep_name = dep.rename.as_deref().unwrap_or(&dep.name);
let new_feature: toml_edit::Value =
[format!("dep:{dep_name}")].iter().collect();
table[dep_key] = toml_edit::value(new_feature);
options
.gctx
.shell()
.status("Adding", format!("feature `{dep_key}`"))?;
}
}
}
manifest.gc_dep(dep.toml_key());
}
if was_sorted {
if let Some(table) = manifest
.get_table_mut(&dep_table)
.ok()
.and_then(TomlItem::as_table_like_mut)
{
table.sort_values();
}
}
if options.gctx.locked() {
let new_raw_manifest = manifest.to_string();
if original_raw_manifest != new_raw_manifest {
anyhow::bail!(
"the manifest file {} needs to be updated but --locked was passed to prevent this",
manifest.path.display()
);
}
}
if options.dry_run {
options.gctx.shell().warn("aborting add due to dry run")?;
} else {
manifest.write()?;
}
Ok(())
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DepOp {
pub crate_spec: Option<String>,
pub rename: Option<String>,
pub features: Option<IndexSet<String>>,
pub default_features: Option<bool>,
pub optional: Option<bool>,
pub public: Option<bool>,
pub registry: Option<String>,
pub path: Option<String>,
pub git: Option<String>,
pub branch: Option<String>,
pub rev: Option<String>,
pub tag: Option<String>,
}
fn resolve_dependency(
manifest: &LocalManifest,
arg: &DepOp,
ws: &Workspace<'_>,
spec: &Package,
section: &DepTable,
honor_rust_version: Option<bool>,
gctx: &GlobalContext,
registry: &mut PackageRegistry<'_>,
) -> CargoResult<DependencyUI> {
let crate_spec = arg
.crate_spec
.as_deref()
.map(CrateSpec::resolve)
.transpose()?;
let mut selected_dep = if let Some(url) = &arg.git {
let mut src = GitSource::new(url);
if let Some(branch) = &arg.branch {
src = src.set_branch(branch);
}
if let Some(tag) = &arg.tag {
src = src.set_tag(tag);
}
if let Some(rev) = &arg.rev {
src = src.set_rev(rev);
}
let selected = if let Some(crate_spec) = &crate_spec {
if let Some(v) = crate_spec.version_req() {
anyhow::bail!("cannot specify a git URL (`{url}`) with a version (`{v}`).");
}
let dependency = crate_spec.to_dependency()?.set_source(src);
let selected = select_package(&dependency, gctx, registry)?;
if dependency.name != selected.name {
gctx.shell().warn(format!(
"translating `{}` to `{}`",
dependency.name, selected.name,
))?;
}
selected
} else {
let mut source = crate::sources::GitSource::new(src.source_id()?, gctx)?;
let packages = source.read_packages()?;
let package = infer_package_for_git_source(packages, &src)?;
Dependency::from(package.summary())
};
selected
} else if let Some(raw_path) = &arg.path {
let path = paths::normalize_path(&std::env::current_dir()?.join(raw_path));
let src = PathSource::new(&path);
let selected = if let Some(crate_spec) = &crate_spec {
if let Some(v) = crate_spec.version_req() {
anyhow::bail!("cannot specify a path (`{raw_path}`) with a version (`{v}`).");
}
let dependency = crate_spec.to_dependency()?.set_source(src);
let selected = select_package(&dependency, gctx, registry)?;
if dependency.name != selected.name {
gctx.shell().warn(format!(
"translating `{}` to `{}`",
dependency.name, selected.name,
))?;
}
selected
} else {
let mut source = crate::sources::PathSource::new(&path, src.source_id()?, gctx);
let package = source.root_package()?;
Dependency::from(package.summary())
};
selected
} else if let Some(crate_spec) = &crate_spec {
crate_spec.to_dependency()?
} else {
anyhow::bail!("dependency name is required");
};
selected_dep = populate_dependency(selected_dep, arg);
let old_dep = get_existing_dependency(manifest, selected_dep.toml_key(), section)?;
let mut dependency = if let Some(mut old_dep) = old_dep.clone() {
if old_dep.name != selected_dep.name {
if selected_dep.optional.is_none() {
selected_dep.optional = old_dep.optional;
}
selected_dep
} else {
if selected_dep.source().is_some() {
old_dep.source = selected_dep.source;
}
populate_dependency(old_dep, arg)
}
} else {
selected_dep
};
if dependency.source().is_none() {
if let Some(_dep) = find_workspace_dep(dependency.toml_key(), ws.root_manifest()).ok() {
dependency = dependency.set_source(WorkspaceSource::new());
} else if let Some(package) = ws.members().find(|p| p.name().as_str() == dependency.name) {
let mut src = PathSource::new(package.root());
if section.kind() != DepKind::Development {
let op = "";
let v = format!("{op}{version}", version = package.version());
src = src.set_version(v);
}
dependency = dependency.set_source(src);
} else {
let latest =
get_latest_dependency(spec, &dependency, honor_rust_version, gctx, registry)?;
if dependency.name != latest.name {
gctx.shell().warn(format!(
"translating `{}` to `{}`",
dependency.name, latest.name,
))?;
dependency.name = latest.name; }
dependency = dependency.set_source(latest.source.expect("latest always has a source"));
}
}
if let Some(Source::Workspace(_)) = dependency.source() {
check_invalid_ws_keys(dependency.toml_key(), arg)?;
}
let version_required = dependency.source().and_then(|s| s.as_registry()).is_some();
let version_optional_in_section = section.kind() == DepKind::Development;
let preserve_existing_version = old_dep
.as_ref()
.map(|d| d.version().is_some())
.unwrap_or(false);
if !version_required && !preserve_existing_version && version_optional_in_section {
dependency = dependency.clear_version();
}
let query = dependency.query(gctx)?;
let query = match query {
MaybeWorkspace::Workspace(_workspace) => {
let dep = find_workspace_dep(dependency.toml_key(), ws.root_manifest())?;
if let Some(features) = dep.features.clone() {
dependency = dependency.set_inherited_features(features);
}
let query = dep.query(gctx)?;
match query {
MaybeWorkspace::Workspace(_) => {
unreachable!("This should have been caught when parsing a workspace root")
}
MaybeWorkspace::Other(query) => query,
}
}
MaybeWorkspace::Other(query) => query,
};
let dependency = populate_available_features(dependency, &query, registry)?;
Ok(dependency)
}
fn check_invalid_ws_keys(toml_key: &str, arg: &DepOp) -> CargoResult<()> {
fn err_msg(toml_key: &str, flag: &str, field: &str) -> String {
format!(
"cannot override workspace dependency with `{flag}`, \
either change `workspace.dependencies.{toml_key}.{field}` \
or define the dependency exclusively in the package's manifest"
)
}
if arg.default_features.is_some() {
anyhow::bail!(
"{}",
err_msg(toml_key, "--default-features", "default-features")
)
}
if arg.registry.is_some() {
anyhow::bail!("{}", err_msg(toml_key, "--registry", "registry"))
}
if arg.rename.is_some() {
anyhow::bail!("{}", err_msg(toml_key, "--rename", "package"))
}
Ok(())
}
fn check_rust_version_for_optional_dependency(
rust_version: Option<&RustVersion>,
) -> CargoResult<bool> {
match rust_version {
Some(version) => {
let syntax_support_version = RustVersion::from_str("1.60.0")?;
Ok(&syntax_support_version <= version)
}
None => Ok(true),
}
}
fn get_existing_dependency(
manifest: &LocalManifest,
dep_key: &str,
section: &DepTable,
) -> CargoResult<Option<Dependency>> {
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
enum Key {
Error,
Dev,
Build,
Normal,
Existing,
}
let mut possible: Vec<_> = manifest
.get_dependency_versions(dep_key)
.map(|(path, dep)| {
let key = if path == *section {
(Key::Existing, true)
} else if dep.is_err() {
(Key::Error, path.target().is_some())
} else {
let key = match path.kind() {
DepKind::Normal => Key::Normal,
DepKind::Build => Key::Build,
DepKind::Development => Key::Dev,
};
(key, path.target().is_some())
};
(key, dep)
})
.collect();
possible.sort_by_key(|(key, _)| *key);
let Some((key, dep)) = possible.pop() else {
return Ok(None);
};
let mut dep = dep?;
if key.0 != Key::Existing {
let unrelated = dep;
dep = Dependency::new(&unrelated.name);
dep.source = unrelated.source.clone();
dep.registry = unrelated.registry.clone();
let version_required = unrelated.source().and_then(|s| s.as_registry()).is_some();
let version_optional_in_section = section.kind() == DepKind::Development;
if !version_required && version_optional_in_section {
dep = dep.clear_version();
}
}
Ok(Some(dep))
}
fn get_latest_dependency(
spec: &Package,
dependency: &Dependency,
honor_rust_version: Option<bool>,
gctx: &GlobalContext,
registry: &mut PackageRegistry<'_>,
) -> CargoResult<Dependency> {
let query = dependency.query(gctx)?;
match query {
MaybeWorkspace::Workspace(_) => {
unreachable!("registry dependencies required, found a workspace dependency");
}
MaybeWorkspace::Other(query) => {
let possibilities = loop {
match registry.query_vec(&query, QueryKind::Normalized) {
std::task::Poll::Ready(res) => {
break res?;
}
std::task::Poll::Pending => registry.block_until_ready()?,
}
};
let mut possibilities: Vec<_> = possibilities
.into_iter()
.map(|s| s.into_summary())
.collect();
possibilities.sort_by_key(|s| {
let stable = s.version().pre.is_empty();
(stable, s.version().clone())
});
let mut latest = possibilities.last().ok_or_else(|| {
anyhow::format_err!(
"the crate `{dependency}` could not be found in registry index."
)
})?;
if honor_rust_version.unwrap_or(true) {
let (req_msrv, is_msrv) = spec
.rust_version()
.cloned()
.map(|msrv| CargoResult::Ok((msrv.clone().into_partial(), true)))
.unwrap_or_else(|| {
let rustc = gctx.load_global_rustc(None)?;
let rustc_version = rustc.version.clone().into();
Ok((rustc_version, false))
})?;
let msrvs = possibilities
.iter()
.map(|s| (s, s.rust_version()))
.collect::<Vec<_>>();
let latest_msrv = latest_compatible(&msrvs, &req_msrv).ok_or_else(|| {
let name = spec.name();
let dep_name = &dependency.name;
let latest_version = latest.version();
let latest_msrv = latest
.rust_version()
.expect("as `None` are compatible, we can't be here");
if is_msrv {
anyhow::format_err!(
"\
no version of crate `{dep_name}` can maintain {name}'s rust-version of {req_msrv}
help: pass `--ignore-rust-version` to select {dep_name}@{latest_version} which requires rustc {latest_msrv}"
)
} else {
anyhow::format_err!(
"\
no version of crate `{dep_name}` is compatible with rustc {req_msrv}
help: pass `--ignore-rust-version` to select {dep_name}@{latest_version} which requires rustc {latest_msrv}"
)
}
})?;
if latest_msrv.version() < latest.version() {
let latest_version = latest.version();
let latest_rust_version = latest.rust_version().unwrap();
let name = spec.name();
if is_msrv {
gctx.shell().warn(format_args!(
"\
ignoring {dependency}@{latest_version} (which requires rustc {latest_rust_version}) to maintain {name}'s rust-version of {req_msrv}",
))?;
} else {
gctx.shell().warn(format_args!(
"\
ignoring {dependency}@{latest_version} (which requires rustc {latest_rust_version}) as it is incompatible with rustc {req_msrv}",
))?;
}
latest = latest_msrv;
}
}
let mut dep = Dependency::from(latest);
if let Some(reg_name) = dependency.registry.as_deref() {
dep = dep.set_registry(reg_name);
}
Ok(dep)
}
}
}
fn latest_compatible<'s>(
msrvs: &[(&'s Summary, Option<&RustVersion>)],
pkg_msrv: &PartialVersion,
) -> Option<&'s Summary> {
msrvs
.iter()
.filter(|(_, dep_msrv)| {
dep_msrv
.as_ref()
.map(|dep_msrv| dep_msrv.is_compatible_with(pkg_msrv))
.unwrap_or(true)
})
.map(|(s, _)| s)
.last()
.copied()
}
fn select_package(
dependency: &Dependency,
gctx: &GlobalContext,
registry: &mut PackageRegistry<'_>,
) -> CargoResult<Dependency> {
let query = dependency.query(gctx)?;
match query {
MaybeWorkspace::Workspace(_) => {
unreachable!("path or git dependency expected, found workspace dependency");
}
MaybeWorkspace::Other(query) => {
let possibilities = loop {
match registry.query_vec(&query, QueryKind::Normalized) {
std::task::Poll::Ready(res) => {
break res?;
}
std::task::Poll::Pending => registry.block_until_ready()?,
}
};
let possibilities: Vec<_> = possibilities
.into_iter()
.map(|s| s.into_summary())
.collect();
match possibilities.len() {
0 => {
let source = dependency
.source()
.expect("source should be resolved before here");
anyhow::bail!("the crate `{dependency}` could not be found at `{source}`")
}
1 => {
let mut dep = Dependency::from(&possibilities[0]);
if let Some(reg_name) = dependency.registry.as_deref() {
dep = dep.set_registry(reg_name);
}
Ok(dep)
}
_ => {
let source = dependency
.source()
.expect("source should be resolved before here");
anyhow::bail!(
"unexpectedly found multiple copies of crate `{dependency}` at `{source}`"
)
}
}
}
}
}
fn infer_package_for_git_source(
mut packages: Vec<Package>,
src: &dyn std::fmt::Display,
) -> CargoResult<Package> {
let package = match packages.len() {
0 => unreachable!(
"this function should only be called with packages from `GitSource::read_packages` \
and that call should error for us when there are no packages"
),
1 => packages.pop().expect("match ensured element is present"),
_ => {
let mut names: Vec<_> = packages
.iter()
.map(|p| p.name().as_str().to_owned())
.collect();
names.sort_unstable();
anyhow::bail!(
"multiple packages found at `{src}`:\n {}\nTo disambiguate, run `cargo add --git {src} <package>`",
names
.iter()
.map(|s| s.to_string())
.coalesce(|x, y| if x.len() + y.len() < 78 {
Ok(format!("{x}, {y}"))
} else {
Err((x, y))
})
.into_iter()
.format("\n "),
);
}
};
Ok(package)
}
fn populate_dependency(mut dependency: Dependency, arg: &DepOp) -> Dependency {
if let Some(registry) = &arg.registry {
if registry.is_empty() {
dependency.registry = None;
} else {
dependency.registry = Some(registry.to_owned());
}
}
if let Some(value) = arg.optional {
if value {
dependency.optional = Some(true);
} else {
dependency.optional = None;
}
}
if let Some(value) = arg.public {
if value {
dependency.public = Some(true);
} else {
dependency.public = None;
}
}
if let Some(value) = arg.default_features {
if value {
dependency.default_features = None;
} else {
dependency.default_features = Some(false);
}
}
if let Some(value) = arg.features.as_ref() {
dependency = dependency.extend_features(value.iter().cloned());
}
if let Some(rename) = &arg.rename {
dependency = dependency.set_rename(rename);
}
dependency
}
pub struct DependencyUI {
dep: Dependency,
available_version: Option<semver::Version>,
available_features: BTreeMap<String, Vec<String>>,
}
impl DependencyUI {
fn new(dep: Dependency) -> Self {
Self {
dep,
available_version: None,
available_features: Default::default(),
}
}
fn apply_summary(&mut self, summary: &Summary) {
self.available_version = Some(summary.version().clone());
self.available_features = summary
.features()
.iter()
.map(|(k, v)| {
(
k.as_str().to_owned(),
v.iter()
.filter_map(|v| match v {
FeatureValue::Feature(f) => Some(f.as_str().to_owned()),
FeatureValue::Dep { .. } | FeatureValue::DepFeature { .. } => None,
})
.collect::<Vec<_>>(),
)
})
.collect();
}
fn features(&self) -> (IndexSet<&str>, IndexSet<&str>) {
let mut activated: IndexSet<_> =
self.features.iter().flatten().map(|s| s.as_str()).collect();
if self.default_features().unwrap_or(true) {
activated.insert("default");
}
activated.extend(self.inherited_features.iter().flatten().map(|s| s.as_str()));
let mut walk: VecDeque<_> = activated.iter().cloned().collect();
while let Some(next) = walk.pop_front() {
walk.extend(
self.available_features
.get(next)
.into_iter()
.flatten()
.map(|s| s.as_str())
.filter(|s| !activated.contains(s)),
);
activated.extend(
self.available_features
.get(next)
.into_iter()
.flatten()
.map(|s| s.as_str()),
);
}
activated.swap_remove("default");
activated.sort();
let mut deactivated = self
.available_features
.keys()
.filter(|f| !activated.contains(f.as_str()) && *f != "default")
.map(|f| f.as_str())
.collect::<IndexSet<_>>();
deactivated.sort();
(activated, deactivated)
}
}
impl<'s> From<&'s Summary> for DependencyUI {
fn from(other: &'s Summary) -> Self {
let dep = Dependency::from(other);
let mut dep = Self::new(dep);
dep.apply_summary(other);
dep
}
}
impl std::fmt::Display for DependencyUI {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.dep.fmt(f)
}
}
impl std::ops::Deref for DependencyUI {
type Target = Dependency;
fn deref(&self) -> &Self::Target {
&self.dep
}
}
fn populate_available_features(
dependency: Dependency,
query: &crate::core::dependency::Dependency,
registry: &mut PackageRegistry<'_>,
) -> CargoResult<DependencyUI> {
let mut dependency = DependencyUI::new(dependency);
if !dependency.available_features.is_empty() {
return Ok(dependency);
}
let possibilities = loop {
match registry.query_vec(&query, QueryKind::Normalized) {
std::task::Poll::Ready(res) => {
break res?;
}
std::task::Poll::Pending => registry.block_until_ready()?,
}
};
let lowest_common_denominator = possibilities
.iter()
.map(|s| s.as_summary())
.min_by_key(|s| {
let is_pre = !s.version().pre.is_empty();
(is_pre, s.version())
})
.ok_or_else(|| {
anyhow::format_err!("the crate `{dependency}` could not be found in registry index.")
})?;
dependency.apply_summary(&lowest_common_denominator);
Ok(dependency)
}
fn print_action_msg(shell: &mut Shell, dep: &DependencyUI, section: &[String]) -> CargoResult<()> {
if matches!(shell.verbosity(), crate::core::shell::Verbosity::Quiet) {
return Ok(());
}
let mut message = String::new();
write!(message, "{}", dep.name)?;
match dep.source() {
Some(Source::Registry(src)) => {
if src.version.chars().next().unwrap_or('0').is_ascii_digit() {
write!(message, " v{}", src.version)?;
} else {
write!(message, " {}", src.version)?;
}
}
Some(Source::Path(_)) => {
write!(message, " (local)")?;
}
Some(Source::Git(_)) => {
write!(message, " (git)")?;
}
Some(Source::Workspace(_)) => {
write!(message, " (workspace)")?;
}
None => {}
}
write!(message, " to")?;
if dep.optional().unwrap_or(false) {
write!(message, " optional")?;
}
if dep.public().unwrap_or(false) {
write!(message, " public")?;
}
let section = if section.len() == 1 {
section[0].clone()
} else {
format!("{} for target `{}`", §ion[2], §ion[1])
};
write!(message, " {section}")?;
shell.status("Adding", message)
}
fn print_dep_table_msg(shell: &mut Shell, dep: &DependencyUI) -> CargoResult<()> {
if matches!(shell.verbosity(), crate::core::shell::Verbosity::Quiet) {
return Ok(());
}
let stderr = shell.err();
let good = style::GOOD;
let error = style::ERROR;
let (activated, deactivated) = dep.features();
if !activated.is_empty() || !deactivated.is_empty() {
let prefix = format!("{:>13}", " ");
let suffix = format_features_version_suffix(&dep);
writeln!(stderr, "{prefix}Features{suffix}:")?;
const MAX_FEATURE_PRINTS: usize = 30;
let total_activated = activated.len();
let total_deactivated = deactivated.len();
if total_activated <= MAX_FEATURE_PRINTS {
for feat in activated {
writeln!(stderr, "{prefix}{good}+{good:#} {feat}")?;
}
} else {
writeln!(stderr, "{prefix}{total_activated} activated features")?;
}
if total_activated + total_deactivated <= MAX_FEATURE_PRINTS {
for feat in deactivated {
writeln!(stderr, "{prefix}{error}-{error:#} {feat}")?;
}
} else {
writeln!(stderr, "{prefix}{total_deactivated} deactivated features")?;
}
}
Ok(())
}
fn format_features_version_suffix(dep: &DependencyUI) -> String {
if let Some(version) = &dep.available_version {
let mut version = version.clone();
version.build = Default::default();
let version = version.to_string();
let version_req = dep
.version()
.and_then(|v| semver::VersionReq::parse(v).ok())
.and_then(|v| precise_version(&v));
if version_req.as_deref() != Some(version.as_str()) {
format!(" as of v{version}")
} else {
"".to_owned()
}
} else {
"".to_owned()
}
}
fn find_workspace_dep(toml_key: &str, root_manifest: &Path) -> CargoResult<Dependency> {
let manifest = LocalManifest::try_new(root_manifest)?;
let manifest = manifest
.data
.as_item()
.as_table_like()
.context("could not make `manifest.data` into a table")?;
let workspace = manifest
.get("workspace")
.context("could not find `workspace`")?
.as_table_like()
.context("could not make `manifest.data.workspace` into a table")?;
let dependencies = workspace
.get("dependencies")
.context("could not find `dependencies` table in `workspace`")?
.as_table_like()
.context("could not make `dependencies` into a table")?;
let dep_item = dependencies
.get(toml_key)
.with_context(|| format!("could not find {toml_key} in `workspace.dependencies`"))?;
Dependency::from_toml(root_manifest.parent().unwrap(), toml_key, dep_item)
}
fn precise_version(version_req: &semver::VersionReq) -> Option<String> {
version_req
.comparators
.iter()
.filter(|c| {
matches!(
c.op,
semver::Op::Exact
| semver::Op::GreaterEq
| semver::Op::LessEq
| semver::Op::Tilde
| semver::Op::Caret
| semver::Op::Wildcard
)
})
.filter_map(|c| {
c.minor.and_then(|minor| {
c.patch.map(|patch| semver::Version {
major: c.major,
minor,
patch,
pre: c.pre.clone(),
build: Default::default(),
})
})
})
.max()
.map(|v| v.to_string())
}