Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 29 additions & 13 deletions compiler/rustc_codegen_ssa/src/back/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
22 changes: 22 additions & 0 deletions tests/run-make/msvc-localized-linker-output/fake-linker.rs
Original file line number Diff line number Diff line change
@@ -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"
);
}
}
}
1 change: 1 addition & 0 deletions tests/run-make/msvc-localized-linker-output/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fn main() {}
67 changes: 67 additions & 0 deletions tests/run-make/msvc-localized-linker-output/rmake.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//@ only-msvc
Comment thread
rabindra789 marked this conversation as resolved.
//@ 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");
}
Loading