Skip to content

Commit 81defa6

Browse files
committed
Consolidate doc collision detection.
1 parent b1684e2 commit 81defa6

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)