mirror of https://github.com/rust-lang/cargo
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:
commit
de56c1251b
|
@ -168,6 +168,7 @@ fn substitute_macros(input: &str) -> String {
|
|||
("[NOTE]", "note:"),
|
||||
("[HELP]", "help:"),
|
||||
("[DOCUMENTING]", " Documenting"),
|
||||
("[SCRAPING]", " Scraping"),
|
||||
("[FRESH]", " Fresh"),
|
||||
("[UPDATING]", " Updating"),
|
||||
("[ADDING]", " Adding"),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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())),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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(())
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)?,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -45,6 +45,7 @@ mod death;
|
|||
mod dep_info;
|
||||
mod directory;
|
||||
mod doc;
|
||||
mod docscrape;
|
||||
mod edition;
|
||||
mod error;
|
||||
mod features;
|
||||
|
|
|
@ -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 [..]
|
||||
|
|
Loading…
Reference in New Issue