Skip to content

Commit

Permalink
integration-test: shuttle linker errors to user
Browse files Browse the repository at this point in the history
Use CARGO_ENCODED_RUSTFLAGS to get cargo-in-cargo to pass --log-file and
--log-level to bpf-linker, then read the file and show it to the user.

The log output is interleaved because cargo is building all the binaries
in integration-ebpf concurrently, but it's all there. Most importantly
we see:

warning: 14:48:51 [ERROR] llvm: <unknown>:0:0: in function test_log i32 (ptr): A call to built-in function 'memset' is not supported.
warning:
warning: 14:48:51 [ERROR] llvm: <unknown>:0:0: in function test_log i32 (ptr): A call to built-in function 'memset' is not supported.
warning:
warning: 14:48:51 [ERROR] llvm: <unknown>:0:0: in function test_log i32 (ptr): A call to built-in function 'memset' is not supported.
warning:
warning: 14:48:51 [ERROR] llvm: <unknown>:0:0: in function test_log i32 (ptr): A call to built-in function 'memset' is not supported.
warning:
warning: 14:48:51 [ERROR] llvm: <unknown>:0:0: in function test_log i32 (ptr): A call to built-in function 'memset' is not supported.
warning:
warning: 14:48:51 [ERROR] llvm: <unknown>:0:0: in function test_log i32 (ptr): A call to built-in function 'memset' is not supported.
  • Loading branch information
tamird committed Jul 19, 2023
1 parent f554d42 commit d6a70d2
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 24 deletions.
85 changes: 76 additions & 9 deletions test/integration-test/build.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::{
env,
ffi::OsString,
ffi::{OsStr, OsString},
fmt::Write as _,
fs,
io::BufReader,
io::{BufRead as _, BufReader},
path::PathBuf,
process::{Child, Command, Stdio},
};
Expand Down Expand Up @@ -132,16 +132,54 @@ fn main() {
let ebpf_target_dir = out_dir.join("integration-ebpf");
cmd.arg("--target-dir").arg(&ebpf_target_dir);

let bpf_linker_log_path = out_dir.join("bpf-linker.log");
{
use std::os::unix::ffi::OsStrExt as _;
let c_link_arg = OsStr::from_bytes(b"-Clink-arg=");
let sep = OsStr::from_bytes(&[0x1f]);
let rustflags = [
c_link_arg,
OsStr::from_bytes(b"--log-file="),
bpf_linker_log_path.as_os_str(),
sep,
c_link_arg,
OsStr::from_bytes(b"--log-level=warn"),
];
let rustflags = rustflags.into_iter().fold(OsString::new(), |mut acc, arg| {
acc.push(arg);
acc
});
dbg!(&rustflags);
// There are many ways to pass rustflags to cargo, but this environment wins over all the
// others. Since we're cargo-in-cargo here, we must override it to avoid losing to the value
// set by the outer cargo.
cmd.env("CARGO_ENCODED_RUSTFLAGS", rustflags);
}

let mut executables = Vec::new();
let mut child = cmd
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.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 reader = BufReader::new(stdout);
let mut executables = Vec::new();
let stdout = BufReader::new(stdout);
let executables = &mut executables;
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)]
match message.expect("valid JSON") {
Message::CompilerArtifact(Artifact {
Expand All @@ -154,24 +192,53 @@ fn main() {
}
}
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
.wait()
.unwrap_or_else(|err| panic!("failed to wait for {cmd:?}: {err}"));

match status.code() {
Some(code) => match code {
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"),
}

stderr.join().map_err(std::panic::resume_unwind).unwrap();

if bpf_linker_log_path.try_exists().unwrap() {
let bpf_linker_log = fs::OpenOptions::new()
.read(true)
.open(&bpf_linker_log_path)
.unwrap_or_else(|err| panic!("failed to open {bpf_linker_log_path:?}: {err}"));
{
let bpf_linker_log = BufReader::new(bpf_linker_log);
for line in bpf_linker_log.lines() {
let line = line.unwrap();
println!("cargo:warning={line}");
}
}
fs::remove_file(&bpf_linker_log_path)
.unwrap_or_else(|err| panic!("failed to remove {bpf_linker_log_path:?}: {err}"));
}

for (name, binary) in executables {
let dst = out_dir.join(name);
let _: u64 = fs::copy(&binary, &dst)
Expand Down
34 changes: 19 additions & 15 deletions xtask/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ use std::{
fmt::Write as _,
io::BufReader,
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 clap::Parser;
use xtask::AYA_BUILD_INTEGRATION_BPF;
Expand Down Expand Up @@ -48,15 +48,18 @@ pub fn build(opts: BuildOptions) -> Result<Vec<(String, PathBuf)>> {
if let Some(target) = target {
cmd.args(["--target", &target]);
}
let mut cmd = cmd

let mut child = cmd
.stdout(Stdio::piped())
.spawn()
.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 compiler_messages = String::new();
for message in Message::parse_stream(reader) {
for message in Message::parse_stream(stdout) {
#[allow(clippy::collapsible_match)]
match message.context("valid JSON")? {
Message::CompilerArtifact(Artifact {
Expand All @@ -69,26 +72,27 @@ pub fn build(opts: BuildOptions) -> Result<Vec<(String, PathBuf)>> {
}
}
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()
.with_context(|| format!("failed to wait for {cmd:?}"))?;

match status.code() {
Some(code) => match code {
0 => Ok(executables),
code => Err(anyhow::anyhow!(
"{cmd:?} exited with status code {code}:\n{compiler_messages}"
)),
0 => {}
code => bail!("{cmd:?} exited with status code {code}"),
},
None => Err(anyhow::anyhow!("{cmd:?} terminated by signal")),
None => bail!("{cmd:?} terminated by signal"),
}

Ok(executables)
}

/// Build and run the project
Expand Down

0 comments on commit d6a70d2

Please sign in to comment.