Skip to content

Commit 493c981

Browse files
committed
Add a manifest location to public-in-private errors
1 parent 9504f2a commit 493c981

File tree

3 files changed

+194
-42
lines changed

3 files changed

+194
-42
lines changed

src/cargo/core/compiler/mod.rs

Lines changed: 140 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,14 @@ use std::ffi::{OsStr, OsString};
6262
use std::fmt::Display;
6363
use std::fs::{self, File};
6464
use std::io::{BufRead, BufWriter, Write};
65+
use std::ops::Range;
6566
use std::path::{Path, PathBuf};
66-
use std::sync::Arc;
67+
use std::sync::{Arc, LazyLock};
6768

69+
use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet};
6870
use anyhow::{Context as _, Error};
6971
use lazycell::LazyCell;
72+
use regex::Regex;
7073
use tracing::{debug, instrument, trace};
7174

7275
pub use self::build_config::UserIntent;
@@ -98,6 +101,7 @@ use crate::core::{Feature, PackageId, Target, Verbosity};
98101
use crate::util::context::WarningHandling;
99102
use crate::util::errors::{CargoResult, VerboseError};
100103
use crate::util::interning::InternedString;
104+
use crate::util::lints::get_key_value;
101105
use crate::util::machine_message::{self, Message};
102106
use crate::util::{add_path_args, internal};
103107
use cargo_util::{ProcessBuilder, ProcessError, paths};
@@ -215,9 +219,10 @@ fn compile<'gctx>(
215219
// since it might contain future-incompat-report messages
216220
let show_diagnostics = unit.show_warnings(bcx.gctx)
217221
&& build_runner.bcx.gctx.warning_handling()? != WarningHandling::Allow;
222+
let manifest = ManifestErrorContext::new(unit, bcx.gctx.cwd().to_owned());
218223
let work = replay_output_cache(
219224
unit.pkg.package_id(),
220-
PathBuf::from(unit.pkg.manifest_path()),
225+
manifest,
221226
&unit.target,
222227
build_runner.files().message_cache_path(unit),
223228
build_runner.bcx.build_config.message_format,
@@ -283,7 +288,7 @@ fn rustc(
283288
// Prepare the native lib state (extra `-L` and `-l` flags).
284289
let build_script_outputs = Arc::clone(&build_runner.build_script_outputs);
285290
let current_id = unit.pkg.package_id();
286-
let manifest_path = PathBuf::from(unit.pkg.manifest_path());
291+
let manifest = ManifestErrorContext::new(unit, build_runner.bcx.gctx.cwd().to_owned());
287292
let build_scripts = build_runner.build_scripts.get(unit).cloned();
288293

289294
// If we are a binary and the package also contains a library, then we
@@ -424,7 +429,7 @@ fn rustc(
424429
state,
425430
line,
426431
package_id,
427-
&manifest_path,
432+
&manifest,
428433
&target,
429434
&mut output_options,
430435
)
@@ -905,8 +910,8 @@ fn rustdoc(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<W
905910
let name = unit.pkg.name();
906911
let build_script_outputs = Arc::clone(&build_runner.build_script_outputs);
907912
let package_id = unit.pkg.package_id();
908-
let manifest_path = PathBuf::from(unit.pkg.manifest_path());
909913
let target = Target::clone(&unit.target);
914+
let manifest = ManifestErrorContext::new(unit, build_runner.bcx.gctx.cwd().to_owned());
910915

911916
let rustdoc_dep_info_loc = rustdoc_dep_info_loc(build_runner, unit);
912917
let dep_info_loc = fingerprint::dep_info_loc(build_runner, unit);
@@ -996,7 +1001,7 @@ fn rustdoc(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<W
9961001
state,
9971002
line,
9981003
package_id,
999-
&manifest_path,
1004+
&manifest,
10001005
&target,
10011006
&mut output_options,
10021007
)
@@ -1891,6 +1896,33 @@ impl OutputOptions {
18911896
}
18921897
}
18931898

1899+
/// Cloned and sendable context about the manifest file.
1900+
///
1901+
/// Sometimes we enrich rustc's errors with some locations in the manifest file; this
1902+
/// contains a `Send`-able copy of the manifest information that we need for the
1903+
/// enriched errors.
1904+
struct ManifestErrorContext {
1905+
/// The path to the manifest.
1906+
path: PathBuf,
1907+
/// The locations of various spans within the manifest.
1908+
spans: toml::Spanned<toml::de::DeTable<'static>>,
1909+
/// The raw manifest contents.
1910+
contents: String,
1911+
/// Cargo's working directory (for printing out a more friendly manifest path).
1912+
cwd: PathBuf,
1913+
}
1914+
1915+
impl ManifestErrorContext {
1916+
fn new(unit: &Unit, cwd: PathBuf) -> ManifestErrorContext {
1917+
ManifestErrorContext {
1918+
path: unit.pkg.manifest_path().to_owned(),
1919+
spans: unit.pkg.manifest().document().clone(),
1920+
contents: unit.pkg.manifest().contents().to_owned(),
1921+
cwd,
1922+
}
1923+
}
1924+
}
1925+
18941926
fn on_stdout_line(
18951927
state: &JobState<'_, '_>,
18961928
line: &str,
@@ -1905,11 +1937,11 @@ fn on_stderr_line(
19051937
state: &JobState<'_, '_>,
19061938
line: &str,
19071939
package_id: PackageId,
1908-
manifest_path: &std::path::Path,
1940+
manifest: &ManifestErrorContext,
19091941
target: &Target,
19101942
options: &mut OutputOptions,
19111943
) -> CargoResult<()> {
1912-
if on_stderr_line_inner(state, line, package_id, manifest_path, target, options)? {
1944+
if on_stderr_line_inner(state, line, package_id, manifest, target, options)? {
19131945
// Check if caching is enabled.
19141946
if let Some((path, cell)) = &mut options.cache_cell {
19151947
// Cache the output, which will be replayed later when Fresh.
@@ -1927,7 +1959,7 @@ fn on_stderr_line_inner(
19271959
state: &JobState<'_, '_>,
19281960
line: &str,
19291961
package_id: PackageId,
1930-
manifest_path: &std::path::Path,
1962+
manifest: &ManifestErrorContext,
19311963
target: &Target,
19321964
options: &mut OutputOptions,
19331965
) -> CargoResult<bool> {
@@ -1976,6 +2008,37 @@ fn on_stderr_line_inner(
19762008
return Ok(false);
19772009
}
19782010

2011+
// Returns `true` if the diagnostic was modified.
2012+
let extra_diag_message = |diag: &mut String| -> bool {
2013+
static PRIV_DEP_REGEX: LazyLock<Regex> = LazyLock::new(|| {
2014+
Regex::new("from private dependency '([A-Za-z0-9-_]+)' in public interface").unwrap()
2015+
});
2016+
if let Some(crate_name) = PRIV_DEP_REGEX.captures(diag).and_then(|m| m.get(1)) {
2017+
let crate_name = crate_name.as_str();
2018+
if let Some(span) = find_crate_span(crate_name, &manifest.spans) {
2019+
let rel_path = pathdiff::diff_paths(&manifest.path, &manifest.cwd)
2020+
.unwrap_or_else(|| manifest.path.clone())
2021+
.display()
2022+
.to_string();
2023+
let report = [Group::with_title(
2024+
Level::NOTE
2025+
.secondary_title(format!("dependency `{crate_name}` declared here",)),
2026+
)
2027+
.element(
2028+
Snippet::source(&manifest.contents)
2029+
.path(rel_path)
2030+
.annotation(AnnotationKind::Context.span(span)),
2031+
)];
2032+
2033+
let rendered = Renderer::styled().render(&report);
2034+
diag.push('\n');
2035+
diag.push_str(&rendered);
2036+
return true;
2037+
}
2038+
}
2039+
false
2040+
};
2041+
19792042
// Depending on what we're emitting from Cargo itself, we figure out what to
19802043
// do with this JSON message.
19812044
match options.format {
@@ -2034,7 +2097,7 @@ fn on_stderr_line_inner(
20342097
if msg.rendered.ends_with('\n') {
20352098
msg.rendered.pop();
20362099
}
2037-
let rendered = msg.rendered;
2100+
let mut rendered = msg.rendered;
20382101
if options.show_diagnostics {
20392102
let machine_applicable: bool = msg
20402103
.children
@@ -2048,16 +2111,14 @@ fn on_stderr_line_inner(
20482111
})
20492112
.any(|b| b);
20502113
count_diagnostic(&msg.level, options);
2114+
extra_diag_message(&mut rendered);
20512115
state.emit_diag(&msg.level, rendered, machine_applicable)?;
20522116
}
20532117
return Ok(true);
20542118
}
20552119
}
20562120

2057-
// Remove color information from the rendered string if color is not
2058-
// enabled. Cargo always asks for ANSI colors from rustc. This allows
2059-
// cached replay to enable/disable colors without re-invoking rustc.
2060-
MessageFormat::Json { ansi: false, .. } => {
2121+
MessageFormat::Json { ansi, .. } => {
20612122
#[derive(serde::Deserialize, serde::Serialize)]
20622123
struct CompilerMessage<'a> {
20632124
rendered: String,
@@ -2067,15 +2128,20 @@ fn on_stderr_line_inner(
20672128
if let Ok(mut error) =
20682129
serde_json::from_str::<CompilerMessage<'_>>(compiler_message.get())
20692130
{
2070-
error.rendered = anstream::adapter::strip_str(&error.rendered).to_string();
2071-
let new_line = serde_json::to_string(&error)?;
2072-
compiler_message = serde_json::value::RawValue::from_string(new_line)?;
2131+
let modified_diag = extra_diag_message(&mut error.rendered);
2132+
2133+
// Remove color information from the rendered string if color is not
2134+
// enabled. Cargo always asks for ANSI colors from rustc. This allows
2135+
// cached replay to enable/disable colors without re-invoking rustc.
2136+
if !ansi {
2137+
error.rendered = anstream::adapter::strip_str(&error.rendered).to_string();
2138+
}
2139+
if !ansi || modified_diag {
2140+
let new_line = serde_json::to_string(&error)?;
2141+
compiler_message = serde_json::value::RawValue::from_string(new_line)?;
2142+
}
20732143
}
20742144
}
2075-
2076-
// If ansi colors are desired then we should be good to go! We can just
2077-
// pass through this message as-is.
2078-
MessageFormat::Json { ansi: true, .. } => {}
20792145
}
20802146

20812147
// We always tell rustc to emit messages about artifacts being produced.
@@ -2128,7 +2194,7 @@ fn on_stderr_line_inner(
21282194

21292195
let msg = machine_message::FromCompiler {
21302196
package_id: package_id.to_spec(),
2131-
manifest_path,
2197+
manifest_path: &manifest.path,
21322198
target,
21332199
message: compiler_message,
21342200
}
@@ -2141,12 +2207,62 @@ fn on_stderr_line_inner(
21412207
Ok(true)
21422208
}
21432209

2210+
/// Find a span for the dependency that specifies this unrenamed crate, if it's unique.
2211+
///
2212+
/// rustc diagnostics (at least for public-in-private) mention the un-renamed
2213+
/// crate: if you have `foo = { package = "bar" }`, the rustc diagnostic will
2214+
/// say "bar".
2215+
///
2216+
/// This function does its best to find a span for "bar", but it could fail if
2217+
/// there are multiple candidates:
2218+
///
2219+
/// ```toml
2220+
/// foo = { package = "bar" }
2221+
/// baz = { path = "../bar", package = "bar" }
2222+
/// ```
2223+
fn find_crate_span<'doc>(
2224+
unrenamed: &str,
2225+
manifest_spans: &'doc toml::Spanned<toml::de::DeTable<'static>>,
2226+
) -> Option<Range<usize>> {
2227+
// Easy case: there's a dependency whose key matches the name, and it didn't get renamed.
2228+
if let Some((k, v)) = get_key_value(manifest_spans, &["dependencies", unrenamed]) {
2229+
let Some(table) = v.get_ref().as_table() else {
2230+
return Some(k.span());
2231+
};
2232+
if !table.contains_key("package") {
2233+
return Some(k.span());
2234+
}
2235+
}
2236+
2237+
let Some(deps) =
2238+
get_key_value(manifest_spans, &["dependencies"]).and_then(|(_k, v)| v.get_ref().as_table())
2239+
else {
2240+
return None;
2241+
};
2242+
2243+
let mut unique_crate = None;
2244+
for v in deps.values() {
2245+
if let Some(package) = v.as_ref().as_table().and_then(|t| t.get("package")) {
2246+
if package.get_ref().as_str() == Some(unrenamed) {
2247+
if unique_crate.is_some() {
2248+
// There were two dependencies with the same un-renamed name.
2249+
return None;
2250+
} else {
2251+
unique_crate = Some(package.span());
2252+
}
2253+
}
2254+
}
2255+
}
2256+
2257+
unique_crate
2258+
}
2259+
21442260
/// Creates a unit of work that replays the cached compiler message.
21452261
///
21462262
/// Usually used when a job is fresh and doesn't need to recompile.
21472263
fn replay_output_cache(
21482264
package_id: PackageId,
2149-
manifest_path: PathBuf,
2265+
manifest: ManifestErrorContext,
21502266
target: &Target,
21512267
path: PathBuf,
21522268
format: MessageFormat,
@@ -2177,14 +2293,7 @@ fn replay_output_cache(
21772293
break;
21782294
}
21792295
let trimmed = line.trim_end_matches(&['\n', '\r'][..]);
2180-
on_stderr_line(
2181-
state,
2182-
trimmed,
2183-
package_id,
2184-
&manifest_path,
2185-
&target,
2186-
&mut options,
2187-
)?;
2296+
on_stderr_line(state, trimmed, package_id, &manifest, &target, &mut options)?;
21882297
line.clear();
21892298
}
21902299
Ok(())

src/cargo/util/lints.rs

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::{CargoResult, GlobalContext};
33
use annotate_snippets::{AnnotationKind, Group, Level, Snippet};
44
use cargo_util_schemas::manifest::{TomlLintLevel, TomlToolLints};
55
use pathdiff::diff_paths;
6+
use std::borrow::Cow;
67
use std::fmt::Display;
78
use std::ops::Range;
89
use std::path::Path;
@@ -182,20 +183,20 @@ pub struct TomlSpan {
182183
pub value: Range<usize>,
183184
}
184185

185-
pub fn get_key_value_span(
186-
document: &toml::Spanned<toml::de::DeTable<'static>>,
186+
pub fn get_key_value<'doc>(
187+
document: &'doc toml::Spanned<toml::de::DeTable<'static>>,
187188
path: &[&str],
188-
) -> Option<TomlSpan> {
189+
) -> Option<(
190+
&'doc toml::Spanned<Cow<'doc, str>>,
191+
&'doc toml::Spanned<toml::de::DeValue<'static>>,
192+
)> {
189193
let mut table = document.get_ref();
190194
let mut iter = path.into_iter().peekable();
191195
while let Some(key) = iter.next() {
192196
let key_s: &str = key.as_ref();
193197
let (key, item) = table.get_key_value(key_s)?;
194198
if iter.peek().is_none() {
195-
return Some(TomlSpan {
196-
key: key.span(),
197-
value: item.span(),
198-
});
199+
return Some((key, item));
199200
}
200201
if let Some(next_table) = item.get_ref().as_table() {
201202
table = next_table;
@@ -204,10 +205,7 @@ pub fn get_key_value_span(
204205
if let Some(array) = item.get_ref().as_array() {
205206
let next = iter.next().unwrap();
206207
return array.iter().find_map(|item| match item.get_ref() {
207-
toml::de::DeValue::String(s) if s == next => Some(TomlSpan {
208-
key: key.span(),
209-
value: item.span(),
210-
}),
208+
toml::de::DeValue::String(s) if s == next => Some((key, item)),
211209
_ => None,
212210
});
213211
}
@@ -216,6 +214,16 @@ pub fn get_key_value_span(
216214
None
217215
}
218216

217+
pub fn get_key_value_span(
218+
document: &toml::Spanned<toml::de::DeTable<'static>>,
219+
path: &[&str],
220+
) -> Option<TomlSpan> {
221+
get_key_value(document, path).map(|(k, v)| TomlSpan {
222+
key: k.span(),
223+
value: v.span(),
224+
})
225+
}
226+
219227
/// Gets the relative path to a manifest from the current working directory, or
220228
/// the absolute path of the manifest if a relative path cannot be constructed
221229
pub fn rel_cwd_manifest_path(path: &Path, gctx: &GlobalContext) -> String {

0 commit comments

Comments
 (0)