cargo/core/compiler/
unused_deps.rs1use cargo_util_schemas::manifest;
2use cargo_util_terminal::report::AnnotationKind;
3use cargo_util_terminal::report::Group;
4use cargo_util_terminal::report::Level;
5use cargo_util_terminal::report::Origin;
6use cargo_util_terminal::report::Patch;
7use cargo_util_terminal::report::Snippet;
8use indexmap::IndexMap;
9use indexmap::IndexSet;
10use tracing::trace;
11
12use super::BuildRunner;
13use super::unit::Unit;
14use crate::core::Dependency;
15use crate::core::Package;
16use crate::core::PackageId;
17use crate::core::compiler::build_config::CompileMode;
18use crate::core::dependency::DepKind;
19use crate::core::manifest::TargetKind;
20use crate::lints::LintLevel;
21use crate::lints::get_key_value_span;
22use crate::lints::rel_cwd_manifest_path;
23use crate::lints::rules::unused_dependencies::LINT;
24use crate::util::errors::CargoResult;
25use crate::util::interning::InternedString;
26
27pub struct UnusedDepState {
29 states: IndexMap<PackageId, IndexMap<DepKind, DependenciesState>>,
30}
31
32impl UnusedDepState {
33 pub fn new(build_runner: &mut BuildRunner<'_, '_>) -> Self {
34 let mut states = IndexMap::<_, IndexMap<_, DependenciesState>>::new();
35
36 let roots = &build_runner.bcx.roots;
37
38 let mut root_build_script_builds = IndexSet::new();
40 for root in roots.iter() {
41 for build_script_run in build_runner.unit_deps(root).iter() {
42 if !build_script_run.unit.target.is_custom_build()
43 && build_script_run.unit.pkg.package_id() != root.pkg.package_id()
44 {
45 continue;
46 }
47 for build_script_build in build_runner.unit_deps(&build_script_run.unit).iter() {
48 if !build_script_build.unit.target.is_custom_build()
49 && build_script_build.unit.pkg.package_id() != root.pkg.package_id()
50 {
51 continue;
52 }
53 if build_script_build.unit.mode != CompileMode::Build {
54 continue;
55 }
56 root_build_script_builds.insert(build_script_build.unit.clone());
57 }
58 }
59 }
60
61 trace!(
62 "selected dep kinds: {:?}",
63 build_runner.bcx.selected_dep_kinds
64 );
65 for root in roots.iter().chain(root_build_script_builds.iter()) {
66 let pkg_id = root.pkg.package_id();
67 let dep_kind = dep_kind_of(root);
68 if !build_runner.bcx.selected_dep_kinds.contains(dep_kind) {
69 trace!(
70 "pkg {} v{} ({dep_kind:?}): ignoring unused deps due to non-exhaustive units",
71 pkg_id.name(),
72 pkg_id.version(),
73 );
74 continue;
75 }
76 trace!(
77 "tracking root {} {} ({:?})",
78 root.pkg.name(),
79 unit_desc(root),
80 dep_kind
81 );
82
83 let state = states
84 .entry(pkg_id)
85 .or_default()
86 .entry(dep_kind)
87 .or_default();
88 state.needed_units += 1;
89 for dep in build_runner.unit_deps(root).iter() {
90 trace!(
91 " => {} (deps={})",
92 dep.unit.pkg.name(),
93 dep.manifest_deps.0.is_some()
94 );
95 let manifest_deps = if let Some(manifest_deps) = &dep.manifest_deps.0 {
96 Some(manifest_deps.clone())
97 } else if dep.unit.pkg.package_id() == root.pkg.package_id() {
98 None
99 } else {
100 continue;
101 };
102 state.externs.insert(dep.extern_crate_name, manifest_deps);
103 }
104 }
105
106 Self { states }
107 }
108
109 pub fn record_unused_externs_for_unit(&mut self, unit: &Unit, unused_externs: Vec<String>) {
110 let pkg_id = unit.pkg.package_id();
111 let kind = dep_kind_of(unit);
112 if let Some(state) = self.states.get_mut(&pkg_id).and_then(|s| s.get_mut(&kind)) {
113 state
114 .unused_externs
115 .entry(unit.clone())
116 .or_default()
117 .extend(unused_externs.into_iter().map(|s| InternedString::new(&s)));
118 }
119 }
120
121 pub fn emit_unused_warnings(
122 &self,
123 warn_count: &mut usize,
124 error_count: &mut usize,
125 build_runner: &mut BuildRunner<'_, '_>,
126 ) -> CargoResult<()> {
127 for (pkg_id, states) in &self.states {
128 let Some(pkg) = self.get_package(pkg_id) else {
129 continue;
130 };
131 let toml_lints = pkg
132 .manifest()
133 .normalized_toml()
134 .lints
135 .clone()
136 .map(|lints| lints.lints)
137 .unwrap_or(manifest::TomlLints::default());
138 let cargo_lints = toml_lints
139 .get("cargo")
140 .cloned()
141 .unwrap_or(manifest::TomlToolLints::default());
142 let (lint_level, reason) = LINT.level(
143 &cargo_lints,
144 pkg.rust_version(),
145 pkg.manifest().unstable_features(),
146 );
147
148 if lint_level == LintLevel::Allow {
149 continue;
150 }
151
152 let mut ignore = Vec::new();
153 if let Some(unused_dependencies) = cargo_lints.get("unused_dependencies") {
154 if let Some(config) = unused_dependencies.config() {
155 if let Some(config_ignore) = config.get("ignore") {
156 if let Ok(config_ignore) =
157 toml::Value::try_into::<Vec<InternedString>>(config_ignore.clone())
158 {
159 ignore = config_ignore
160 } else {
161 anyhow::bail!(
162 "`lints.cargo.unused_dependencies.ignore` must be a list of string"
163 );
164 }
165 }
166 }
167 }
168
169 let manifest_path = rel_cwd_manifest_path(pkg.manifest_path(), build_runner.bcx.gctx);
170 let mut lint_count = 0;
171 for (dep_kind, state) in states.iter() {
172 if state.unused_externs.len() != state.needed_units {
173 trace!(
177 "pkg {} v{} ({dep_kind:?}): ignoring unused deps due to {} outstanding units",
178 pkg_id.name(),
179 pkg_id.version(),
180 state.needed_units
181 );
182 continue;
183 }
184
185 for (ext, dependency) in &state.externs {
186 if state
187 .unused_externs
188 .values()
189 .any(|unused| !unused.contains(ext))
190 {
191 trace!(
192 "pkg {} v{} ({dep_kind:?}): extern {} is used",
193 pkg_id.name(),
194 pkg_id.version(),
195 ext
196 );
197 continue;
198 }
199
200 let dependency = if let Some(dependency) = dependency {
202 dependency
203 } else {
204 continue;
205 };
206 for dependency in dependency {
207 if ignore.contains(&dependency.name_in_toml()) {
208 trace!(
209 "pkg {} v{} ({dep_kind:?}): extern {} is ignored",
210 pkg_id.name(),
211 pkg_id.version(),
212 ext
213 );
214 continue;
215 }
216
217 let manifest = pkg.manifest();
218 let document = manifest.document();
219 let contents = manifest.contents();
220 let level = lint_level.to_diagnostic_level();
221 let emitted_source = LINT.emitted_source(lint_level, reason);
222 let toml_path = dependency.toml_path();
223
224 let mut primary = Group::with_title(level.primary_title(LINT.desc));
225 if let Some(document) = document
226 && let Some(contents) = contents
227 && let Some(span) = get_key_value_span(document, &toml_path)
228 {
229 let span = span.key.start..span.value.end;
230 primary = primary.element(
231 Snippet::source(contents)
232 .path(&manifest_path)
233 .annotation(AnnotationKind::Primary.span(span)),
234 );
235 } else {
236 primary = primary.element(Origin::path(&manifest_path));
237 }
238 if lint_count == 0 {
239 primary = primary.element(Level::NOTE.message(emitted_source));
240 }
241 lint_count += 1;
242 let mut report = vec![primary];
243 if let Some(document) = document
244 && let Some(contents) = contents
245 && let Some(span) = get_key_value_span(document, &toml_path)
246 {
247 let span = span.key.start..span.value.end;
248 let mut help = Group::with_title(
249 Level::HELP.secondary_title("remove the dependency"),
250 );
251 help = help.element(
252 Snippet::source(contents)
253 .path(&manifest_path)
254 .patch(Patch::new(span, "")),
255 );
256 report.push(help);
257 }
258
259 if lint_level.is_warn() {
260 *warn_count += 1;
261 }
262 if lint_level.is_error() {
263 *error_count += 1;
264 }
265 build_runner
266 .bcx
267 .gctx
268 .shell()
269 .print_report(&report, lint_level.force())?;
270 }
271 }
272 }
273 }
274 Ok(())
275 }
276
277 fn get_package(&self, pkg_id: &PackageId) -> Option<&Package> {
278 let state = self.states.get(pkg_id)?;
279 let mut iter = state.values();
280 let state = iter.next()?;
281 let mut iter = state.unused_externs.keys();
282 let unit = iter.next()?;
283 Some(&unit.pkg)
284 }
285}
286
287#[derive(Default)]
289struct DependenciesState {
290 externs: IndexMap<InternedString, Option<Vec<Dependency>>>,
292 needed_units: usize,
297 unused_externs: IndexMap<Unit, Vec<InternedString>>,
299}
300
301fn dep_kind_of(unit: &Unit) -> DepKind {
302 match unit.target.kind() {
303 TargetKind::Lib(_) => match unit.mode {
304 CompileMode::Test => DepKind::Development,
306 _ => DepKind::Normal,
307 },
308 TargetKind::Bin => DepKind::Normal,
309 TargetKind::Test => DepKind::Development,
310 TargetKind::Bench => DepKind::Development,
311 TargetKind::ExampleLib(_) => DepKind::Development,
312 TargetKind::ExampleBin => DepKind::Development,
313 TargetKind::CustomBuild => DepKind::Build,
314 }
315}
316
317fn unit_desc(unit: &Unit) -> String {
318 format!(
319 "{}/{}+{:?}",
320 unit.target.name(),
321 unit.target.kind().description(),
322 unit.mode,
323 )
324}