Skip to content

Commit 74c7aab

Browse files
committed
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`
2 parents 1d8cdaa + eb829cf commit 74c7aab

File tree

5 files changed

+96
-102
lines changed

5 files changed

+96
-102
lines changed

src/cargo/core/compiler/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ fn make_failed_scrape_diagnostic(
224224
"\
225225
{top_line}
226226
Try running with `--verbose` to see the error message.
227-
If an example or library should not be scanned, then consider adding `doc-scrape-examples = false` to its `[[example]]` or `[lib]` definition in {}",
227+
If an example should not be scanned, then consider adding `doc-scrape-examples = false` to its `[[example]]` definition in {}",
228228
relative_manifest_path.display()
229229
)
230230
}

src/cargo/ops/cargo_compile/mod.rs

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ use std::collections::{HashMap, HashSet};
3333
use std::hash::{Hash, Hasher};
3434
use std::sync::Arc;
3535

36-
use crate::core::compiler::rustdoc::RustdocScrapeExamples;
3736
use crate::core::compiler::unit_dependencies::build_unit_dependencies;
3837
use crate::core::compiler::unit_graph::{self, UnitDep, UnitGraph};
3938
use crate::core::compiler::{standard_lib, CrateType, TargetInfo};
@@ -371,19 +370,11 @@ pub fn create_bcx<'a, 'cfg>(
371370

372371
let should_scrape = build_config.mode.is_doc() && config.cli_unstable().rustdoc_scrape_examples;
373372
let mut scrape_units = if should_scrape {
374-
let scrape_generator = UnitGenerator {
373+
UnitGenerator {
375374
mode: CompileMode::Docscrape,
376375
..generator
377-
};
378-
let mut scrape_units = scrape_generator.generate_root_units()?;
379-
scrape_units.retain(|unit| {
380-
ws.unit_needs_doc_scrape(unit)
381-
&& !matches!(
382-
unit.target.doc_scrape_examples(),
383-
RustdocScrapeExamples::Disabled
384-
)
385-
});
386-
scrape_units
376+
}
377+
.generate_scrape_units(&units)?
387378
} else {
388379
Vec::new()
389380
};

src/cargo/ops/cargo_compile/unit_generator.rs

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ impl<'a> UnitGenerator<'a, '_> {
189189
.iter()
190190
.filter(|t| t.is_bin() || t.is_lib())
191191
.collect(),
192-
CompileMode::Doc { .. } | CompileMode::Docscrape => {
192+
CompileMode::Doc { .. } => {
193193
// `doc` does lib and bins (bin with same name as lib is skipped).
194194
targets
195195
.iter()
@@ -200,7 +200,7 @@ impl<'a> UnitGenerator<'a, '_> {
200200
})
201201
.collect()
202202
}
203-
CompileMode::Doctest | CompileMode::RunCustomBuild => {
203+
CompileMode::Doctest | CompileMode::RunCustomBuild | CompileMode::Docscrape => {
204204
panic!("Invalid mode {:?}", self.mode)
205205
}
206206
}
@@ -426,15 +426,11 @@ impl<'a> UnitGenerator<'a, '_> {
426426
}
427427
}
428428

429-
if self.mode.is_doc_scrape() {
430-
self.add_docscrape_proposals(&mut proposals);
431-
}
432-
433429
Ok(proposals)
434430
}
435431

436-
/// Add additional targets from which to scrape examples for documentation
437-
fn add_docscrape_proposals(&self, proposals: &mut Vec<Proposal<'a>>) {
432+
/// Proposes targets from which to scrape examples for documentation
433+
fn create_docscrape_proposals(&self, doc_units: &[Unit]) -> CargoResult<Vec<Proposal<'a>>> {
438434
// In general, the goal is to scrape examples from (a) whatever targets
439435
// the user is documenting, and (b) Example targets. However, if the user
440436
// is documenting a library with dev-dependencies, those dev-deps are not
@@ -456,23 +452,30 @@ impl<'a> UnitGenerator<'a, '_> {
456452
let reqs_dev_deps = matches!(self.has_dev_units, HasDevUnits::Yes);
457453
let safe_to_scrape_example_targets = no_pkg_has_dev_deps || reqs_dev_deps;
458454

459-
let proposed_targets: HashSet<&Target> = proposals.iter().map(|p| p.target).collect();
455+
let pkgs_to_scrape = doc_units
456+
.iter()
457+
.filter(|unit| self.ws.unit_needs_doc_scrape(unit))
458+
.map(|u| &u.pkg)
459+
.collect::<HashSet<_>>();
460+
460461
let can_scrape = |target: &Target| {
461-
let not_redundant = !proposed_targets.contains(target);
462-
not_redundant
463-
&& match (target.doc_scrape_examples(), target.is_example()) {
464-
// Targets configured by the user to not be scraped should never be scraped
465-
(RustdocScrapeExamples::Disabled, _) => false,
466-
// Targets configured by the user to be scraped should always be scraped
467-
(RustdocScrapeExamples::Enabled, _) => true,
468-
// Example targets with no configuration should be conditionally scraped if
469-
// it's guaranteed not to break the build
470-
(RustdocScrapeExamples::Unset, true) => safe_to_scrape_example_targets,
471-
// All other targets are ignored for now. This may change in the future!
472-
(RustdocScrapeExamples::Unset, false) => false,
473-
}
462+
match (target.doc_scrape_examples(), target.is_example()) {
463+
// Targets configured by the user to not be scraped should never be scraped
464+
(RustdocScrapeExamples::Disabled, _) => false,
465+
// Targets configured by the user to be scraped should always be scraped
466+
(RustdocScrapeExamples::Enabled, _) => true,
467+
// Example targets with no configuration should be conditionally scraped if
468+
// it's guaranteed not to break the build
469+
(RustdocScrapeExamples::Unset, true) => safe_to_scrape_example_targets,
470+
// All other targets are ignored for now. This may change in the future!
471+
(RustdocScrapeExamples::Unset, false) => false,
472+
}
474473
};
475-
proposals.extend(self.filter_targets(can_scrape, false, CompileMode::Docscrape));
474+
475+
let mut scrape_proposals = self.filter_targets(can_scrape, false, CompileMode::Docscrape);
476+
scrape_proposals.retain(|proposal| pkgs_to_scrape.contains(proposal.pkg));
477+
478+
Ok(scrape_proposals)
476479
}
477480

478481
/// Checks if the unit list is empty and the user has passed any combination of
@@ -674,4 +677,16 @@ impl<'a> UnitGenerator<'a, '_> {
674677
let proposals = self.create_proposals()?;
675678
self.proposals_to_units(proposals)
676679
}
680+
681+
/// Generates units specfically for doc-scraping.
682+
///
683+
/// This requires a separate entrypoint from [`generate_root_units`] because it
684+
/// takes the documented units as input.
685+
///
686+
/// [`generate_root_units`]: Self::generate_root_units
687+
pub fn generate_scrape_units(&self, doc_units: &[Unit]) -> CargoResult<Vec<Unit>> {
688+
let scrape_proposals = self.create_docscrape_proposals(&doc_units)?;
689+
let scrape_units = self.proposals_to_units(scrape_proposals)?;
690+
Ok(scrape_units)
691+
}
677692
}

src/doc/src/reference/unstable.md

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1124,34 +1124,35 @@ like this:
11241124
cargo doc -Z unstable-options -Z rustdoc-scrape-examples
11251125
```
11261126

1127-
By default, Cargo will scrape from the packages that are being documented, as well as scrape from
1128-
examples for the packages (i.e. those corresponding to the `--examples` flag). You can individually
1129-
enable or disable targets from being scraped with the `doc-scrape-examples` flag, such as:
1127+
By default, Cargo will scrape examples from the example targets of packages being documented.
1128+
You can individually enable or disable targets from being scraped with the `doc-scrape-examples` flag, such as:
11301129

11311130
```toml
1132-
# Disable scraping examples from a library
1131+
# Enable scraping examples from a library
11331132
[lib]
1134-
doc-scrape-examples = false
1135-
1136-
# Enable scraping examples from a binary
1137-
[[bin]]
1138-
name = "my-bin"
11391133
doc-scrape-examples = true
1134+
1135+
# Disable scraping examples from an example target
1136+
[[example]]
1137+
name = "my-example"
1138+
doc-scrape-examples = false
11401139
```
11411140

11421141
**Note on tests:** enabling `doc-scrape-examples` on test targets will not currently have any effect. Scraping
11431142
examples from tests is a work-in-progress.
11441143

11451144
**Note on dev-dependencies:** documenting a library does not normally require the crate's dev-dependencies. However,
1146-
example units do require dev-deps. For backwards compatibility, `-Z rustdoc-scrape-examples` will *not* introduce a
1145+
example targets require dev-deps. For backwards compatibility, `-Z rustdoc-scrape-examples` will *not* introduce a
11471146
dev-deps requirement for `cargo doc`. Therefore examples will *not* be scraped from example targets under the
11481147
following conditions:
11491148

11501149
1. No target being documented requires dev-deps, AND
1151-
2. At least one crate being documented requires dev-deps, AND
1152-
3. The `doc-scrape-examples` parameter is unset for `[[example]]` targets.
1150+
2. At least one crate with targets being documented has dev-deps, AND
1151+
3. The `doc-scrape-examples` parameter is unset or false for all `[[example]]` targets.
11531152

11541153
If you want examples to be scraped from example targets, then you must not satisfy one of the above conditions.
1154+
For example, you can set `doc-scrape-examples` to true for one example target, and that signals to Cargo that
1155+
you are ok with dev-deps being build for `cargo doc`.
11551156

11561157

11571158
### check-cfg

tests/testsuite/docscrape.rs

Lines changed: 41 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ fn basic() {
3737

3838
let doc_html = p.read_file("target/doc/foo/fn.foo.html");
3939
assert!(doc_html.contains("Examples found in repository"));
40-
assert!(doc_html.contains("More examples"));
40+
assert!(!doc_html.contains("More examples"));
4141

4242
// Ensure that the reverse-dependency has its sources generated
4343
assert!(p.build_dir().join("doc/src/ex/ex.rs.html").exists());
@@ -178,18 +178,25 @@ fn configure_target() {
178178
authors = []
179179
180180
[lib]
181-
doc-scrape-examples = false
181+
doc-scrape-examples = true
182182
183183
[[bin]]
184184
name = "a_bin"
185185
doc-scrape-examples = true
186+
187+
[[example]]
188+
name = "a"
189+
doc-scrape-examples = false
186190
"#,
187191
)
188192
.file(
189193
"src/lib.rs",
190-
"pub fn foo() {} fn lib_must_not_appear() { foo(); }",
194+
"pub fn foo() {} fn lib_must_appear() { foo(); }",
195+
)
196+
.file(
197+
"examples/a.rs",
198+
"fn example_must_not_appear() { foo::foo(); }",
191199
)
192-
.file("examples/a.rs", "fn example_must_appear() { foo::foo(); }")
193200
.file(
194201
"src/bin/a_bin.rs",
195202
"fn bin_must_appear() { foo::foo(); } fn main(){}",
@@ -201,9 +208,9 @@ fn configure_target() {
201208
.run();
202209

203210
let doc_html = p.read_file("target/doc/foo/fn.foo.html");
204-
assert!(!doc_html.contains("lib_must_not_appear"));
205-
assert!(doc_html.contains("example_must_appear"));
211+
assert!(doc_html.contains("lib_must_appear"));
206212
assert!(doc_html.contains("bin_must_appear"));
213+
assert!(!doc_html.contains("example_must_not_appear"));
207214
}
208215

209216
#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
@@ -231,7 +238,6 @@ fn configure_profile_issue_10500() {
231238

232239
let doc_html = p.read_file("target/doc/foo/fn.foo.html");
233240
assert!(doc_html.contains("Examples found in repository"));
234-
assert!(doc_html.contains("More examples"));
235241
}
236242

237243
#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
@@ -342,21 +348,17 @@ fn no_fail_bad_lib() {
342348
"\
343349
[CHECKING] foo v0.0.1 ([CWD])
344350
[SCRAPING] foo v0.0.1 ([CWD])
345-
warning: failed to scan lib in package `foo` for example code usage
346-
Try running with `--verbose` to see the error message.
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
348-
warning: `foo` (lib) generated 1 warning
349351
warning: failed to check lib in package `foo` as a prerequisite for scraping examples from: example \"ex\", example \"ex2\"
350352
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
353+
If an example should not be scanned, then consider adding `doc-scrape-examples = false` to its `[[example]]` definition in Cargo.toml
352354
warning: `foo` (lib) generated 1 warning
353355
warning: failed to scan example \"ex\" in package `foo` for example code usage
354356
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
357+
If an example should not be scanned, then consider adding `doc-scrape-examples = false` to its `[[example]]` definition in Cargo.toml
356358
warning: `foo` (example \"ex\") generated 1 warning
357359
warning: failed to scan example \"ex2\" in package `foo` for example code usage
358360
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
361+
If an example should not be scanned, then consider adding `doc-scrape-examples = false` to its `[[example]]` definition in Cargo.toml
360362
warning: `foo` (example \"ex2\") generated 1 warning
361363
[DOCUMENTING] foo v0.0.1 ([CWD])
362364
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
@@ -389,7 +391,7 @@ fn no_fail_bad_example() {
389391
[SCRAPING] foo v0.0.1 ([CWD])
390392
warning: failed to scan example \"ex1\" in package `foo` for example code usage
391393
Try running with `--verbose` to see the error message.
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
394+
If an example should not be scanned, then consider adding `doc-scrape-examples = false` to its `[[example]]` definition in Cargo.toml
393395
warning: `foo` (example \"ex1\") generated 1 warning
394396
[DOCUMENTING] foo v0.0.1 ([CWD])
395397
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
@@ -415,7 +417,6 @@ error: expected one of `!` or `::`, found `NOT`
415417
| ^^^ expected one of `!` or `::`
416418
417419
[DOCUMENTING] foo v0.0.1 ([CWD])
418-
[RUNNING] `rustdoc[..] --crate-name foo[..]
419420
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
420421
)
421422
.run();
@@ -538,62 +539,48 @@ fn use_dev_deps_if_explicitly_enabled() {
538539
#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
539540
fn only_scrape_documented_targets() {
540541
// package bar has doc = false and should not be eligible for documtation.
541-
let run_with_config = |config: &str, should_scrape: bool| {
542-
let p = project()
543-
.file(
544-
"Cargo.toml",
545-
&format!(
546-
r#"
542+
let p = project()
543+
.file(
544+
"Cargo.toml",
545+
&format!(
546+
r#"
547547
[package]
548548
name = "bar"
549549
version = "0.0.1"
550550
authors = []
551551
552552
[lib]
553-
{config}
553+
doc = false
554554
555555
[workspace]
556556
members = ["foo"]
557557
558558
[dependencies]
559559
foo = {{ path = "foo" }}
560560
"#
561-
),
562-
)
563-
.file("src/lib.rs", "pub fn bar() { foo::foo(); }")
564-
.file(
565-
"foo/Cargo.toml",
566-
r#"
561+
),
562+
)
563+
.file("src/lib.rs", "")
564+
.file("examples/ex.rs", "pub fn main() { foo::foo(); }")
565+
.file(
566+
"foo/Cargo.toml",
567+
r#"
567568
[package]
568569
name = "foo"
569570
version = "0.0.1"
570571
authors = []
571572
"#,
572-
)
573-
.file("foo/src/lib.rs", "pub fn foo() {}")
574-
.build();
575-
576-
p.cargo("doc --workspace -Zunstable-options -Zrustdoc-scrape-examples")
577-
.masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
578-
.run();
579-
580-
let doc_html = p.read_file("target/doc/foo/fn.foo.html");
581-
let example_found = doc_html.contains("Examples found in repository");
582-
if should_scrape {
583-
assert!(example_found);
584-
} else {
585-
assert!(!example_found);
586-
}
587-
};
588-
589-
// By default, bar should be scraped.
590-
run_with_config("", true);
591-
// If bar isn't supposed to be documented, then it is not eligible
592-
// for scraping.
593-
run_with_config("doc = false", false);
594-
// But if the user explicitly says bar should be scraped, then it should
595-
// be scraped.
596-
run_with_config("doc = false\ndoc-scrape-examples = true", true);
573+
)
574+
.file("foo/src/lib.rs", "pub fn foo() {}")
575+
.build();
576+
577+
p.cargo("doc --workspace -Zunstable-options -Zrustdoc-scrape-examples")
578+
.masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
579+
.run();
580+
581+
let doc_html = p.read_file("target/doc/foo/fn.foo.html");
582+
let example_found = doc_html.contains("Examples found in repository");
583+
assert!(!example_found);
597584
}
598585

599586
#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]

0 commit comments

Comments
 (0)