Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

show linker output even if the linker succeeds #119286

Closed
wants to merge 7 commits into from
Closed
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
2 changes: 2 additions & 0 deletions compiler/rustc_codegen_ssa/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ codegen_ssa_linker_file_stem = couldn't extract file stem from specified linker
codegen_ssa_linker_not_found = linker `{$linker_path}` not found
.note = {$error}

codegen_ssa_linker_output = {$inner}

codegen_ssa_linker_unsupported_modifier = `as-needed` modifier not supported for current linker

codegen_ssa_linking_failed = linking with `{$linker_path}` failed: {$exit_status}
Expand Down
29 changes: 26 additions & 3 deletions compiler/rustc_codegen_ssa/src/back/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use rustc_data_structures::temp_dir::MaybeTempDir;
use rustc_errors::{DiagCtxtHandle, ErrorGuaranteed, FatalError};
use rustc_fs_util::{fix_windows_verbatim_for_gcc, try_canonicalize};
use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
use rustc_macros::Diagnostic;
use rustc_metadata::fs::{copy_to_stdout, emit_wrapper_file, METADATA_FILENAME};
use rustc_metadata::{find_native_static_library, walk_native_lib_search_dirs};
use rustc_middle::bug;
Expand Down Expand Up @@ -750,6 +751,14 @@ fn link_dwarf_object(sess: &Session, cg_results: &CodegenResults, executable_out
}
}

#[derive(Diagnostic)]
#[diag(codegen_ssa_linker_output)]
/// Translating this is kind of useless. We don't pass translation flags to the linker, so we'd just
/// end up with inconsistent languages within the same diagnostic.
struct LinkerOutput {
inner: String,
}

/// Create a dynamic library or executable.
///
/// This will invoke the system linker/cc to create the resulting file. This links to all upstream
Expand Down Expand Up @@ -976,12 +985,12 @@ fn link_natively(
let mut output = prog.stderr.clone();
output.extend_from_slice(&prog.stdout);
let escaped_output = escape_linker_output(&output, flavor);
// FIXME: Add UI tests for this error.
let err = errors::LinkingFailed {
linker_path: &linker_path,
exit_status: prog.status,
command: &cmd,
escaped_output,
verbose: sess.opts.verbose,
};
sess.dcx().emit_err(err);
// If MSVC's `link.exe` was expected but the return code
Expand Down Expand Up @@ -1022,8 +1031,22 @@ fn link_natively(

sess.dcx().abort_if_errors();
}
info!("linker stderr:\n{}", escape_string(&prog.stderr));
info!("linker stdout:\n{}", escape_string(&prog.stdout));

if !prog.stderr.is_empty() {
// We already print `warning:` at the start of the diagnostic. Remove it from the linker output if present.
let stderr = escape_string(&prog.stderr);
debug!("original stderr: {stderr}");
bjorn3 marked this conversation as resolved.
Show resolved Hide resolved
let stderr = stderr
.strip_prefix("warning: ")
.unwrap_or(&stderr)
.replace(": warning: ", ": ");
sess.dcx().emit_warn(LinkerOutput { inner: format!("linker stderr: {stderr}") });
}
if !prog.stdout.is_empty() && sess.opts.verbose {
sess.dcx().emit_warn(LinkerOutput {
inner: format!("linker stdout: {}", escape_string(&prog.stdout)),
});
}
}
Err(e) => {
let linker_not_found = e.kind() == io::ErrorKind::NotFound;
Expand Down
9 changes: 8 additions & 1 deletion compiler/rustc_codegen_ssa/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ pub struct LinkingFailed<'a> {
pub exit_status: ExitStatus,
pub command: &'a Command,
pub escaped_output: String,
pub verbose: bool,
}

impl<G: EmissionGuarantee> Diagnostic<'_, G> for LinkingFailed<'_> {
Expand All @@ -358,7 +359,13 @@ impl<G: EmissionGuarantee> Diagnostic<'_, G> for LinkingFailed<'_> {

let contains_undefined_ref = self.escaped_output.contains("undefined reference to");

diag.note(format!("{:?}", self.command)).note(self.escaped_output);
if self.verbose {
diag.note(format!("{:?}", self.command));
} else {
diag.note("use `--verbose` to show all linker arguments");
}

diag.note(self.escaped_output);

// Trying to match an error from OS linkers
// which by now we have no way to translate.
Expand Down
5 changes: 4 additions & 1 deletion compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,10 @@ impl DiagnosticDeriveVariantBuilder {
let mut field_binding = binding_info.binding.clone();
field_binding.set_span(field.ty.span());

let ident = field.ident.as_ref().unwrap();
let Some(ident) = field.ident.as_ref() else {
span_err(field.span().unwrap(), "tuple structs are not supported").emit();
return TokenStream::new();
};
let ident = format_ident!("{}", ident); // strip `r#` prefix, if present

quote! {
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_macros/src/diagnostics/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ fn path_to_string(path: &syn::Path) -> String {
/// Returns an error diagnostic on span `span` with msg `msg`.
#[must_use]
pub(crate) fn span_err<T: Into<String>>(span: impl MultiSpan, msg: T) -> Diagnostic {
Diagnostic::spanned(span, Level::Error, msg)
Diagnostic::spanned(span, Level::Error, format!("derive(Diagnostic): {}", msg.into()))
}

/// Emit a diagnostic on span `$span` with msg `$msg` (optionally performing additional decoration
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_macros/src/diagnostics/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ impl<T> SetOnce<T> for SpannedOption<T> {
*self = Some((value, span));
}
Some((_, prev_span)) => {
span_err(span, "specified multiple times")
span_err(span, "attribute specified multiple times")
.span_note(*prev_span, "previously specified here")
.emit();
}
Expand Down
19 changes: 17 additions & 2 deletions src/ci/github-actions/jobs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,23 @@ envs:
# These jobs automatically inherit envs.pr, to avoid repeating
# it in each job definition.
pr:
- image: mingw-check
<<: *job-linux-4c
# This target only needs to support 11.0 and up as nothing else supports the hardware
- image: aarch64-apple
env:
SCRIPT: ./x.py --stage 2 test --host=aarch64-apple-darwin --target=aarch64-apple-darwin
RUST_CONFIGURE_ARGS: >-
--enable-sanitizers
--enable-profiler
--set rust.jemalloc
RUSTC_RETRY_LINKER_ON_SEGFAULT: 1
SELECT_XCODE: /Applications/Xcode_14.3.1.app
USE_XCODE_CLANG: 1
MACOSX_DEPLOYMENT_TARGET: 11.0
MACOSX_STD_DEPLOYMENT_TARGET: 11.0
NO_LLVM_ASSERTIONS: 1
NO_DEBUG_ASSERTIONS: 1
NO_OVERFLOW_CHECKS: 1
<<: *job-macos-m1
- image: mingw-check-tidy
continue_on_error: true
<<: *job-linux-4c
Expand Down
1 change: 0 additions & 1 deletion src/etc/cat-and-grep.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ while getopts ':vieh' OPTION; do
case "$OPTION" in
v)
INVERT=1
ERROR_MSG='should not be found'
;;
i)
GREPFLAGS="i$GREPFLAGS"
Expand Down
10 changes: 9 additions & 1 deletion src/tools/compiletest/src/runtest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1778,6 +1778,10 @@ impl<'test> TestCx<'test> {
self.config.target.contains("vxworks") && !self.is_vxworks_pure_static()
}

fn has_aux_dir(&self) -> bool {
!self.props.aux_builds.is_empty() || !self.props.aux_crates.is_empty()
}

fn aux_output_dir(&self) -> PathBuf {
let aux_dir = self.aux_output_dir_name();

Expand Down Expand Up @@ -2324,7 +2328,11 @@ impl<'test> TestCx<'test> {
}

if let LinkToAux::Yes = link_to_aux {
rustc.arg("-L").arg(self.aux_output_dir_name());
// if we pass an `-L` argument to a directory that doesn't exist,
// macOS ld emits warnings which disrupt the .stderr files
if self.has_aux_dir() {
rustc.arg("-L").arg(self.aux_output_dir_name());
}
}

rustc.args(&self.props.compile_flags);
Expand Down
6 changes: 6 additions & 0 deletions src/tools/run-make-support/src/external_deps/rustc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,12 @@ impl Rustc {
self
}

/// Pass the `--verbose` flag.
pub fn verbose(&mut self) -> &mut Self {
self.cmd.arg("--verbose");
self
}

/// `EXTRARSCXXFLAGS`
pub fn extra_rs_cxx_flags(&mut self) -> &mut Self {
// Adapted from tools.mk (trimmed):
Expand Down
6 changes: 4 additions & 2 deletions tests/run-make/link-args-order/rmake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,17 @@ fn main() {
.link_args("b c")
.link_args("d e")
.link_arg("f")
.arg("--print=link-args")
.run_fail()
.assert_stderr_contains(r#""a" "b" "c" "d" "e" "f""#);
.assert_stdout_contains(r#""a" "b" "c" "d" "e" "f""#);
rustc()
.input("empty.rs")
.linker_flavor(linker)
.arg("-Zpre-link-arg=a")
.arg("-Zpre-link-args=b c")
.arg("-Zpre-link-args=d e")
.arg("-Zpre-link-arg=f")
.arg("--print=link-args")
.run_fail()
.assert_stderr_contains(r#""a" "b" "c" "d" "e" "f""#);
.assert_stdout_contains(r#""a" "b" "c" "d" "e" "f""#);
}
12 changes: 6 additions & 6 deletions tests/run-make/link-dedup/rmake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ fn main() {
rustc().input("depb.rs").run();
rustc().input("depc.rs").run();

let output = rustc().input("empty.rs").cfg("bar").run_fail();
output.assert_stderr_contains(needle_from_libs(&["testa", "testb", "testa"]));
let output = rustc().input("empty.rs").cfg("bar").arg("--print=link-args").run_fail();
output.assert_stdout_contains(needle_from_libs(&["testa", "testb", "testa"]));

let output = rustc().input("empty.rs").run_fail();
output.assert_stderr_contains(needle_from_libs(&["testa"]));
output.assert_stderr_not_contains(needle_from_libs(&["testb"]));
output.assert_stderr_not_contains(needle_from_libs(&["testa", "testa", "testa"]));
let output = rustc().input("empty.rs").arg("--print=link-args").run_fail();
output.assert_stdout_contains(needle_from_libs(&["testa"]));
output.assert_stdout_not_contains(needle_from_libs(&["testb"]));
output.assert_stdout_not_contains(needle_from_libs(&["testa", "testa", "testa"]));
// Adjacent identical native libraries are no longer deduplicated if
// they come from different crates (https://github.com/rust-lang/rust/pull/103311)
// so the following will fail:
Expand Down
13 changes: 13 additions & 0 deletions tests/run-make/linker-warning/fake-linker.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
fn main() {
for arg in std::env::args() {
match &*arg {
"run_make_info" => println!("foo"),
"run_make_warn" => eprintln!("warning: bar"),
"run_make_error" => {
eprintln!("error: baz");
std::process::exit(1);
}
_ => (),
}
}
}
17 changes: 17 additions & 0 deletions tests/run-make/linker-warning/fake-linker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/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
1 change: 1 addition & 0 deletions tests/run-make/linker-warning/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fn main() {}
45 changes: 45 additions & 0 deletions tests/run-make/linker-warning/rmake.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use std::path::Path;

use run_make_support::rfs::remove_file;
use run_make_support::{rustc, Rustc};

fn run_rustc() -> Rustc {
let mut rustc = rustc();
rustc.arg("main.rs").output("main").linker("./fake-linker");
rustc
}

fn main() {
// first, compile our linker
rustc().arg("fake-linker.rs").output("fake-linker").run();

// Run rustc with our fake linker, and make sure it shows warnings
let warnings = run_rustc().link_arg("run_make_warn").run();
warnings.assert_stderr_contains("warning: linker stderr: bar");

// Make sure it shows stdout, but only when --verbose is passed
run_rustc()
.link_arg("run_make_info")
.verbose()
.run()
.assert_stderr_contains("warning: linker stdout: foo");
run_rustc()
.link_arg("run_make_info")
.run()
.assert_stderr_not_contains("warning: linker stdout: foo");

// Make sure we short-circuit this new path if the linker exits with an error
// (so the diagnostic is less verbose)
run_rustc().link_arg("run_make_error").run_fail().assert_stderr_contains("note: error: baz");

// Make sure we don't show the linker args unless `--verbose` is passed
run_rustc()
.link_arg("run_make_error")
.verbose()
.run_fail()
.assert_stderr_contains_regex("fake-linker.*run_make_error");
run_rustc()
.link_arg("run_make_error")
.run_fail()
.assert_stderr_not_contains_regex("fake-linker.*run_make_error");
}
9 changes: 5 additions & 4 deletions tests/run-make/rust-lld/rmake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ fn main() {
// Opt-in to lld and the self-contained linker, to link with rust-lld. We'll check that by
// asking the linker to display its version number with a link-arg.
let output = rustc()
.env("RUSTC_LOG", "rustc_codegen_ssa::back::link=info")
.arg("-Zlinker-features=+lld")
.arg("-Clink-self-contained=+linker")
.arg("-Zunstable-options")
.arg("--verbose")
.link_arg(linker_version_flag)
.input("main.rs")
.run();
Expand All @@ -29,8 +29,8 @@ fn main() {

// It should not be used when we explicitly opt-out of lld.
let output = rustc()
.env("RUSTC_LOG", "rustc_codegen_ssa::back::link=info")
.link_arg(linker_version_flag)
.arg("--verbose")
.arg("-Zlinker-features=-lld")
.input("main.rs")
.run();
Expand All @@ -43,8 +43,8 @@ fn main() {
// While we're here, also check that the last linker feature flag "wins" when passed multiple
// times to rustc.
let output = rustc()
.env("RUSTC_LOG", "rustc_codegen_ssa::back::link=info")
.link_arg(linker_version_flag)
.arg("--verbose")
.arg("-Clink-self-contained=+linker")
.arg("-Zunstable-options")
.arg("-Zlinker-features=-lld")
Expand All @@ -60,6 +60,7 @@ fn main() {
}

fn find_lld_version_in_logs(stderr: String) -> bool {
let lld_version_re = Regex::new(r"^LLD [0-9]+\.[0-9]+\.[0-9]+").unwrap();
let lld_version_re =
Regex::new(r"^warning: linker stdout: LLD [0-9]+\.[0-9]+\.[0-9]+").unwrap();
stderr.lines().any(|line| lld_version_re.is_match(line.trim()))
}
Loading
Loading