use crate::{
config::Config,
dep::{DepKind, RootCrate},
error::{Error, Result},
graph::DepGraph,
registries::Registries,
util,
};
use std::{collections::HashMap, path::PathBuf};
use toml::{
value::{Array, Table},
Value,
};
pub type DepKindsMap = HashMap<String, Vec<DepKind>>;
pub type RootDepsMap = HashMap<String, DepKindsMap>;
#[derive(Debug)]
pub struct Project {
cfg: Config,
}
impl Project {
pub fn with_config(cfg: Config) -> Result<Self> {
Ok(Self { cfg })
}
pub fn graph(self, manifest_path: PathBuf, lock_path: PathBuf) -> Result<DepGraph> {
let (root_crates, root_deps_map) = self.parse_root_deps(&manifest_path)?;
let mut dg = self.parse_lock_file(lock_path, &root_crates, root_deps_map)?;
dg.topological_sort()?;
dg.set_resolved_kind()?;
if !self.cfg.include_versions {
dg.show_version_on_duplicates();
}
Ok(dg)
}
pub fn parse_root_deps(
&self,
manifest_path: &PathBuf,
) -> Result<(Vec<RootCrate>, RootDepsMap)> {
let manifest_toml = util::toml_from_file(manifest_path)?;
let root_crates_tomls = {
if let Some(table) = manifest_toml.get("package") {
if let Some(table) = table.as_table() {
if let (Some(&Value::String(ref name)), Some(&Value::String(ref ver))) =
(table.get("name"), table.get("version"))
{
let (name, ver) = (name.to_string(), ver.to_string());
vec![(RootCrate { name, ver }, manifest_toml)]
} else {
return Err(Error::Toml(
"No 'name' or 'version' fields in [package] table".into(),
));
}
} else {
return Err(Error::Toml("Could not parse [package] as a table".into()));
}
} else {
return Err(Error::Toml("No [package] table found".into()));
}
};
let mut root_deps_map = HashMap::new();
for (root_crate, manifest_toml) in root_crates_tomls.iter() {
let root_name = &root_crate.name;
let mut dep_kinds_map = HashMap::new();
if let Some(table) = manifest_toml.get("dependencies") {
if let Some(table) = table.as_table() {
for (mut dep_name, dep_table) in table.iter() {
if let Some(&Value::String(ref name)) = dep_table.get("package") {
dep_name = name;
}
if let Some(&Value::Boolean(true)) = dep_table.get("optional") {
if self.cfg.optional_deps {
add_kind(
&mut dep_kinds_map,
dep_name.to_string(),
DepKind::Optional,
);
}
} else if self.cfg.regular_deps {
add_kind(&mut dep_kinds_map, dep_name.to_string(), DepKind::Regular);
}
}
}
}
if self.cfg.build_deps {
if let Some(table) = manifest_toml.get("build-dependencies") {
if let Some(table) = table.as_table() {
for (mut dep_name, dep_table) in table.iter() {
if let Some(&Value::String(ref name)) = dep_table.get("package") {
dep_name = name;
}
add_kind(&mut dep_kinds_map, dep_name.to_string(), DepKind::Build);
}
}
}
}
if self.cfg.dev_deps {
if let Some(table) = manifest_toml.get("dev-dependencies") {
if let Some(table) = table.as_table() {
for (mut dep_name, dep_table) in table.iter() {
if let Some(&Value::String(ref name)) = dep_table.get("package") {
dep_name = name;
}
add_kind(&mut dep_kinds_map, dep_name.to_string(), DepKind::Dev);
}
}
}
}
root_deps_map.insert(root_name.to_string(), dep_kinds_map);
}
Ok((
root_crates_tomls
.iter()
.map(|(root_crate, _)| root_crate.clone())
.collect(),
root_deps_map,
))
}
fn parse_lock_file(
&self,
lock_path: PathBuf,
root_crates: &[RootCrate],
root_deps_map: RootDepsMap,
) -> Result<DepGraph> {
let lock_toml = util::toml_from_file(lock_path)?;
let registries = Registries::new();
let mut dg = DepGraph::new(self.cfg.clone());
dg.root_deps_map = root_deps_map;
if let Some(&Value::Array(ref packages)) = lock_toml.get("package") {
for pkg in packages {
parse_package(
&mut dg,
®istries,
pkg.as_table().unwrap(),
packages,
root_crates,
)?;
}
} else if lock_toml.get("root").is_some() {
return Err(Error::Toml(
"Deprecated [root] table found in lock file. Please build the lock file with a \
newer version of Rust."
.into(),
));
} else {
return Err(Error::Toml("Missing [package] table in lock file".into()));
}
for &RootCrate { ref name, ref ver } in root_crates.iter() {
if dep_is_excluded(name, self.cfg.clone()) {
continue;
}
if dg.find(name, ver).is_none() {
return Err(Error::Toml(format!(
"Missing 'name': {} and 'version': {} in lock file",
name, ver
)));
}
}
Ok(dg)
}
}
fn add_kind(dep_kinds_map: &mut DepKindsMap, key: String, kind: DepKind) {
let kinds = dep_kinds_map.entry(key).or_insert_with(Vec::new);
kinds.push(kind);
}
#[allow(clippy::ptr_arg)]
fn parse_package(
dg: &mut DepGraph,
registries: &Registries,
pkg: &Table,
packages: &Array,
root_crates: &[RootCrate],
) -> Result<()> {
let name = pkg
.get("name")
.expect("No 'name' field in Cargo.lock [package] table")
.as_str()
.expect("'name' field of [package] table in Cargo.lock was not a valid string")
.to_owned();
let ver = pkg
.get("version")
.expect("No 'version' field in Cargo.lock [package] table")
.as_str()
.expect("'version' field of [package] table in Cargo.lock was not a valid string")
.to_owned();
let source =
pkg.get("source")
.map(|source| {
registries.from_source(source.as_str().expect(
"'source' field of [package] table in Cargo.lock was not a valid string",
))
})
.flatten();
if dep_is_excluded(&name, dg.cfg.clone()) {
return Ok(());
}
let id = dg.find_or_add(&name, &ver, &source);
if dg.root_deps_map.contains_key(&name) {
if !root_crates
.iter()
.any(|root_crate| root_crate.name == name && root_crate.ver == ver)
{
return Err(Error::Generic(format!(
"Version {} of root crate '{}' in Cargo.lock does not match version specified in \
Cargo.toml",
ver, name
)));
}
}
if let Some(&Value::Array(ref deps)) = pkg.get("dependencies") {
for dep in deps {
let dep_vec = dep.as_str().unwrap_or("").split(' ').collect::<Vec<_>>();
let dep_name = dep_vec[0].to_string();
let dep_ver = if dep_vec.len() > 1 {
dep_vec[1]
} else {
packages
.iter()
.find(|pkg| pkg.get("name").unwrap().as_str().unwrap() == dep_name)
.unwrap()
.get("version")
.unwrap()
.as_str()
.unwrap()
};
let dep_registry = packages
.iter()
.find(|pkg| pkg.get("name").unwrap().as_str().unwrap() == dep_name)
.unwrap()
.get("source")
.map(|source| registries.from_source(source.as_str().unwrap()))
.flatten();
if dep_is_excluded(&dep_name, dg.cfg.clone()) {
continue;
}
if let Some(dep_kinds_map) = dg.root_deps_map.get(&name) {
if dep_kinds_map.get(&dep_name).is_none() {
continue;
}
}
dg.add_child(id, &dep_name, dep_ver, &dep_registry);
}
}
Ok(())
}
pub fn dep_is_excluded(name: &str, cfg: Config) -> bool {
let name = &name.into();
let filter = cfg.filter;
if let Some(ref filter_deps) = filter {
if !filter_deps.contains(name) {
return true;
}
}
let exclude = cfg.exclude;
if let Some(ref exclude_deps) = exclude {
if exclude_deps.contains(name) {
return true;
}
}
false
}