Skip to main content

cargo/compiler/
unused_deps.rs

1use std::collections::BTreeSet;
2
3use crate::util::data_structures::IndexMap;
4use crate::util::data_structures::IndexSet;
5use tracing::{instrument, trace};
6
7use super::BuildContext;
8use super::unit::Unit;
9use crate::compiler::build_config::CompileMode;
10use crate::util::interning::InternedString;
11use crate::workspace::Dependency;
12use crate::workspace::PackageId;
13use crate::workspace::dependency::DepKind;
14use crate::workspace::manifest::TargetKind;
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::default();
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>>::default();
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            let sibling_targets = root.pkg.targets();
62            let siibling_lib_exists = || sibling_targets.iter().any(|target| target.is_lib());
63            let linkable_sibling_exists =
64                || sibling_targets.iter().any(|target| target.is_linkable());
65            let sibling_lib_present = || {
66                bcx.unit_graph.keys().any(|unit| {
67                    unit.pkg.package_id() == pkg_id
68                        && unit.target.is_lib()
69                        && matches!(
70                            unit.mode,
71                            CompileMode::Build | CompileMode::Check { test: false }
72                        )
73                })
74            };
75            if dep_kind == DepKind::Normal
76                && !root.target.is_lib()
77                && siibling_lib_exists()
78                // don't bother walking all units if all libs are linkable
79                && !linkable_sibling_exists()
80                && !sibling_lib_present()
81            {
82                // In some cases a lib target without an rlib isn't built,
83                // preventing any other normal dep from identifying unused deps,
84                // like bin and staticlib when running `cargo test`
85                //
86                // This would be messier if lib's supported `required-features`
87                trace!(
88                    "pkg {} v{} ({dep_kind:?}): ignoring unused deps due to the lib's build being skipped",
89                    pkg_id.name(),
90                    pkg_id.version(),
91                );
92                continue;
93            }
94
95            trace!(
96                "tracking root {} {} ({:?})",
97                root.pkg.name(),
98                unit_desc(root),
99                dep_kind
100            );
101
102            let state = states
103                .entry(pkg_id)
104                .or_default()
105                .entry(dep_kind)
106                .or_default();
107            state.needed_units += 1;
108            for dep in bcx.unit_graph[root].iter() {
109                trace!(
110                    "    => {} (deps={})",
111                    dep.unit.pkg.name(),
112                    dep.manifest_deps.0.is_some()
113                );
114                let manifest_deps = if let Some(manifest_deps) = &dep.manifest_deps.0 {
115                    Some(manifest_deps.clone())
116                } else if dep.unit.pkg.package_id() == root.pkg.package_id() {
117                    None
118                } else {
119                    continue;
120                };
121                state.externs.insert(
122                    dep.extern_crate_name,
123                    ExternState {
124                        unit: dep.unit.clone(),
125                        manifest_deps,
126                    },
127                );
128            }
129        }
130
131        Self { states }
132    }
133
134    pub fn record_unused_externs_for_unit(
135        &mut self,
136        unit: &Unit,
137        unused_externs: BTreeSet<InternedString>,
138    ) {
139        let pkg_id = unit.pkg.package_id();
140        let dep_kind = dep_kind_of(unit);
141        trace!(
142            "pkg {} v{} ({dep_kind:?}): unused externs {unused_externs:?}",
143            pkg_id.name(),
144            pkg_id.version(),
145        );
146        let state = self
147            .states
148            .entry(pkg_id)
149            .or_default()
150            .entry(dep_kind)
151            .or_default();
152        state.seen_units.push(unit.clone());
153        if let Some(existing) = state.unused_externs.as_mut() {
154            existing.retain(|ext| unused_externs.contains(ext));
155        } else {
156            state.unused_externs = Some(unused_externs);
157        }
158    }
159}
160
161/// Track a package's [`DepKind`]
162#[derive(Default)]
163pub struct DependenciesState {
164    /// All declared dependencies
165    pub externs: IndexMap<InternedString, ExternState>,
166    /// Expected [`Self::seen_units`] entries to know we've received them all
167    ///
168    /// To avoid warning in cases where we didn't,
169    /// e.g. if a [`Unit`] errored and didn't report unused externs.
170    pub needed_units: usize,
171    /// Units that have reported their unused externs
172    pub seen_units: Vec<Unit>,
173    /// Intersection of unused externs across all [`Self::seen_units`]
174    pub unused_externs: Option<BTreeSet<InternedString>>,
175}
176
177#[derive(Clone)]
178pub struct ExternState {
179    pub unit: Unit,
180    pub manifest_deps: Option<Vec<Dependency>>,
181}
182
183fn dep_kind_of(unit: &Unit) -> DepKind {
184    match unit.target.kind() {
185        TargetKind::Lib(_) => match unit.mode {
186            // To support lib.rs with #[cfg(test)] use foo_crate as _;
187            CompileMode::Test => DepKind::Development,
188            _ => DepKind::Normal,
189        },
190        TargetKind::Bin => DepKind::Normal,
191        TargetKind::Test => DepKind::Development,
192        TargetKind::Bench => DepKind::Development,
193        TargetKind::ExampleLib(_) => DepKind::Development,
194        TargetKind::ExampleBin => DepKind::Development,
195        TargetKind::CustomBuild => DepKind::Build,
196    }
197}
198
199fn unit_desc(unit: &Unit) -> String {
200    format!(
201        "{}/{}+{:?}",
202        unit.target.name(),
203        unit.target.kind().description(),
204        unit.mode,
205    )
206}