From a1b86a7edfd0365463ea4865bdfdd1ae8f47638e Mon Sep 17 00:00:00 2001 From: rabindra789 Date: Mon, 3 Aug 2026 19:09:42 +0530 Subject: [PATCH] codegen: classify localized MSVC linker progress as linker_info link.exe progress messages (e.g. "Creating library ...") are detected by matching their English text, which fails when the English language pack is not installed and the output is localized despite VSLANG=1033. Since all actual warnings and errors carry a locale-independent LNK#### code, classify every line without one as linker_info instead of linker_messages. Diagnostics are recognized by their structured form, `LINK : warning LNK####:`: the code must be followed by a `:` that is the second colon in the line, so the matcher cannot accidentally hit file names. The one code-bearing informational line, LNK6004 ("performing full link"), keeps the exception that was previously handled by matching its English text. --- compiler/rustc_codegen_ssa/src/back/link.rs | 42 ++++++++---- .../fake-linker.rs | 22 ++++++ .../msvc-localized-linker-output/main.rs | 1 + .../msvc-localized-linker-output/rmake.rs | 67 +++++++++++++++++++ 4 files changed, 119 insertions(+), 13 deletions(-) create mode 100644 tests/run-make/msvc-localized-linker-output/fake-linker.rs create mode 100644 tests/run-make/msvc-localized-linker-output/main.rs create mode 100644 tests/run-make/msvc-localized-linker-output/rmake.rs diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 8cbf3647f5630..b9e3ba4ab9ada 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -1073,27 +1073,43 @@ fn report_linker_output( escape_string(output.trim().as_bytes()) } + fn has_lnk_code(line: &str) -> bool { + // link.exe diagnostics are structured as `LINK : warning LNK####:` or + // `LINK : fatal error LNK####:`. The code is always followed by a `:` + // that is the second colon in the line, so matching that structure + // instead of scanning for `LNK####` anywhere avoids false positives on + // file names. + let Some((code_colon, _)) = line.match_indices(':').nth(1) else { + return false; + }; + let Some(code) = code_colon.checked_sub(7) else { + return false; + }; + let code = &line.as_bytes()[code..code_colon]; + code.starts_with(b"LNK") && code[3..].iter().all(u8::is_ascii_digit) + } + if is_msvc_link_exe(sess) { info!("inferred MSVC link.exe"); 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: + // Hide progress messages from link.exe that we don't care about. + // These include localized variants of the English messages (e.g. + // "Creating library ..."), which rustc cannot recognize by text + // without the English language pack. + // See https://github.com/rust-lang/rust/issues/159133 + // 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"; - } else { + // LNK6004 is the one code-bearing line that is still informational. + if has_lnk_code(line) && !line.ends_with(ilk_but_no_exe) { *output += line; *output += "\r\n" + } else { + linker_info += line; + linker_info += "\r\n"; } }); } else if is_macos_linker(sess) { diff --git a/tests/run-make/msvc-localized-linker-output/fake-linker.rs b/tests/run-make/msvc-localized-linker-output/fake-linker.rs new file mode 100644 index 0000000000000..2cbb68c4518bf --- /dev/null +++ b/tests/run-make/msvc-localized-linker-output/fake-linker.rs @@ -0,0 +1,22 @@ +fn main() { + // Simulate a localized (e.g. Japanese) `link.exe`, as printed when the + // English language pack is not installed and `VSLANG=1033` has no effect. + // This is "Creating library foo.dll.lib and object foo.dll.exp" in Japanese. + println!("ライブラリ foo.dll.lib とオブジェクト foo.dll.exp を作成中"); + // A file name containing an `LNK####`-looking fragment must not be + // mistaken for a diagnostic, which is why the matcher requires the + // structured `LINK : warning LNK####:` form. + println!("LNK2001.lib: progress message, not a diagnostic"); + for arg in std::env::args() { + if arg == "run_make_lnk" { + // Real diagnostics are structured as `LINK : warning LNK####:`. + println!("LINK : warning LNK2001: unresolved external symbol foo"); + // The one code-bearing informational line has no `LINK : ` prefix + // and keeps the exception that classifies it as `linker_info`. + println!( + "LNK6004: 'foo.exe' not found or not built by the last incremental link; \ + performing full link" + ); + } + } +} diff --git a/tests/run-make/msvc-localized-linker-output/main.rs b/tests/run-make/msvc-localized-linker-output/main.rs new file mode 100644 index 0000000000000..f328e4d9d04c3 --- /dev/null +++ b/tests/run-make/msvc-localized-linker-output/main.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/tests/run-make/msvc-localized-linker-output/rmake.rs b/tests/run-make/msvc-localized-linker-output/rmake.rs new file mode 100644 index 0000000000000..3d0e4c0d7b61d --- /dev/null +++ b/tests/run-make/msvc-localized-linker-output/rmake.rs @@ -0,0 +1,67 @@ +//@ only-msvc +//@ ignore-cross-compile (need to run the fake link.exe on the host) + +//! Tests that localized (non-English) MSVC `link.exe` progress messages are +//! classified as `linker_info`, not `linker_messages`. +//! +//! `link.exe` is hardcoded by rustc to run with `VSLANG=1033`, which only works +//! when an English language pack is installed. Without it, messages like +//! "Creating library ..." are printed in another language, and the English +//! string matching that used to detect them fails. Since all real diagnostics +//! carry a locale-independent `LNK####` code, printed in the structured +//! `LINK : warning LNK####:` form, any line without one is informational, no +//! matter the language it was printed in. + +use run_make_support::{bare_rustc, rustc, target}; + +fn main() { + // rustc prepends the sysroot's tools bin directory to the linker's `PATH`, + // which bare names like `link.exe` are resolved against. Put the fake + // `link.exe` there so it wins over the real linker; `-L` below keeps std + // available from the real sysroot. + let fake_sysroot = std::env::current_dir().unwrap().join("fake-sysroot"); + let tools_bin = fake_sysroot.join(format!("lib/rustlib/{}/bin", target())); + std::fs::create_dir_all(&tools_bin).unwrap(); + rustc().arg("fake-linker.rs").output(tools_bin.join("link.exe")).run(); + + let real_libdir = rustc().print("target-libdir").run().stdout_utf8(); + let real_libdir = real_libdir.trim(); + + let fake_link = |extra: &[&str]| { + let mut r = bare_rustc(); + r.input("main.rs") + .output("main") + .arg(format!("--sysroot={}", fake_sysroot.display())) + .arg(format!("-L{real_libdir}")) + // Matched by name against the linker's `PATH`, so the fake in the + // tools bin directory is used instead of the real VS linker. + .arg("-Clinker=link.exe") + // Overrides `rust.lld=true` on CI. + .arg("-Clinker-flavor=msvc"); + for a in extra { + r.arg(a); + } + r + }; + + // The localized progress line must not warn by default. + fake_link(&[]) + .run() + .assert_stderr_not_contains("linker stdout") + .assert_stderr_not_contains("ライブラリ foo.dll.lib とオブジェクト foo.dll.exp を作成中"); + + // It is still visible through `linker_info`, and must not be misclassified + // as `linker_messages`. + fake_link(&["-Wlinker_info", "-Dlinker_messages"]) // Fail if the message is misclassified. + .run() + .assert_stderr_contains("ライブラリ foo.dll.lib とオブジェクト foo.dll.exp を作成中"); + + // Real diagnostics keep their `LNK####` code and still warn. + fake_link(&["-Clink-arg=run_make_lnk"]) + .run() + .assert_stderr_contains( + "warning: linker stdout: LINK : warning LNK2001: unresolved external symbol foo", + ) + // The informational LNK6004 line stays hidden. + .assert_stderr_not_contains("LNK6004"); +}