Skip to content

Commit 55e59bd

Browse files
committed
Auto merge of #8657 - ehuss:fix-doctest-lto, r=alexcrichton
Fix LTO with doctests. This fixes an issue where `cargo test --release` would fail to run doctests if LTO is set in `profile.release` or `profile.bench`. The issue is that dependencies were built with `-Clinker-plugin-lto`, but the final rustdoc invocation did not issue `-C lto`. This causes a link failure (or crash!) because the necessary object code was missing. This is because rustdoc historically did not support codegen flags, so Cargo has never passed them in. Rustdoc now supports codegen flags (via rust-lang/rust#63827), so it should be safe to start passing them in. For now, I am only adding LTO, but more should be added in the future. There are two bugs here. One is that LTO flags aren't passed to rustdoc. The other is that the "doctest" unit was using the wrong profile (it was using dev/release when it should be using test/bench). There are two distinct scenarios here. One where just `release` has `lto` set. And one where both `release` and `bench` have `lto` set. This is relevant because LTO mostly cares about what the final artifact wants, and in the case where `bench` does not have `lto` set, then LTO will not be used for tests. This will hopefully be a little cleaner in the future when #6988 is stabilized, which causes the test/bench profiles to *inherit* from the dev/bench profiles, which means you won't need to manually synchronize the test/bench profiles with dev/release. Fixes #8654
2 parents 888ae72 + 5bbad10 commit 55e59bd

File tree

4 files changed

+87
-34
lines changed

4 files changed

+87
-34
lines changed

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,8 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
210210
// Collect information for `rustdoc --test`.
211211
if unit.mode.is_doc_test() {
212212
let mut unstable_opts = false;
213-
let args = compiler::extern_args(&self, unit, &mut unstable_opts)?;
213+
let mut args = compiler::extern_args(&self, unit, &mut unstable_opts)?;
214+
args.extend(compiler::lto_args(&self, unit));
214215
self.compilation.to_doc_test.push(compilation::Doctest {
215216
unit: unit.clone(),
216217
args,

src/cargo/core/compiler/mod.rs

+18-20
Original file line numberDiff line numberDiff line change
@@ -797,28 +797,9 @@ fn build_base_args(
797797
cmd.arg("-C").arg(format!("panic={}", panic));
798798
}
799799

800-
match cx.lto[unit] {
801-
lto::Lto::Run(None) => {
802-
cmd.arg("-C").arg("lto");
803-
}
804-
lto::Lto::Run(Some(s)) => {
805-
cmd.arg("-C").arg(format!("lto={}", s));
806-
}
807-
lto::Lto::Off => {
808-
cmd.arg("-C").arg("lto=off");
809-
}
810-
lto::Lto::ObjectAndBitcode => {} // this is rustc's default
811-
lto::Lto::OnlyBitcode => {
812-
cmd.arg("-C").arg("linker-plugin-lto");
813-
}
814-
lto::Lto::OnlyObject => {
815-
cmd.arg("-C").arg("embed-bitcode=no");
816-
}
817-
}
800+
cmd.args(&lto_args(cx, unit));
818801

819802
if let Some(n) = codegen_units {
820-
// There are some restrictions with LTO and codegen-units, so we
821-
// only add codegen units when LTO is not used.
822803
cmd.arg("-C").arg(&format!("codegen-units={}", n));
823804
}
824805

@@ -946,6 +927,23 @@ fn build_base_args(
946927
Ok(())
947928
}
948929

930+
fn lto_args(cx: &Context<'_, '_>, unit: &Unit) -> Vec<OsString> {
931+
let mut result = Vec::new();
932+
let mut push = |arg: &str| {
933+
result.push(OsString::from("-C"));
934+
result.push(OsString::from(arg));
935+
};
936+
match cx.lto[unit] {
937+
lto::Lto::Run(None) => push("lto"),
938+
lto::Lto::Run(Some(s)) => push(&format!("lto={}", s)),
939+
lto::Lto::Off => push("lto=off"),
940+
lto::Lto::ObjectAndBitcode => {} // this is rustc's default
941+
lto::Lto::OnlyBitcode => push("linker-plugin-lto"),
942+
lto::Lto::OnlyObject => push("embed-bitcode=no"),
943+
}
944+
result
945+
}
946+
949947
fn build_deps_args(
950948
cmd: &mut ProcessBuilder,
951949
cx: &mut Context<'_, '_>,

src/cargo/core/profiles.rs

+2-5
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ impl Profiles {
299299
let release = matches!(self.requested_profile.as_str(), "release" | "bench");
300300

301301
match mode {
302-
CompileMode::Test | CompileMode::Bench => {
302+
CompileMode::Test | CompileMode::Bench | CompileMode::Doctest => {
303303
if release {
304304
(
305305
InternedString::new("bench"),
@@ -312,10 +312,7 @@ impl Profiles {
312312
)
313313
}
314314
}
315-
CompileMode::Build
316-
| CompileMode::Check { .. }
317-
| CompileMode::Doctest
318-
| CompileMode::RunCustomBuild => {
315+
CompileMode::Build | CompileMode::Check { .. } | CompileMode::RunCustomBuild => {
319316
// Note: `RunCustomBuild` doesn't normally use this code path.
320317
// `build_unit_profiles` normally ensures that it selects the
321318
// ancestor's profile. However, `cargo clean -p` can hit this

tests/testsuite/lto.rs

+65-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use cargo::core::compiler::Lto;
22
use cargo_test_support::registry::Package;
3-
use cargo_test_support::{project, Project};
3+
use cargo_test_support::{basic_manifest, project, Project};
44
use std::process::Output;
55

66
#[cargo_test]
@@ -514,16 +514,19 @@ fn cdylib_and_rlib() {
514514
p.cargo("test --release -v --manifest-path bar/Cargo.toml")
515515
.with_stderr_unordered(
516516
"\
517-
[FRESH] registry v0.0.1
518-
[FRESH] registry-shared v0.0.1
517+
[COMPILING] registry v0.0.1
518+
[COMPILING] registry-shared v0.0.1
519+
[RUNNING] `rustc --crate-name registry [..]-C embed-bitcode=no[..]
520+
[RUNNING] `rustc --crate-name registry_shared [..]-C embed-bitcode=no[..]
519521
[COMPILING] bar [..]
522+
[RUNNING] `rustc --crate-name bar [..]--crate-type cdylib --crate-type rlib [..]-C embed-bitcode=no[..]
520523
[RUNNING] `rustc --crate-name bar [..]-C embed-bitcode=no --test[..]
521524
[RUNNING] `rustc --crate-name b [..]-C embed-bitcode=no --test[..]
522525
[FINISHED] [..]
523-
[RUNNING] [..]
524-
[RUNNING] [..]
526+
[RUNNING] [..]target/release/deps/bar-[..]
527+
[RUNNING] [..]target/release/deps/b-[..]
525528
[DOCTEST] bar
526-
[RUNNING] `rustdoc --crate-type cdylib --crate-type rlib --test [..]
529+
[RUNNING] `rustdoc --crate-type cdylib --crate-type rlib --test [..]-C embed-bitcode=no[..]
527530
",
528531
)
529532
.run();
@@ -627,7 +630,7 @@ fn test_profile() {
627630
[COMPILING] bar v0.0.1
628631
[RUNNING] `rustc --crate-name bar [..]crate-type lib[..]
629632
[COMPILING] foo [..]
630-
[RUNNING] `rustc --crate-name foo [..]--crate-type lib --emit=dep-info,metadata,link -C embed-bitcode=no[..]
633+
[RUNNING] `rustc --crate-name foo [..]--crate-type lib --emit=dep-info,metadata,link -C linker-plugin-lto[..]
631634
[RUNNING] `rustc --crate-name foo [..]--emit=dep-info,link -C lto=thin [..]--test[..]
632635
[FINISHED] [..]
633636
[RUNNING] [..]
@@ -680,7 +683,7 @@ fn dev_profile() {
680683
[COMPILING] bar v0.0.1
681684
[RUNNING] `rustc --crate-name bar [..]crate-type lib[..]
682685
[COMPILING] foo [..]
683-
[RUNNING] `rustc --crate-name foo [..]--crate-type lib --emit=dep-info,metadata,link -C linker-plugin-lto [..]
686+
[RUNNING] `rustc --crate-name foo [..]--crate-type lib --emit=dep-info,metadata,link -C embed-bitcode=no [..]
684687
[RUNNING] `rustc --crate-name foo [..]--emit=dep-info,link -C embed-bitcode=no [..]--test[..]
685688
[FINISHED] [..]
686689
[RUNNING] [..]
@@ -689,3 +692,57 @@ fn dev_profile() {
689692
")
690693
.run();
691694
}
695+
696+
#[cargo_test]
697+
fn doctest() {
698+
let p = project()
699+
.file(
700+
"Cargo.toml",
701+
r#"
702+
[package]
703+
name = "foo"
704+
version = "0.1.0"
705+
edition = "2018"
706+
707+
[profile.release]
708+
lto = true
709+
710+
[dependencies]
711+
bar = { path = "bar" }
712+
"#,
713+
)
714+
.file(
715+
"src/lib.rs",
716+
r#"
717+
/// Foo!
718+
///
719+
/// ```
720+
/// foo::foo();
721+
/// ```
722+
pub fn foo() { bar::bar(); }
723+
"#,
724+
)
725+
.file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
726+
.file(
727+
"bar/src/lib.rs",
728+
r#"
729+
pub fn bar() { println!("hi!"); }
730+
"#,
731+
)
732+
.build();
733+
734+
p.cargo("test --doc --release -v")
735+
.with_stderr_contains("[..]`rustc --crate-name bar[..]-C embed-bitcode=no[..]")
736+
.with_stderr_contains("[..]`rustc --crate-name foo[..]-C embed-bitcode=no[..]")
737+
// embed-bitcode should be harmless here
738+
.with_stderr_contains("[..]`rustdoc [..]-C embed-bitcode=no[..]")
739+
.run();
740+
741+
// Try with bench profile.
742+
p.cargo("test --doc --release -v")
743+
.env("CARGO_PROFILE_BENCH_LTO", "true")
744+
.with_stderr_contains("[..]`rustc --crate-name bar[..]-C linker-plugin-lto[..]")
745+
.with_stderr_contains("[..]`rustc --crate-name foo[..]-C linker-plugin-lto[..]")
746+
.with_stderr_contains("[..]`rustdoc [..]-C lto[..]")
747+
.run();
748+
}

0 commit comments

Comments
 (0)