Skip to content

LTO can cause dependencies to rebuild between build and test #8762

Open
@ehuss

Description

@ehuss

If you have lto enabled in the profile, then cargo build followed by cargo test can result in all dependencies being rebuilt. This is probably not what people want or expect.

#[cargo_test]
fn build_test_rebuild() {
    Package::new("bar", "1.0.0").publish();
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.1.0"

                [dependencies]
                bar = "1.0"

                [profile.release]
                lto = true
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("build --release -v")
        .with_stderr(
            "\
[UPDATING] [..]
[DOWNLOADING] crates ...
[DOWNLOADED] bar v1.0.0 [..]
[COMPILING] bar v1.0.0
[RUNNING] `rustc --crate-name bar [..]-C linker-plugin-lto[..]
[COMPILING] foo v0.1.0 [..]
[RUNNING] `rustc --crate-name foo [..]-C linker-plugin-lto[..]
[FINISHED] [..]
",
        )
        .run();
    // FIXME: this rebuilds `bar`, but that is probably undesired.
    p.cargo("test --release -v")
        .with_stderr(
            "\
[COMPILING] bar v1.0.0
[RUNNING] `rustc --crate-name bar [..]-C embed-bitcode=no[..]
[COMPILING] foo v0.1.0 [..]
[RUNNING] `rustc --crate-name foo src/lib.rs [..]-C embed-bitcode=no [..]
[RUNNING] `rustc --crate-name foo src/lib.rs [..]-C embed-bitcode=no [..]--test[..]
[FINISHED] [..]
[RUNNING] [..]
[DOCTEST] foo
[RUNNING] `rustdoc --crate-type lib --test [..]-C embed-bitcode=no[..]
",
        )
        .run();
}

The exact scenario depends on the structure of the project and the flags used, but for the above example:

  • build builds dependencies with bitcode, because that's all LTO needs.
  • test builds only with object code, because tests need that to link, but there isn't anything that actually needs LTO.

A similar scenario, if you have a main.rs in the project, it is slightly different:

  • build builds dependencies with bitcode, because that's all LTO needs.
  • test builds dependencies with both bitcode and object code, because the tests need object code, and the binary needs bitcode for LTO.

The user can fix this by adding lto to the test or bench profile (depending on whether you use the --release flag). Alternatively, one can just not assume that test --release will share artifacts. I guess another option would be to give the user the ability to disable the bitcode-only optimization.

This particular scenario will be fixed by named-profiles, because the test/bench profiles implicitly inherit the dev/release profile settings. Of course, that means tests will then be LTO'd, which can be quite expensive.

I think it should be fine to wait for named-profiles to be stabilized to resolve this issue. However, I wanted to file it to track the concern. I also think it might warrant having a higher priority for moving named-profiles forward.

A real-world example of this is rustup's CI setup.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-ltoArea: link-time optimizationA-rebuild-detectionArea: rebuild detection and fingerprintingS-triageStatus: This issue is waiting on initial triage.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions