use cargo_metadata::{DependencyKind, Metadata, Node, PackageId, Version};
use core::fmt;
use futures_util::future::join_all;
use miette::IntoDiagnostic;
use serde::Serialize;
use serde_json::json;
use std::cmp::Reverse;
use std::collections::BinaryHeap;
use std::mem;
use std::sync::Arc;
use tracing::{error, trace, trace_span};
use crate::errors::{RegenerateExemptionsError, SuggestError};
use crate::format::{
self, AuditKind, AuditsFile, CriteriaName, CriteriaStr, Delta, DependencyCriteria, DiffStat,
ExemptedDependency, ImportName, PackageName, PackageStr, PolicyEntry, RemoteImport,
SAFE_TO_DEPLOY, SAFE_TO_RUN,
};
use crate::format::{FastMap, FastSet, SortedMap, SortedSet};
use crate::network::Network;
use crate::out::{progress_bar, IncProgressOnDrop, Out};
use crate::{
AuditEntry, Cache, Config, CriteriaEntry, DumpGraphArgs, GraphFilter, GraphFilterProperty,
GraphFilterQuery, PackageExt, Store,
};
#[derive(Debug, Clone)]
pub struct ResolveReport<'a> {
pub graph: DepGraph<'a>,
pub criteria_mapper: CriteriaMapper,
pub results: Vec<ResolveResult<'a>>,
pub conclusion: Conclusion,
}
#[derive(Debug, Clone)]
pub enum Conclusion {
Success(Success),
FailForViolationConflict(FailForViolationConflict),
FailForVet(FailForVet),
}
#[derive(Debug, Clone)]
pub struct Success {
pub vetted_with_exemptions: Vec<PackageIdx>,
pub vetted_partially: Vec<PackageIdx>,
pub vetted_fully: Vec<PackageIdx>,
pub needed_fresh_imports: SortedSet<PackageIdx>,
}
#[derive(Debug, Clone)]
pub struct FailForViolationConflict {
pub violations: SortedMap<PackageIdx, Vec<ViolationConflict>>,
}
#[derive(Debug, Clone)]
pub struct FailForVet {
pub failures: SortedMap<PackageIdx, AuditFailure>,
pub suggest: Option<Suggest>,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, Serialize)]
pub enum ViolationConflict {
UnauditedConflict {
violation_source: CriteriaNamespace,
violation: AuditEntry,
exemptions: ExemptedDependency,
},
AuditConflict {
violation_source: CriteriaNamespace,
violation: AuditEntry,
audit_source: CriteriaNamespace,
audit: AuditEntry,
},
}
#[derive(Debug, Clone, Default)]
pub struct Suggest {
pub suggestions: Vec<SuggestItem>,
pub suggestions_by_criteria: SortedMap<CriteriaName, Vec<SuggestItem>>,
pub total_lines: u64,
}
#[derive(Debug, Clone)]
pub struct SuggestItem {
pub package: PackageIdx,
pub suggested_criteria: CriteriaFailureSet,
pub suggested_diff: DiffRecommendation,
pub notable_parents: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct DiffRecommendation {
pub from: Option<Version>,
pub to: Version,
pub diffstat: DiffStat,
}
#[derive(Clone)]
pub struct CriteriaSet(u64);
const MAX_CRITERIA: usize = u64::BITS as usize;
#[derive(Debug, Clone)]
pub struct CriteriaFailureSet {
pub confident: CriteriaSet,
pub all: CriteriaSet,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
pub enum CriteriaNamespace {
Local,
Foreign(ImportName),
}
#[derive(Debug, Clone)]
pub struct CriteriaInfo {
pub namespace: CriteriaNamespace,
pub namespaced_name: String,
raw_name: CriteriaName,
implies: Vec<CriteriaName>,
}
#[derive(Debug, Clone)]
pub struct CriteriaMapper {
pub list: Vec<CriteriaInfo>,
pub index: FastMap<CriteriaNamespace, FastMap<CriteriaName, usize>>,
pub implied_criteria: Vec<CriteriaSet>,
}
pub type PackageIdx = usize;
#[derive(Debug, Clone, Serialize)]
pub struct PackageNode<'a> {
#[serde(skip_serializing_if = "pkgid_unstable")]
pub package_id: &'a PackageId,
pub name: PackageStr<'a>,
pub version: &'a Version,
pub normal_deps: Vec<PackageIdx>,
pub build_deps: Vec<PackageIdx>,
pub dev_deps: Vec<PackageIdx>,
pub normal_and_build_deps: Vec<PackageIdx>,
pub all_deps: Vec<PackageIdx>,
pub reverse_deps: SortedSet<PackageIdx>,
pub is_workspace_member: bool,
pub is_third_party: bool,
pub is_root: bool,
pub is_dev_only: bool,
}
fn pkgid_unstable(pkgid: &PackageId) -> bool {
pkgid.repr.contains("(path+file:/")
}
#[derive(Debug, Clone)]
pub struct DepGraph<'a> {
pub nodes: Vec<PackageNode<'a>>,
pub interner_by_pkgid: SortedMap<&'a PackageId, PackageIdx>,
pub topo_index: Vec<PackageIdx>,
}
#[derive(Debug, Clone)]
pub struct ResolveResult<'a> {
pub validated_criteria: CriteriaSet,
pub search_results: Vec<SearchResult<'a>>,
pub directly_exempted: bool,
}
pub type PolicyFailures = SortedMap<PackageIdx, CriteriaSet>;
pub type RootFailures = Vec<(PackageIdx, PolicyFailures, bool)>;
#[derive(Debug, Clone)]
pub struct AuditFailure {
pub criteria_failures: CriteriaFailureSet,
}
#[derive(Debug, Clone)]
pub enum SearchResult<'a> {
Connected {
caveats: Caveats,
},
PossiblyConnected {
failed_deps: SortedMap<PackageIdx, CriteriaSet>,
},
Disconnected {
reachable_from_root: SortedSet<Option<&'a Version>>,
reachable_from_target: SortedSet<Option<&'a Version>>,
},
}
#[derive(Debug, Clone, Default)]
pub struct Caveats {
pub needed_exemptions: SortedSet<PackageIdx>,
pub needed_fresh_imports: SortedSet<PackageIdx>,
}
impl Caveats {
fn add(&mut self, other: &Caveats) {
self.needed_exemptions.extend(&other.needed_exemptions);
self.needed_fresh_imports
.extend(&other.needed_fresh_imports);
}
}
pub type AuditGraph<'a> = SortedMap<Option<&'a Version>, Vec<DeltaEdge<'a>>>;
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum DeltaEdgeOrigin {
StoredAudit,
Exemption,
FreshImportedAudit,
}
#[derive(Debug, Clone)]
pub struct DeltaEdge<'a> {
version: Option<&'a Version>,
criteria: CriteriaSet,
dependency_criteria: FastMap<PackageStr<'a>, CriteriaSet>,
origin: DeltaEdgeOrigin,
}
const NUM_BUILTINS: usize = 2;
fn builtin_criteria() -> [CriteriaInfo; NUM_BUILTINS] {
[
CriteriaInfo {
namespace: CriteriaNamespace::Local,
raw_name: SAFE_TO_RUN.to_string(),
namespaced_name: SAFE_TO_RUN.to_string(),
implies: vec![],
},
CriteriaInfo {
namespace: CriteriaNamespace::Local,
raw_name: SAFE_TO_DEPLOY.to_string(),
namespaced_name: SAFE_TO_DEPLOY.to_string(),
implies: vec!["safe-to-run".to_string()],
},
]
}
impl CriteriaMapper {
pub fn new(
criteria: &SortedMap<CriteriaName, CriteriaEntry>,
imports: &SortedMap<ImportName, AuditsFile>,
mappings: &SortedMap<ImportName, RemoteImport>,
) -> CriteriaMapper {
let locals = criteria.iter().map(|(k, v)| CriteriaInfo {
namespace: CriteriaNamespace::Local,
raw_name: k.clone(),
namespaced_name: k.clone(),
implies: v.implies.iter().map(|s| s.to_string()).collect::<Vec<_>>(),
});
let builtins = builtin_criteria();
let foreigns = imports.iter().flat_map(|(import, audit_file)| {
audit_file.criteria.iter().map(move |(k, v)| CriteriaInfo {
namespace: CriteriaNamespace::Foreign(import.clone()),
raw_name: k.clone(),
namespaced_name: format!("{}::{}", import, k),
implies: v.implies.iter().map(|s| s.to_string()).collect::<Vec<_>>(),
})
});
let list = builtins
.into_iter()
.chain(locals)
.chain(foreigns)
.collect::<Vec<_>>();
let num_criteria = list.len();
let mut index = FastMap::<CriteriaNamespace, FastMap<CriteriaName, usize>>::new();
let all_import_names = mappings.iter().map(|(import_name, _)| import_name);
for (idx, info) in list.iter().enumerate() {
let prev = index
.entry(info.namespace.clone())
.or_default()
.insert(info.raw_name.clone(), idx);
assert!(prev.is_none(), "criteria name was multiply defined???");
}
for import_name in all_import_names {
for (idx, info) in list[0..NUM_BUILTINS].iter().enumerate() {
index
.entry(CriteriaNamespace::Foreign(import_name.clone()))
.or_default()
.insert(info.raw_name.clone(), idx);
}
}
let mut direct_implies = FastMap::<usize, CriteriaSet>::with_capacity(num_criteria);
for (idx, info) in list.iter().enumerate() {
let mut edges = CriteriaSet::none(num_criteria);
for implied in &info.implies {
let their_idx = index[&info.namespace][&**implied];
edges.set_criteria(their_idx);
}
direct_implies.insert(idx, edges);
}
for (import_name, mappings) in mappings
.iter()
.map(|(name, entry)| (name, &entry.criteria_map))
{
let foreign = CriteriaNamespace::Foreign(import_name.clone());
let local = CriteriaNamespace::Local;
for mapping in mappings {
assert!(
mapping.theirs.len() == 1,
"criteria_map doesn't yet support multi-mapping, must be 1:1"
);
let our_idx = index[&local][&mapping.ours];
let their_idx = index[&foreign][&*mapping.theirs[0]];
direct_implies
.get_mut(&our_idx)
.unwrap()
.set_criteria(their_idx);
direct_implies
.get_mut(&their_idx)
.unwrap()
.set_criteria(our_idx);
}
}
let mut implied_criteria = Vec::with_capacity(num_criteria);
for idx in 0..num_criteria {
let mut implied = CriteriaSet::none(num_criteria);
implied.set_criteria(idx);
recursive_implies(&mut implied, &direct_implies, idx);
implied_criteria.push(implied);
fn recursive_implies(
result: &mut CriteriaSet,
direct_implies: &FastMap<usize, CriteriaSet>,
cur_idx: usize,
) {
for implied_idx in direct_implies[&cur_idx].indices() {
if result.has_criteria(implied_idx) {
continue;
}
result.set_criteria(implied_idx);
recursive_implies(result, direct_implies, implied_idx);
}
}
}
Self {
list,
index,
implied_criteria,
}
}
pub fn criteria_from_entry(&self, entry: &AuditEntry) -> CriteriaSet {
self.criteria_from_namespaced_entry(&CriteriaNamespace::Local, entry)
}
pub fn criteria_from_namespaced_entry(
&self,
namespace: &CriteriaNamespace,
entry: &AuditEntry,
) -> CriteriaSet {
self.criteria_from_namespaced_list(namespace, &entry.criteria)
}
pub fn criteria_from_list<'b, S: AsRef<str> + 'b + ?Sized>(
&self,
list: impl IntoIterator<Item = &'b S>,
) -> CriteriaSet {
self.criteria_from_namespaced_list(&CriteriaNamespace::Local, list)
}
pub fn criteria_from_namespaced_list<'b, S: AsRef<str> + 'b + ?Sized>(
&self,
namespace: &CriteriaNamespace,
list: impl IntoIterator<Item = &'b S>,
) -> CriteriaSet {
let mut result = self.no_criteria();
for criteria in list {
let idx = self.index[namespace][criteria.as_ref()];
result.unioned_with(&self.implied_criteria[idx]);
}
result
}
pub fn set_criteria(&self, set: &mut CriteriaSet, criteria: CriteriaStr) {
self.set_namespaced_criteria(set, &CriteriaNamespace::Local, criteria);
}
pub fn set_namespaced_criteria(
&self,
set: &mut CriteriaSet,
namespace: &CriteriaNamespace,
criteria: CriteriaStr,
) {
set.unioned_with(&self.implied_criteria[self.index[namespace][criteria]])
}
pub fn clear_criteria(&self, set: &mut CriteriaFailureSet, criteria: CriteriaStr) {
self.clear_namespaced_criteria(set, &CriteriaNamespace::Local, criteria)
}
pub fn clear_namespaced_criteria(
&self,
set: &mut CriteriaFailureSet,
namespace: &CriteriaNamespace,
criteria: CriteriaStr,
) {
set.clear_criteria(&self.implied_criteria[self.index[namespace][criteria]])
}
pub fn all_criteria_iter(&self) -> impl Iterator<Item = &CriteriaSet> {
self.implied_criteria.iter()
}
pub fn all_local_criteria_iter(&self) -> impl Iterator<Item = &CriteriaSet> {
self.implied_criteria
.iter()
.enumerate()
.filter(|(idx, _)| matches!(self.list[*idx].namespace, CriteriaNamespace::Local))
.map(|(_, set)| set)
}
pub fn len(&self) -> usize {
self.list.len()
}
pub fn is_empty(&self) -> bool {
self.list.is_empty()
}
pub fn no_criteria(&self) -> CriteriaSet {
CriteriaSet::none(self.len())
}
pub fn all_criteria(&self) -> CriteriaSet {
CriteriaSet::_all(self.len())
}
pub fn no_criteria_failures(&self) -> CriteriaFailureSet {
CriteriaFailureSet::none(self.len())
}
pub fn minimal_indices<'a>(
&'a self,
criteria: &'a CriteriaSet,
) -> impl Iterator<Item = usize> + 'a {
criteria.indices().filter(|&cur_idx| {
criteria.indices().all(|other_idx| {
let is_identity = cur_idx == other_idx;
let isnt_implied = !self.implied_criteria[other_idx].has_criteria(cur_idx);
let cur_is_local = self.list[cur_idx].namespace == CriteriaNamespace::Local;
let other_is_foreign = self.list[other_idx].namespace != CriteriaNamespace::Local;
let is_mapping = cur_is_local && other_is_foreign;
is_identity || isnt_implied || is_mapping
})
})
}
pub fn criteria_names<'a>(
&'a self,
criteria: &'a CriteriaSet,
) -> impl Iterator<Item = CriteriaStr<'a>> + 'a {
self.minimal_indices(criteria)
.map(|idx| &*self.list[idx].namespaced_name)
}
pub fn all_criteria_names<'a>(
&'a self,
criteria: &'a CriteriaFailureSet,
) -> impl Iterator<Item = CriteriaStr<'a>> + 'a {
self.criteria_names(criteria.all())
}
pub fn confident_criteria_names<'a>(
&'a self,
criteria: &'a CriteriaFailureSet,
) -> impl Iterator<Item = CriteriaStr<'a>> + 'a {
self.criteria_names(criteria.confident())
}
pub fn unconfident_criteria_names<'a>(
&'a self,
criteria: &'a CriteriaFailureSet,
) -> impl Iterator<Item = CriteriaStr<'a>> + 'a {
self.minimal_indices(criteria.all())
.filter(|idx| !criteria.confident().has_criteria(*idx))
.map(|idx| &*self.list[idx].namespaced_name)
}
}
impl CriteriaSet {
pub fn none(count: usize) -> Self {
assert!(
count <= MAX_CRITERIA,
"{MAX_CRITERIA} was not Enough For Everyone ({count} criteria)"
);
CriteriaSet(0)
}
pub fn _all(count: usize) -> Self {
assert!(
count <= MAX_CRITERIA,
"{MAX_CRITERIA} was not Enough For Everyone ({count} criteria)"
);
if count == MAX_CRITERIA {
CriteriaSet(!0)
} else {
CriteriaSet((1 << count) - 1)
}
}
pub fn set_criteria(&mut self, idx: usize) {
self.0 |= 1 << idx;
}
pub fn clear_criteria(&mut self, other: &CriteriaSet) {
self.0 &= !other.0;
}
pub fn has_criteria(&self, idx: usize) -> bool {
(self.0 & (1 << idx)) != 0
}
pub fn _intersected_with(&mut self, other: &CriteriaSet) {
self.0 &= other.0;
}
pub fn unioned_with(&mut self, other: &CriteriaSet) {
self.0 |= other.0;
}
pub fn contains(&self, other: &CriteriaSet) -> bool {
(self.0 & other.0) == other.0
}
pub fn is_empty(&self) -> bool {
self.0 == 0
}
pub fn indices(&self) -> impl Iterator<Item = usize> + '_ {
let mut raw = self.0;
std::iter::from_fn(move || {
if raw == 0 {
None
} else {
let next = raw.trailing_zeros() as usize;
raw &= !(1 << next);
Some(next)
}
})
}
fn clear(&mut self) {
self.0 = 0;
}
}
impl fmt::Debug for CriteriaSet {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{:08b}", self.0)
}
}
impl CriteriaFailureSet {
pub fn none(count: usize) -> Self {
CriteriaFailureSet {
confident: CriteriaSet::none(count),
all: CriteriaSet::none(count),
}
}
pub fn from(criteria: &CriteriaSet, confident: bool) -> Self {
CriteriaFailureSet {
confident: if confident {
criteria.clone()
} else {
let mut set = criteria.clone();
set.clear();
set
},
all: criteria.clone(),
}
}
pub fn set_criteria(&mut self, idx: usize, confident: bool) {
self.all.set_criteria(idx);
if confident {
self.confident.set_criteria(idx);
}
}
pub fn clear_criteria(&mut self, other: &CriteriaSet) {
self.confident.clear_criteria(other);
self.all.clear_criteria(other);
}
pub fn unioned_with(&mut self, other: &CriteriaFailureSet) {
self.all.unioned_with(&other.all);
self.confident.unioned_with(&other.confident);
}
pub fn contains(&self, other: &CriteriaFailureSet) -> bool {
self.all.contains(&other.all) && self.confident.contains(&other.confident)
}
pub fn is_empty(&self) -> bool {
self.all.is_empty()
}
pub fn is_fully_confident(&self) -> bool {
self.confident.contains(&self.all)
}
pub fn is_fully_unconfident(&self) -> bool {
self.confident.is_empty()
}
pub fn all(&self) -> &CriteriaSet {
&self.all
}
pub fn confident(&self) -> &CriteriaSet {
&self.confident
}
}
impl ResolveResult<'_> {
fn with_no_criteria(empty: CriteriaSet) -> Self {
Self {
validated_criteria: empty,
search_results: vec![],
directly_exempted: false,
}
}
}
impl<'a> DepGraph<'a> {
pub fn new(
metadata: &'a Metadata,
filter_graph: Option<&Vec<GraphFilter>>,
policy: Option<&SortedMap<PackageName, PolicyEntry>>,
) -> Self {
let empty_override = SortedMap::new();
let policy = policy.unwrap_or(&empty_override);
let package_list = &*metadata.packages;
let resolve_list = &*metadata
.resolve
.as_ref()
.expect("cargo metadata did not yield resolve!")
.nodes;
let package_index_by_pkgid = package_list
.iter()
.enumerate()
.map(|(idx, pkg)| (&pkg.id, idx))
.collect::<SortedMap<_, _>>();
let resolve_index_by_pkgid = resolve_list
.iter()
.enumerate()
.map(|(idx, pkg)| (&pkg.id, idx))
.collect();
let mut interner_by_pkgid = SortedMap::<&PackageId, PackageIdx>::new();
let mut nodes = vec![];
for resolve_node in resolve_list {
let package = &package_list[package_index_by_pkgid[&resolve_node.id]];
nodes.push(PackageNode {
package_id: &resolve_node.id,
name: &package.name,
version: &package.version,
is_third_party: package.is_third_party(policy),
normal_deps: vec![],
build_deps: vec![],
dev_deps: vec![],
normal_and_build_deps: vec![],
all_deps: vec![],
reverse_deps: SortedSet::new(),
is_workspace_member: false,
is_root: false,
is_dev_only: true,
});
}
nodes.sort_by_key(|k| k.package_id);
for (idx, node) in nodes.iter_mut().enumerate() {
assert!(interner_by_pkgid.insert(node.package_id, idx).is_none());
}
let mut topo_index = vec![];
{
let mut visited = FastMap::new();
for pkgid in &metadata.workspace_members {
let node_idx = interner_by_pkgid[pkgid];
nodes[node_idx].is_workspace_member = true;
visit_node(
&mut nodes,
&mut topo_index,
&mut visited,
&interner_by_pkgid,
&resolve_index_by_pkgid,
resolve_list,
node_idx,
);
}
for (&node_idx, ()) in &visited {
nodes[node_idx].is_dev_only = false;
}
for pkgid in &metadata.workspace_members {
let node = &mut nodes[interner_by_pkgid[pkgid]];
node.is_root = node.reverse_deps.is_empty();
}
for pkgid in &metadata.workspace_members {
let node_idx = interner_by_pkgid[pkgid];
let resolve_node = &resolve_list[resolve_index_by_pkgid[pkgid]];
let dev_deps = deps(
resolve_node,
&[DependencyKind::Development],
&interner_by_pkgid,
);
for &child in &dev_deps {
visit_node(
&mut nodes,
&mut topo_index,
&mut visited,
&interner_by_pkgid,
&resolve_index_by_pkgid,
resolve_list,
child,
);
nodes[child].reverse_deps.insert(node_idx);
}
let node = &mut nodes[node_idx];
node.dev_deps = dev_deps;
}
fn visit_node<'a>(
nodes: &mut Vec<PackageNode<'a>>,
topo_index: &mut Vec<PackageIdx>,
visited: &mut FastMap<PackageIdx, ()>,
interner_by_pkgid: &SortedMap<&'a PackageId, PackageIdx>,
resolve_index_by_pkgid: &SortedMap<&'a PackageId, usize>,
resolve_list: &'a [cargo_metadata::Node],
normal_idx: PackageIdx,
) {
let query = visited.entry(normal_idx);
if matches!(query, std::collections::hash_map::Entry::Vacant(..)) {
query.or_insert(());
let resolve_node =
&resolve_list[resolve_index_by_pkgid[nodes[normal_idx].package_id]];
let all_deps = resolve_node
.dependencies
.iter()
.map(|pkgid| interner_by_pkgid[pkgid])
.collect::<Vec<_>>();
let build_deps =
deps(resolve_node, &[DependencyKind::Build], interner_by_pkgid);
let normal_deps =
deps(resolve_node, &[DependencyKind::Normal], interner_by_pkgid);
let normal_and_build_deps = deps(
resolve_node,
&[DependencyKind::Normal, DependencyKind::Build],
interner_by_pkgid,
);
for &child in &normal_and_build_deps {
visit_node(
nodes,
topo_index,
visited,
interner_by_pkgid,
resolve_index_by_pkgid,
resolve_list,
child,
);
nodes[child].reverse_deps.insert(normal_idx);
}
topo_index.push(normal_idx);
let cur_node = &mut nodes[normal_idx];
cur_node.build_deps = build_deps;
cur_node.normal_deps = normal_deps;
cur_node.normal_and_build_deps = normal_and_build_deps;
cur_node.all_deps = all_deps;
}
}
fn deps(
resolve_node: &Node,
kinds: &[DependencyKind],
interner_by_pkgid: &SortedMap<&PackageId, PackageIdx>,
) -> Vec<PackageIdx> {
resolve_node
.deps
.iter()
.filter(|dep| {
dep.dep_kinds
.iter()
.any(|dep_kind| kinds.contains(&dep_kind.kind))
})
.map(|dep| interner_by_pkgid[&dep.pkg])
.collect()
}
}
let result = Self {
interner_by_pkgid,
nodes,
topo_index,
};
if let Some(filters) = filter_graph {
result.filter(filters)
} else {
result
}
}
pub fn filter(self, filters: &[GraphFilter]) -> Self {
use GraphFilter::*;
use GraphFilterProperty::*;
use GraphFilterQuery::*;
fn matches_query(package: &PackageNode, query: &GraphFilterQuery) -> bool {
match query {
All(queries) => queries.iter().all(|q| matches_query(package, q)),
Any(queries) => queries.iter().any(|q| matches_query(package, q)),
Not(query) => !matches_query(package, query),
Prop(property) => matches_property(package, property),
}
}
fn matches_property(package: &PackageNode, property: &GraphFilterProperty) -> bool {
match property {
Name(val) => package.name == val,
Version(val) => package.version == val,
IsRoot(val) => &package.is_root == val,
IsWorkspaceMember(val) => &package.is_workspace_member == val,
IsThirdParty(val) => &package.is_third_party == val,
IsDevOnly(val) => &package.is_dev_only == val,
}
}
let mut passed_filters = FastSet::new();
'nodes: for (idx, package) in self.nodes.iter().enumerate() {
for filter in filters {
match filter {
Include(query) => {
if !matches_query(package, query) {
continue 'nodes;
}
}
Exclude(query) => {
if matches_query(package, query) {
continue 'nodes;
}
}
}
}
passed_filters.insert(idx);
}
let mut reachable = FastMap::new();
for (idx, package) in self.nodes.iter().enumerate() {
if package.is_workspace_member {
visit(&mut reachable, &self, &passed_filters, idx);
}
fn visit(
visited: &mut FastMap<PackageIdx, ()>,
graph: &DepGraph,
passed_filters: &FastSet<PackageIdx>,
node_idx: PackageIdx,
) {
if !passed_filters.contains(&node_idx) {
return;
}
let query = visited.entry(node_idx);
if matches!(query, std::collections::hash_map::Entry::Vacant(..)) {
query.or_insert(());
for &child in &graph.nodes[node_idx].all_deps {
visit(visited, graph, passed_filters, child);
}
}
}
}
let mut old_to_new = FastMap::new();
let mut nodes = Vec::new();
let mut interner_by_pkgid = SortedMap::new();
let mut topo_index = Vec::new();
for (old_idx, package) in self.nodes.iter().enumerate() {
if !reachable.contains_key(&old_idx) {
continue;
}
let new_idx = nodes.len();
old_to_new.insert(old_idx, new_idx);
nodes.push(PackageNode {
package_id: package.package_id,
name: package.name,
version: package.version,
normal_deps: vec![],
build_deps: vec![],
dev_deps: vec![],
normal_and_build_deps: vec![],
all_deps: vec![],
reverse_deps: SortedSet::new(),
is_workspace_member: package.is_workspace_member,
is_third_party: package.is_third_party,
is_root: package.is_root,
is_dev_only: package.is_dev_only,
});
interner_by_pkgid.insert(package.package_id, new_idx);
}
for old_idx in &self.topo_index {
if let Some(&new_idx) = old_to_new.get(old_idx) {
topo_index.push(new_idx);
}
}
for (old_idx, old_package) in self.nodes.iter().enumerate() {
if let Some(&new_idx) = old_to_new.get(&old_idx) {
let new_package = &mut nodes[new_idx];
for old_dep in &old_package.normal_deps {
if let Some(&new_dep) = old_to_new.get(old_dep) {
new_package.normal_deps.push(new_dep);
}
}
for old_dep in &old_package.build_deps {
if let Some(&new_dep) = old_to_new.get(old_dep) {
new_package.build_deps.push(new_dep);
}
}
for old_dep in &old_package.dev_deps {
if let Some(&new_dep) = old_to_new.get(old_dep) {
new_package.dev_deps.push(new_dep);
}
}
for old_dep in &old_package.normal_and_build_deps {
if let Some(&new_dep) = old_to_new.get(old_dep) {
new_package.normal_and_build_deps.push(new_dep);
}
}
for old_dep in &old_package.all_deps {
if let Some(&new_dep) = old_to_new.get(old_dep) {
new_package.all_deps.push(new_dep);
}
}
for old_dep in &old_package.reverse_deps {
if let Some(&new_dep) = old_to_new.get(old_dep) {
new_package.reverse_deps.insert(new_dep);
}
}
}
}
Self {
nodes,
interner_by_pkgid,
topo_index,
}
}
pub fn print_mermaid(
&self,
out: &Arc<dyn Out>,
sub_args: &DumpGraphArgs,
) -> Result<(), std::io::Error> {
use crate::DumpGraphDepth::*;
let depth = sub_args.depth;
let mut visible_nodes = SortedSet::new();
let mut nodes_with_children = SortedSet::new();
let mut shown = SortedSet::new();
for (idx, package) in self.nodes.iter().enumerate() {
if (package.is_root && depth >= Roots)
|| (package.is_workspace_member && depth >= Workspace)
|| (!package.is_third_party && depth >= FirstParty)
|| depth >= Full
{
visible_nodes.insert(idx);
nodes_with_children.insert(idx);
if depth >= FirstPartyAndDirects {
for &dep in &package.all_deps {
visible_nodes.insert(dep);
}
}
}
}
writeln!(out, "graph LR");
writeln!(out, " subgraph roots");
for &idx in &visible_nodes {
let package = &self.nodes[idx];
if package.is_root && shown.insert(idx) {
writeln!(
out,
" node{idx}{{{}:{}}}",
package.name, package.version
);
}
}
writeln!(out, " end");
writeln!(out, " subgraph workspace-members");
for &idx in &visible_nodes {
let package = &self.nodes[idx];
if package.is_workspace_member && shown.insert(idx) {
writeln!(
out,
" node{idx}[/{}:{}/]",
package.name, package.version
);
}
}
writeln!(out, " end");
writeln!(out, " subgraph first-party");
for &idx in &visible_nodes {
let package = &self.nodes[idx];
if !package.is_third_party && shown.insert(idx) {
writeln!(
out,
" node{idx}[{}:{}]",
package.name, package.version
);
}
}
writeln!(out, " end");
writeln!(out, " subgraph third-party");
for &idx in &visible_nodes {
let package = &self.nodes[idx];
if shown.insert(idx) {
writeln!(
out,
" node{idx}({}:{})",
package.name, package.version
);
}
}
writeln!(out, " end");
for &idx in &nodes_with_children {
let package = &self.nodes[idx];
for &dep_idx in &package.all_deps {
if visible_nodes.contains(&dep_idx) {
writeln!(out, " node{idx} --> node{dep_idx}");
}
}
}
Ok(())
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum ResolveDepth {
Deep,
Shallow,
}
pub fn resolve<'a>(
metadata: &'a Metadata,
filter_graph: Option<&Vec<GraphFilter>>,
store: &'a Store,
resolve_depth: ResolveDepth,
) -> ResolveReport<'a> {
let graph = DepGraph::new(metadata, filter_graph, Some(&store.config.policy));
trace!("built DepGraph!");
let criteria_mapper = CriteriaMapper::new(
&store.audits.criteria,
store.imported_audits(),
&store.config.imports,
);
trace!("built CriteriaMapper!");
let (results, conclusion) = resolve_core(&graph, store, &criteria_mapper, resolve_depth);
ResolveReport {
graph,
criteria_mapper,
results,
conclusion,
}
}
fn resolve_core<'a>(
graph: &DepGraph<'a>,
store: &'a Store,
criteria_mapper: &CriteriaMapper,
resolve_depth: ResolveDepth,
) -> (Vec<ResolveResult<'a>>, Conclusion) {
let _resolve_span = trace_span!("validate").entered();
let mut results =
vec![ResolveResult::with_no_criteria(criteria_mapper.no_criteria()); graph.nodes.len()];
let mut root_failures = RootFailures::new();
let mut violations = SortedMap::new();
let mut root_caveats = Caveats::default();
for &pkgidx in &graph.topo_index {
let package = &graph.nodes[pkgidx];
trace!("resolving {}:{}", package.name, package.version,);
if package.is_third_party {
resolve_third_party(
store,
graph,
criteria_mapper,
&mut results,
&mut violations,
&mut root_failures,
pkgidx,
);
} else {
resolve_first_party(
store,
graph,
criteria_mapper,
&mut results,
&mut violations,
&mut root_failures,
pkgidx,
);
}
resolve_self_policy(
store,
graph,
criteria_mapper,
&mut results,
&mut violations,
&mut root_failures,
&mut root_caveats,
pkgidx,
);
}
for &pkgidx in &graph.topo_index {
let package = &graph.nodes[pkgidx];
trace!("resolving dev {}:{}", package.name, package.version,);
if package.is_workspace_member {
resolve_dev(
store,
graph,
criteria_mapper,
&mut results,
&mut violations,
&mut root_failures,
&mut root_caveats,
pkgidx,
);
} else {
assert!(
package.dev_deps.is_empty(),
"{}:{} isn't a workspace member but has dev-deps!",
package.name,
package.version
);
}
}
if !violations.is_empty() {
return (
results,
Conclusion::FailForViolationConflict(FailForViolationConflict { violations }),
);
}
_resolve_span.exit();
let _blame_span = trace_span!("blame").entered();
let mut failures = SortedMap::<PackageIdx, AuditFailure>::new();
visit_failures(
graph,
criteria_mapper,
&results,
&root_failures,
resolve_depth,
|failure, depth, own_failure| {
if let Some(criteria_failures) = own_failure {
trace!(
" {:width$}blaming: {}:{} for {:?} + {:?}",
"",
graph.nodes[failure].name,
graph.nodes[failure].version,
criteria_mapper
.confident_criteria_names(criteria_failures)
.collect::<Vec<_>>(),
criteria_mapper
.unconfident_criteria_names(criteria_failures)
.collect::<Vec<_>>(),
width = depth
);
failures
.entry(failure)
.or_insert_with(|| AuditFailure {
criteria_failures: criteria_mapper.no_criteria_failures(),
})
.criteria_failures
.unioned_with(criteria_failures);
}
Ok::<(), ()>(())
},
)
.unwrap();
assert_eq!(
root_failures.is_empty(),
failures.is_empty(),
"failure blaming system bugged out"
);
if !failures.is_empty() {
return (
results,
Conclusion::FailForVet(FailForVet {
failures,
suggest: None,
}),
);
}
let mut vetted_with_exemptions = vec![];
let mut vetted_partially = vec![];
let mut vetted_fully = vec![];
for &pkgidx in &graph.topo_index {
let package = &graph.nodes[pkgidx];
if !package.is_third_party {
continue;
}
let result = &results[pkgidx];
if !root_caveats.needed_exemptions.contains(&pkgidx) {
vetted_fully.push(pkgidx);
} else if result.directly_exempted {
vetted_with_exemptions.push(pkgidx);
} else {
vetted_partially.push(pkgidx);
}
}
(
results,
Conclusion::Success(Success {
vetted_with_exemptions,
vetted_partially,
vetted_fully,
needed_fresh_imports: root_caveats.needed_fresh_imports,
}),
)
}
fn resolve_third_party<'a>(
store: &'a Store,
graph: &DepGraph<'a>,
criteria_mapper: &CriteriaMapper,
results: &mut [ResolveResult<'a>],
violations: &mut SortedMap<PackageIdx, Vec<ViolationConflict>>,
_root_failures: &mut RootFailures,
pkgidx: PackageIdx,
) {
let package = &graph.nodes[pkgidx];
let exemptions = store.config.exemptions.get(package.name);
let foreign_namespaces: Vec<CriteriaNamespace> = store
.imported_audits()
.keys()
.map(|import_name| CriteriaNamespace::Foreign(import_name.clone()))
.collect();
let own_audits = store
.audits
.audits
.get(package.name)
.map(|v| &v[..])
.unwrap_or(&[])
.iter()
.map(|audit| (&CriteriaNamespace::Local, audit));
let foreign_audits = store
.imported_audits()
.values()
.enumerate()
.flat_map(|(idx, audits)| {
let namespace = &foreign_namespaces[idx];
audits
.audits
.get(package.name)
.map(|v| &v[..])
.unwrap_or(&[])
.iter()
.map(move |audit| (namespace, audit))
});
let all_audits = own_audits.chain(foreign_audits);
let mut forward_audits = AuditGraph::new();
let mut backward_audits = AuditGraph::new();
let mut violation_nodes = Vec::new();
for (namespace, entry) in all_audits.clone() {
let (from_ver, to_ver, dependency_criteria) = match &entry.kind {
AuditKind::Full {
version,
dependency_criteria,
} => (None, version, dependency_criteria),
AuditKind::Delta {
from,
to,
dependency_criteria,
} => (Some(from), to, dependency_criteria),
AuditKind::Violation { .. } => {
violation_nodes.push((namespace.clone(), entry));
continue;
}
};
let criteria = criteria_mapper.criteria_from_namespaced_entry(namespace, entry);
let dependency_criteria: FastMap<_, _> = dependency_criteria
.iter()
.map(|(pkg_name, criteria)| {
(
&**pkg_name,
criteria_mapper.criteria_from_namespaced_list(namespace, criteria),
)
})
.collect();
let origin = if entry.is_fresh_import {
DeltaEdgeOrigin::FreshImportedAudit
} else {
DeltaEdgeOrigin::StoredAudit
};
forward_audits.entry(from_ver).or_default().push(DeltaEdge {
version: Some(to_ver),
criteria: criteria.clone(),
dependency_criteria: dependency_criteria.clone(),
origin,
});
backward_audits
.entry(Some(to_ver))
.or_default()
.push(DeltaEdge {
version: from_ver,
criteria,
dependency_criteria,
origin,
});
}
for (violation_source, violation_entry) in &violation_nodes {
let violation_criterias = violation_entry
.criteria
.iter()
.map(|c| criteria_mapper.criteria_from_namespaced_list(violation_source, [&c]))
.collect::<Vec<_>>();
let violation_range = if let AuditKind::Violation { violation } = &violation_entry.kind {
violation
} else {
unreachable!("violation_entry wasn't a Violation?");
};
if let Some(alloweds) = exemptions {
for allowed in alloweds {
let audit_criteria = criteria_mapper.criteria_from_list(&allowed.criteria);
let has_violation = violation_criterias
.iter()
.any(|v| audit_criteria.contains(v));
if !has_violation {
continue;
}
if violation_range.matches(&allowed.version) {
violations.entry(pkgidx).or_default().push(
ViolationConflict::UnauditedConflict {
violation_source: violation_source.clone(),
violation: (*violation_entry).clone(),
exemptions: allowed.clone(),
},
);
}
}
}
for (namespace, audit) in all_audits.clone() {
let audit_criteria = criteria_mapper.criteria_from_namespaced_entry(namespace, audit);
let has_violation = violation_criterias
.iter()
.any(|v| audit_criteria.contains(v));
if !has_violation {
continue;
}
match &audit.kind {
AuditKind::Full { version, .. } => {
if violation_range.matches(version) {
violations.entry(pkgidx).or_default().push(
ViolationConflict::AuditConflict {
violation_source: violation_source.clone(),
violation: (*violation_entry).clone(),
audit_source: namespace.clone(),
audit: audit.clone(),
},
);
}
}
AuditKind::Delta { from, to, .. } => {
if violation_range.matches(from) || violation_range.matches(to) {
violations.entry(pkgidx).or_default().push(
ViolationConflict::AuditConflict {
violation_source: violation_source.clone(),
violation: (*violation_entry).clone(),
audit_source: namespace.clone(),
audit: audit.clone(),
},
);
}
}
AuditKind::Violation { .. } => {
}
}
}
}
let mut directly_exempted = false;
if let Some(alloweds) = exemptions {
for allowed in alloweds {
if &allowed.version == package.version {
directly_exempted = true;
}
let from_ver = None;
let to_ver = Some(&allowed.version);
let criteria = criteria_mapper.criteria_from_list(&allowed.criteria);
let dependency_criteria: FastMap<_, _> = allowed
.dependency_criteria
.iter()
.map(|(pkg_name, criteria)| {
(&**pkg_name, criteria_mapper.criteria_from_list(criteria))
})
.collect();
forward_audits.entry(from_ver).or_default().push(DeltaEdge {
version: to_ver,
criteria: criteria.clone(),
dependency_criteria: dependency_criteria.clone(),
origin: DeltaEdgeOrigin::Exemption,
});
backward_audits.entry(to_ver).or_default().push(DeltaEdge {
version: from_ver,
criteria,
dependency_criteria,
origin: DeltaEdgeOrigin::Exemption,
});
}
}
let mut validated_criteria = criteria_mapper.no_criteria();
let mut search_results = vec![];
for criteria in criteria_mapper.all_criteria_iter() {
let result = search_for_path(
criteria,
None,
Some(package.version),
&forward_audits,
graph,
criteria_mapper,
results,
pkgidx,
&package.normal_and_build_deps,
);
match result {
SearchResult::Connected { caveats } => {
validated_criteria.unioned_with(criteria);
search_results.push(SearchResult::Connected { caveats });
}
SearchResult::PossiblyConnected { failed_deps } => {
search_results.push(SearchResult::PossiblyConnected { failed_deps });
}
SearchResult::Disconnected {
reachable_from_root,
..
} => {
let rev_result = search_for_path(
criteria,
Some(package.version),
None,
&backward_audits,
graph,
criteria_mapper,
results,
pkgidx,
&package.normal_and_build_deps,
);
if let SearchResult::Disconnected {
reachable_from_root: reachable_from_target,
..
} = rev_result
{
search_results.push(SearchResult::Disconnected {
reachable_from_root,
reachable_from_target,
})
} else {
unreachable!("We managed to find a path but only from one direction?!");
}
}
}
}
trace!(
" third-party validation: {:?}",
criteria_mapper
.criteria_names(&validated_criteria)
.collect::<Vec<_>>()
);
results[pkgidx] = ResolveResult {
validated_criteria,
search_results,
directly_exempted,
};
}
#[allow(clippy::too_many_arguments)]
fn get_dependency_criteria_caveats(
dep_graph: &DepGraph,
criteria_mapper: &CriteriaMapper,
results: &[ResolveResult<'_>],
dependencies: &[PackageIdx],
base_criteria: &CriteriaSet,
dependency_criteria: &FastMap<PackageStr<'_>, CriteriaSet>,
caveats: &mut Caveats,
failed_deps: &mut SortedMap<PackageIdx, CriteriaSet>,
) {
for &depidx in dependencies {
let dep_package = &dep_graph.nodes[depidx];
let dep_results = &results[depidx];
let dep_req = dependency_criteria
.get(dep_package.name)
.unwrap_or(base_criteria);
if !dep_results.validated_criteria.contains(dep_req) {
failed_deps
.entry(depidx)
.or_insert_with(|| criteria_mapper.no_criteria())
.unioned_with(dep_req);
continue;
}
for required_criteria_idx in criteria_mapper.minimal_indices(dep_req) {
if let SearchResult::Connected {
caveats: dep_caveats,
} = &dep_results.search_results[required_criteria_idx]
{
caveats.add(dep_caveats);
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn search_for_path<'a>(
cur_criteria: &CriteriaSet,
from_version: Option<&'a Version>,
to_version: Option<&'a Version>,
audit_graph: &AuditGraph<'a>,
dep_graph: &DepGraph<'a>,
criteria_mapper: &CriteriaMapper,
results: &[ResolveResult],
pkgidx: PackageIdx,
dependencies: &[PackageIdx],
) -> SearchResult<'a> {
struct Node<'a> {
version: Option<&'a Version>,
caveats: Caveats,
failed_deps: SortedMap<PackageIdx, CriteriaSet>,
}
impl<'a> Node<'a> {
fn key(&self) -> Reverse<(usize, usize, usize)> {
Reverse((
self.failed_deps.len(),
self.caveats.needed_fresh_imports.len(),
self.caveats.needed_exemptions.len(),
))
}
}
impl<'a> PartialEq for Node<'a> {
fn eq(&self, other: &Self) -> bool {
self.key() == other.key()
}
}
impl<'a> Eq for Node<'a> {}
impl<'a> PartialOrd for Node<'a> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<'a> Ord for Node<'a> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.key().cmp(&other.key())
}
}
let mut queue = BinaryHeap::new();
queue.push(Node {
version: from_version,
caveats: Caveats::default(),
failed_deps: SortedMap::new(),
});
let mut visited = SortedSet::new();
while let Some(Node {
version,
caveats,
failed_deps,
}) = queue.pop()
{
if !visited.insert(version) {
continue;
}
if version == to_version {
return if failed_deps.is_empty() {
SearchResult::Connected { caveats }
} else {
SearchResult::PossiblyConnected { failed_deps }
};
}
let edges = audit_graph.get(&version).map(|v| &v[..]).unwrap_or(&[]);
for edge in edges {
if !edge.criteria.contains(cur_criteria) {
continue;
}
if visited.contains(&edge.version) {
continue;
}
let mut edge_caveats = caveats.clone();
let mut edge_failed_deps = failed_deps.clone();
match edge.origin {
DeltaEdgeOrigin::Exemption => {
edge_caveats.needed_exemptions.insert(pkgidx);
}
DeltaEdgeOrigin::FreshImportedAudit => {
edge_caveats.needed_fresh_imports.insert(pkgidx);
}
DeltaEdgeOrigin::StoredAudit => {}
}
get_dependency_criteria_caveats(
dep_graph,
criteria_mapper,
results,
dependencies,
cur_criteria,
&edge.dependency_criteria,
&mut edge_caveats,
&mut edge_failed_deps,
);
queue.push(Node {
version: edge.version,
caveats: edge_caveats,
failed_deps: edge_failed_deps,
});
}
}
SearchResult::Disconnected {
reachable_from_root: visited,
reachable_from_target: Default::default(),
}
}
fn resolve_first_party<'a>(
store: &'a Store,
graph: &DepGraph<'a>,
criteria_mapper: &CriteriaMapper,
results: &mut [ResolveResult<'a>],
_violations: &mut SortedMap<PackageIdx, Vec<ViolationConflict>>,
_root_failures: &mut RootFailures,
pkgidx: PackageIdx,
) {
let package = &graph.nodes[pkgidx];
let dep_criteria = store
.config
.policy
.get(package.name)
.map(|policy| {
policy
.dependency_criteria
.iter()
.map(|(dep_name, criteria)| {
(&**dep_name, criteria_mapper.criteria_from_list(criteria))
})
.collect::<FastMap<_, _>>()
})
.unwrap_or_default();
let mut validated_criteria = criteria_mapper.no_criteria();
let mut search_results = Vec::with_capacity(criteria_mapper.len());
for criteria in criteria_mapper.all_criteria_iter() {
let mut caveats = Caveats::default();
let mut failed_deps = SortedMap::new();
get_dependency_criteria_caveats(
graph,
criteria_mapper,
results,
&package.normal_and_build_deps,
criteria,
&dep_criteria,
&mut caveats,
&mut failed_deps,
);
if failed_deps.is_empty() {
search_results.push(SearchResult::Connected { caveats });
validated_criteria.unioned_with(criteria);
} else {
search_results.push(SearchResult::PossiblyConnected { failed_deps })
}
}
trace!(
" first-party validation: {:?}",
criteria_mapper
.criteria_names(&validated_criteria)
.collect::<Vec<_>>()
);
results[pkgidx] = ResolveResult {
validated_criteria,
search_results,
directly_exempted: false,
};
}
fn get_policy_caveats<'a>(
criteria_mapper: &CriteriaMapper,
search_results: &[SearchResult<'a>],
own_policy: &CriteriaSet,
pkgidx: PackageIdx,
root_caveats: &mut Caveats,
policy_failures: &mut PolicyFailures,
) {
for criteria_idx in own_policy.indices() {
match &search_results[criteria_idx] {
SearchResult::PossiblyConnected { failed_deps } => {
for (&dep, failed_criteria) in failed_deps {
policy_failures
.entry(dep)
.or_insert_with(|| criteria_mapper.no_criteria())
.unioned_with(failed_criteria);
}
}
SearchResult::Disconnected { .. } => {
policy_failures
.entry(pkgidx)
.or_insert_with(|| criteria_mapper.no_criteria())
.set_criteria(criteria_idx)
}
SearchResult::Connected { caveats } => {
root_caveats.add(caveats);
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn resolve_self_policy<'a>(
store: &'a Store,
graph: &DepGraph<'a>,
criteria_mapper: &CriteriaMapper,
results: &mut [ResolveResult<'a>],
_violations: &mut SortedMap<PackageIdx, Vec<ViolationConflict>>,
root_failures: &mut RootFailures,
root_caveats: &mut Caveats,
pkgidx: PackageIdx,
) {
let package = &graph.nodes[pkgidx];
let entry = store.config.policy.get(package.name);
let own_policy = if let Some(c) = entry.and_then(|p| p.criteria.as_ref()) {
trace!(" explicit policy: {:?}", c);
criteria_mapper.criteria_from_list(c)
} else if package.is_root {
trace!(" root policy: {:?}", [format::DEFAULT_POLICY_CRITERIA]);
criteria_mapper.criteria_from_list([format::DEFAULT_POLICY_CRITERIA])
} else {
trace!(" has no policy, done");
return;
};
let mut policy_failures = PolicyFailures::new();
get_policy_caveats(
criteria_mapper,
&results[pkgidx].search_results,
&own_policy,
pkgidx,
root_caveats,
&mut policy_failures,
);
if policy_failures.is_empty() {
trace!(" passed policy, all_criteria");
results[pkgidx].validated_criteria = criteria_mapper.all_criteria();
} else {
trace!(" failed policy, no_criteria");
results[pkgidx].validated_criteria = criteria_mapper.no_criteria();
root_failures.push((pkgidx, policy_failures, false));
}
}
#[allow(clippy::too_many_arguments)]
fn resolve_dev<'a>(
store: &'a Store,
graph: &DepGraph<'a>,
criteria_mapper: &CriteriaMapper,
results: &mut [ResolveResult<'a>],
_violations: &mut SortedMap<PackageIdx, Vec<ViolationConflict>>,
root_failures: &mut RootFailures,
root_caveats: &mut Caveats,
pkgidx: PackageIdx,
) {
let package = &graph.nodes[pkgidx];
let dep_criteria = store
.config
.policy
.get(package.name)
.map(|policy| {
policy
.dependency_criteria
.iter()
.map(|(dep_name, criteria)| {
(&**dep_name, criteria_mapper.criteria_from_list(criteria))
})
.collect::<FastMap<_, _>>()
})
.unwrap_or_default();
let mut validated_criteria = criteria_mapper.no_criteria();
let mut search_results = vec![];
for criteria in criteria_mapper.all_criteria_iter() {
let mut caveats = Caveats::default();
let mut failed_deps = SortedMap::new();
get_dependency_criteria_caveats(
graph,
criteria_mapper,
results,
&package.dev_deps,
criteria,
&dep_criteria,
&mut caveats,
&mut failed_deps,
);
if failed_deps.is_empty() {
search_results.push(SearchResult::Connected { caveats });
validated_criteria.unioned_with(criteria);
} else {
search_results.push(SearchResult::PossiblyConnected { failed_deps })
}
}
trace!(
" dev validation: {:?}",
criteria_mapper
.criteria_names(&validated_criteria)
.collect::<Vec<_>>()
);
let entry = store.config.policy.get(package.name);
let own_policy = if let Some(c) = entry.and_then(|p| p.dev_criteria.as_ref()) {
trace!(" explicit policy: {:?}", c);
criteria_mapper.criteria_from_list(c)
} else {
trace!(" root policy: {:?}", [format::DEFAULT_POLICY_DEV_CRITERIA]);
criteria_mapper.criteria_from_list([format::DEFAULT_POLICY_DEV_CRITERIA])
};
let mut policy_failures = PolicyFailures::new();
get_policy_caveats(
criteria_mapper,
&search_results,
&own_policy,
pkgidx,
root_caveats,
&mut policy_failures,
);
if policy_failures.is_empty() {
trace!(" passed dev policy");
} else {
trace!(" failed dev policy");
root_failures.push((pkgidx, policy_failures, true));
}
}
fn visit_failures<'a, T>(
graph: &DepGraph<'a>,
criteria_mapper: &CriteriaMapper,
results: &[ResolveResult<'a>],
root_failures: &RootFailures,
resolve_depth: ResolveDepth,
mut callback: impl FnMut(PackageIdx, usize, Option<&CriteriaFailureSet>) -> Result<(), T>,
) -> Result<(), T> {
trace!(" traversing blame tree");
let mut search_stack = Vec::new();
let mut immune_to_parent_demands = FastSet::new();
for (failed_idx, policy_failures, is_dev) in root_failures {
let failed_package = &graph.nodes[*failed_idx];
trace!(
" policy failure for {}:{}",
failed_package.name,
failed_package.version
);
for (&failed_dep_idx, failed_criteria) in policy_failures {
let failed_dep = &graph.nodes[failed_dep_idx];
trace!(
" {}:{} needed {:?}",
failed_dep.name,
failed_dep.version,
criteria_mapper
.criteria_names(failed_criteria)
.collect::<Vec<_>>()
);
search_stack.push((
failed_dep_idx,
0,
Some(CriteriaFailureSet::from(failed_criteria, true)),
));
}
if !is_dev {
immune_to_parent_demands.insert(failed_idx);
}
}
let mut visited = FastMap::<PackageIdx, CriteriaFailureSet>::new();
let no_criteria = criteria_mapper.no_criteria_failures();
while let Some((failure_idx, depth, cur_criteria)) = search_stack.pop() {
match visited.entry(failure_idx) {
std::collections::hash_map::Entry::Vacant(entry) => {
entry.insert(cur_criteria.clone().unwrap_or_else(|| no_criteria.clone()));
}
std::collections::hash_map::Entry::Occupied(mut entry) => {
let cur = cur_criteria.as_ref().unwrap_or(&no_criteria);
if entry.get().contains(cur) {
continue;
} else {
entry.get_mut().unioned_with(cur);
}
}
}
let result = &results[failure_idx];
let package = &graph.nodes[failure_idx];
trace!(
" {:width$}visiting {}:{} for {:?} + {:?}",
"",
package.name,
package.version,
cur_criteria.as_ref().map(|c| criteria_mapper
.confident_criteria_names(c)
.collect::<Vec<_>>()),
cur_criteria.as_ref().map(|c| criteria_mapper
.unconfident_criteria_names(c)
.collect::<Vec<_>>()),
width = depth
);
if let Some(failed_criteria) = cur_criteria {
let mut own_fault = no_criteria.clone();
let mut dep_faults = FastMap::<PackageIdx, CriteriaFailureSet>::new();
for criteria_idx in failed_criteria.all().indices() {
let confident = failed_criteria.confident().has_criteria(criteria_idx);
match &result.search_results[criteria_idx] {
SearchResult::Connected { .. } => {
}
SearchResult::PossiblyConnected { failed_deps } => {
for (&failed_dep, failed_criteria) in failed_deps {
dep_faults
.entry(failed_dep)
.or_insert_with(|| no_criteria.clone())
.unioned_with(&CriteriaFailureSet::from(
failed_criteria,
confident,
));
}
}
SearchResult::Disconnected { .. } => {
own_fault.set_criteria(criteria_idx, confident);
if resolve_depth != ResolveDepth::Shallow {
for &dep_idx in &package.normal_and_build_deps {
let dep_result = &results[dep_idx];
if !dep_result.validated_criteria.has_criteria(criteria_idx) {
dep_faults
.entry(dep_idx)
.or_insert_with(|| no_criteria.clone())
.set_criteria(criteria_idx, false);
}
}
}
}
}
}
if own_fault.is_empty() {
callback(failure_idx, depth, None)?;
} else {
callback(failure_idx, depth, Some(&own_fault))?;
}
for (failed_dep, failed_criteria) in dep_faults {
if immune_to_parent_demands.contains(&failed_dep) {
continue;
}
search_stack.push((failed_dep, depth + 1, Some(failed_criteria.clone())));
}
} else {
unreachable!("I don't think this should happen..?");
}
}
Ok(())
}
impl<'a> ResolveReport<'a> {
pub fn has_errors(&self) -> bool {
!matches!(self.conclusion, Conclusion::Success(_))
}
pub fn _has_warnings(&self) -> bool {
false
}
pub fn compute_suggest(
&self,
cfg: &Config,
network: Option<&Network>,
allow_deltas: bool,
) -> Result<Option<Suggest>, SuggestError> {
let _suggest_span = trace_span!("suggest").entered();
let fail = if let Conclusion::FailForVet(fail) = &self.conclusion {
fail
} else {
return Ok(None);
};
let cache = Cache::acquire(cfg)?;
let suggest_progress =
progress_bar("Suggesting", "relevant audits", fail.failures.len() as u64);
let mut suggestions = tokio::runtime::Handle::current()
.block_on(join_all(fail.failures.iter().map(
|(failure_idx, audit_failure)| async {
let _guard = IncProgressOnDrop(&suggest_progress, 1);
let failure_idx = *failure_idx;
let package = &self.graph.nodes[failure_idx];
let result = &self.results[failure_idx];
let notable_parents = {
let mut reverse_deps = self.graph.nodes[failure_idx]
.reverse_deps
.iter()
.map(|&parent| self.graph.nodes[parent].name.to_string())
.collect::<Vec<_>>();
reverse_deps.sort();
reverse_deps.sort_by_key(|item| item.len());
let cutoff_index = reverse_deps
.iter()
.scan(0, |sum, s| {
*sum += s.len();
Some(*sum)
})
.position(|count| count > 20);
let remainder = cutoff_index.map(|i| reverse_deps.len() - i).unwrap_or(0);
if remainder > 1 {
reverse_deps.truncate(cutoff_index.unwrap());
reverse_deps.push(format!("and {} others", remainder));
}
reverse_deps.join(", ")
};
let mut from_root = None::<SortedSet<Option<&Version>>>;
let mut from_target = None::<SortedSet<Option<&Version>>>;
for criteria_idx in audit_failure.criteria_failures.all().indices() {
let search_result = &result.search_results[criteria_idx];
if let SearchResult::Disconnected {
reachable_from_root,
reachable_from_target,
} = search_result
{
if let (Some(from_root), Some(from_target)) =
(from_root.as_mut(), from_target.as_mut())
{
*from_root = &*from_root & reachable_from_root;
*from_target = &*from_target & reachable_from_target;
} else {
from_root = Some(reachable_from_root.clone());
from_target = Some(reachable_from_target.clone());
}
} else {
unreachable!("messed up suggest...");
}
}
let mut candidates = SortedSet::new();
if allow_deltas {
for &dest in from_target.as_ref().unwrap() {
let mut closest_above = None;
let mut closest_below = None;
for &src in from_root.as_ref().unwrap() {
if src < dest {
if let Some(closest) = closest_below {
if src > closest {
closest_below = Some(src);
}
} else {
closest_below = Some(src);
}
} else if let Some(closest) = closest_above {
if src < closest {
closest_above = Some(src);
}
} else {
closest_above = Some(src);
}
}
for closest in closest_below.into_iter().chain(closest_above) {
candidates.insert(Delta {
from: closest.cloned(),
to: dest.unwrap().clone(),
});
}
}
} else {
for &dest in from_target.as_ref().unwrap() {
candidates.insert(Delta {
from: None,
to: dest.unwrap().clone(),
});
}
}
let diffstats = join_all(candidates.iter().map(|delta| async {
match cache
.fetch_and_diffstat_package(network, package.name, delta)
.await
{
Ok(diffstat) => Some(DiffRecommendation {
diffstat,
from: delta.from.clone(),
to: delta.to.clone(),
}),
Err(err) => {
error!(
"error diffing {}:{}: {:?}",
package.name, package.version, err
);
None
}
}
}))
.await;
Some(SuggestItem {
package: failure_idx,
suggested_diff: diffstats
.into_iter()
.flatten()
.min_by_key(|diff| diff.diffstat.count)?,
suggested_criteria: audit_failure.criteria_failures.clone(),
notable_parents,
})
},
)))
.into_iter()
.flatten()
.collect::<Vec<_>>();
let total_lines = suggestions
.iter()
.map(|s| s.suggested_diff.diffstat.count)
.sum();
suggestions.sort_by_key(|item| self.graph.nodes[item.package].version);
suggestions.sort_by_key(|item| self.graph.nodes[item.package].name);
suggestions.sort_by_key(|item| item.suggested_diff.diffstat.count);
suggestions.sort_by_key(|item| item.suggested_criteria.is_fully_unconfident());
let mut suggestions_by_criteria = SortedMap::<CriteriaName, Vec<SuggestItem>>::new();
for s in suggestions.clone().into_iter() {
let criteria_names = self
.criteria_mapper
.all_criteria_names(&s.suggested_criteria)
.collect::<Vec<_>>()
.join(", ");
suggestions_by_criteria
.entry(criteria_names)
.or_default()
.push(s);
}
Ok(Some(Suggest {
suggestions,
suggestions_by_criteria,
total_lines,
}))
}
pub fn compute_suggested_criteria(
&self,
package_name: PackageStr<'_>,
from: Option<&Version>,
to: &Version,
) -> Vec<CriteriaName> {
let fail = if let Conclusion::FailForVet(fail) = &self.conclusion {
fail
} else {
return Vec::new();
};
let mut criteria = self.criteria_mapper.no_criteria();
for (&failure_idx, audit_failure) in &fail.failures {
let package = &self.graph.nodes[failure_idx];
if package.name != package_name {
continue;
}
let result = &self.results[failure_idx];
for criteria_idx in audit_failure.criteria_failures.all().indices() {
let search_result = &result.search_results[criteria_idx];
if let SearchResult::Disconnected {
reachable_from_root,
reachable_from_target,
} = search_result
{
if reachable_from_target.contains(&Some(to))
&& from.map_or(true, |v| reachable_from_root.contains(&Some(v)))
{
criteria.set_criteria(criteria_idx);
}
}
}
}
self.criteria_mapper
.criteria_names(&criteria)
.map(str::to_owned)
.collect()
}
pub fn print_human(
&self,
out: &Arc<dyn Out>,
cfg: &Config,
suggest: Option<&Suggest>,
) -> Result<(), std::io::Error> {
match &self.conclusion {
Conclusion::Success(res) => res.print_human(out, self, cfg),
Conclusion::FailForViolationConflict(res) => res.print_human(out, self, cfg),
Conclusion::FailForVet(res) => res.print_human(out, self, cfg, suggest),
}
}
pub fn print_suggest_human(
&self,
out: &Arc<dyn Out>,
_cfg: &Config,
suggest: Option<&Suggest>,
) -> Result<(), std::io::Error> {
if let Some(suggest) = suggest {
suggest.print_human(out, self)?;
} else {
writeln!(out, "Nothing to suggest, you're fully audited!");
}
Ok(())
}
pub fn print_json(
&self,
out: &Arc<dyn Out>,
_cfg: &Config,
suggest: Option<&Suggest>,
) -> Result<(), miette::Report> {
let result = match &self.conclusion {
Conclusion::Success(success) => {
let json_package = |pkgidx: &PackageIdx| {
let package = &self.graph.nodes[*pkgidx];
json!({
"name": package.name,
"version": package.version,
})
};
json!({
"conclusion": "success",
"vetted_fully": success.vetted_fully.iter().map(json_package).collect::<Vec<_>>(),
"vetted_partially": success.vetted_partially.iter().map(json_package).collect::<Vec<_>>(),
"vetted_with_exemptions": success.vetted_with_exemptions.iter().map(json_package).collect::<Vec<_>>(),
})
}
Conclusion::FailForViolationConflict(fail) => json!({
"conclusion": "fail (violation)",
"violations": fail.violations.iter().map(|(pkgidx, violations)| {
let package = &self.graph.nodes[*pkgidx];
let key = format!("{}:{}", package.name, package.version);
(key, violations)
}).collect::<SortedMap<_,_>>(),
}),
Conclusion::FailForVet(fail) => {
let json_suggest_item = |item: &SuggestItem| {
let package = &self.graph.nodes[item.package];
json!({
"name": package.name,
"notable_parents": item.notable_parents,
"suggested_criteria": self.criteria_mapper.all_criteria_names(&item.suggested_criteria).collect::<Vec<_>>(),
"suggested_diff": item.suggested_diff,
})
};
json!({
"conclusion": "fail (vetting)",
"failures": fail.failures.iter().map(|(&pkgidx, audit_fail)| {
let package = &self.graph.nodes[pkgidx];
json!({
"name": package.name,
"version": package.version,
"missing_criteria": self.criteria_mapper.all_criteria_names(&audit_fail.criteria_failures).collect::<Vec<_>>(),
})
}).collect::<Vec<_>>(),
"suggest": suggest.map(|suggest| json!({
"suggestions": suggest.suggestions.iter().map(json_suggest_item).collect::<Vec<_>>(),
"suggest_by_criteria": suggest.suggestions_by_criteria.iter().map(|(criteria, items)| (criteria, items.iter().map(json_suggest_item).collect::<Vec<_>>())).collect::<SortedMap<_,_>>(),
"total_lines": suggest.total_lines,
})),
})
}
};
serde_json::to_writer_pretty(&**out, &result).into_diagnostic()?;
Ok(())
}
}
impl Success {
pub fn print_human(
&self,
out: &Arc<dyn Out>,
_report: &ResolveReport,
_cfg: &Config,
) -> Result<(), std::io::Error> {
let fully_audited_count = self.vetted_fully.len();
let partially_audited_count: usize = self.vetted_partially.len();
let exemptions_count = self.vetted_with_exemptions.len();
let mut count_count = (fully_audited_count != 0) as usize
+ (partially_audited_count != 0) as usize
+ (exemptions_count != 0) as usize;
if count_count == 0 {
writeln!(
out,
"Vetting Succeeded (because you have no third-party dependencies)"
);
} else {
write!(out, "Vetting Succeeded (");
if fully_audited_count != 0 {
write!(out, "{} fully audited", fully_audited_count);
count_count -= 1;
if count_count > 0 {
write!(out, ", ");
}
}
if partially_audited_count != 0 {
write!(out, "{} partially audited", partially_audited_count);
count_count -= 1;
if count_count > 0 {
write!(out, ", ");
}
}
if exemptions_count != 0 {
write!(out, "{} exempted", exemptions_count);
count_count -= 1;
if count_count > 0 {
write!(out, ", ");
}
}
writeln!(out, ")");
}
Ok(())
}
}
impl Suggest {
pub fn print_human(
&self,
out: &Arc<dyn Out>,
report: &ResolveReport,
) -> Result<(), std::io::Error> {
for (criteria, suggestions) in &self.suggestions_by_criteria {
writeln!(out, "recommended audits for {}:", criteria);
let strings = suggestions
.iter()
.map(|item| {
let package = &report.graph.nodes[item.package];
let cmd = match &item.suggested_diff.from {
Some(from) => format!(
"cargo vet diff {} {} {}",
package.name, from, item.suggested_diff.to
),
None => format!(
"cargo vet inspect {} {}",
package.name, item.suggested_diff.to
),
};
let parents = format!("(used by {})", item.notable_parents);
let diffstat = match &item.suggested_diff.from {
Some(_) => format!("({})", item.suggested_diff.diffstat.raw.trim()),
None => format!("({} lines)", item.suggested_diff.diffstat.count),
};
let style = if item.suggested_criteria.is_fully_unconfident() {
out.style().dim()
} else {
out.style()
};
(cmd, parents, diffstat, style)
})
.collect::<Vec<_>>();
let mut max0 = 0;
let mut max1 = 0;
for (s0, s1, ..) in &strings {
max0 = max0.max(console::measure_text_width(s0));
max1 = max1.max(console::measure_text_width(s1));
}
for (s0, s1, s2, style) in strings {
write!(
out,
"{}",
style
.clone()
.cyan()
.bold()
.apply_to(format_args!(" {s0:width$}", width = max0))
);
writeln!(
out,
"{}",
style.apply_to(format_args!(" {s1:width$} {s2}", width = max1))
);
}
writeln!(out);
}
writeln!(out, "estimated audit backlog: {} lines", self.total_lines);
writeln!(out);
writeln!(out, "Use |cargo vet certify| to record the audits.");
Ok(())
}
}
impl FailForVet {
fn print_human(
&self,
out: &Arc<dyn Out>,
report: &ResolveReport,
_cfg: &Config,
suggest: Option<&Suggest>,
) -> Result<(), std::io::Error> {
writeln!(out, "Vetting Failed!");
writeln!(out);
writeln!(out, "{} unvetted dependencies:", self.failures.len());
let mut failures = self
.failures
.iter()
.map(|(&failed_idx, failure)| (&report.graph.nodes[failed_idx], failure))
.collect::<Vec<_>>();
failures.sort_by_key(|(failed, _)| failed.version);
failures.sort_by_key(|(failed, _)| failed.name);
failures.sort_by_key(|(_, failure)| failure.criteria_failures.is_fully_unconfident());
for (failed_package, failed_audit) in failures {
let confident_criteria = report
.criteria_mapper
.confident_criteria_names(&failed_audit.criteria_failures)
.collect::<Vec<_>>();
let unconfident_criteria = report
.criteria_mapper
.unconfident_criteria_names(&failed_audit.criteria_failures)
.collect::<Vec<_>>();
let label = format!(" {}:{}", failed_package.name, failed_package.version);
if !confident_criteria.is_empty() {
writeln!(out, "{} missing {:?}", label, confident_criteria);
}
if !unconfident_criteria.is_empty() {
writeln!(
out,
"{}",
out.style().dim().apply_to(format_args!(
"{} likely missing {:?}",
if confident_criteria.is_empty() {
label
} else {
format!("{:width$}", "", width = console::measure_text_width(&label))
},
unconfident_criteria
))
);
}
}
if let Some(suggest) = suggest {
writeln!(out);
suggest.print_human(out, report)?;
}
Ok(())
}
}
impl FailForViolationConflict {
fn print_human(
&self,
out: &Arc<dyn Out>,
report: &ResolveReport,
_cfg: &Config,
) -> Result<(), std::io::Error> {
writeln!(out, "Violations Found!");
for (&pkgidx, violations) in &self.violations {
let package = &report.graph.nodes[pkgidx];
writeln!(out, " {}:{}", package.name, package.version);
for violation in violations {
match violation {
ViolationConflict::UnauditedConflict {
violation_source,
violation,
exemptions,
} => {
write!(out, " the ");
print_exemption(out, exemptions)?;
write!(out, " conflicts with ");
print_entry(out, violation_source, violation)?;
}
ViolationConflict::AuditConflict {
violation_source,
violation,
audit_source,
audit,
} => {
write!(out, " the ");
print_entry(out, audit_source, audit)?;
write!(out, " conflicts with ");
print_entry(out, violation_source, violation)?;
}
}
writeln!(out);
}
}
fn print_exemption(
out: &Arc<dyn Out>,
entry: &ExemptedDependency,
) -> Result<(), std::io::Error> {
writeln!(out, "exemption {}", entry.version);
writeln!(out, " criteria: {:?}", entry.criteria);
if let Some(notes) = &entry.notes {
writeln!(out, " notes: {notes}");
}
Ok(())
}
fn print_entry(
out: &Arc<dyn Out>,
source: &CriteriaNamespace,
entry: &AuditEntry,
) -> Result<(), std::io::Error> {
match source {
CriteriaNamespace::Local => write!(out, "own "),
CriteriaNamespace::Foreign(name) => write!(out, "foreign ({name}) "),
}
match &entry.kind {
AuditKind::Full { version, .. } => {
writeln!(out, "audit {version}");
}
AuditKind::Delta { from, to, .. } => {
writeln!(out, "audit {} -> {}", from, to);
}
AuditKind::Violation { violation } => {
writeln!(out, "violation against {violation}");
}
}
writeln!(out, " criteria: {:?}", entry.criteria);
if let Some(who) = &entry.who {
writeln!(out, " who: {who}");
}
if let Some(notes) = &entry.notes {
writeln!(out, " notes: {notes}");
}
Ok(())
}
Ok(())
}
}
pub fn regenerate_exemptions(
cfg: &Config,
store: &mut Store,
allow_new_exemptions: bool,
force_update_imports: bool,
) -> Result<(), RegenerateExemptionsError> {
let graph = DepGraph::new(
&cfg.metadata,
cfg.cli.filter_graph.as_ref(),
Some(&store.config.policy),
);
let criteria_mapper = CriteriaMapper::new(
&store.audits.criteria,
store.imported_audits(),
&store.config.imports,
);
if !allow_new_exemptions {
let (_, conclusion) = resolve_core(&graph, store, &criteria_mapper, ResolveDepth::Shallow);
if !matches!(conclusion, Conclusion::Success(_)) || cfg.cli.no_minimize_exemptions {
store.imports =
store.get_updated_imports_file(&graph, &conclusion, force_update_imports);
return Ok(());
}
}
let old_exemptions = mem::take(&mut store.config.exemptions);
let mut pkg_versions_by_name: SortedMap<PackageStr<'_>, Vec<&Version>> = SortedMap::new();
for package in &graph.nodes {
pkg_versions_by_name
.entry(package.name)
.or_default()
.push(package.version);
}
for versions in pkg_versions_by_name.values_mut() {
versions.sort();
}
struct PotentialExemption<'a> {
version: &'a Version,
max_criteria: CriteriaSet,
useful_criteria: CriteriaSet,
suggest: bool,
dependency_criteria: &'a DependencyCriteria,
notes: &'a Option<String>,
}
impl<'a> PotentialExemption<'a> {
fn is_special(&self) -> bool {
!self.suggest || !self.dependency_criteria.is_empty()
}
}
let no_dependency_criteria = DependencyCriteria::new();
let mut potential_exemptions = SortedMap::<PackageStr<'_>, Vec<PotentialExemption<'_>>>::new();
loop {
let (results, conclusion) =
resolve_core(&graph, store, &criteria_mapper, ResolveDepth::Shallow);
let fail = match &conclusion {
Conclusion::FailForVet(fail) => fail,
Conclusion::FailForViolationConflict(..) => {
store.imports =
store.get_updated_imports_file(&graph, &conclusion, force_update_imports);
return Err(RegenerateExemptionsError::ViolationConflict);
}
Conclusion::Success(..) => {
store.imports =
store.get_updated_imports_file(&graph, &conclusion, force_update_imports);
return Ok(());
}
};
let mut made_progress = false;
let mut update_useful_criteria =
|potential: &mut PotentialExemption<'_>, new_criteria: &CriteriaSet| {
if potential.useful_criteria.contains(new_criteria) {
return;
}
made_progress = true;
potential.useful_criteria.unioned_with(new_criteria);
};
for (&failure_idx, failure) in &fail.failures {
let package = &graph.nodes[failure_idx];
let result = &results[failure_idx];
trace!("minimizing exemptions for {}", package.name);
let potential_exemptions =
potential_exemptions.entry(package.name).or_insert_with(|| {
let existing_exemptions = old_exemptions
.get(package.name)
.map(|v| &v[..])
.unwrap_or(&[]);
let mut potentials: Vec<_> = existing_exemptions
.iter()
.map(|exemption| {
let mut potential = PotentialExemption {
version: &exemption.version,
max_criteria: criteria_mapper.all_criteria(),
useful_criteria: criteria_mapper.no_criteria(),
suggest: exemption.suggest,
dependency_criteria: &exemption.dependency_criteria,
notes: &exemption.notes,
};
if potential.is_special() || !allow_new_exemptions {
potential.max_criteria = criteria_mapper
.criteria_from_namespaced_list(
&CriteriaNamespace::Local,
&exemption.criteria,
);
}
potential
})
.collect();
if allow_new_exemptions {
for exemption in existing_exemptions {
if !exemption.suggest && exemption.dependency_criteria.is_empty() {
potentials.push(PotentialExemption {
version: &exemption.version,
max_criteria: criteria_mapper.all_criteria(),
useful_criteria: criteria_mapper.no_criteria(),
suggest: true,
dependency_criteria: &no_dependency_criteria,
notes: &exemption.notes,
})
}
}
potentials.extend(
pkg_versions_by_name
.get(package.name)
.expect("no versions of failed package?")
.iter()
.map(|version| PotentialExemption {
version,
max_criteria: criteria_mapper.all_criteria(),
useful_criteria: criteria_mapper.no_criteria(),
suggest: true,
dependency_criteria: &no_dependency_criteria,
notes: &None,
}),
);
}
potentials
});
'min_criteria: for criteria_idx in
criteria_mapper.minimal_indices(failure.criteria_failures.confident())
{
let implied = &criteria_mapper.implied_criteria[criteria_idx];
let mut missed_criteria = false;
for implied_idx in implied.indices() {
let reachable_from_target = match &result.search_results[implied_idx] {
SearchResult::Disconnected {
reachable_from_target,
..
} => reachable_from_target,
_ => continue,
};
let mut found = false;
for potential in &mut potential_exemptions[..] {
if potential.is_special()
&& potential.max_criteria.has_criteria(implied_idx)
&& reachable_from_target.contains(&Some(potential.version))
{
found = true;
update_useful_criteria(
potential,
&criteria_mapper.implied_criteria[implied_idx],
);
}
}
if !found {
missed_criteria = true;
}
}
if !missed_criteria {
continue;
}
let reachable_from_target = match &result.search_results[criteria_idx] {
SearchResult::Disconnected {
reachable_from_target,
..
} => reachable_from_target,
_ => unreachable!("minimal criteria didn't actually fail?"),
};
for potential in &mut potential_exemptions[..] {
if potential.max_criteria.has_criteria(criteria_idx)
&& reachable_from_target.contains(&Some(potential.version))
{
update_useful_criteria(
potential,
&criteria_mapper.implied_criteria[criteria_idx],
);
continue 'min_criteria;
}
}
if allow_new_exemptions {
unreachable!("couldn't find an exemption which satisfies the criteria?");
}
for potential in &mut potential_exemptions[..] {
let new_criteria = potential.max_criteria.clone();
update_useful_criteria(potential, &new_criteria);
}
break;
}
}
if made_progress {
store.config.exemptions = potential_exemptions
.iter()
.filter_map(|(&package_name, potential)| {
let mut exemptions: Vec<_> = potential
.iter()
.filter(|potential| !potential.useful_criteria.is_empty())
.map(|potential| ExemptedDependency {
version: potential.version.clone(),
criteria: criteria_mapper
.criteria_names(&potential.useful_criteria)
.map(|criteria| criteria.to_owned().into())
.collect(),
suggest: potential.suggest,
dependency_criteria: potential.dependency_criteria.clone(),
notes: potential.notes.clone(),
})
.collect();
exemptions.sort();
if exemptions.is_empty() {
None
} else {
Some((package_name.to_owned(), exemptions))
}
})
.collect();
} else {
assert!(
!allow_new_exemptions,
"failed to make progress while allowing new exemptions?"
);
store.config.exemptions = old_exemptions.clone();
}
}
}