Skip to content

Commit de34d60

Browse files
committed
Allow Check targets needed for optional doc-scraping to fail without killing the build
1 parent 7b9069e commit de34d60

File tree

4 files changed

+144
-54
lines changed

4 files changed

+144
-54
lines changed

src/cargo/core/compiler/job_queue.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -812,10 +812,7 @@ impl<'cfg> DrainState<'cfg> {
812812
debug!("end ({:?}): {:?}", unit, result);
813813
match result {
814814
Ok(()) => self.finish(id, &unit, artifact, cx)?,
815-
Err(_)
816-
if unit.mode.is_doc_scrape()
817-
&& unit.target.doc_scrape_examples().is_unset() =>
818-
{
815+
Err(_) if cx.bcx.unit_can_fail_for_docscraping(&unit) => {
819816
cx.failed_scrape_units
820817
.lock()
821818
.unwrap()

src/cargo/core/compiler/mod.rs

Lines changed: 87 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,23 @@ fn compile<'cfg>(
207207
Ok(())
208208
}
209209

210+
/// Generates the warning message used when fallible doc-scrape units fail,
211+
/// either for rustdoc or rustc.
212+
fn make_failed_scrape_diagnostic(cx: &Context<'_, '_>, unit: &Unit, top_line: String) -> String {
213+
let manifest_path = unit.pkg.manifest_path();
214+
let relative_manifest_path = manifest_path
215+
.strip_prefix(cx.bcx.ws.root())
216+
.unwrap_or(&manifest_path)
217+
.to_owned();
218+
format!(
219+
"\
220+
{top_line}
221+
Try running with `--verbose` to see the error message.
222+
If an example or library should not be scanned, then consider adding `doc-scrape-examples = false` to its `[[example]]` or `[lib]` definition in {}",
223+
relative_manifest_path.display()
224+
)
225+
}
226+
210227
fn rustc(cx: &mut Context<'_, '_>, unit: &Unit, exec: &Arc<dyn Executor>) -> CargoResult<Work> {
211228
let mut rustc = prepare_rustc(cx, &unit.target.rustc_crate_types(), unit)?;
212229
let build_plan = cx.bcx.build_config.build_plan;
@@ -265,6 +282,25 @@ fn rustc(cx: &mut Context<'_, '_>, unit: &Unit, exec: &Arc<dyn Executor>) -> Car
265282
let is_local = unit.is_local();
266283
let artifact = unit.artifact;
267284

285+
// If this unit is needed for doc-scraping, then we generate a diagnostic that
286+
// describes the set of reverse-dependencies that cause the unit to be needed.
287+
let target_desc = unit.target.description_named();
288+
let mut for_scrape_units = cx
289+
.bcx
290+
.scrape_units_have_dep_on(unit)
291+
.into_iter()
292+
.map(|unit| unit.target.description_named())
293+
.collect::<Vec<_>>();
294+
for_scrape_units.sort();
295+
let for_scrape_units = for_scrape_units.join(", ");
296+
let failed_scrape_diagnostic = make_failed_scrape_diagnostic(cx, unit, format!("failed to check {target_desc} in package `{name}` as a prerequisite for scraping examples from: {for_scrape_units}"));
297+
298+
let hide_diagnostics_for_scrape_unit = cx.bcx.unit_can_fail_for_docscraping(unit)
299+
&& !matches!(cx.bcx.config.shell().verbosity(), Verbosity::Verbose);
300+
if hide_diagnostics_for_scrape_unit {
301+
output_options.show_diagnostics = false;
302+
}
303+
268304
return Ok(Work::new(move |state| {
269305
// Artifacts are in a different location than typical units,
270306
// hence we must assure the crate- and target-dependent
@@ -339,38 +375,48 @@ fn rustc(cx: &mut Context<'_, '_>, unit: &Unit, exec: &Arc<dyn Executor>) -> Car
339375
if build_plan {
340376
state.build_plan(buildkey, rustc.clone(), outputs.clone());
341377
} else {
342-
exec.exec(
343-
&rustc,
344-
package_id,
345-
&target,
346-
mode,
347-
&mut |line| on_stdout_line(state, line, package_id, &target),
348-
&mut |line| {
349-
on_stderr_line(
350-
state,
351-
line,
352-
package_id,
353-
&manifest_path,
354-
&target,
355-
&mut output_options,
356-
)
357-
},
358-
)
359-
.map_err(verbose_if_simple_exit_code)
360-
.with_context(|| {
361-
// adapted from rustc_errors/src/lib.rs
362-
let warnings = match output_options.warnings_seen {
363-
0 => String::new(),
364-
1 => "; 1 warning emitted".to_string(),
365-
count => format!("; {} warnings emitted", count),
366-
};
367-
let errors = match output_options.errors_seen {
368-
0 => String::new(),
369-
1 => " due to previous error".to_string(),
370-
count => format!(" due to {} previous errors", count),
371-
};
372-
format!("could not compile `{}`{}{}", name, errors, warnings)
373-
})?;
378+
let result = exec
379+
.exec(
380+
&rustc,
381+
package_id,
382+
&target,
383+
mode,
384+
&mut |line| on_stdout_line(state, line, package_id, &target),
385+
&mut |line| {
386+
on_stderr_line(
387+
state,
388+
line,
389+
package_id,
390+
&manifest_path,
391+
&target,
392+
&mut output_options,
393+
)
394+
},
395+
)
396+
.map_err(verbose_if_simple_exit_code)
397+
.with_context(|| {
398+
// adapted from rustc_errors/src/lib.rs
399+
let warnings = match output_options.warnings_seen {
400+
0 => String::new(),
401+
1 => "; 1 warning emitted".to_string(),
402+
count => format!("; {} warnings emitted", count),
403+
};
404+
let errors = match output_options.errors_seen {
405+
0 => String::new(),
406+
1 => " due to previous error".to_string(),
407+
count => format!(" due to {} previous errors", count),
408+
};
409+
format!("could not compile `{}`{}{}", name, errors, warnings)
410+
});
411+
412+
if let Err(e) = result {
413+
if hide_diagnostics_for_scrape_unit {
414+
state.warning(failed_scrape_diagnostic)?;
415+
}
416+
417+
return Err(e);
418+
}
419+
374420
// Exec should never return with success *and* generate an error.
375421
debug_assert_eq!(output_options.errors_seen, 0);
376422
}
@@ -713,20 +759,22 @@ fn rustdoc(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Work> {
713759
let build_script_outputs = Arc::clone(&cx.build_script_outputs);
714760
let package_id = unit.pkg.package_id();
715761
let manifest_path = PathBuf::from(unit.pkg.manifest_path());
716-
let relative_manifest_path = manifest_path
717-
.strip_prefix(cx.bcx.ws.root())
718-
.unwrap_or(&manifest_path)
719-
.to_owned();
720762
let target = Target::clone(&unit.target);
721763
let mut output_options = OutputOptions::new(cx, unit);
722764
let script_metadata = cx.find_build_script_metadata(unit);
765+
723766
let failed_scrape_units = Arc::clone(&cx.failed_scrape_units);
724-
let hide_diagnostics_for_scrape_unit = unit.mode.is_doc_scrape()
725-
&& unit.target.doc_scrape_examples().is_unset()
767+
let hide_diagnostics_for_scrape_unit = cx.bcx.unit_can_fail_for_docscraping(unit)
726768
&& !matches!(cx.bcx.config.shell().verbosity(), Verbosity::Verbose);
769+
let failed_scrape_diagnostic = make_failed_scrape_diagnostic(
770+
cx,
771+
unit,
772+
format!("failed to scan {target_desc} in package `{name}` for example code usage"),
773+
);
727774
if hide_diagnostics_for_scrape_unit {
728775
output_options.show_diagnostics = false;
729776
}
777+
730778
Ok(Work::new(move |state| {
731779
add_custom_flags(
732780
&mut rustdoc,
@@ -775,14 +823,7 @@ fn rustdoc(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Work> {
775823

776824
if let Err(e) = result {
777825
if hide_diagnostics_for_scrape_unit {
778-
let diag = format!(
779-
"\
780-
failed to scan {target_desc} in package `{name}` for example code usage
781-
Try running with `--verbose` to see the error message.
782-
If this example should not be scanned, consider adding `doc-scrape-examples = false` to the `[[example]]` definition in {}",
783-
relative_manifest_path.display()
784-
);
785-
state.warning(diag)?;
826+
state.warning(failed_scrape_diagnostic)?;
786827
}
787828

788829
return Err(e);

src/cargo/core/compiler/rustdoc.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use crate::core::compiler::context::Context;
44
use crate::core::compiler::unit::Unit;
5-
use crate::core::compiler::CompileKind;
5+
use crate::core::compiler::{BuildContext, CompileKind};
66
use crate::sources::CRATES_IO_REGISTRY;
77
use crate::util::errors::{internal, CargoResult};
88
use cargo_util::ProcessBuilder;
@@ -209,3 +209,40 @@ impl RustdocScrapeExamples {
209209
matches!(self, RustdocScrapeExamples::Unset)
210210
}
211211
}
212+
213+
impl BuildContext<'_, '_> {
214+
/// Returns the set of Docscrape units that have a direct dependency on `unit`
215+
pub fn scrape_units_have_dep_on<'a>(&'a self, unit: &'a Unit) -> Vec<&'a Unit> {
216+
self.scrape_units
217+
.iter()
218+
.filter(|scrape_unit| {
219+
self.unit_graph[scrape_unit]
220+
.iter()
221+
.any(|dep| &dep.unit == unit)
222+
})
223+
.collect::<Vec<_>>()
224+
}
225+
226+
/// Returns true if this unit is needed for doing doc-scraping and is also
227+
/// allowed to fail without killing the build.
228+
pub fn unit_can_fail_for_docscraping(&self, unit: &Unit) -> bool {
229+
// If the unit is not a Docscrape unit, e.g. a Lib target that is
230+
// checked to scrape an Example target, then we need to get the doc-scrape-examples
231+
// configuration for the reverse-dependent Example target.
232+
let for_scrape_units = if unit.mode.is_doc_scrape() {
233+
vec![unit]
234+
} else {
235+
self.scrape_units_have_dep_on(unit)
236+
};
237+
238+
if for_scrape_units.is_empty() {
239+
false
240+
} else {
241+
// All Docscrape units must have doc-scrape-examples unset. If any are true,
242+
// then the unit is not allowed to fail.
243+
for_scrape_units
244+
.iter()
245+
.all(|unit| unit.target.doc_scrape_examples().is_unset())
246+
}
247+
}
248+
}

tests/testsuite/docscrape.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -332,17 +332,32 @@ fn no_fail_bad_lib() {
332332
"#,
333333
)
334334
.file("src/lib.rs", "pub fn foo() { CRASH_THE_BUILD() }")
335+
.file("examples/ex.rs", "fn main() { foo::foo(); }")
336+
.file("examples/ex2.rs", "fn main() { foo::foo(); }")
335337
.build();
336338

337339
p.cargo("doc -Zunstable-options -Z rustdoc-scrape-examples")
338340
.masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
339-
.with_stderr(
341+
.with_stderr_unordered(
340342
"\
343+
[CHECKING] foo v0.0.1 ([CWD])
341344
[SCRAPING] foo v0.0.1 ([CWD])
342345
warning: failed to scan lib in package `foo` for example code usage
343346
Try running with `--verbose` to see the error message.
344-
If this example should not be scanned, consider adding `doc-scrape-examples = false` to the `[[example]]` definition in Cargo.toml
347+
If an example or library should not be scanned, then consider adding `doc-scrape-examples = false` to its `[[example]]` or `[lib]` definition in Cargo.toml
345348
warning: `foo` (lib) generated 1 warning
349+
warning: failed to check lib in package `foo` as a prerequisite for scraping examples from: example \"ex\", example \"ex2\"
350+
Try running with `--verbose` to see the error message.
351+
If an example or library should not be scanned, then consider adding `doc-scrape-examples = false` to its `[[example]]` or `[lib]` definition in Cargo.toml
352+
warning: `foo` (lib) generated 1 warning
353+
warning: failed to scan example \"ex\" in package `foo` for example code usage
354+
Try running with `--verbose` to see the error message.
355+
If an example or library should not be scanned, then consider adding `doc-scrape-examples = false` to its `[[example]]` or `[lib]` definition in Cargo.toml
356+
warning: `foo` (example \"ex\") generated 1 warning
357+
warning: failed to scan example \"ex2\" in package `foo` for example code usage
358+
Try running with `--verbose` to see the error message.
359+
If an example or library should not be scanned, then consider adding `doc-scrape-examples = false` to its `[[example]]` or `[lib]` definition in Cargo.toml
360+
warning: `foo` (example \"ex2\") generated 1 warning
346361
[DOCUMENTING] foo v0.0.1 ([CWD])
347362
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
348363
)
@@ -374,7 +389,7 @@ fn no_fail_bad_example() {
374389
[SCRAPING] foo v0.0.1 ([CWD])
375390
warning: failed to scan example \"ex1\" in package `foo` for example code usage
376391
Try running with `--verbose` to see the error message.
377-
If this example should not be scanned, consider adding `doc-scrape-examples = false` to the `[[example]]` definition in Cargo.toml
392+
If an example or library should not be scanned, then consider adding `doc-scrape-examples = false` to its `[[example]]` or `[lib]` definition in Cargo.toml
378393
warning: `foo` (example \"ex1\") generated 1 warning
379394
[DOCUMENTING] foo v0.0.1 ([CWD])
380395
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",

0 commit comments

Comments
 (0)