Treat MSVC "performing full link" message as informational

When the MSVC incremental linker finds a .ilk file for incremental
linking but its associated .exe file is missing, this message is
printed to stdout in 1 line:

LINK : ...\foo.exe not found or not build by the last incremental link;
performing full link

However, if both the .ilk and .exe files are missing (for a clean
build), the message isn't printed and it still does a full link. So, the
presence of the message doesn't affect the result of the build.
This commit is contained in:
Alyssa Haroldsen
2026-05-07 13:38:16 -07:00
parent 32bd660612
commit 39c766e310
3 changed files with 53 additions and 0 deletions
@@ -759,10 +759,15 @@ fn for_each(bytes: &[u8], mut f: impl FnMut(&str, &mut String)) -> String {
escaped_stdout = for_each(&stdout, |line, output| {
// Hide some progress messages from link.exe that we don't care about.
// See https://github.com/chromium/chromium/blob/bfa41e41145ffc85f041384280caf2949bb7bd72/build/toolchain/win/tool_wrapper.py#L144-L146
// When incremental linking is enabled and an .ilk exists, but its associated .exe is
// missing, link.exe prints the path of the missing .exe followed by:
let ilk_but_no_exe =
"not found or not built by the last incremental link; performing full link";
let trimmed = line.trim_start();
if trimmed.starts_with("Creating library")
|| trimmed.starts_with("Generating code")
|| trimmed.starts_with("Finished generating code")
|| trimmed.ends_with(ilk_but_no_exe)
{
linker_info += line;
linker_info += "\r\n";
@@ -0,0 +1 @@
fn main() {}
@@ -0,0 +1,47 @@
//@ only-msvc
//! Tests that the MSVC incremental linker's "performing full link" message is classified as
//! `linker_info`, not `linker_messages`.
//!
//! The MSVC incremental linker prints this message to stdout when it finds a `.ilk` file for
//! incremental linking but its associated `.exe` file is missing:
//!
//! ```
//! LINK : ...\main.exe not found or not built by the last incremental link; performing full link
//! ```
use std::fs;
use run_make_support::bare_rustc;
fn incremental_rustc() -> run_make_support::Rustc {
let mut r = bare_rustc();
r.input("main.rs")
// Overrides `rust.lld=true` on CI.
.arg("-Clinker=link.exe")
.arg("-Clinker-flavor=msvc")
// Without this, Rust passes /OPT:REF to link.exe, which
// disables incremental linking entirely and suppresses the message.
.arg("-Clink-dead-code")
// /DEBUG is required: it implies /INCREMENTAL, which is what makes
// link.exe create the .ilk file and later emit the message.
.arg("-Cdebuginfo=2");
r
}
fn main() {
// First link: produces main.exe and main.ilk.
incremental_rustc().run();
// Delete the .exe but leave the .ilk.
fs::remove_file("main.exe").unwrap();
// Second link: link.exe finds the .ilk but not the .exe and emits the
// "performing full link" message on stdout.
let output = incremental_rustc()
.arg("-Dlinker_messages") // Fail if the message is misclassified.
.arg("-Wlinker_info")
.run();
// Verify the message was actually emitted and routed to linker_info.
output.assert_stderr_contains("performing full link");
}