Split out linker-info from linker-messages

- Hide common linker output behind `linker-info`
- Add tests
- Account for different capitalization on windows-gnu when removing
  "warning" prefix
- Add some more comments
- Add macOS deployment-target test
- Ignore linker warnings from trying to statically link glibc

  I don't know what's going on in `nofile-limit.rs` but I want no part
  of it.

- Use a fake linker so tests are platform-independent
This commit is contained in:
Jynn Nelson
2025-12-12 17:17:24 -05:00
committed by jyn
parent bbc45c16c4
commit 20404bf4a6
20 changed files with 268 additions and 44 deletions
+107 -23
View File
@@ -22,6 +22,7 @@
use rustc_fs_util::{TempDirBuilder, fix_windows_verbatim_for_gcc, try_canonicalize};
use rustc_hir::attrs::NativeLibKind;
use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
use rustc_lint_defs::builtin::LINKER_INFO;
use rustc_macros::Diagnostic;
use rustc_metadata::fs::{METADATA_FILENAME, copy_to_stdout, emit_wrapper_file};
use rustc_metadata::{
@@ -60,10 +61,8 @@
use super::{apple, versioned_llvm_target};
use crate::base::needs_allocator_shim_for_linking;
use crate::{
CompiledModule, CompiledModules, CrateInfo, NativeLib, errors, looks_like_rust_object_file,
};
use crate::{
CodegenLintLevels, CodegenResults, CompiledModule, CrateInfo, NativeLib, errors, looks_like_rust_object_file
CodegenLintLevels, CompiledModule, CompiledModules, CrateInfo, NativeLib, errors,
looks_like_rust_object_file,
};
pub fn ensure_removed(dcx: DiagCtxtHandle<'_>, path: &Path) {
@@ -683,30 +682,105 @@ fn is_msvc_link_exe(sess: &Session) -> bool {
&& linker_path.to_str() == Some("link.exe")
}
fn is_macos_ld(sess: &Session) -> bool {
let (_, flavor) = linker_and_flavor(sess);
sess.target.is_like_darwin && matches!(flavor, LinkerFlavor::Darwin(_, Lld::No))
}
fn is_windows_gnu_ld(sess: &Session) -> bool {
let (_, flavor) = linker_and_flavor(sess);
sess.target.is_like_windows
&& !sess.target.is_like_msvc
&& matches!(flavor, LinkerFlavor::Gnu(_, Lld::No))
}
fn report_linker_output(sess: &Session, levels: CodegenLintLevels, stdout: &[u8], stderr: &[u8]) {
let escaped_stderr = escape_string(&stderr);
let mut escaped_stderr = escape_string(&stderr);
let mut escaped_stdout = escape_string(&stdout);
let mut linker_info = String::new();
info!("linker stderr:\n{}", &escaped_stderr);
info!("linker stdout:\n{}", &escaped_stdout);
// 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
if is_msvc_link_exe(sess) {
if let Ok(str) = str::from_utf8(&stdout) {
let mut output = String::with_capacity(str.len());
fn for_each(bytes: &[u8], mut f: impl FnMut(&str, &mut String)) -> String {
let mut output = String::new();
if let Ok(str) = str::from_utf8(bytes) {
info!("line: {str}");
output = String::with_capacity(str.len());
for line in str.lines() {
if line.starts_with(" Creating library")
|| line.starts_with("Generating code")
|| line.starts_with("Finished generating code")
{
continue;
} else {
output += line;
output += "\r\n"
}
f(line.trim(), &mut output);
}
escaped_stdout = escape_string(output.trim().as_bytes())
}
escape_string(output.trim().as_bytes())
}
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
if line.starts_with(" Creating library")
|| line.starts_with("Generating code")
|| line.starts_with("Finished generating code")
{
linker_info += line;
linker_info += "\r\n";
} else {
*output += line;
*output += "\r\n"
}
});
} else if is_macos_ld(sess) {
info!("inferred macOS LD");
// FIXME: Tracked by https://github.com/rust-lang/rust/issues/136113
let deployment_mismatch = |line: &str| {
line.starts_with("ld: warning: object file (")
&& line.contains("was built for newer 'macOS' version")
&& line.contains("than being linked")
};
// FIXME: This is a real warning we would like to show, but it hits too many crates
// to want to turn it on immediately.
let search_path = |line: &str| {
line.starts_with("ld: warning: search path '") && line.ends_with("' not found")
};
escaped_stderr = for_each(&stderr, |line, output| {
// This duplicate library warning is just not helpful at all.
if line.starts_with("ld: warning: ignoring duplicate libraries: ")
|| deployment_mismatch(line)
|| search_path(line)
{
linker_info += line;
linker_info += "\n";
} else {
*output += line;
*output += "\n"
}
});
} else if is_windows_gnu_ld(sess) {
info!("inferred Windows GNU LD");
let mut saw_exclude_symbol = false;
// See https://github.com/rust-lang/rust/issues/112368.
// FIXME: maybe check that binutils is older than 2.40 before downgrading this warning?
let exclude_symbols = |line: &str| {
line.starts_with("Warning: .drectve `-exclude-symbols:")
&& line.ends_with("' unrecognized")
};
escaped_stderr = for_each(&stderr, |line, output| {
if exclude_symbols(line) {
saw_exclude_symbol = true;
linker_info += line;
linker_info += "\n";
} else if saw_exclude_symbol && line == "Warning: corrupt .drectve at end of def file" {
linker_info += line;
linker_info += "\n";
} else {
*output += line;
*output += "\n"
}
});
}
let lint_msg = |msg| {
@@ -718,18 +792,27 @@ fn report_linker_output(sess: &Session, levels: CodegenLintLevels, stdout: &[u8]
LinkerOutput { inner: msg },
);
};
let lint_info = |msg| {
diag_lint_level(sess, LINKER_INFO, levels.linker_info, None, LinkerOutput { inner: msg });
};
if !escaped_stderr.is_empty() {
// We already print `warning:` at the start of the diagnostic. Remove it from the linker output if present.
let stderr = escaped_stderr
.strip_prefix("warning: ")
escaped_stderr =
escaped_stderr.strip_prefix("warning: ").unwrap_or(&escaped_stderr).to_owned();
// Windows GNU LD prints uppercase Warning
escaped_stderr = escaped_stderr
.strip_prefix("Warning: ")
.unwrap_or(&escaped_stderr)
.replace(": warning: ", ": ");
lint_msg(format!("linker stderr: {stderr}"));
lint_msg(format!("linker stderr: {escaped_stderr}"));
}
if !escaped_stdout.is_empty() {
lint_msg(format!("linker stdout: {}", escaped_stdout))
}
if !linker_info.is_empty() {
lint_info(linker_info);
}
}
/// Create a dynamic library or executable.
@@ -971,6 +1054,7 @@ fn link_natively(
sess.dcx().abort_if_errors();
}
info!("reporting linker output: flavor={flavor:?}");
report_linker_output(sess, crate_info.lint_levels, &prog.stdout, &prog.stderr);
}
Err(e) => {
+6 -1
View File
@@ -24,6 +24,7 @@
use rustc_hir::CRATE_HIR_ID;
use rustc_hir::attrs::{CfgEntry, NativeLibKind, WindowsSubsystemKind};
use rustc_hir::def_id::CrateNum;
use rustc_lint_defs::builtin::LINKER_INFO;
use rustc_macros::{Decodable, Encodable};
use rustc_metadata::EncodedMetadata;
use rustc_middle::dep_graph::WorkProduct;
@@ -364,10 +365,14 @@ pub fn deserialize_rlink(
#[derive(Copy, Clone, Debug, Encodable, Decodable)]
pub struct CodegenLintLevels {
linker_messages: LevelAndSource,
linker_info: LevelAndSource,
}
impl CodegenLintLevels {
pub fn from_tcx(tcx: TyCtxt<'_>) -> Self {
Self { linker_messages: tcx.lint_level_at_node(LINKER_MESSAGES, CRATE_HIR_ID) }
Self {
linker_messages: tcx.lint_level_at_node(LINKER_MESSAGES, CRATE_HIR_ID),
linker_info: tcx.lint_level_at_node(LINKER_INFO, CRATE_HIR_ID),
}
}
}
+35
View File
@@ -61,6 +61,7 @@
LARGE_ASSIGNMENTS,
LATE_BOUND_LIFETIME_ARGUMENTS,
LEGACY_DERIVE_HELPERS,
LINKER_INFO,
LINKER_MESSAGES,
LONG_RUNNING_CONST_EVAL,
LOSSY_PROVENANCE_CASTS,
@@ -4062,6 +4063,40 @@
"warnings emitted at runtime by the target-specific linker program"
}
declare_lint! {
/// The `linker_info` lint forwards warnings from the linker that are known to be informational-only.
///
/// ### Example
///
/// ```rust,ignore (needs CLI args, platform-specific)
/// #[warn(linker_info)]
/// fn main () {}
/// ```
///
/// On MacOS, using `-C link-arg=-lc` and the default linker, this will produce
///
/// ```text
/// warning: linker stderr: ld: ignoring duplicate libraries: '-lc'
/// |
/// note: the lint level is defined here
/// --> ex.rs:1:9
/// |
/// 1 | #![warn(linker_info)]
/// | ^^^^^^^^^^^^^^^
/// ```
///
/// ### Explanation
///
/// Many linkers are very "chatty" and print lots of information that is not necessarily
/// indicative of an issue. This output has been ignored for many years and is often not
/// actionable by developers. It is silenced unless the developer specifically requests for it
/// to be printed. See this tracking issue for more details:
/// <https://github.com/rust-lang/rust/issues/136096>.
pub LINKER_INFO,
Allow,
"linker warnings known to be informational-only and not indicative of a problem"
}
declare_lint! {
/// The `named_arguments_used_positionally` lint detects cases where named arguments are only
/// used positionally in format strings. This usage is valid but potentially very confusing.
+3 -1
View File
@@ -1621,7 +1621,9 @@ fn check_unused_attribute(&self, hir_id: HirId, attr: &Attribute, style: Option<
sym::expect,
]) && let Some(meta) = attr.meta_item_list()
&& meta.iter().any(|meta| {
meta.meta_item().map_or(false, |item| item.path == sym::linker_messages)
meta.meta_item().map_or(false, |item| {
item.path == sym::linker_messages || item.path == sym::linker_info
})
})
{
if hir_id != CRATE_HIR_ID {
+1 -1
View File
@@ -310,7 +310,7 @@ pub(crate) enum UnusedNote {
#[note("`default_method_body_is_const` has been replaced with `const` on traits")]
DefaultMethodBodyConst,
#[note(
"the `linker_messages` lint can only be controlled at the root of a crate that needs to be linked"
"the `linker_messages` and `linker_info` lints can only be controlled at the root of a crate that needs to be linked"
)]
LinkerMessagesBinaryCrateOnly,
}
+1
View File
@@ -1152,6 +1152,7 @@
link_section,
linkage,
linker,
linker_info,
linker_messages,
linkonce,
linkonce_odr,
@@ -1,17 +0,0 @@
#!/bin/sh
code=0
while ! [ $# = 0 ]; do
case "$1" in
run_make_info) echo "foo"
;;
run_make_warn) echo "warning: bar" >&2
;;
run_make_error) echo "error: baz" >&2; code=1
;;
*) ;; # rustc passes lots of args we don't care about
esac
shift
done
exit $code
@@ -0,0 +1 @@
void foo() {}
@@ -0,0 +1,8 @@
#![warn(linker_info, linker_messages)]
unsafe extern "C" {
safe fn foo();
}
fn main() {
foo();
}
@@ -0,0 +1,29 @@
//@ only-apple
//! Tests that deployment target linker warnings are shown as `linker-info`, not `linker-messages`
use run_make_support::external_deps::c_cxx_compiler::cc;
use run_make_support::external_deps::llvm::llvm_ar;
use run_make_support::{bare_rustc, diff};
fn main() {
let cwd = std::env::current_dir().unwrap().to_str().unwrap().to_owned();
cc().arg("-c").arg("-mmacosx-version-min=15.5").output("foo.o").input("foo.c").run();
llvm_ar().obj_to_ar().output_input("libfoo.a", "foo.o").run();
let warnings = bare_rustc()
.arg("-L")
.arg(format!("native={cwd}"))
.arg("-lstatic=foo")
.link_arg("-mmacosx-version-min=11.2")
.input("main.rs")
.crate_type("bin")
.run()
.stderr_utf8();
diff()
.expected_file("warnings.txt")
.actual_text("(rustc -W linker-info)", &warnings)
.normalize(r"\(.*/rmake_out/", "(TEST_DIR/")
.run()
}
@@ -0,0 +1,11 @@
warning: ld: warning: object file (TEST_DIR/libfoo.a[2](foo.o)) was built for newer 'macOS' version (15.5) than being linked (11.2)
|
note: the lint level is defined here
--> main.rs:1:9
|
1 | #![warn(linker_info, linker_messages)]
| ^^^^^^^^^^^
warning: 1 warning emitted
@@ -0,0 +1,8 @@
// ignore-tidy-linelength
fn main() {
println!(
"Warning: .drectve `-exclude-symbols:_ZN28windows_gnu_corrupt_drective4main17h291ed884c1aada69E ' unrecognized"
);
println!("Warning: corrupt .drectve at end of def file");
}
@@ -0,0 +1,6 @@
//@ only-windows-gnu
//@ build-fail
//@ compile-flags: -C linker={{src-base}}/linking/auxiliary/fake-linker.ps1
#![deny(linker_info)]
//~? ERROR Warning: .drectve
fn main() {}
@@ -0,0 +1,14 @@
//@ only-windows-gnu
use run_make_support::{bare_rustc, rustc};
fn main() {
// bare_rustc so that this doesn't try to cross-compile our linker
bare_rustc().input("fake-linker.rs").output("fake-linker").run();
rustc()
.input("main.rs")
.linker("./fake-linker")
.arg("-Wlinker-messages")
.run()
.assert_stderr_contains("Warning: .drectve");
}
@@ -0,0 +1,6 @@
//@ only-apple
//@ compile-flags: -C link-arg=-lc -C link-arg=-lc
//@ build-fail
#![deny(linker_info)]
//~? ERROR ignoring duplicate libraries
fn main() {}
@@ -0,0 +1,11 @@
error: ld: warning: ignoring duplicate libraries: '-lc'
|
note: the lint level is defined here
--> $DIR/macos-ignoring-duplicate.rs:4:9
|
LL | #![deny(linker_info)]
| ^^^^^^^^^^^
error: aborting due to 1 previous error
+6
View File
@@ -0,0 +1,6 @@
//@ only-apple
//@ compile-flags: -C link-arg=-Wl,-L/no/such/file/or/directory
//@ build-fail
#![deny(linker_info)]
//~? ERROR search path
fn main() {}
+11
View File
@@ -0,0 +1,11 @@
error: ld: warning: search path '/no/such/file/or/directory' not found
|
note: the lint level is defined here
--> $DIR/macos-search-path.rs:4:9
|
LL | #![deny(linker_info)]
| ^^^^^^^^^^^
error: aborting due to 1 previous error
+1 -1
View File
@@ -20,7 +20,7 @@ warning: unused attribute
LL | #![allow(linker_messages)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove this attribute
|
= note: the `linker_messages` lint can only be controlled at the root of a crate that needs to be linked
= note: the `linker_messages` and `linker_info` lints can only be controlled at the root of a crate that needs to be linked
warning: 2 warnings emitted
+3
View File
@@ -11,6 +11,9 @@
#![feature(exit_status_error)]
#![feature(rustc_private)]
// on aarch64, "Using 'getaddrinfo' in statically linked applications requires at runtime the shared
// libraries from the glibc version used for linking"
#![allow(linker_messages)]
extern crate libc;
use std::os::unix::process::CommandExt;