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

Change rustdoc-scrape-examples to be a target-level configuration

This PR addresses issues raised in rust-lang/cargo#9525. Specifically:
1. It enables examples to be scraped from `#[test]` functions, by passing additional flags to Rustdoc to ensure that these functions aren't ignored by rustc.
2. It moves the `arg` from `-Z rustdoc-scrape-examples={arg}` into a target-level configuration that can be added to Cargo.toml.

The added test `scrape_examples_configure_target` shows a concrete example. In short, examples will be default scraped from Example and Lib targets. Then the user can enable or disable scraping like so:

```toml
[lib]
doc-scrape-examples = false

[[test]]
name = "my_test"
doc-scrape-examples = true
```
This commit is contained in:
bors 2022-11-25 06:58:20 +00:00
commit de56c1251b
18 changed files with 832 additions and 365 deletions

View File

@ -168,6 +168,7 @@ fn substitute_macros(input: &str) -> String {
("[NOTE]", "note:"),
("[HELP]", "help:"),
("[DOCUMENTING]", " Documenting"),
("[SCRAPING]", " Scraping"),
("[FRESH]", " Fresh"),
("[UPDATING]", " Updating"),
("[ADDING]", " Adding"),

View File

@ -451,9 +451,11 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
vec![]
}
CompileMode::Docscrape => {
let path = self
.deps_dir(unit)
.join(format!("{}.examples", unit.buildkey()));
// The file name needs to be stable across Cargo sessions.
// This originally used unit.buildkey(), but that isn't stable,
// so we use metadata instead (prefixed with name for debugging).
let file_name = format!("{}-{}.examples", unit.pkg.name(), self.metadata(unit));
let path = self.deps_dir(unit).join(file_name);
vec![OutputFile {
path,
hardlink: None,

View File

@ -77,6 +77,11 @@ pub struct Context<'a, 'cfg> {
/// Map of Doc/Docscrape units to metadata for their -Cmetadata flag.
/// See Context::find_metadata_units for more details.
pub metadata_for_doc_units: HashMap<Unit, Metadata>,
/// Set of metadata of Docscrape units that fail before completion, e.g.
/// because the target has a type error. This is in an Arc<Mutex<..>>
/// because it is continuously updated as the job progresses.
pub failed_scrape_units: Arc<Mutex<HashSet<Metadata>>>,
}
impl<'a, 'cfg> Context<'a, 'cfg> {
@ -115,6 +120,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
rustc_clients: HashMap::new(),
lto: HashMap::new(),
metadata_for_doc_units: HashMap::new(),
failed_scrape_units: Arc::new(Mutex::new(HashSet::new())),
})
}

View File

@ -1275,7 +1275,7 @@ fn calculate_normal(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Finger
// Afterwards calculate our own fingerprint information.
let target_root = target_root(cx);
let local = if unit.mode.is_doc() {
let local = if unit.mode.is_doc() || unit.mode.is_doc_scrape() {
// rustdoc does not have dep-info files.
let fingerprint = pkg_fingerprint(cx.bcx, &unit.pkg).with_context(|| {
format!(
@ -1302,7 +1302,7 @@ fn calculate_normal(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Finger
// Fill out a bunch more information that we'll be tracking typically
// hashed to take up less space on disk as we just need to know when things
// change.
let extra_flags = if unit.mode.is_doc() {
let extra_flags = if unit.mode.is_doc() || unit.mode.is_doc_scrape() {
cx.bcx.rustdocflags_args(unit)
} else {
cx.bcx.rustflags_args(unit)

View File

@ -134,6 +134,7 @@ struct DrainState<'cfg> {
active: HashMap<JobId, Unit>,
compiled: HashSet<PackageId>,
documented: HashSet<PackageId>,
scraped: HashSet<PackageId>,
counts: HashMap<PackageId, usize>,
progress: Progress<'cfg>,
next_id: u32,
@ -347,17 +348,29 @@ enum Message {
BuildPlanMsg(String, ProcessBuilder, Arc<Vec<OutputFile>>),
Stdout(String),
Stderr(String),
// This is for general stderr output from subprocesses
Diagnostic {
id: JobId,
level: String,
diag: String,
fixable: bool,
},
// This handles duplicate output that is suppressed, for showing
// only a count of duplicate messages instead
WarningCount {
id: JobId,
emitted: bool,
fixable: bool,
},
// This is for warnings generated by Cargo's interpretation of the
// subprocess output, e.g. scrape-examples prints a warning if a
// unit fails to be scraped
Warning {
id: JobId,
warning: String,
},
FixDiagnostic(diagnostic_server::Message),
Token(io::Result<Acquired>),
Finish(JobId, Artifact, CargoResult<()>),
@ -405,6 +418,7 @@ impl<'a, 'cfg> JobState<'a, 'cfg> {
Ok(())
}
/// See [`Message::Diagnostic`] and [`Message::WarningCount`].
pub fn emit_diag(&self, level: String, diag: String, fixable: bool) -> CargoResult<()> {
if let Some(dedupe) = self.output {
let emitted = dedupe.emit_diag(&diag)?;
@ -426,6 +440,15 @@ impl<'a, 'cfg> JobState<'a, 'cfg> {
Ok(())
}
/// See [`Message::Warning`].
pub fn warning(&self, warning: String) -> CargoResult<()> {
self.messages.push_bounded(Message::Warning {
id: self.id,
warning,
});
Ok(())
}
/// A method used to signal to the coordinator thread that the rmeta file
/// for an rlib has been produced. This is only called for some rmeta
/// builds when required, and can be called at any time before a job ends.
@ -475,8 +498,10 @@ impl<'cfg> JobQueue<'cfg> {
.filter(|dep| {
// Binaries aren't actually needed to *compile* tests, just to run
// them, so we don't include this dependency edge in the job graph.
// But we shouldn't filter out dependencies being scraped for Rustdoc.
(!dep.unit.target.is_test() && !dep.unit.target.is_bin())
|| dep.unit.artifact.is_true()
|| dep.unit.mode.is_doc_scrape()
})
.map(|dep| {
// Handle the case here where our `unit -> dep` dependency may
@ -563,6 +588,7 @@ impl<'cfg> JobQueue<'cfg> {
active: HashMap::new(),
compiled: HashSet::new(),
documented: HashSet::new(),
scraped: HashSet::new(),
counts: self.counts,
progress,
next_id: 0,
@ -739,6 +765,10 @@ impl<'cfg> DrainState<'cfg> {
cnts.disallow_fixable();
}
}
Message::Warning { id, warning } => {
cx.bcx.config.shell().warn(warning)?;
self.bump_warning_count(id, true, false);
}
Message::WarningCount {
id,
emitted,
@ -782,6 +812,16 @@ impl<'cfg> DrainState<'cfg> {
debug!("end ({:?}): {:?}", unit, result);
match result {
Ok(()) => self.finish(id, &unit, artifact, cx)?,
Err(_)
if unit.mode.is_doc_scrape()
&& unit.target.doc_scrape_examples().is_unset() =>
{
cx.failed_scrape_units
.lock()
.unwrap()
.insert(cx.files().metadata(&unit));
self.queue.finish(&unit, &artifact);
}
Err(error) => {
let msg = "The following warnings were emitted during compilation:";
self.emit_warnings(Some(msg), &unit, cx)?;
@ -1303,8 +1343,11 @@ impl<'cfg> DrainState<'cfg> {
unit: &Unit,
fresh: Freshness,
) -> CargoResult<()> {
if (self.compiled.contains(&unit.pkg.package_id()) && !unit.mode.is_doc())
if (self.compiled.contains(&unit.pkg.package_id())
&& !unit.mode.is_doc()
&& !unit.mode.is_doc_scrape())
|| (self.documented.contains(&unit.pkg.package_id()) && unit.mode.is_doc())
|| (self.scraped.contains(&unit.pkg.package_id()) && unit.mode.is_doc_scrape())
{
return Ok(());
}
@ -1318,6 +1361,9 @@ impl<'cfg> DrainState<'cfg> {
config.shell().status("Documenting", &unit.pkg)?;
} else if unit.mode.is_doc_test() {
// Skip doc test.
} else if unit.mode.is_doc_scrape() {
self.scraped.insert(unit.pkg.package_id());
config.shell().status("Scraping", &unit.pkg)?;
} else {
self.compiled.insert(unit.pkg.package_id());
if unit.mode.is_check() {

View File

@ -22,7 +22,7 @@ mod unit;
pub mod unit_dependencies;
pub mod unit_graph;
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
use std::env;
use std::ffi::{OsStr, OsString};
use std::fs::{self, File};
@ -55,7 +55,7 @@ use crate::core::compiler::future_incompat::FutureIncompatReport;
pub use crate::core::compiler::unit::{Unit, UnitInterner};
use crate::core::manifest::TargetSourcePath;
use crate::core::profiles::{PanicStrategy, Profile, Strip};
use crate::core::{Feature, PackageId, Target};
use crate::core::{Feature, PackageId, Target, Verbosity};
use crate::util::errors::{CargoResult, VerboseError};
use crate::util::interning::InternedString;
use crate::util::machine_message::{self, Message};
@ -654,13 +654,16 @@ fn rustdoc(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Work> {
rustdoc.arg("-C").arg(format!("metadata={}", metadata));
let scrape_output_path = |unit: &Unit| -> CargoResult<PathBuf> {
let output_dir = cx.files().deps_dir(unit);
Ok(output_dir.join(format!("{}.examples", unit.buildkey())))
cx.outputs(unit).map(|outputs| outputs[0].path.clone())
};
if unit.mode.is_doc_scrape() {
debug_assert!(cx.bcx.scrape_units.contains(unit));
if unit.target.is_test() {
rustdoc.arg("--scrape-tests");
}
rustdoc.arg("-Zunstable-options");
rustdoc
@ -678,19 +681,24 @@ fn rustdoc(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Work> {
rustdoc.arg("--scrape-examples-target-crate").arg(name);
}
}
} else if cx.bcx.scrape_units.len() > 0 && cx.bcx.ws.unit_needs_doc_scrape(unit) {
// We only pass scraped examples to packages in the workspace
// since examples are only coming from reverse-dependencies of workspace packages
rustdoc.arg("-Zunstable-options");
for scrape_unit in &cx.bcx.scrape_units {
rustdoc
.arg("--with-examples")
.arg(scrape_output_path(scrape_unit)?);
}
}
let should_include_scrape_units = unit.mode.is_doc()
&& cx.bcx.scrape_units.len() > 0
&& cx.bcx.ws.unit_needs_doc_scrape(unit);
let scrape_outputs = if should_include_scrape_units {
rustdoc.arg("-Zunstable-options");
Some(
cx.bcx
.scrape_units
.iter()
.map(|unit| Ok((cx.files().metadata(unit), scrape_output_path(unit)?)))
.collect::<CargoResult<HashMap<_, _>>>()?,
)
} else {
None
};
build_deps_args(&mut rustdoc, cx, unit)?;
rustdoc::add_root_urls(cx, unit, &mut rustdoc)?;
@ -700,19 +708,45 @@ fn rustdoc(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Work> {
append_crate_version_flag(unit, &mut rustdoc);
}
let target_desc = unit.target.description_named();
let name = unit.pkg.name().to_string();
let build_script_outputs = Arc::clone(&cx.build_script_outputs);
let package_id = unit.pkg.package_id();
let manifest_path = PathBuf::from(unit.pkg.manifest_path());
let relative_manifest_path = manifest_path
.strip_prefix(cx.bcx.ws.root())
.unwrap_or(&manifest_path)
.to_owned();
let target = Target::clone(&unit.target);
let mut output_options = OutputOptions::new(cx, unit);
let script_metadata = cx.find_build_script_metadata(unit);
let failed_scrape_units = Arc::clone(&cx.failed_scrape_units);
let hide_diagnostics_for_scrape_unit = unit.mode.is_doc_scrape()
&& unit.target.doc_scrape_examples().is_unset()
&& !matches!(cx.bcx.config.shell().verbosity(), Verbosity::Verbose);
if hide_diagnostics_for_scrape_unit {
output_options.show_diagnostics = false;
}
Ok(Work::new(move |state| {
add_custom_flags(
&mut rustdoc,
&build_script_outputs.lock().unwrap(),
script_metadata,
)?;
// Add the output of scraped examples to the rustdoc command.
// This action must happen after the unit's dependencies have finished,
// because some of those deps may be Docscrape units which have failed.
// So we dynamically determine which `--with-examples` flags to pass here.
if let Some(scrape_outputs) = scrape_outputs {
let failed_scrape_units = failed_scrape_units.lock().unwrap();
for (metadata, output_path) in &scrape_outputs {
if !failed_scrape_units.contains(metadata) {
rustdoc.arg("--with-examples").arg(output_path);
}
}
}
let crate_dir = doc_dir.join(&crate_name);
if crate_dir.exists() {
// Remove output from a previous build. This ensures that stale
@ -722,7 +756,7 @@ fn rustdoc(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Work> {
}
state.running(&rustdoc);
rustdoc
let result = rustdoc
.exec_with_streaming(
&mut |line| on_stdout_line(state, line, package_id, &target),
&mut |line| {
@ -737,7 +771,23 @@ fn rustdoc(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Work> {
},
false,
)
.with_context(|| format!("could not document `{}`", name))?;
.with_context(|| format!("could not document `{}`", name));
if let Err(e) = result {
if hide_diagnostics_for_scrape_unit {
let diag = format!(
"\
failed to scan {target_desc} in package `{name}` for example code usage
Try running with `--verbose` to see the error message.
If this example should not be scanned, consider adding `doc-scrape-examples = false` to the `[[example]]` definition in {}",
relative_manifest_path.display()
);
state.warning(diag)?;
}
return Err(e);
}
Ok(())
}))
}

View File

@ -190,3 +190,22 @@ pub fn add_root_urls(
}
Ok(())
}
/// Indicates whether a target should have examples scraped from it
/// by rustdoc. Configured within Cargo.toml.
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Copy)]
pub enum RustdocScrapeExamples {
Enabled,
Disabled,
Unset,
}
impl RustdocScrapeExamples {
pub fn is_enabled(&self) -> bool {
matches!(self, RustdocScrapeExamples::Enabled)
}
pub fn is_unset(&self) -> bool {
matches!(self, RustdocScrapeExamples::Unset)
}
}

View File

@ -718,13 +718,14 @@ fn compute_deps_doc(
// Add all units being scraped for examples as a dependency of top-level Doc units.
if state.ws.unit_needs_doc_scrape(unit) {
for scrape_unit in state.scrape_units.iter() {
deps_of(scrape_unit, state, unit_for)?;
let scrape_unit_for = UnitFor::new_normal(scrape_unit.kind);
deps_of(scrape_unit, state, scrape_unit_for)?;
ret.push(new_unit_dep(
state,
scrape_unit,
&scrape_unit.pkg,
&scrape_unit.target,
unit_for,
scrape_unit_for,
scrape_unit.kind,
scrape_unit.mode,
IS_NO_ARTIFACT_DEP,
@ -1088,7 +1089,6 @@ impl<'a, 'cfg> State<'a, 'cfg> {
if !dep.is_transitive()
&& !unit.target.is_test()
&& !unit.target.is_example()
&& !unit.mode.is_doc_scrape()
&& !unit.mode.is_any_test()
{
return false;

View File

@ -689,10 +689,8 @@ unstable_cli_options!(
terminal_width: Option<Option<usize>> = ("Provide a terminal width to rustc for error truncation"),
publish_timeout: bool = ("Enable the `publish.timeout` key in .cargo/config.toml file"),
unstable_options: bool = ("Allow the usage of unstable options"),
// TODO(wcrichto): move scrape example configuration into Cargo.toml before stabilization
// See: https://github.com/rust-lang/cargo/pull/9525#discussion_r728470927
rustdoc_scrape_examples: Option<String> = ("Allow rustdoc to scrape examples from reverse-dependencies for documentation"),
skip_rustdoc_fingerprint: bool = (HIDDEN),
rustdoc_scrape_examples: bool = ("Allows Rustdoc to scrape code examples from reverse-dependencies"),
);
const STABILIZED_COMPILE_PROGRESS: &str = "The progress bar is now always \
@ -963,15 +961,7 @@ impl CliUnstable {
"namespaced-features" => stabilized_warn(k, "1.60", STABILISED_NAMESPACED_FEATURES),
"weak-dep-features" => stabilized_warn(k, "1.60", STABILIZED_WEAK_DEP_FEATURES),
"credential-process" => self.credential_process = parse_empty(k, v)?,
"rustdoc-scrape-examples" => {
if let Some(s) = v {
self.rustdoc_scrape_examples = Some(s.to_string())
} else {
bail!(
r#"-Z rustdoc-scrape-examples must take "all" or "examples" as an argument"#
)
}
}
"rustdoc-scrape-examples" => self.rustdoc_scrape_examples = parse_empty(k, v)?,
"skip-rustdoc-fingerprint" => self.skip_rustdoc_fingerprint = parse_empty(k, v)?,
"compile-progress" => stabilized_warn(k, "1.30", STABILIZED_COMPILE_PROGRESS),
"offline" => stabilized_err(k, "1.36", STABILIZED_OFFLINE)?,

View File

@ -12,6 +12,7 @@ use serde::Serialize;
use toml_edit::easy as toml;
use url::Url;
use crate::core::compiler::rustdoc::RustdocScrapeExamples;
use crate::core::compiler::{CompileKind, CrateType};
use crate::core::resolver::ResolveBehavior;
use crate::core::{Dependency, PackageId, PackageIdSpec, SourceId, Summary};
@ -220,6 +221,7 @@ struct TargetInner {
for_host: bool,
proc_macro: bool,
edition: Edition,
doc_scrape_examples: RustdocScrapeExamples,
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
@ -373,6 +375,7 @@ compact_debug! {
for_host
proc_macro
edition
doc_scrape_examples
)]
}
}
@ -648,6 +651,7 @@ impl Target {
harness: true,
for_host: false,
proc_macro: false,
doc_scrape_examples: RustdocScrapeExamples::Unset,
edition,
tested: true,
benched: true,
@ -699,7 +703,8 @@ impl Target {
.set_name(name)
.set_for_host(true)
.set_benched(false)
.set_tested(false);
.set_tested(false)
.set_doc_scrape_examples(RustdocScrapeExamples::Disabled);
target
}
@ -710,7 +715,8 @@ impl Target {
.set_name(name)
.set_for_host(true)
.set_benched(false)
.set_tested(false);
.set_tested(false)
.set_doc_scrape_examples(RustdocScrapeExamples::Disabled);
target
}
@ -804,6 +810,9 @@ impl Target {
pub fn edition(&self) -> Edition {
self.inner.edition
}
pub fn doc_scrape_examples(&self) -> RustdocScrapeExamples {
self.inner.doc_scrape_examples
}
pub fn benched(&self) -> bool {
self.inner.benched
}
@ -918,6 +927,13 @@ impl Target {
Arc::make_mut(&mut self.inner).edition = edition;
self
}
pub fn set_doc_scrape_examples(
&mut self,
doc_scrape_examples: RustdocScrapeExamples,
) -> &mut Target {
Arc::make_mut(&mut self.inner).doc_scrape_examples = doc_scrape_examples;
self
}
pub fn set_harness(&mut self, harness: bool) -> &mut Target {
Arc::make_mut(&mut self.inner).harness = harness;
self

View File

@ -1,10 +1,12 @@
//! Filters and their rules to select which Cargo targets will be built.
use crate::core::compiler::CompileMode;
use crate::core::{Target, TargetKind};
use crate::core::dependency::DepKind;
use crate::core::resolver::HasDevUnits;
use crate::core::{Package, Target, TargetKind};
use crate::util::restricted_names::is_glob_pattern;
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Clone)]
/// Indicates whether or not the library target gets included.
pub enum LibRule {
/// Include the library, fail if not present
@ -15,7 +17,7 @@ pub enum LibRule {
False,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
/// Indicates which Cargo targets will be selected to be built.
pub enum FilterRule {
/// All included.
@ -299,4 +301,67 @@ impl CompileFilter {
}
}
}
/// Generate a CompileFilter that represents the maximal set of targets
/// that should be considered for scraped examples.
pub(super) fn refine_for_docscrape(
&self,
to_builds: &[&Package],
has_dev_units: HasDevUnits,
) -> CompileFilter {
// In general, the goal is to scrape examples from (a) whatever targets
// the user is documenting, and (b) Example targets. However, if the user
// is documenting a library with dev-dependencies, those dev-deps are not
// needed for the library, while dev-deps are needed for the examples.
//
// If scrape-examples caused `cargo doc` to start requiring dev-deps, this
// would be a breaking change to crates whose dev-deps don't compile.
// Therefore we ONLY want to scrape Example targets if either:
// (1) No package has dev-dependencies, so this is a moot issue, OR
// (2) The provided CompileFilter requires dev-dependencies anyway.
//
// The next two variables represent these two conditions.
let no_pkg_has_dev_deps = to_builds.iter().all(|pkg| {
pkg.summary()
.dependencies()
.iter()
.all(|dep| !matches!(dep.kind(), DepKind::Development))
});
let reqs_dev_deps = matches!(has_dev_units, HasDevUnits::Yes);
let example_filter = if no_pkg_has_dev_deps || reqs_dev_deps {
FilterRule::All
} else {
FilterRule::none()
};
match self {
CompileFilter::Only {
all_targets,
lib,
bins,
tests,
benches,
..
} => CompileFilter::Only {
all_targets: *all_targets,
lib: lib.clone(),
bins: bins.clone(),
examples: example_filter,
tests: tests.clone(),
benches: benches.clone(),
},
CompileFilter::Default { .. } => CompileFilter::Only {
all_targets: false,
lib: LibRule::Default,
bins: FilterRule::none(),
examples: example_filter,
tests: FilterRule::none(),
benches: FilterRule::none(),
},
}
}
}

View File

@ -34,6 +34,7 @@ use std::fmt::Write;
use std::hash::{Hash, Hasher};
use std::sync::Arc;
use crate::core::compiler::rustdoc::RustdocScrapeExamples;
use crate::core::compiler::unit_dependencies::{build_unit_dependencies, IsArtifact};
use crate::core::compiler::unit_graph::{self, UnitDep, UnitGraph};
use crate::core::compiler::{standard_lib, CrateType, TargetInfo};
@ -233,27 +234,34 @@ pub fn create_bcx<'a, 'cfg>(
let target_data = RustcTargetData::new(ws, &build_config.requested_kinds)?;
let all_packages = &Packages::All;
let rustdoc_scrape_examples = &config.cli_unstable().rustdoc_scrape_examples;
let need_reverse_dependencies = rustdoc_scrape_examples.is_some();
let full_specs = if need_reverse_dependencies {
all_packages
} else {
spec
};
let specs = spec.to_package_id_specs(ws)?;
let has_dev_units = {
// Rustdoc itself doesn't need dev-dependencies. But to scrape examples from packages in the
// workspace, if any of those packages need dev-dependencies, then we need include dev-dependencies
// to scrape those packages.
let any_pkg_has_scrape_enabled = ws
.members_with_features(&specs, cli_features)?
.iter()
.any(|(pkg, _)| {
pkg.targets()
.iter()
.any(|target| target.is_example() && target.doc_scrape_examples().is_enabled())
});
let resolve_specs = full_specs.to_package_id_specs(ws)?;
let has_dev_units = if filter.need_dev_deps(build_config.mode) || need_reverse_dependencies {
HasDevUnits::Yes
} else {
HasDevUnits::No
if filter.need_dev_deps(build_config.mode)
|| (build_config.mode.is_doc() && any_pkg_has_scrape_enabled)
{
HasDevUnits::Yes
} else {
HasDevUnits::No
}
};
let resolve = ops::resolve_ws_with_opts(
ws,
&target_data,
&build_config.requested_kinds,
cli_features,
&resolve_specs,
&specs,
has_dev_units,
crate::core::resolver::features::ForceAllTargets::No,
)?;
@ -276,11 +284,6 @@ pub fn create_bcx<'a, 'cfg>(
// Find the packages in the resolver that the user wants to build (those
// passed in with `-p` or the defaults from the workspace), and convert
// Vec<PackageIdSpec> to a Vec<PackageId>.
let specs = if need_reverse_dependencies {
spec.to_package_id_specs(ws)?
} else {
resolve_specs.clone()
};
let to_build_ids = resolve.specs_to_ids(&specs)?;
// Now get the `Package` for each `PackageId`. This may trigger a download
// if the user specified `-p` for a dependency that is not downloaded.
@ -364,47 +367,45 @@ pub fn create_bcx<'a, 'cfg>(
override_rustc_crate_types(&mut units, args, interner)?;
}
let mut scrape_units = match rustdoc_scrape_examples {
Some(arg) => {
let filter = match arg.as_str() {
"all" => CompileFilter::new_all_targets(),
"examples" => CompileFilter::new(
LibRule::False,
FilterRule::none(),
FilterRule::none(),
FilterRule::All,
FilterRule::none(),
),
_ => {
anyhow::bail!(
r#"-Z rustdoc-scrape-examples must take "all" or "examples" as an argument"#
)
}
};
let to_build_ids = resolve.specs_to_ids(&resolve_specs)?;
let to_builds = pkg_set.get_many(to_build_ids)?;
let mode = CompileMode::Docscrape;
let should_scrape = build_config.mode.is_doc() && config.cli_unstable().rustdoc_scrape_examples;
let mut scrape_units = if should_scrape {
let scrape_filter = filter.refine_for_docscrape(&to_builds, has_dev_units);
let all_units = generate_targets(
ws,
&to_builds,
&scrape_filter,
&build_config.requested_kinds,
explicit_host_kind,
CompileMode::Docscrape,
&resolve,
&workspace_resolve,
&resolved_features,
&pkg_set,
&profiles,
interner,
)?;
generate_targets(
ws,
&to_builds,
&filter,
&build_config.requested_kinds,
explicit_host_kind,
mode,
&resolve,
&workspace_resolve,
&resolved_features,
&pkg_set,
&profiles,
interner,
)?
.into_iter()
// Proc macros should not be scraped for functions, since they only export macros
.filter(|unit| !unit.target.proc_macro())
.collect::<Vec<_>>()
// The set of scraped targets should be a strict subset of the set of documented targets,
// except in the special case of examples targets.
if cfg!(debug_assertions) {
let valid_targets = units.iter().map(|u| &u.target).collect::<HashSet<_>>();
for unit in &all_units {
assert!(unit.target.is_example() || valid_targets.contains(&unit.target));
}
}
None => Vec::new(),
let valid_units = all_units
.into_iter()
.filter(|unit| {
!matches!(
unit.target.doc_scrape_examples(),
RustdocScrapeExamples::Disabled
)
})
.collect::<Vec<_>>();
valid_units
} else {
Vec::new()
};
let std_roots = if let Some(crates) = standard_lib::std_crates(config, Some(&units)) {
@ -1232,6 +1233,9 @@ fn rebuild_unit_graph_shared(
traverse_and_share(interner, &mut memo, &mut result, &unit_graph, root, to_host)
})
.collect();
// If no unit in the unit graph ended up having scrape units attached as dependencies,
// then they won't have been discovered in traverse_and_share and hence won't be in
// memo. So we filter out missing scrape units.
let new_scrape_units = scrape_units
.iter()
.map(|unit| memo.get(unit).unwrap().clone())

View File

@ -2862,12 +2862,12 @@ impl DetailedTomlDependency {
}
#[derive(Default, Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "kebab-case")]
struct TomlTarget {
name: Option<String>,
// The intention was to only accept `crate-type` here but historical
// versions of Cargo also accepted `crate_type`, so look for both.
#[serde(rename = "crate-type")]
crate_type: Option<Vec<String>>,
#[serde(rename = "crate_type")]
crate_type2: Option<Vec<String>>,
@ -2880,12 +2880,12 @@ struct TomlTarget {
bench: Option<bool>,
doc: Option<bool>,
plugin: Option<bool>,
doc_scrape_examples: Option<bool>,
#[serde(rename = "proc-macro")]
proc_macro_raw: Option<bool>,
#[serde(rename = "proc_macro")]
proc_macro_raw2: Option<bool>,
harness: Option<bool>,
#[serde(rename = "required-features")]
required_features: Option<Vec<String>>,
edition: Option<String>,
}

View File

@ -18,6 +18,7 @@ use super::{
PathValue, StringOrBool, StringOrVec, TomlBenchTarget, TomlBinTarget, TomlExampleTarget,
TomlLibTarget, TomlManifest, TomlTarget, TomlTestTarget,
};
use crate::core::compiler::rustdoc::RustdocScrapeExamples;
use crate::core::compiler::CrateType;
use crate::core::{Edition, Feature, Features, Target};
use crate::util::errors::CargoResult;
@ -808,6 +809,11 @@ fn configure(toml: &TomlTarget, target: &mut Target) -> CargoResult<()> {
.set_benched(toml.bench.unwrap_or_else(|| t2.benched()))
.set_harness(toml.harness.unwrap_or_else(|| t2.harness()))
.set_proc_macro(toml.proc_macro().unwrap_or_else(|| t2.proc_macro()))
.set_doc_scrape_examples(match toml.doc_scrape_examples {
None => RustdocScrapeExamples::Unset,
Some(false) => RustdocScrapeExamples::Disabled,
Some(true) => RustdocScrapeExamples::Enabled,
})
.set_for_host(match (toml.plugin, toml.proc_macro()) {
(None, None) => t2.for_host(),
(Some(true), _) | (_, Some(true)) => true,

View File

@ -2343,265 +2343,6 @@ fn doc_fingerprint_unusual_behavior() {
assert!(real_doc.join("somefile").exists());
}
#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
fn scrape_examples_basic() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
"#,
)
.file("examples/ex.rs", "fn main() { foo::foo(); }")
.file("src/lib.rs", "pub fn foo() {}\npub fn bar() { foo(); }")
.build();
p.cargo("doc -Zunstable-options -Z rustdoc-scrape-examples=all")
.masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
.with_stderr(
"\
[..] foo v0.0.1 ([CWD])
[..] foo v0.0.1 ([CWD])
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
)
.run();
let doc_html = p.read_file("target/doc/foo/fn.foo.html");
assert!(doc_html.contains("Examples found in repository"));
assert!(doc_html.contains("More examples"));
// Ensure that the reverse-dependency has its sources generated
assert!(p.build_dir().join("doc/src/ex/ex.rs.html").exists());
}
#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
fn scrape_examples_avoid_build_script_cycle() {
let p = project()
// package with build dependency
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
links = "foo"
[workspace]
members = ["bar"]
[build-dependencies]
bar = {path = "bar"}
"#,
)
.file("src/lib.rs", "")
.file("build.rs", "fn main(){}")
// dependency
.file(
"bar/Cargo.toml",
r#"
[package]
name = "bar"
version = "0.0.1"
authors = []
links = "bar"
"#,
)
.file("bar/src/lib.rs", "")
.file("bar/build.rs", "fn main(){}")
.build();
p.cargo("doc --all -Zunstable-options -Z rustdoc-scrape-examples=all")
.masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
.run();
}
// FIXME: This test is broken with latest nightly 2022-08-02.
// The example is calling a function from a proc-macro, but proc-macros don't
// export functions. It is not clear what this test is trying to exercise.
// #[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
#[ignore = "broken, needs fixing"]
#[cargo_test]
fn scrape_examples_complex_reverse_dependencies() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
[dev-dependencies]
a = {path = "a", features = ["feature"]}
b = {path = "b"}
[workspace]
members = ["b"]
"#,
)
.file("src/lib.rs", "")
.file("examples/ex.rs", "fn main() { a::f(); }")
.file(
"a/Cargo.toml",
r#"
[package]
name = "a"
version = "0.0.1"
authors = []
[lib]
proc-macro = true
[dependencies]
b = {path = "../b"}
[features]
feature = []
"#,
)
.file("a/src/lib.rs", "#[cfg(feature)] pub fn f();")
.file(
"b/Cargo.toml",
r#"
[package]
name = "b"
version = "0.0.1"
authors = []
"#,
)
.file("b/src/lib.rs", "")
.build();
p.cargo("doc -Zunstable-options -Z rustdoc-scrape-examples=all")
.masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
.run();
}
#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
fn scrape_examples_crate_with_dash() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "da-sh"
version = "0.0.1"
authors = []
"#,
)
.file("src/lib.rs", "pub fn foo() {}")
.file("examples/a.rs", "fn main() { da_sh::foo(); }")
.build();
p.cargo("doc -Zunstable-options -Z rustdoc-scrape-examples=all")
.masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
.run();
let doc_html = p.read_file("target/doc/da_sh/fn.foo.html");
assert!(doc_html.contains("Examples found in repository"));
}
#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
fn scrape_examples_missing_flag() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "1.2.4"
authors = []
"#,
)
.file("src/lib.rs", "//! These are the docs!")
.build();
p.cargo("doc -Zrustdoc-scrape-examples")
.masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
.with_status(101)
.with_stderr("error: -Z rustdoc-scrape-examples must take [..] an argument")
.run();
}
#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
fn scrape_examples_configure_profile() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
[profile.dev]
panic = "abort"
"#,
)
.file("examples/ex.rs", "fn main() { foo::foo(); }")
.file("src/lib.rs", "pub fn foo() {}\npub fn bar() { foo(); }")
.build();
p.cargo("doc -Zunstable-options -Z rustdoc-scrape-examples=all")
.masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
.run();
let doc_html = p.read_file("target/doc/foo/fn.foo.html");
assert!(doc_html.contains("Examples found in repository"));
assert!(doc_html.contains("More examples"));
}
#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
fn scrape_examples_issue_10545() {
let p = project()
.file(
"Cargo.toml",
r#"
[workspace]
resolver = "2"
members = ["a", "b"]
"#,
)
.file(
"a/Cargo.toml",
r#"
[package]
name = "a"
version = "0.0.1"
authors = []
edition = "2021"
[features]
default = ["foo"]
foo = []
"#,
)
.file("a/src/lib.rs", "")
.file(
"b/Cargo.toml",
r#"
[package]
name = "b"
version = "0.0.1"
authors = []
edition = "2021"
[lib]
proc-macro = true
"#,
)
.file("b/src/lib.rs", "")
.build();
p.cargo("doc -Zunstable-options -Z rustdoc-scrape-examples=all")
.masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
.run();
}
#[cargo_test]
fn lib_before_bin() {
// Checks that the library is documented before the binary.

View File

@ -0,0 +1,521 @@
//! Tests for the `cargo doc` command with `-Zrustdoc-scrape-examples`.
use cargo_test_support::project;
#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
fn basic() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
"#,
)
.file("examples/ex.rs", "fn main() { foo::foo(); }")
.file("src/lib.rs", "pub fn foo() {}\npub fn bar() { foo(); }")
.build();
p.cargo("doc -Zunstable-options -Zrustdoc-scrape-examples")
.masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
.with_stderr(
"\
[CHECKING] foo v0.0.1 ([CWD])
[SCRAPING] foo v0.0.1 ([CWD])
[DOCUMENTING] foo v0.0.1 ([CWD])
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
)
.run();
p.cargo("doc -Zunstable-options -Z rustdoc-scrape-examples")
.masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
.with_stderr("[FINISHED] [..]")
.run();
let doc_html = p.read_file("target/doc/foo/fn.foo.html");
assert!(doc_html.contains("Examples found in repository"));
assert!(doc_html.contains("More examples"));
// Ensure that the reverse-dependency has its sources generated
assert!(p.build_dir().join("doc/src/ex/ex.rs.html").exists());
}
#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
fn avoid_build_script_cycle() {
let p = project()
// package with build dependency
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
links = "foo"
[workspace]
members = ["bar"]
[build-dependencies]
bar = {path = "bar"}
"#,
)
.file("src/lib.rs", "")
.file("build.rs", "fn main(){}")
// dependency
.file(
"bar/Cargo.toml",
r#"
[package]
name = "bar"
version = "0.0.1"
authors = []
links = "bar"
"#,
)
.file("bar/src/lib.rs", "")
.file("bar/build.rs", "fn main(){}")
.build();
p.cargo("doc --workspace -Zunstable-options -Zrustdoc-scrape-examples")
.masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
.run();
}
#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
fn complex_reverse_dependencies() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
[dev-dependencies]
a = {path = "a", features = ["feature"]}
b = {path = "b"}
[workspace]
members = ["b"]
"#,
)
.file("src/lib.rs", "")
.file("examples/ex.rs", "fn main() {}")
.file(
"a/Cargo.toml",
r#"
[package]
name = "a"
version = "0.0.1"
authors = []
[lib]
proc-macro = true
[dependencies]
b = {path = "../b"}
[features]
feature = []
"#,
)
.file("a/src/lib.rs", "")
.file(
"b/Cargo.toml",
r#"
[package]
name = "b"
version = "0.0.1"
authors = []
"#,
)
.file("b/src/lib.rs", "")
.build();
p.cargo("doc --workspace --examples -Zunstable-options -Zrustdoc-scrape-examples")
.masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
.run();
}
#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
fn crate_with_dash() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "da-sh"
version = "0.0.1"
authors = []
"#,
)
.file("src/lib.rs", "pub fn foo() {}")
.file("examples/a.rs", "fn main() { da_sh::foo(); }")
.build();
p.cargo("doc -Zunstable-options -Zrustdoc-scrape-examples")
.masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
.run();
let doc_html = p.read_file("target/doc/da_sh/fn.foo.html");
assert!(doc_html.contains("Examples found in repository"));
}
#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
fn configure_target() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
[lib]
doc-scrape-examples = false
[[bin]]
name = "a_bin"
doc-scrape-examples = true
"#,
)
.file(
"src/lib.rs",
"pub fn foo() {} fn lib_must_not_appear() { foo(); }",
)
.file("examples/a.rs", "fn example_must_appear() { foo::foo(); }")
.file(
"src/bin/a_bin.rs",
"fn bin_must_appear() { foo::foo(); } fn main(){}",
)
.build();
p.cargo("doc --lib --bins -Zunstable-options -Zrustdoc-scrape-examples")
.masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
.run();
let doc_html = p.read_file("target/doc/foo/fn.foo.html");
assert!(!doc_html.contains("lib_must_not_appear"));
assert!(doc_html.contains("example_must_appear"));
assert!(doc_html.contains("bin_must_appear"));
}
#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
fn configure_profile_issue_10500() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
[profile.dev]
panic = "abort"
"#,
)
.file("examples/ex.rs", "fn main() { foo::foo(); }")
.file("src/lib.rs", "pub fn foo() {}\npub fn bar() { foo(); }")
.build();
p.cargo("doc -Zunstable-options -Zrustdoc-scrape-examples")
.masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
.run();
let doc_html = p.read_file("target/doc/foo/fn.foo.html");
assert!(doc_html.contains("Examples found in repository"));
assert!(doc_html.contains("More examples"));
}
#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
fn issue_10545() {
let p = project()
.file(
"Cargo.toml",
r#"
[workspace]
resolver = "2"
members = ["a", "b"]
"#,
)
.file(
"a/Cargo.toml",
r#"
[package]
name = "a"
version = "0.0.1"
authors = []
edition = "2021"
[features]
default = ["foo"]
foo = []
"#,
)
.file("a/src/lib.rs", "")
.file(
"b/Cargo.toml",
r#"
[package]
name = "b"
version = "0.0.1"
authors = []
edition = "2021"
[lib]
proc-macro = true
"#,
)
.file("b/src/lib.rs", "")
.build();
p.cargo("doc --workspace -Zunstable-options -Zrustdoc-scrape-examples")
.masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
.run();
}
#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
fn cache() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
"#,
)
.file("examples/ex.rs", "fn main() { foo::foo(); }")
.file("src/lib.rs", "pub fn foo() {}\npub fn bar() { foo(); }")
.build();
p.cargo("doc -Zunstable-options -Zrustdoc-scrape-examples")
.masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
.with_stderr(
"\
[CHECKING] foo v0.0.1 ([CWD])
[SCRAPING] foo v0.0.1 ([CWD])
[DOCUMENTING] foo v0.0.1 ([CWD])
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
)
.run();
p.cargo("doc -Zunstable-options -Zrustdoc-scrape-examples")
.masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
.with_stderr(
"\
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
)
.run();
}
#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
fn no_fail_bad_lib() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
"#,
)
.file("src/lib.rs", "pub fn foo() { CRASH_THE_BUILD() }")
.build();
p.cargo("doc -Zunstable-options -Z rustdoc-scrape-examples")
.masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
.with_stderr(
"\
[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 this example should not be scanned, consider adding `doc-scrape-examples = false` to the `[[example]]` definition in Cargo.toml
warning: `foo` (lib) generated 1 warning
[DOCUMENTING] foo v0.0.1 ([CWD])
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
)
.run();
}
#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
fn no_fail_bad_example() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
"#,
)
.file("examples/ex1.rs", "DOES NOT COMPILE")
.file("examples/ex2.rs", "fn main() { foo::foo(); }")
.file("src/lib.rs", "pub fn foo(){}")
.build();
p.cargo("doc -Zunstable-options -Z rustdoc-scrape-examples")
.masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
.with_stderr(
"\
[CHECKING] foo v0.0.1 ([CWD])
[SCRAPING] foo v0.0.1 ([CWD])
warning: failed to scan example \"ex1\" in package `foo` for example code usage
Try running with `--verbose` to see the error message.
If this example should not be scanned, consider adding `doc-scrape-examples = false` to the `[[example]]` definition in Cargo.toml
warning: `foo` (example \"ex1\") generated 1 warning
[DOCUMENTING] foo v0.0.1 ([CWD])
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
)
.run();
p.cargo("clean").run();
p.cargo("doc -v -Zunstable-options -Z rustdoc-scrape-examples")
.masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
.with_stderr_unordered(
"\
[CHECKING] foo v0.0.1 ([CWD])
[RUNNING] `rustc --crate-name foo[..]
[SCRAPING] foo v0.0.1 ([CWD])
[RUNNING] `rustdoc[..] --crate-name ex1[..]
[RUNNING] `rustdoc[..] --crate-name ex2[..]
[RUNNING] `rustdoc[..] --crate-name foo[..]
error: expected one of `!` or `::`, found `NOT`
--> examples/ex1.rs:1:6
|
1 | DOES NOT COMPILE
| ^^^ expected one of `!` or `::`
[DOCUMENTING] foo v0.0.1 ([CWD])
[RUNNING] `rustdoc[..] --crate-name foo[..]
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
)
.run();
let doc_html = p.read_file("target/doc/foo/fn.foo.html");
assert!(doc_html.contains("Examples found in repository"));
}
#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
fn no_scrape_with_dev_deps() {
// Tests that a crate with dev-dependencies does not have its examples
// scraped unless explicitly prompted to check them. See
// `CompileFilter::refine_for_docscrape` for details on why.
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
[lib]
doc-scrape-examples = false
[dev-dependencies]
a = {path = "a"}
"#,
)
.file("src/lib.rs", "")
.file("examples/ex.rs", "fn main() { a::f(); }")
.file(
"a/Cargo.toml",
r#"
[package]
name = "a"
version = "0.0.1"
authors = []
"#,
)
.file("a/src/lib.rs", "pub fn f() {}")
.build();
// If --examples is not provided, then the example is never scanned.
p.cargo("doc -Zunstable-options -Z rustdoc-scrape-examples")
.masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
.with_stderr(
"\
[DOCUMENTING] foo v0.0.1 ([CWD])
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
)
.run();
// If --examples is provided, then the bad example is scanned and ignored.
p.cargo("doc --examples -Zunstable-options -Z rustdoc-scrape-examples")
.masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
.with_stderr_unordered(
"\
[CHECKING] a v0.0.1 ([CWD]/a)
[CHECKING] foo v0.0.1 ([CWD])
[DOCUMENTING] a v0.0.1 ([CWD]/a)
[SCRAPING] foo v0.0.1 ([CWD])
[DOCUMENTING] foo v0.0.1 ([CWD])
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
)
.run();
}
#[cargo_test(nightly, reason = "rustdoc scrape examples flags are unstable")]
fn use_dev_deps_if_explicitly_enabled() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
[lib]
doc-scrape-examples = false
[[example]]
name = "ex"
doc-scrape-examples = true
[dev-dependencies]
a = {path = "a"}
"#,
)
.file("src/lib.rs", "")
.file("examples/ex.rs", "fn main() { a::f(); }")
.file(
"a/Cargo.toml",
r#"
[package]
name = "a"
version = "0.0.1"
authors = []
"#,
)
.file("a/src/lib.rs", "pub fn f() {}")
.build();
// If --examples is not provided, then the example is never scanned.
p.cargo("doc -Zunstable-options -Z rustdoc-scrape-examples")
.masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"])
.with_stderr_unordered(
"\
[CHECKING] foo v0.0.1 ([CWD])
[CHECKING] a v0.0.1 ([CWD]/a)
[SCRAPING] foo v0.0.1 ([CWD])
[DOCUMENTING] foo v0.0.1 ([CWD])
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
)
.run();
}

View File

@ -45,6 +45,7 @@ mod death;
mod dep_info;
mod directory;
mod doc;
mod docscrape;
mod edition;
mod error;
mod features;

View File

@ -468,9 +468,7 @@ fn non_member() {
p.cargo("build -p dep --features f1")
.with_status(101)
.with_stderr(
"[UPDATING][..]\n[ERROR] cannot specify features for packages outside of workspace",
)
.with_stderr("[ERROR] cannot specify features for packages outside of workspace")
.run();
p.cargo("build -p dep --all-features")
@ -486,6 +484,7 @@ fn non_member() {
p.cargo("build -p dep")
.with_stderr(
"\
[UPDATING] [..]
[DOWNLOADING] [..]
[DOWNLOADED] [..]
[COMPILING] dep [..]