Auto merge of #11499 - willcrichton:example-analyzer, r=weihanglo

Don't scrape examples from library targets by default

### What does this PR try to resolve?

Based on some [early feedback](https://www.reddit.com/r/rust/comments/zosle6/feedback_requested_rustdocs_scraped_examples/) about the scrape-examples feature, both documentation authors and consumers did not consider examples useful if they are scraped from a library's internals, at least in the common case. Therefore this PR changes the default behavior of `-Zrustdoc-scrape-examples` to *only* scrape from example targets, although library targets can still be configured for scraping.

### How should we test and review this PR?

I have updated the `docscrape` tests to reflect this new policy, as well as the Unstable Options page in the Cargo book.

r? `@weihanglo`
This commit is contained in:
bors 2022-12-20 21:10:16 +00:00
commit 74c7aab19a
5 changed files with 94 additions and 100 deletions

View File

@ -224,7 +224,7 @@ fn make_failed_scrape_diagnostic(
"\ "\
{top_line} {top_line}
Try running with `--verbose` to see the error message. Try running with `--verbose` to see the error message.
If an example or library should not be scanned, then consider adding `doc-scrape-examples = false` to its `[[example]]` or `[lib]` definition in {}", If an example should not be scanned, then consider adding `doc-scrape-examples = false` to its `[[example]]` definition in {}",
relative_manifest_path.display() relative_manifest_path.display()
) )
} }

View File

@ -33,7 +33,6 @@ use std::collections::{HashMap, HashSet};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::sync::Arc; use std::sync::Arc;
use crate::core::compiler::rustdoc::RustdocScrapeExamples;
use crate::core::compiler::unit_dependencies::build_unit_dependencies; use crate::core::compiler::unit_dependencies::build_unit_dependencies;
use crate::core::compiler::unit_graph::{self, UnitDep, UnitGraph}; use crate::core::compiler::unit_graph::{self, UnitDep, UnitGraph};
use crate::core::compiler::{standard_lib, CrateType, TargetInfo}; use crate::core::compiler::{standard_lib, CrateType, TargetInfo};
@ -371,19 +370,11 @@ pub fn create_bcx<'a, 'cfg>(
let should_scrape = build_config.mode.is_doc() && config.cli_unstable().rustdoc_scrape_examples; let should_scrape = build_config.mode.is_doc() && config.cli_unstable().rustdoc_scrape_examples;
let mut scrape_units = if should_scrape { let mut scrape_units = if should_scrape {
let scrape_generator = UnitGenerator { UnitGenerator {
mode: CompileMode::Docscrape, mode: CompileMode::Docscrape,
..generator ..generator
}; }
let mut scrape_units = scrape_generator.generate_root_units()?; .generate_scrape_units(&units)?
scrape_units.retain(|unit| {
ws.unit_needs_doc_scrape(unit)
&& !matches!(
unit.target.doc_scrape_examples(),
RustdocScrapeExamples::Disabled
)
});
scrape_units
} else { } else {
Vec::new() Vec::new()
}; };

View File

@ -189,7 +189,7 @@ impl<'a> UnitGenerator<'a, '_> {
.iter() .iter()
.filter(|t| t.is_bin() || t.is_lib()) .filter(|t| t.is_bin() || t.is_lib())
.collect(), .collect(),
CompileMode::Doc { .. } | CompileMode::Docscrape => { CompileMode::Doc { .. } => {
// `doc` does lib and bins (bin with same name as lib is skipped). // `doc` does lib and bins (bin with same name as lib is skipped).
targets targets
.iter() .iter()
@ -200,7 +200,7 @@ impl<'a> UnitGenerator<'a, '_> {
}) })
.collect() .collect()
} }
CompileMode::Doctest | CompileMode::RunCustomBuild => { CompileMode::Doctest | CompileMode::RunCustomBuild | CompileMode::Docscrape => {
panic!("Invalid mode {:?}", self.mode) panic!("Invalid mode {:?}", self.mode)
} }
} }
@ -426,15 +426,11 @@ impl<'a> UnitGenerator<'a, '_> {
} }
} }
if self.mode.is_doc_scrape() {
self.add_docscrape_proposals(&mut proposals);
}
Ok(proposals) Ok(proposals)
} }
/// Add additional targets from which to scrape examples for documentation /// Proposes targets from which to scrape examples for documentation
fn add_docscrape_proposals(&self, proposals: &mut Vec<Proposal<'a>>) { fn create_docscrape_proposals(&self, doc_units: &[Unit]) -> CargoResult<Vec<Proposal<'a>>> {
// In general, the goal is to scrape examples from (a) whatever targets // In general, the goal is to scrape examples from (a) whatever targets
// the user is documenting, and (b) Example targets. However, if the user // the user is documenting, and (b) Example targets. However, if the user
// is documenting a library with dev-dependencies, those dev-deps are not // is documenting a library with dev-dependencies, those dev-deps are not
@ -456,23 +452,30 @@ impl<'a> UnitGenerator<'a, '_> {
let reqs_dev_deps = matches!(self.has_dev_units, HasDevUnits::Yes); let reqs_dev_deps = matches!(self.has_dev_units, HasDevUnits::Yes);
let safe_to_scrape_example_targets = no_pkg_has_dev_deps || reqs_dev_deps; let safe_to_scrape_example_targets = no_pkg_has_dev_deps || reqs_dev_deps;
let proposed_targets: HashSet<&Target> = proposals.iter().map(|p| p.target).collect(); let pkgs_to_scrape = doc_units
.iter()
.filter(|unit| self.ws.unit_needs_doc_scrape(unit))
.map(|u| &u.pkg)
.collect::<HashSet<_>>();
let can_scrape = |target: &Target| { let can_scrape = |target: &Target| {
let not_redundant = !proposed_targets.contains(target); match (target.doc_scrape_examples(), target.is_example()) {
not_redundant // Targets configured by the user to not be scraped should never be scraped
&& match (target.doc_scrape_examples(), target.is_example()) { (RustdocScrapeExamples::Disabled, _) => false,
// Targets configured by the user to not be scraped should never be scraped // Targets configured by the user to be scraped should always be scraped
(RustdocScrapeExamples::Disabled, _) => false, (RustdocScrapeExamples::Enabled, _) => true,
// Targets configured by the user to be scraped should always be scraped // Example targets with no configuration should be conditionally scraped if
(RustdocScrapeExamples::Enabled, _) => true, // it's guaranteed not to break the build
// Example targets with no configuration should be conditionally scraped if (RustdocScrapeExamples::Unset, true) => safe_to_scrape_example_targets,
// it's guaranteed not to break the build // All other targets are ignored for now. This may change in the future!
(RustdocScrapeExamples::Unset, true) => safe_to_scrape_example_targets, (RustdocScrapeExamples::Unset, false) => false,
// All other targets are ignored for now. This may change in the future! }
(RustdocScrapeExamples::Unset, false) => false,
}
}; };
proposals.extend(self.filter_targets(can_scrape, false, CompileMode::Docscrape));
let mut scrape_proposals = self.filter_targets(can_scrape, false, CompileMode::Docscrape);
scrape_proposals.retain(|proposal| pkgs_to_scrape.contains(proposal.pkg));
Ok(scrape_proposals)
} }
/// Checks if the unit list is empty and the user has passed any combination of /// Checks if the unit list is empty and the user has passed any combination of
@ -674,4 +677,16 @@ impl<'a> UnitGenerator<'a, '_> {
let proposals = self.create_proposals()?; let proposals = self.create_proposals()?;
self.proposals_to_units(proposals) self.proposals_to_units(proposals)
} }
/// Generates units specfically for doc-scraping.
///
/// This requires a separate entrypoint from [`generate_root_units`] because it
/// takes the documented units as input.
///
/// [`generate_root_units`]: Self::generate_root_units
pub fn generate_scrape_units(&self, doc_units: &[Unit]) -> CargoResult<Vec<Unit>> {
let scrape_proposals = self.create_docscrape_proposals(&doc_units)?;
let scrape_units = self.proposals_to_units(scrape_proposals)?;
Ok(scrape_units)
}
} }

View File

@ -1124,34 +1124,35 @@ like this:
cargo doc -Z unstable-options -Z rustdoc-scrape-examples cargo doc -Z unstable-options -Z rustdoc-scrape-examples
``` ```
By default, Cargo will scrape from the packages that are being documented, as well as scrape from By default, Cargo will scrape examples from the example targets of packages being documented.
examples for the packages (i.e. those corresponding to the `--examples` flag). You can individually You can individually enable or disable targets from being scraped with the `doc-scrape-examples` flag, such as:
enable or disable targets from being scraped with the `doc-scrape-examples` flag, such as:
```toml ```toml
# Disable scraping examples from a library # Enable scraping examples from a library
[lib] [lib]
doc-scrape-examples = false
# Enable scraping examples from a binary
[[bin]]
name = "my-bin"
doc-scrape-examples = true doc-scrape-examples = true
# Disable scraping examples from an example target
[[example]]
name = "my-example"
doc-scrape-examples = false
``` ```
**Note on tests:** enabling `doc-scrape-examples` on test targets will not currently have any effect. Scraping **Note on tests:** enabling `doc-scrape-examples` on test targets will not currently have any effect. Scraping
examples from tests is a work-in-progress. examples from tests is a work-in-progress.
**Note on dev-dependencies:** documenting a library does not normally require the crate's dev-dependencies. However, **Note on dev-dependencies:** documenting a library does not normally require the crate's dev-dependencies. However,
example units do require dev-deps. For backwards compatibility, `-Z rustdoc-scrape-examples` will *not* introduce a example targets require dev-deps. For backwards compatibility, `-Z rustdoc-scrape-examples` will *not* introduce a
dev-deps requirement for `cargo doc`. Therefore examples will *not* be scraped from example targets under the dev-deps requirement for `cargo doc`. Therefore examples will *not* be scraped from example targets under the
following conditions: following conditions:
1. No target being documented requires dev-deps, AND 1. No target being documented requires dev-deps, AND
2. At least one crate being documented requires dev-deps, AND 2. At least one crate with targets being documented has dev-deps, AND
3. The `doc-scrape-examples` parameter is unset for `[[example]]` targets. 3. The `doc-scrape-examples` parameter is unset or false for all `[[example]]` targets.
If you want examples to be scraped from example targets, then you must not satisfy one of the above conditions. If you want examples to be scraped from example targets, then you must not satisfy one of the above conditions.
For example, you can set `doc-scrape-examples` to true for one example target, and that signals to Cargo that
you are ok with dev-deps being build for `cargo doc`.
### check-cfg ### check-cfg

View File

@ -37,7 +37,7 @@ fn basic() {
let doc_html = p.read_file("target/doc/foo/fn.foo.html"); let doc_html = p.read_file("target/doc/foo/fn.foo.html");
assert!(doc_html.contains("Examples found in repository")); assert!(doc_html.contains("Examples found in repository"));
assert!(doc_html.contains("More examples")); assert!(!doc_html.contains("More examples"));
// Ensure that the reverse-dependency has its sources generated // Ensure that the reverse-dependency has its sources generated
assert!(p.build_dir().join("doc/src/ex/ex.rs.html").exists()); assert!(p.build_dir().join("doc/src/ex/ex.rs.html").exists());
@ -178,18 +178,25 @@ fn configure_target() {
authors = [] authors = []
[lib] [lib]
doc-scrape-examples = false doc-scrape-examples = true
[[bin]] [[bin]]
name = "a_bin" name = "a_bin"
doc-scrape-examples = true doc-scrape-examples = true
[[example]]
name = "a"
doc-scrape-examples = false
"#, "#,
) )
.file( .file(
"src/lib.rs", "src/lib.rs",
"pub fn foo() {} fn lib_must_not_appear() { foo(); }", "pub fn foo() {} fn lib_must_appear() { foo(); }",
)
.file(
"examples/a.rs",
"fn example_must_not_appear() { foo::foo(); }",
) )
.file("examples/a.rs", "fn example_must_appear() { foo::foo(); }")
.file( .file(
"src/bin/a_bin.rs", "src/bin/a_bin.rs",
"fn bin_must_appear() { foo::foo(); } fn main(){}", "fn bin_must_appear() { foo::foo(); } fn main(){}",
@ -201,9 +208,9 @@ fn configure_target() {
.run(); .run();
let doc_html = p.read_file("target/doc/foo/fn.foo.html"); let doc_html = p.read_file("target/doc/foo/fn.foo.html");
assert!(!doc_html.contains("lib_must_not_appear")); assert!(doc_html.contains("lib_must_appear"));
assert!(doc_html.contains("example_must_appear"));
assert!(doc_html.contains("bin_must_appear")); assert!(doc_html.contains("bin_must_appear"));
assert!(!doc_html.contains("example_must_not_appear"));
} }
#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")] #[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
@ -231,7 +238,6 @@ fn configure_profile_issue_10500() {
let doc_html = p.read_file("target/doc/foo/fn.foo.html"); let doc_html = p.read_file("target/doc/foo/fn.foo.html");
assert!(doc_html.contains("Examples found in repository")); assert!(doc_html.contains("Examples found in repository"));
assert!(doc_html.contains("More examples"));
} }
#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")] #[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
@ -342,21 +348,17 @@ fn no_fail_bad_lib() {
"\ "\
[CHECKING] foo v0.0.1 ([CWD]) [CHECKING] foo v0.0.1 ([CWD])
[SCRAPING] foo v0.0.1 ([CWD]) [SCRAPING] foo v0.0.1 ([CWD])
warning: failed to scan lib in package `foo` for example code usage
Try running with `--verbose` to see the error message.
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
warning: `foo` (lib) generated 1 warning
warning: failed to check lib in package `foo` as a prerequisite for scraping examples from: example \"ex\", example \"ex2\" warning: failed to check lib in package `foo` as a prerequisite for scraping examples from: example \"ex\", example \"ex2\"
Try running with `--verbose` to see the error message. Try running with `--verbose` to see the error message.
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 If an example should not be scanned, then consider adding `doc-scrape-examples = false` to its `[[example]]` definition in Cargo.toml
warning: `foo` (lib) generated 1 warning warning: `foo` (lib) generated 1 warning
warning: failed to scan example \"ex\" in package `foo` for example code usage warning: failed to scan example \"ex\" in package `foo` for example code usage
Try running with `--verbose` to see the error message. Try running with `--verbose` to see the error message.
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 If an example should not be scanned, then consider adding `doc-scrape-examples = false` to its `[[example]]` definition in Cargo.toml
warning: `foo` (example \"ex\") generated 1 warning warning: `foo` (example \"ex\") generated 1 warning
warning: failed to scan example \"ex2\" in package `foo` for example code usage warning: failed to scan example \"ex2\" in package `foo` for example code usage
Try running with `--verbose` to see the error message. Try running with `--verbose` to see the error message.
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 If an example should not be scanned, then consider adding `doc-scrape-examples = false` to its `[[example]]` definition in Cargo.toml
warning: `foo` (example \"ex2\") generated 1 warning warning: `foo` (example \"ex2\") generated 1 warning
[DOCUMENTING] foo v0.0.1 ([CWD]) [DOCUMENTING] foo v0.0.1 ([CWD])
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]", [FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
@ -389,7 +391,7 @@ fn no_fail_bad_example() {
[SCRAPING] foo v0.0.1 ([CWD]) [SCRAPING] foo v0.0.1 ([CWD])
warning: failed to scan example \"ex1\" in package `foo` for example code usage warning: failed to scan example \"ex1\" in package `foo` for example code usage
Try running with `--verbose` to see the error message. Try running with `--verbose` to see the error message.
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 If an example should not be scanned, then consider adding `doc-scrape-examples = false` to its `[[example]]` definition in Cargo.toml
warning: `foo` (example \"ex1\") generated 1 warning warning: `foo` (example \"ex1\") generated 1 warning
[DOCUMENTING] foo v0.0.1 ([CWD]) [DOCUMENTING] foo v0.0.1 ([CWD])
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]", [FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
@ -415,7 +417,6 @@ error: expected one of `!` or `::`, found `NOT`
| ^^^ expected one of `!` or `::` | ^^^ expected one of `!` or `::`
[DOCUMENTING] foo v0.0.1 ([CWD]) [DOCUMENTING] foo v0.0.1 ([CWD])
[RUNNING] `rustdoc[..] --crate-name foo[..]
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]", [FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
) )
.run(); .run();
@ -538,19 +539,18 @@ fn use_dev_deps_if_explicitly_enabled() {
#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")] #[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
fn only_scrape_documented_targets() { fn only_scrape_documented_targets() {
// package bar has doc = false and should not be eligible for documtation. // package bar has doc = false and should not be eligible for documtation.
let run_with_config = |config: &str, should_scrape: bool| { let p = project()
let p = project() .file(
.file( "Cargo.toml",
"Cargo.toml", &format!(
&format!( r#"
r#"
[package] [package]
name = "bar" name = "bar"
version = "0.0.1" version = "0.0.1"
authors = [] authors = []
[lib] [lib]
{config} doc = false
[workspace] [workspace]
members = ["foo"] members = ["foo"]
@ -558,42 +558,29 @@ fn only_scrape_documented_targets() {
[dependencies] [dependencies]
foo = {{ path = "foo" }} foo = {{ path = "foo" }}
"# "#
), ),
) )
.file("src/lib.rs", "pub fn bar() { foo::foo(); }") .file("src/lib.rs", "")
.file( .file("examples/ex.rs", "pub fn main() { foo::foo(); }")
"foo/Cargo.toml", .file(
r#" "foo/Cargo.toml",
r#"
[package] [package]
name = "foo" name = "foo"
version = "0.0.1" version = "0.0.1"
authors = [] authors = []
"#, "#,
) )
.file("foo/src/lib.rs", "pub fn foo() {}") .file("foo/src/lib.rs", "pub fn foo() {}")
.build(); .build();
p.cargo("doc --workspace -Zunstable-options -Zrustdoc-scrape-examples") p.cargo("doc --workspace -Zunstable-options -Zrustdoc-scrape-examples")
.masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"]) .masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
.run(); .run();
let doc_html = p.read_file("target/doc/foo/fn.foo.html"); let doc_html = p.read_file("target/doc/foo/fn.foo.html");
let example_found = doc_html.contains("Examples found in repository"); let example_found = doc_html.contains("Examples found in repository");
if should_scrape { assert!(!example_found);
assert!(example_found);
} else {
assert!(!example_found);
}
};
// By default, bar should be scraped.
run_with_config("", true);
// If bar isn't supposed to be documented, then it is not eligible
// for scraping.
run_with_config("doc = false", false);
// But if the user explicitly says bar should be scraped, then it should
// be scraped.
run_with_config("doc = false\ndoc-scrape-examples = true", true);
} }
#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")] #[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]