Skip to content

Commit fb00b47

Browse files
committed
Auto merge of #6998 - ehuss:doc-duplicate-tracking, r=alexcrichton
Catch filename output collisions in rustdoc. This does some general cleanup so that rustdoc output is computed correctly. This allows the collision detection code to work. There are some known issues with rustdoc creating collisions (rust-lang/rust#61378, rust-lang/rust#56169). This is related to issue #6313. This includes a change that `CompileMode::is_doc` no longer returns `true` for doc tests. This has bothered me for a while, as various points in the code was not handling it correctly. Separating it into `is_doc` and `is_doc_test` I think is better and more explicit. Note for reviewing: There is a big diff in `calc_outputs`, but this is just rearranging code. Everything in `calc_outputs_rustc` is unchanged from the original. The only functional changes should be: - A warning is emitted for colliding rustdoc output. - Don't create an empty fingerprint directory for doc tests.
2 parents 32cf756 + de44054 commit fb00b47

File tree

10 files changed

+207
-123
lines changed

10 files changed

+207
-123
lines changed

src/cargo/core/compiler/build_config.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -183,16 +183,19 @@ impl CompileMode {
183183
}
184184
}
185185

186-
/// Returns `true` if this is a doc or doc test. Be careful using this.
187-
/// Although both run rustdoc, the dependencies for those two modes are
188-
/// very different.
186+
/// Returns `true` if this is generating documentation.
189187
pub fn is_doc(self) -> bool {
190188
match self {
191-
CompileMode::Doc { .. } | CompileMode::Doctest => true,
189+
CompileMode::Doc { .. } => true,
192190
_ => false,
193191
}
194192
}
195193

194+
/// Returns `true` if this a doc test.
195+
pub fn is_doc_test(self) -> bool {
196+
self == CompileMode::Doctest
197+
}
198+
196199
/// Returns `true` if this is any type of test (test, benchmark, doc test, or
197200
/// check test).
198201
pub fn is_any_test(self) -> bool {

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

+130-94
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use lazycell::LazyCell;
99
use log::info;
1010

1111
use super::{BuildContext, Context, FileFlavor, Kind, Layout};
12-
use crate::core::compiler::Unit;
12+
use crate::core::compiler::{CompileMode, Unit};
1313
use crate::core::{TargetKind, Workspace};
1414
use crate::util::{self, CargoResult};
1515

@@ -146,6 +146,8 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
146146
pub fn out_dir(&self, unit: &Unit<'a>) -> PathBuf {
147147
if unit.mode.is_doc() {
148148
self.layout(unit.kind).root().parent().unwrap().join("doc")
149+
} else if unit.mode.is_doc_test() {
150+
panic!("doc tests do not have an out dir");
149151
} else if unit.target.is_custom_build() {
150152
self.build_script_dir(unit)
151153
} else if unit.target.is_example() {
@@ -293,107 +295,139 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
293295
unit: &Unit<'a>,
294296
bcx: &BuildContext<'a, 'cfg>,
295297
) -> CargoResult<Arc<Vec<OutputFile>>> {
298+
let ret = match unit.mode {
299+
CompileMode::Check { .. } => {
300+
// This may be confusing. rustc outputs a file named `lib*.rmeta`
301+
// for both libraries and binaries.
302+
let file_stem = self.file_stem(unit);
303+
let path = self.out_dir(unit).join(format!("lib{}.rmeta", file_stem));
304+
vec![OutputFile {
305+
path,
306+
hardlink: None,
307+
export_path: None,
308+
flavor: FileFlavor::Linkable { rmeta: false },
309+
}]
310+
}
311+
CompileMode::Doc { .. } => {
312+
let path = self
313+
.out_dir(unit)
314+
.join(unit.target.crate_name())
315+
.join("index.html");
316+
vec![OutputFile {
317+
path,
318+
hardlink: None,
319+
export_path: None,
320+
flavor: FileFlavor::Normal,
321+
}]
322+
}
323+
CompileMode::RunCustomBuild => {
324+
// At this time, this code path does not handle build script
325+
// outputs.
326+
vec![]
327+
}
328+
CompileMode::Doctest => {
329+
// Doctests are built in a temporary directory and then
330+
// deleted. There is the `--persist-doctests` unstable flag,
331+
// but Cargo does not know about that.
332+
vec![]
333+
}
334+
CompileMode::Test | CompileMode::Build | CompileMode::Bench => {
335+
self.calc_outputs_rustc(unit, bcx)?
336+
}
337+
};
338+
info!("Target filenames: {:?}", ret);
339+
340+
Ok(Arc::new(ret))
341+
}
342+
343+
fn calc_outputs_rustc(
344+
&self,
345+
unit: &Unit<'a>,
346+
bcx: &BuildContext<'a, 'cfg>,
347+
) -> CargoResult<Vec<OutputFile>> {
348+
let mut ret = Vec::new();
349+
let mut unsupported = Vec::new();
350+
296351
let out_dir = self.out_dir(unit);
297-
let file_stem = self.file_stem(unit);
298352
let link_stem = self.link_stem(unit);
299353
let info = if unit.kind == Kind::Host {
300354
&bcx.host_info
301355
} else {
302356
&bcx.target_info
303357
};
358+
let file_stem = self.file_stem(unit);
304359

305-
let mut ret = Vec::new();
306-
let mut unsupported = Vec::new();
307-
{
308-
if unit.mode.is_check() {
309-
// This may be confusing. rustc outputs a file named `lib*.rmeta`
310-
// for both libraries and binaries.
311-
let path = out_dir.join(format!("lib{}.rmeta", file_stem));
312-
ret.push(OutputFile {
313-
path,
314-
hardlink: None,
315-
export_path: None,
316-
flavor: FileFlavor::Linkable { rmeta: false },
317-
});
360+
let mut add = |crate_type: &str, flavor: FileFlavor| -> CargoResult<()> {
361+
let crate_type = if crate_type == "lib" {
362+
"rlib"
318363
} else {
319-
let mut add = |crate_type: &str, flavor: FileFlavor| -> CargoResult<()> {
320-
let crate_type = if crate_type == "lib" {
321-
"rlib"
322-
} else {
323-
crate_type
324-
};
325-
let file_types = info.file_types(
326-
crate_type,
327-
flavor,
328-
unit.target.kind(),
329-
bcx.target_triple(),
330-
)?;
331-
332-
match file_types {
333-
Some(types) => {
334-
for file_type in types {
335-
let path = out_dir.join(file_type.filename(&file_stem));
336-
let hardlink = link_stem
337-
.as_ref()
338-
.map(|&(ref ld, ref ls)| ld.join(file_type.filename(ls)));
339-
let export_path = if unit.target.is_custom_build() {
340-
None
341-
} else {
342-
self.export_dir.as_ref().and_then(|export_dir| {
343-
hardlink.as_ref().and_then(|hardlink| {
344-
Some(export_dir.join(hardlink.file_name().unwrap()))
345-
})
346-
})
347-
};
348-
ret.push(OutputFile {
349-
path,
350-
hardlink,
351-
export_path,
352-
flavor: file_type.flavor,
353-
});
354-
}
355-
}
356-
// Not supported; don't worry about it.
357-
None => {
358-
unsupported.push(crate_type.to_string());
359-
}
360-
}
361-
Ok(())
362-
};
363-
// info!("{:?}", unit);
364-
match *unit.target.kind() {
365-
TargetKind::Bin
366-
| TargetKind::CustomBuild
367-
| TargetKind::ExampleBin
368-
| TargetKind::Bench
369-
| TargetKind::Test => {
370-
add("bin", FileFlavor::Normal)?;
371-
}
372-
TargetKind::Lib(..) | TargetKind::ExampleLib(..) if unit.mode.is_any_test() => {
373-
add("bin", FileFlavor::Normal)?;
374-
}
375-
TargetKind::ExampleLib(ref kinds) | TargetKind::Lib(ref kinds) => {
376-
for kind in kinds {
377-
add(
378-
kind.crate_type(),
379-
if kind.linkable() {
380-
FileFlavor::Linkable { rmeta: false }
381-
} else {
382-
FileFlavor::Normal
383-
},
384-
)?;
385-
}
386-
let path = out_dir.join(format!("lib{}.rmeta", file_stem));
387-
if !unit.target.requires_upstream_objects() {
388-
ret.push(OutputFile {
389-
path,
390-
hardlink: None,
391-
export_path: None,
392-
flavor: FileFlavor::Linkable { rmeta: true },
393-
});
394-
}
364+
crate_type
365+
};
366+
let file_types =
367+
info.file_types(crate_type, flavor, unit.target.kind(), bcx.target_triple())?;
368+
369+
match file_types {
370+
Some(types) => {
371+
for file_type in types {
372+
let path = out_dir.join(file_type.filename(&file_stem));
373+
let hardlink = link_stem
374+
.as_ref()
375+
.map(|&(ref ld, ref ls)| ld.join(file_type.filename(ls)));
376+
let export_path = if unit.target.is_custom_build() {
377+
None
378+
} else {
379+
self.export_dir.as_ref().and_then(|export_dir| {
380+
hardlink.as_ref().and_then(|hardlink| {
381+
Some(export_dir.join(hardlink.file_name().unwrap()))
382+
})
383+
})
384+
};
385+
ret.push(OutputFile {
386+
path,
387+
hardlink,
388+
export_path,
389+
flavor: file_type.flavor,
390+
});
395391
}
396392
}
393+
// Not supported; don't worry about it.
394+
None => {
395+
unsupported.push(crate_type.to_string());
396+
}
397+
}
398+
Ok(())
399+
};
400+
match *unit.target.kind() {
401+
TargetKind::Bin
402+
| TargetKind::CustomBuild
403+
| TargetKind::ExampleBin
404+
| TargetKind::Bench
405+
| TargetKind::Test => {
406+
add("bin", FileFlavor::Normal)?;
407+
}
408+
TargetKind::Lib(..) | TargetKind::ExampleLib(..) if unit.mode.is_any_test() => {
409+
add("bin", FileFlavor::Normal)?;
410+
}
411+
TargetKind::ExampleLib(ref kinds) | TargetKind::Lib(ref kinds) => {
412+
for kind in kinds {
413+
add(
414+
kind.crate_type(),
415+
if kind.linkable() {
416+
FileFlavor::Linkable { rmeta: false }
417+
} else {
418+
FileFlavor::Normal
419+
},
420+
)?;
421+
}
422+
let path = out_dir.join(format!("lib{}.rmeta", file_stem));
423+
if !unit.target.requires_upstream_objects() {
424+
ret.push(OutputFile {
425+
path,
426+
hardlink: None,
427+
export_path: None,
428+
flavor: FileFlavor::Linkable { rmeta: true },
429+
});
430+
}
397431
}
398432
}
399433
if ret.is_empty() {
@@ -413,9 +447,7 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
413447
bcx.target_triple()
414448
);
415449
}
416-
info!("Target filenames: {:?}", ret);
417-
418-
Ok(Arc::new(ret))
450+
Ok(ret)
419451
}
420452
}
421453

@@ -439,6 +471,10 @@ fn compute_metadata<'a, 'cfg>(
439471
cx: &Context<'a, 'cfg>,
440472
metas: &mut HashMap<Unit<'a>, Option<Metadata>>,
441473
) -> Option<Metadata> {
474+
if unit.mode.is_doc_test() {
475+
// Doc tests do not have metadata.
476+
return None;
477+
}
442478
// No metadata for dylibs because of a couple issues:
443479
// - macOS encodes the dylib name in the executable,
444480
// - Windows rustc multiple files of which we can't easily link all of them.

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
166166
}
167167
}
168168

169-
if unit.mode == CompileMode::Doctest {
169+
if unit.mode.is_doc_test() {
170170
// Note that we can *only* doc-test rlib outputs here. A
171171
// staticlib output cannot be linked by the compiler (it just
172172
// doesn't do that). A dylib output, however, can be linked by

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ fn compute_deps<'a, 'cfg, 'tmp>(
129129
) -> CargoResult<Vec<(Unit<'a>, UnitFor)>> {
130130
if unit.mode.is_run_custom_build() {
131131
return compute_deps_custom_build(unit, state.cx.bcx);
132-
} else if unit.mode.is_doc() && !unit.mode.is_any_test() {
132+
} else if unit.mode.is_doc() {
133133
// Note: this does not include doc test.
134134
return compute_deps_doc(unit, state);
135135
}

src/cargo/core/compiler/fingerprint.rs

+10-14
Original file line numberDiff line numberDiff line change
@@ -1015,6 +1015,8 @@ fn calculate<'a, 'cfg>(
10151015
}
10161016
let mut fingerprint = if unit.mode.is_run_custom_build() {
10171017
calculate_run_custom_build(cx, unit)?
1018+
} else if unit.mode.is_doc_test() {
1019+
panic!("doc tests do not fingerprint");
10181020
} else {
10191021
calculate_normal(cx, unit)?
10201022
};
@@ -1066,19 +1068,12 @@ fn calculate_normal<'a, 'cfg>(
10661068

10671069
// Figure out what the outputs of our unit is, and we'll be storing them
10681070
// into the fingerprint as well.
1069-
let outputs = if unit.mode.is_doc() {
1070-
vec![cx
1071-
.files()
1072-
.out_dir(unit)
1073-
.join(unit.target.crate_name())
1074-
.join("index.html")]
1075-
} else {
1076-
cx.outputs(unit)?
1077-
.iter()
1078-
.filter(|output| output.flavor != FileFlavor::DebugInfo)
1079-
.map(|output| output.path.clone())
1080-
.collect()
1081-
};
1071+
let outputs = cx
1072+
.outputs(unit)?
1073+
.iter()
1074+
.filter(|output| output.flavor != FileFlavor::DebugInfo)
1075+
.map(|output| output.path.clone())
1076+
.collect();
10821077

10831078
// Fill out a bunch more information that we'll be tracking typically
10841079
// hashed to take up less space on disk as we just need to know when things
@@ -1339,7 +1334,8 @@ fn write_fingerprint(loc: &Path, fingerprint: &Fingerprint) -> CargoResult<()> {
13391334
pub fn prepare_init<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoResult<()> {
13401335
let new1 = cx.files().fingerprint_dir(unit);
13411336

1342-
if fs::metadata(&new1).is_err() {
1337+
// Doc tests have no output, thus no fingerprint.
1338+
if !new1.exists() && !unit.mode.is_doc_test() {
13431339
fs::create_dir(&new1)?;
13441340
}
13451341

src/cargo/core/compiler/job_queue.rs

+4-6
Original file line numberDiff line numberDiff line change
@@ -596,11 +596,10 @@ impl<'a, 'cfg> JobQueue<'a, 'cfg> {
596596
// being a compiled package.
597597
Dirty => {
598598
if unit.mode.is_doc() {
599+
self.documented.insert(unit.pkg.package_id());
600+
config.shell().status("Documenting", unit.pkg)?;
601+
} else if unit.mode.is_doc_test() {
599602
// Skip doc test.
600-
if !unit.mode.is_any_test() {
601-
self.documented.insert(unit.pkg.package_id());
602-
config.shell().status("Documenting", unit.pkg)?;
603-
}
604603
} else {
605604
self.compiled.insert(unit.pkg.package_id());
606605
if unit.mode.is_check() {
@@ -613,8 +612,7 @@ impl<'a, 'cfg> JobQueue<'a, 'cfg> {
613612
Fresh => {
614613
// If doc test are last, only print "Fresh" if nothing has been printed.
615614
if self.counts[&unit.pkg.package_id()] == 0
616-
&& !(unit.mode == CompileMode::Doctest
617-
&& self.compiled.contains(&unit.pkg.package_id()))
615+
&& !(unit.mode.is_doc_test() && self.compiled.contains(&unit.pkg.package_id()))
618616
{
619617
self.compiled.insert(unit.pkg.package_id());
620618
config.shell().verbose(|c| c.status("Fresh", unit.pkg))?;

src/cargo/core/compiler/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ fn compile<'a, 'cfg: 'a>(
125125

126126
let job = if unit.mode.is_run_custom_build() {
127127
custom_build::prepare(cx, unit)?
128-
} else if unit.mode == CompileMode::Doctest {
128+
} else if unit.mode.is_doc_test() {
129129
// We run these targets later, so this is just a no-op for now.
130130
Job::new(Work::noop(), Freshness::Fresh)
131131
} else if build_plan {

0 commit comments

Comments
 (0)