use crate::cache;
use crate::size;
use crate::types::{Config, CrateUnit, Edition, Error, Result, UnknownEdition};
use cargo_metadata::MetadataCommand;
use crossbeam_channel::Sender;
use std::collections::{HashMap, HashSet};
use std::io::Write;
use std::path::PathBuf;
pub(crate) fn run(cfg: &Config, tx: Sender<CrateUnit>) -> Result<Option<cache::Cache>> {
let mut cmd = MetadataCommand::new();
cmd.no_deps();
if let Some(p) = &cfg.manifest_path {
cmd.manifest_path(p);
}
let metadata = cmd.exec()?;
let mut cache_opt = cfg
.experimental_cache
.then(|| cache::Cache::load(metadata.workspace_root.as_std_path()));
let workspace_members: HashSet<&cargo_metadata::PackageId> =
metadata.workspace_members.iter().collect();
let at_root = at_workspace_root(cfg, metadata.workspace_root.as_std_path());
let selected: HashSet<&cargo_metadata::PackageId> = if cfg.all {
workspace_members.clone()
} else if !cfg.packages.is_empty() {
let member_names: HashSet<&str> = metadata
.packages
.iter()
.filter(|p| workspace_members.contains(&p.id))
.map(|p| p.name.as_str())
.collect();
let unknown: Vec<String> = cfg
.packages
.iter()
.filter(|n| !member_names.contains(n.as_str()))
.cloned()
.collect();
if !unknown.is_empty() {
return Err(Error::UnknownPackages(unknown));
}
let names: HashSet<&str> = cfg.packages.iter().map(String::as_str).collect();
metadata
.packages
.iter()
.filter(|p| workspace_members.contains(&p.id) && names.contains(p.name.as_str()))
.map(|p| &p.id)
.collect()
} else if at_root {
workspace_members.clone()
} else if let Some(root) = metadata.root_package() {
std::iter::once(&root.id).collect()
} else {
metadata
.workspace_default_packages()
.into_iter()
.map(|p| &p.id)
.collect()
};
let mut claimed: HashMap<PathBuf, String> = HashMap::new();
for pkg in &metadata.packages {
if !selected.contains(&pkg.id) {
continue;
}
let edition: Edition =
pkg.edition
.try_into()
.map_err(|UnknownEdition(year)| Error::UnsupportedEdition {
edition: year,
package: pkg.name.to_string(),
})?;
let manifest_dir: PathBuf = pkg
.manifest_path
.parent()
.map(|p| p.as_std_path().to_path_buf())
.ok_or_else(|| {
Error::Io(std::io::Error::other(format!(
"manifest_path has no parent: {}",
pkg.manifest_path
)))
})?;
let mut entry_points: Vec<PathBuf> = Vec::new();
for tgt in &pkg.targets {
let raw = tgt.src_path.as_std_path().to_path_buf();
let canon = raw.canonicalize().unwrap_or(raw);
match claimed.entry(canon.clone()) {
std::collections::hash_map::Entry::Vacant(v) => {
v.insert(pkg.name.to_string());
entry_points.push(canon);
}
std::collections::hash_map::Entry::Occupied(o) => {
if cfg.warnings {
let _ = writeln!(
std::io::stderr(),
"warning: crate `{}` claims `{}`, already owned by `{}`; using `{}`'s edition",
pkg.name,
canon.display(),
o.get(),
o.get(),
);
}
}
}
}
if entry_points.is_empty() {
continue;
}
let size_bytes = if let Some(c) = cache_opt.as_mut() {
let (fp, bytes) = cache::build(&manifest_dir, size::HUGE_CUTOFF_BYTES);
if c.matches(&manifest_dir, &fp) {
continue;
}
c.stage(manifest_dir.clone(), fp);
bytes
} else {
size::estimate(&manifest_dir)
};
let unit = CrateUnit {
edition,
manifest_dir,
files: entry_points,
size_bytes,
};
if tx.send(unit).is_err() {
return Err(Error::SendClosed);
}
}
Ok(cache_opt)
}
fn at_workspace_root(cfg: &Config, ws_root: &std::path::Path) -> bool {
let ws_manifest = match ws_root.join("Cargo.toml").canonicalize() {
Ok(p) => p,
Err(_) => return false,
};
let effective = match &cfg.manifest_path {
Some(p) => p.canonicalize().ok(),
None => std::env::current_dir()
.ok()
.and_then(|cwd| find_manifest_upward(&cwd)),
};
effective.map(|m| m == ws_manifest).unwrap_or(false)
}
fn find_manifest_upward(start: &std::path::Path) -> Option<PathBuf> {
let mut p = start.canonicalize().ok()?;
loop {
let cand = p.join("Cargo.toml");
if cand.is_file() {
return cand.canonicalize().ok();
}
if !p.pop() {
return None;
}
}
}