integration-test: shuttle linker errors to user

Run cargo with RUSTC_LOG=rustc_codegen_ssa:🔙:link=warn to get
bpf-linker logs back. Turns out we get a good bit of output, including
errors from LLVM.

Cargo ignores stderr from build scripts, so extra care is taken to
shuttle stderr from cargo-in-cargo back to the user.
reviewable/pr669/r1
Tamir Duberstein
parent 2755713cf0
commit bbcbb4d2a1
No known key found for this signature in database

@ -4,7 +4,7 @@ use std::{
ffi::OsString, ffi::OsString,
fmt::Write as _, fmt::Write as _,
fs, fs,
io::BufReader, io::{BufRead as _, BufReader},
path::PathBuf, path::PathBuf,
process::{Child, Command, Stdio}, process::{Child, Command, Stdio},
}; };
@ -153,6 +153,8 @@ fn main() {
// stat through the symlink and discover that bpf-linker has changed. // stat through the symlink and discover that bpf-linker has changed.
// //
// This was introduced in https://github.com/rust-lang/cargo/commit/99f841c. // This was introduced in https://github.com/rust-lang/cargo/commit/99f841c.
//
// TODO(https://github.com/rust-lang/cargo/pull/12369): Remove this when the fix hits nightly.
{ {
let bpf_linker = which("bpf-linker").unwrap(); let bpf_linker = which("bpf-linker").unwrap();
let bpf_linker_symlink = out_dir.join("bpf-linker"); let bpf_linker_symlink = out_dir.join("bpf-linker");
@ -172,32 +174,47 @@ fn main() {
} }
let mut cmd = Command::new("cargo"); let mut cmd = Command::new("cargo");
cmd.args([ cmd.env("RUSTC_LOG", "rustc_codegen_ssa::back::link=warn")
"build", .args([
"-p", "build",
"integration-ebpf", "-p",
"-Z", "integration-ebpf",
"build-std=core", "-Z",
"--release", "build-std=core",
"--message-format=json", "--release",
"--target", "--message-format=json",
&target, "--target",
]); &target,
]);
// Workaround for https://github.com/rust-lang/cargo/issues/6412 where cargo flocks itself. // Workaround for https://github.com/rust-lang/cargo/issues/6412 where cargo flocks itself.
let ebpf_target_dir = out_dir.join("integration-ebpf"); let ebpf_target_dir = out_dir.join("integration-ebpf");
cmd.arg("--target-dir").arg(&ebpf_target_dir); cmd.arg("--target-dir").arg(&ebpf_target_dir);
let mut executables = Vec::new();
let mut child = cmd let mut child = cmd
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn() .spawn()
.unwrap_or_else(|err| panic!("failed to spawn {cmd:?}: {err}")); .unwrap_or_else(|err| panic!("failed to spawn {cmd:?}: {err}"));
let Child { stdout, .. } = &mut child; let Child { stdout, stderr, .. } = &mut child;
// Trampoline stdout to cargo warnings.
let stderr = stderr.take().unwrap();
let stderr = BufReader::new(stderr);
let stderr = std::thread::spawn(move || {
for line in stderr.lines() {
let line = line.unwrap();
println!("cargo:warning={line}");
}
});
let stdout = stdout.take().unwrap(); let stdout = stdout.take().unwrap();
let reader = BufReader::new(stdout); let stdout = BufReader::new(stdout);
let mut executables = Vec::new(); let executables = &mut executables;
let mut compiler_messages = String::new(); let mut compiler_messages = String::new();
for message in Message::parse_stream(reader) { let mut text_lines = Vec::new();
for message in Message::parse_stream(stdout) {
#[allow(clippy::collapsible_match)] #[allow(clippy::collapsible_match)]
match message.expect("valid JSON") { match message.expect("valid JSON") {
Message::CompilerArtifact(Artifact { Message::CompilerArtifact(Artifact {
@ -210,24 +227,37 @@ fn main() {
} }
} }
Message::CompilerMessage(CompilerMessage { message, .. }) => { Message::CompilerMessage(CompilerMessage { message, .. }) => {
writeln!(&mut compiler_messages, "{message}").unwrap() // Infallible because writing to a string.
writeln!(&mut compiler_messages, "{message}").unwrap();
}
Message::TextLine(line) => {
text_lines.push(line);
} }
_ => {} _ => {}
} }
} }
if !compiler_messages.is_empty() {
println!("carg:warning=compiler messages:\n{compiler_messages}");
}
if !text_lines.is_empty() {
let text_lines = text_lines.join("\n");
println!("cargo:warning=text lines:\n{text_lines}");
}
let status = child let status = child
.wait() .wait()
.unwrap_or_else(|err| panic!("failed to wait for {cmd:?}: {err}")); .unwrap_or_else(|err| panic!("failed to wait for {cmd:?}: {err}"));
match status.code() { match status.code() {
Some(code) => match code { Some(code) => match code {
0 => {} 0 => {}
code => panic!("{cmd:?} exited with status code {code}:\n{compiler_messages}"), code => panic!("{cmd:?} exited with status code {code}"),
}, },
None => panic!("{cmd:?} terminated by signal"), None => panic!("{cmd:?} terminated by signal"),
} }
stderr.join().map_err(std::panic::resume_unwind).unwrap();
for (name, binary) in executables { for (name, binary) in executables {
let dst = out_dir.join(name); let dst = out_dir.join(name);
let _: u64 = fs::copy(&binary, &dst) let _: u64 = fs::copy(&binary, &dst)

@ -2,10 +2,10 @@ use std::{
fmt::Write as _, fmt::Write as _,
io::BufReader, io::BufReader,
path::PathBuf, path::PathBuf,
process::{Command, Stdio}, process::{Child, Command, Stdio},
}; };
use anyhow::{Context as _, Result}; use anyhow::{bail, Context as _, Result};
use cargo_metadata::{Artifact, CompilerMessage, Message, Target}; use cargo_metadata::{Artifact, CompilerMessage, Message, Target};
use clap::Parser; use clap::Parser;
@ -47,15 +47,18 @@ pub fn build(opts: BuildOptions) -> Result<Vec<(String, PathBuf)>> {
if let Some(target) = target { if let Some(target) = target {
cmd.args(["--target", &target]); cmd.args(["--target", &target]);
} }
let mut cmd = cmd
let mut child = cmd
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.spawn() .spawn()
.with_context(|| format!("failed to spawn {cmd:?}"))?; .with_context(|| format!("failed to spawn {cmd:?}"))?;
let Child { stdout, .. } = &mut child;
let reader = BufReader::new(cmd.stdout.take().unwrap()); // Infallible because we passed Stdio::piped().
let stdout = stdout.take().unwrap();
let stdout = BufReader::new(stdout);
let mut executables = Vec::new(); let mut executables = Vec::new();
let mut compiler_messages = String::new(); for message in Message::parse_stream(stdout) {
for message in Message::parse_stream(reader) {
#[allow(clippy::collapsible_match)] #[allow(clippy::collapsible_match)]
match message.context("valid JSON")? { match message.context("valid JSON")? {
Message::CompilerArtifact(Artifact { Message::CompilerArtifact(Artifact {
@ -68,26 +71,27 @@ pub fn build(opts: BuildOptions) -> Result<Vec<(String, PathBuf)>> {
} }
} }
Message::CompilerMessage(CompilerMessage { message, .. }) => { Message::CompilerMessage(CompilerMessage { message, .. }) => {
writeln!(&mut compiler_messages, "{message}").context("String write failed")? println!("{message}");
}
Message::TextLine(line) => {
println!("{line}");
} }
_ => {} _ => {}
} }
} }
let status = cmd let status = child
.wait() .wait()
.with_context(|| format!("failed to wait for {cmd:?}"))?; .with_context(|| format!("failed to wait for {cmd:?}"))?;
match status.code() { match status.code() {
Some(code) => match code { Some(code) => match code {
0 => Ok(executables), 0 => {}
code => Err(anyhow::anyhow!( code => bail!("{cmd:?} exited with status code {code}"),
"{cmd:?} exited with status code {code}:\n{compiler_messages}"
)),
}, },
None => Err(anyhow::anyhow!("{cmd:?} terminated by signal")), None => bail!("{cmd:?} terminated by signal"),
} }
Ok(executables)
} }
/// Build and run the project /// Build and run the project

Loading…
Cancel
Save