Skip to content

Commit

Permalink
Auto merge of rust-lang#119286 - jyn514:linker-output, r=bjorn3
Browse files Browse the repository at this point in the history
show linker output even if the linker succeeds

- show stderr by default
- show stdout if `--verbose` is passed
- remove both from RUSTC_LOG
- hide the linker cli args unless `--verbose` is passed

fixes rust-lang#83436. fixes rust-lang#38206. fixes rust-lang#109979. helps with rust-lang#46998. cc https://rust-lang.zulipchat.com/#narrow/stream/233931-t-compiler.2Fmajor-changes/topic/uplift.20some.20-Zverbose.20calls.20and.20rename.20to.E2.80.A6.20compiler-team.23706/near/408986134

this is based on rust-lang#119129 for convenience so i didn't have to duplicate the changes around saving `--verbose` in rust-lang@cb6d033#diff-7a49efa20548d6806dbe1c66dd4dc445fda18fcbbf1709520cadecc4841aae12

r? `@bjorn3`
  • Loading branch information
bors committed Sep 11, 2024
2 parents 8d6b88b + 56b8c94 commit 5f6d46f
Show file tree
Hide file tree
Showing 19 changed files with 284 additions and 157 deletions.
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}");
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
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 @@ -319,6 +319,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

0 comments on commit 5f6d46f

Please sign in to comment.