@@ -62,11 +62,14 @@ use std::ffi::{OsStr, OsString};
6262use std:: fmt:: Display ;
6363use std:: fs:: { self , File } ;
6464use std:: io:: { BufRead , BufWriter , Write } ;
65+ use std:: ops:: Range ;
6566use 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 } ;
6870use anyhow:: { Context as _, Error } ;
6971use lazycell:: LazyCell ;
72+ use regex:: Regex ;
7073use tracing:: { debug, instrument, trace} ;
7174
7275pub use self :: build_config:: UserIntent ;
@@ -98,6 +101,7 @@ use crate::core::{Feature, PackageId, Target, Verbosity};
98101use crate :: util:: context:: WarningHandling ;
99102use crate :: util:: errors:: { CargoResult , VerboseError } ;
100103use crate :: util:: interning:: InternedString ;
104+ use crate :: util:: lints:: get_key_value;
101105use crate :: util:: machine_message:: { self , Message } ;
102106use crate :: util:: { add_path_args, internal} ;
103107use 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+
18941926fn 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.
21472263fn 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 ( ( ) )
0 commit comments