Skip to main content

cargo/core/compiler/
unused_deps.rs

1use std::collections::BTreeSet;
2
3use indexmap::IndexMap;
4use indexmap::IndexSet;
5use tracing::{instrument, trace};
6
7use super::BuildContext;
8use super::unit::Unit;
9use crate::core::Dependency;
10use crate::core::PackageId;
11use crate::core::compiler::build_config::CompileMode;
12use crate::core::dependency::DepKind;
13use crate::core::manifest::TargetKind;
14use crate::util::interning::InternedString;
15
16/// Track and translate `unused_externs` to `unused_dependencies`
17pub struct UnusedDepState {
18    pub states: IndexMap<PackageId, IndexMap<DepKind, DependenciesState>>,
19}
20
21impl UnusedDepState {
22    #[instrument(name = "UnusedDepState::new", skip_all)]
23    pub fn new(bcx: &BuildContext<'_, '_>) -> Self {
24        // Find all units for a package that can report unused externs
25        let mut root_build_script_builds = IndexSet::new();
26        let roots = &bcx.roots;
27        for root in roots.iter() {
28            for build_script_run in bcx.unit_graph[root].iter() {
29                if !build_script_run.unit.target.is_custom_build()
30                    && build_script_run.unit.pkg.package_id() != root.pkg.package_id()
31                {
32                    continue;
33                }
34                for build_script_build in bcx.unit_graph[&build_script_run.unit].iter() {
35                    if !build_script_build.unit.target.is_custom_build()
36                        && build_script_build.unit.pkg.package_id() != root.pkg.package_id()
37                    {
38                        continue;
39                    }
40                    if build_script_build.unit.mode != CompileMode::Build {
41                        continue;
42                    }
43                    root_build_script_builds.insert(build_script_build.unit.clone());
44                }
45            }
46        }
47
48        trace!("selected dep kinds: {:?}", bcx.selected_dep_kinds);
49        let mut states = IndexMap::<_, IndexMap<_, DependenciesState>>::new();
50        for root in roots.iter().chain(root_build_script_builds.iter()) {
51            let pkg_id = root.pkg.package_id();
52            let dep_kind = dep_kind_of(root);
53            if !bcx.selected_dep_kinds.contains(dep_kind) {
54                trace!(
55                    "pkg {} v{} ({dep_kind:?}): ignoring unused deps due to non-exhaustive units",
56                    pkg_id.name(),
57                    pkg_id.version(),
58                );
59                continue;
60            }
61            trace!(
62                "tracking root {} {} ({:?})",
63                root.pkg.name(),
64                unit_desc(root),
65                dep_kind
66            );
67
68            let state = states
69                .entry(pkg_id)
70                .or_default()
71                .entry(dep_kind)
72                .or_default();
73            state.needed_units += 1;
74            for dep in bcx.unit_graph[root].iter() {
75                trace!(
76                    "    => {} (deps={})",
77                    dep.unit.pkg.name(),
78                    dep.manifest_deps.0.is_some()
79                );
80                let manifest_deps = if let Some(manifest_deps) = &dep.manifest_deps.0 {
81                    Some(manifest_deps.clone())
82                } else if dep.unit.pkg.package_id() == root.pkg.package_id() {
83                    None
84                } else {
85                    continue;
86                };
87                state.externs.insert(
88                    dep.extern_crate_name,
89                    ExternState {
90                        unit: dep.unit.clone(),
91                        manifest_deps,
92                    },
93                );
94            }
95        }
96
97        Self { states }
98    }
99
100    pub fn record_unused_externs_for_unit(
101        &mut self,
102        unit: &Unit,
103        unused_externs: BTreeSet<InternedString>,
104    ) {
105        let pkg_id = unit.pkg.package_id();
106        let dep_kind = dep_kind_of(unit);
107        trace!(
108            "pkg {} v{} ({dep_kind:?}): unused externs {unused_externs:?}",
109            pkg_id.name(),
110            pkg_id.version(),
111        );
112        let state = self
113            .states
114            .entry(pkg_id)
115            .or_default()
116            .entry(dep_kind)
117            .or_default();
118        state.seen_units.push(unit.clone());
119        if let Some(existing) = state.unused_externs.as_mut() {
120            existing.retain(|ext| unused_externs.contains(ext));
121        } else {
122            state.unused_externs = Some(unused_externs);
123        }
124    }
125}
126
127/// Track a package's [`DepKind`]
128#[derive(Default)]
129pub struct DependenciesState {
130    /// All declared dependencies
131    pub externs: IndexMap<InternedString, ExternState>,
132    /// Expected [`Self::seen_units`] entries to know we've received them all
133    ///
134    /// To avoid warning in cases where we didn't,
135    /// e.g. if a [`Unit`] errored and didn't report unused externs.
136    pub needed_units: usize,
137    /// Units that have reported their unused externs
138    pub seen_units: Vec<Unit>,
139    /// Intersection of unused externs across all [`Self::seen_units`]
140    pub unused_externs: Option<BTreeSet<InternedString>>,
141}
142
143#[derive(Clone)]
144pub struct ExternState {
145    pub unit: Unit,
146    pub manifest_deps: Option<Vec<Dependency>>,
147}
148
149fn dep_kind_of(unit: &Unit) -> DepKind {
150    match unit.target.kind() {
151        TargetKind::Lib(_) => match unit.mode {
152            // To support lib.rs with #[cfg(test)] use foo_crate as _;
153            CompileMode::Test => DepKind::Development,
154            _ => DepKind::Normal,
155        },
156        TargetKind::Bin => DepKind::Normal,
157        TargetKind::Test => DepKind::Development,
158        TargetKind::Bench => DepKind::Development,
159        TargetKind::ExampleLib(_) => DepKind::Development,
160        TargetKind::ExampleBin => DepKind::Development,
161        TargetKind::CustomBuild => DepKind::Build,
162    }
163}
164
165fn unit_desc(unit: &Unit) -> String {
166    format!(
167        "{}/{}+{:?}",
168        unit.target.name(),
169        unit.target.kind().description(),
170        unit.mode,
171    )
172}