Skip to content

Commit 0c9eb20

Browse files
committed
Auto merge of #9526 - ehuss:doc-collision-resolve, r=alexcrichton
Consolidate doc collision detection. This removes the separate collision detection pass (in cargo_doc.rs) and uses the global collision detection instead. This removes the separate pass for running the resolver (which resulted in running the resolver four times every time `cargo doc` was run). The `--open` option needs to know which path to open, so this is passed in a roundabout way via `Compilation`. Since this method uses the root units, I added a sort on the roots so that the crate that is opened is consistent between runs. This results in some slight behavioral changes. This also makes the collision check slightly more stringent. The test `doc_multiple_targets_same_name` now generates an error instead of a warning because the old code did not check for collisions between libs and bins across packages (which should be very rare).
2 parents b1684e2 + 81defa6 commit 0c9eb20

File tree

5 files changed

+74
-82
lines changed

5 files changed

+74
-82
lines changed

src/cargo/core/compiler/compilation.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ pub struct Compilation<'cfg> {
5151
/// An array of all cdylibs created.
5252
pub cdylibs: Vec<UnitOutput>,
5353

54+
/// The crate names of the root units specified on the command-line.
55+
pub root_crate_names: Vec<String>,
56+
5457
/// All directories for the output of native build commands.
5558
///
5659
/// This is currently used to drive some entries which are added to the
@@ -136,6 +139,7 @@ impl<'cfg> Compilation<'cfg> {
136139
tests: Vec::new(),
137140
binaries: Vec::new(),
138141
cdylibs: Vec::new(),
142+
root_crate_names: Vec::new(),
139143
extra_env: HashMap::new(),
140144
to_doc_test: Vec::new(),
141145
config: bcx.config,

src/cargo/core/compiler/context/mod.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::collections::{BTreeSet, HashMap, HashSet};
22
use std::path::{Path, PathBuf};
33
use std::sync::{Arc, Mutex};
44

5-
use anyhow::Context as _;
5+
use anyhow::{bail, Context as _};
66
use filetime::FileTime;
77
use jobserver::Client;
88

@@ -309,6 +309,9 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
309309
}
310310
self.primary_packages
311311
.extend(self.bcx.roots.iter().map(|u| u.pkg.package_id()));
312+
self.compilation
313+
.root_crate_names
314+
.extend(self.bcx.roots.iter().map(|u| u.target.crate_name()));
312315

313316
self.record_units_requiring_metadata();
314317

@@ -480,6 +483,22 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
480483
}
481484
};
482485

486+
fn doc_collision_error(unit: &Unit, other_unit: &Unit) -> CargoResult<()> {
487+
bail!(
488+
"document output filename collision\n\
489+
The {} `{}` in package `{}` has the same name as the {} `{}` in package `{}`.\n\
490+
Only one may be documented at once since they output to the same path.\n\
491+
Consider documenting only one, renaming one, \
492+
or marking one with `doc = false` in Cargo.toml.",
493+
unit.target.kind().description(),
494+
unit.target.name(),
495+
unit.pkg,
496+
other_unit.target.kind().description(),
497+
other_unit.target.name(),
498+
other_unit.pkg,
499+
);
500+
}
501+
483502
let mut keys = self
484503
.bcx
485504
.unit_graph
@@ -494,6 +513,11 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
494513
if unit.mode.is_doc() {
495514
// See https://github.com/rust-lang/rust/issues/56169
496515
// and https://github.com/rust-lang/rust/issues/61378
516+
if self.is_primary_package(unit) {
517+
// This has been an error since before 1.0, so it
518+
// is not a warning like the other situations.
519+
doc_collision_error(unit, other_unit)?;
520+
}
497521
report_collision(unit, other_unit, &output.path, rustdoc_suggestion)?;
498522
} else {
499523
report_collision(unit, other_unit, &output.path, suggestion)?;

src/cargo/ops/cargo_compile.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1092,6 +1092,9 @@ fn generate_targets(
10921092
// It is computed by the set of enabled features for the package plus
10931093
// every enabled feature of every enabled dependency.
10941094
let mut features_map = HashMap::new();
1095+
// This needs to be a set to de-duplicate units. Due to the way the
1096+
// targets are filtered, it is possible to have duplicate proposals for
1097+
// the same thing.
10951098
let mut units = HashSet::new();
10961099
for Proposal {
10971100
pkg,
@@ -1136,7 +1139,10 @@ fn generate_targets(
11361139
}
11371140
// else, silently skip target.
11381141
}
1139-
Ok(units.into_iter().collect())
1142+
let mut units: Vec<_> = units.into_iter().collect();
1143+
// Keep the roots in a consistent order, which helps with checking test output.
1144+
units.sort_unstable();
1145+
Ok(units)
11401146
}
11411147

11421148
/// Warns if a target's required-features references a feature that doesn't exist.

src/cargo/ops/cargo_doc.rs

Lines changed: 5 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
use crate::core::resolver::HasDevUnits;
21
use crate::core::{Shell, Workspace};
32
use crate::ops;
3+
use crate::util::config::PathAndArgs;
44
use crate::util::CargoResult;
5-
use crate::{core::compiler::RustcTargetData, util::config::PathAndArgs};
65
use serde::Deserialize;
76
use std::path::Path;
7+
use std::path::PathBuf;
88
use std::process::Command;
9-
use std::{collections::HashMap, path::PathBuf};
109

1110
/// Strongly typed options for the `cargo doc` command.
1211
#[derive(Debug)]
@@ -26,64 +25,11 @@ struct CargoDocConfig {
2625

2726
/// Main method for `cargo doc`.
2827
pub fn doc(ws: &Workspace<'_>, options: &DocOptions) -> CargoResult<()> {
29-
let specs = options.compile_opts.spec.to_package_id_specs(ws)?;
30-
let target_data = RustcTargetData::new(ws, &options.compile_opts.build_config.requested_kinds)?;
31-
let ws_resolve = ops::resolve_ws_with_opts(
32-
ws,
33-
&target_data,
34-
&options.compile_opts.build_config.requested_kinds,
35-
&options.compile_opts.cli_features,
36-
&specs,
37-
HasDevUnits::No,
38-
crate::core::resolver::features::ForceAllTargets::No,
39-
)?;
40-
41-
let ids = ws_resolve.targeted_resolve.specs_to_ids(&specs)?;
42-
let pkgs = ws_resolve.pkg_set.get_many(ids)?;
43-
44-
let mut lib_names = HashMap::new();
45-
let mut bin_names = HashMap::new();
46-
let mut names = Vec::new();
47-
for package in &pkgs {
48-
for target in package.targets().iter().filter(|t| t.documented()) {
49-
if target.is_lib() {
50-
if let Some(prev) = lib_names.insert(target.crate_name(), package) {
51-
anyhow::bail!(
52-
"The library `{}` is specified by packages `{}` and \
53-
`{}` but can only be documented once. Consider renaming \
54-
or marking one of the targets as `doc = false`.",
55-
target.crate_name(),
56-
prev,
57-
package
58-
);
59-
}
60-
} else if let Some(prev) = bin_names.insert(target.crate_name(), package) {
61-
anyhow::bail!(
62-
"The binary `{}` is specified by packages `{}` and \
63-
`{}` but can be documented only once. Consider renaming \
64-
or marking one of the targets as `doc = false`.",
65-
target.crate_name(),
66-
prev,
67-
package
68-
);
69-
}
70-
names.push(target.crate_name());
71-
}
72-
}
73-
74-
let open_kind = if options.open_result {
75-
Some(options.compile_opts.build_config.single_requested_kind()?)
76-
} else {
77-
None
78-
};
79-
8028
let compilation = ops::compile(ws, &options.compile_opts)?;
8129

82-
if let Some(kind) = open_kind {
83-
let name = match names.first() {
84-
Some(s) => s.to_string(),
85-
None => return Ok(()),
86-
};
30+
if options.open_result {
31+
let name = &compilation.root_crate_names[0];
32+
let kind = options.compile_opts.build_config.single_requested_kind()?;
8733
let path = compilation.root_output[&kind]
8834
.with_file_name("doc")
8935
.join(&name)

tests/testsuite/doc.rs

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -223,9 +223,15 @@ fn doc_multiple_targets_same_name_lib() {
223223

224224
p.cargo("doc --workspace")
225225
.with_status(101)
226-
.with_stderr_contains("[..] library `foo_lib` is specified [..]")
227-
.with_stderr_contains("[..] `foo v0.1.0[..]` [..]")
228-
.with_stderr_contains("[..] `bar v0.1.0[..]` [..]")
226+
.with_stderr(
227+
"\
228+
error: document output filename collision
229+
The lib `foo_lib` in package `foo v0.1.0 ([ROOT]/foo/foo)` has the same name as \
230+
the lib `foo_lib` in package `bar v0.1.0 ([ROOT]/foo/bar)`.
231+
Only one may be documented at once since they output to the same path.
232+
Consider documenting only one, renaming one, or marking one with `doc = false` in Cargo.toml.
233+
",
234+
)
229235
.run();
230236
}
231237

@@ -265,13 +271,17 @@ fn doc_multiple_targets_same_name() {
265271
.build();
266272

267273
p.cargo("doc --workspace")
268-
.with_stderr_contains("[DOCUMENTING] foo v0.1.0 ([CWD]/foo)")
269-
.with_stderr_contains("[DOCUMENTING] bar v0.1.0 ([CWD]/bar)")
270-
.with_stderr_contains("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
274+
.with_status(101)
275+
.with_stderr(
276+
"\
277+
error: document output filename collision
278+
The bin `foo_lib` in package `foo v0.1.0 ([ROOT]/foo/foo)` has the same name as \
279+
the lib `foo_lib` in package `bar v0.1.0 ([ROOT]/foo/bar)`.
280+
Only one may be documented at once since they output to the same path.
281+
Consider documenting only one, renaming one, or marking one with `doc = false` in Cargo.toml.
282+
",
283+
)
271284
.run();
272-
assert!(p.root().join("target/doc").is_dir());
273-
let doc_file = p.root().join("target/doc/foo_lib/index.html");
274-
assert!(doc_file.is_file());
275285
}
276286

277287
#[cargo_test]
@@ -290,29 +300,31 @@ fn doc_multiple_targets_same_name_bin() {
290300
[package]
291301
name = "foo"
292302
version = "0.1.0"
293-
[[bin]]
294-
name = "foo-cli"
295303
"#,
296304
)
297-
.file("foo/src/foo-cli.rs", "")
305+
.file("foo/src/bin/foo-cli.rs", "")
298306
.file(
299307
"bar/Cargo.toml",
300308
r#"
301309
[package]
302310
name = "bar"
303311
version = "0.1.0"
304-
[[bin]]
305-
name = "foo-cli"
306312
"#,
307313
)
308-
.file("bar/src/foo-cli.rs", "")
314+
.file("bar/src/bin/foo-cli.rs", "")
309315
.build();
310316

311317
p.cargo("doc --workspace")
312318
.with_status(101)
313-
.with_stderr_contains("[..] binary `foo_cli` is specified [..]")
314-
.with_stderr_contains("[..] `foo v0.1.0[..]` [..]")
315-
.with_stderr_contains("[..] `bar v0.1.0[..]` [..]")
319+
.with_stderr(
320+
"\
321+
error: document output filename collision
322+
The bin `foo-cli` in package `foo v0.1.0 ([ROOT]/foo/foo)` has the same name as \
323+
the bin `foo-cli` in package `bar v0.1.0 ([ROOT]/foo/bar)`.
324+
Only one may be documented at once since they output to the same path.
325+
Consider documenting only one, renaming one, or marking one with `doc = false` in Cargo.toml.
326+
",
327+
)
316328
.run();
317329
}
318330

@@ -1152,7 +1164,7 @@ fn doc_workspace_open_help_message() {
11521164
.env("BROWSER", "echo")
11531165
.with_stderr_contains("[..] Documenting bar v0.1.0 ([..])")
11541166
.with_stderr_contains("[..] Documenting foo v0.1.0 ([..])")
1155-
.with_stderr_contains("[..] Opening [..]/foo/index.html")
1167+
.with_stderr_contains("[..] Opening [..]/bar/index.html")
11561168
.run();
11571169
}
11581170

@@ -1378,7 +1390,7 @@ fn doc_private_ws() {
13781390
.file("a/src/lib.rs", "fn p() {}")
13791391
.file("b/Cargo.toml", &basic_manifest("b", "0.0.1"))
13801392
.file("b/src/lib.rs", "fn p2() {}")
1381-
.file("b/src/main.rs", "fn main() {}")
1393+
.file("b/src/bin/b-cli.rs", "fn main() {}")
13821394
.build();
13831395
p.cargo("doc --workspace --bins --lib --document-private-items -v")
13841396
.with_stderr_contains(
@@ -1388,7 +1400,7 @@ fn doc_private_ws() {
13881400
"[RUNNING] `rustdoc [..] b/src/lib.rs [..]--document-private-items[..]",
13891401
)
13901402
.with_stderr_contains(
1391-
"[RUNNING] `rustdoc [..] b/src/main.rs [..]--document-private-items[..]",
1403+
"[RUNNING] `rustdoc [..] b/src/bin/b-cli.rs [..]--document-private-items[..]",
13921404
)
13931405
.run();
13941406
}

0 commit comments

Comments
 (0)