mirror of https://github.com/aya-rs/aya
Merge pull request #644 from aya-rs/build-script
Steps toward hermetic integration testsreviewable/pr629/r18
commit
7def6d7218
@ -0,0 +1,3 @@
|
||||
[submodule "libbpf"]
|
||||
path = libbpf
|
||||
url = https://github.com/libbpf/libbpf
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
"rust-analyzer.checkOnSave.allTargets": false,
|
||||
"rust-analyzer.checkOnSave.command": "clippy"
|
||||
"rust-analyzer.check.allTargets": true,
|
||||
"rust-analyzer.check.command": "clippy"
|
||||
}
|
||||
|
@ -1,6 +0,0 @@
|
||||
[build]
|
||||
target-dir = "../target"
|
||||
target = "bpfel-unknown-none"
|
||||
|
||||
[unstable]
|
||||
build-std = ["core"]
|
@ -0,0 +1 @@
|
||||
Subproject commit a2258003f21d9d52afd48aa64787b65ef80bd355
|
@ -1,6 +0,0 @@
|
||||
[build]
|
||||
target-dir = "../../target"
|
||||
target = "bpfel-unknown-none"
|
||||
|
||||
[unstable]
|
||||
build-std = ["core"]
|
@ -1,2 +0,0 @@
|
||||
[toolchain]
|
||||
channel="nightly"
|
@ -1 +0,0 @@
|
||||
../../rustfmt.toml
|
@ -0,0 +1,196 @@
|
||||
use std::{
|
||||
env,
|
||||
ffi::OsString,
|
||||
fmt::Write as _,
|
||||
fs,
|
||||
io::BufReader,
|
||||
path::PathBuf,
|
||||
process::{Child, Command, Stdio},
|
||||
};
|
||||
|
||||
use cargo_metadata::{
|
||||
Artifact, CompilerMessage, Message, Metadata, MetadataCommand, Package, Target,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
const AYA_BUILD_INTEGRATION_BPF: &str = "AYA_BUILD_INTEGRATION_BPF";
|
||||
|
||||
println!("cargo:rerun-if-env-changed={}", AYA_BUILD_INTEGRATION_BPF);
|
||||
|
||||
let build_integration_bpf = match env::var_os(AYA_BUILD_INTEGRATION_BPF) {
|
||||
None => false,
|
||||
Some(s) => {
|
||||
let s = s.to_str().unwrap();
|
||||
s.parse::<bool>().unwrap()
|
||||
}
|
||||
};
|
||||
|
||||
let manifest_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
|
||||
let manifest_dir = PathBuf::from(manifest_dir);
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
let out_dir = PathBuf::from(out_dir);
|
||||
|
||||
let endian = env::var_os("CARGO_CFG_TARGET_ENDIAN").unwrap();
|
||||
let target = if endian == "big" {
|
||||
"bpfeb"
|
||||
} else if endian == "little" {
|
||||
"bpfel"
|
||||
} else {
|
||||
panic!("unsupported endian={:?}", endian)
|
||||
};
|
||||
|
||||
const C_BPF_PROBES: &[(&str, &str)] = &[
|
||||
("ext.bpf.c", "ext.bpf.o"),
|
||||
("main.bpf.c", "main.bpf.o"),
|
||||
("multimap-btf.bpf.c", "multimap-btf.bpf.o"),
|
||||
("text_64_64_reloc.c", "text_64_64_reloc.o"),
|
||||
];
|
||||
|
||||
let c_bpf_probes = C_BPF_PROBES
|
||||
.iter()
|
||||
.map(|(src, dst)| (src, out_dir.join(dst)));
|
||||
|
||||
if build_integration_bpf {
|
||||
let libbpf_dir = manifest_dir
|
||||
.parent()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("libbpf");
|
||||
|
||||
let libbpf_headers_dir = out_dir.join("libbpf_headers");
|
||||
|
||||
let mut includedir = OsString::new();
|
||||
includedir.push("INCLUDEDIR=");
|
||||
includedir.push(&libbpf_headers_dir);
|
||||
|
||||
let mut cmd = Command::new("make");
|
||||
cmd.arg("-C")
|
||||
.arg(libbpf_dir.join("src"))
|
||||
.arg(includedir)
|
||||
.arg("install_headers");
|
||||
let status = cmd
|
||||
.status()
|
||||
.unwrap_or_else(|err| panic!("failed to run {cmd:?}: {err}"));
|
||||
match status.code() {
|
||||
Some(code) => match code {
|
||||
0 => {}
|
||||
code => panic!("{cmd:?} exited with code {code}"),
|
||||
},
|
||||
None => panic!("{cmd:?} terminated by signal"),
|
||||
}
|
||||
|
||||
let bpf_dir = manifest_dir.join("bpf");
|
||||
|
||||
let mut target_arch = OsString::new();
|
||||
target_arch.push("-D__TARGET_ARCH_");
|
||||
|
||||
let arch = env::var_os("CARGO_CFG_TARGET_ARCH").unwrap();
|
||||
if arch == "x86_64" {
|
||||
target_arch.push("x86");
|
||||
} else if arch == "aarch64" {
|
||||
target_arch.push("arm64");
|
||||
} else {
|
||||
target_arch.push(arch);
|
||||
};
|
||||
|
||||
for (src, dst) in c_bpf_probes {
|
||||
let src = bpf_dir.join(src);
|
||||
let mut cmd = Command::new("clang");
|
||||
cmd.arg("-I")
|
||||
.arg(&libbpf_headers_dir)
|
||||
.args(["-g", "-O2", "-target", target, "-c"])
|
||||
.arg(&target_arch)
|
||||
.arg(src)
|
||||
.arg("-o")
|
||||
.arg(dst);
|
||||
let status = cmd
|
||||
.status()
|
||||
.unwrap_or_else(|err| panic!("failed to run {cmd:?}: {err}"));
|
||||
match status.code() {
|
||||
Some(code) => match code {
|
||||
0 => {}
|
||||
code => panic!("{cmd:?} exited with code {code}"),
|
||||
},
|
||||
None => panic!("{cmd:?} terminated by signal"),
|
||||
}
|
||||
}
|
||||
|
||||
let ebpf_dir = manifest_dir.parent().unwrap().join("integration-ebpf");
|
||||
let target = format!("{target}-unknown-none");
|
||||
|
||||
let mut cmd = Command::new("cargo");
|
||||
cmd.current_dir(&ebpf_dir).args([
|
||||
"build",
|
||||
"-Z",
|
||||
"build-std=core",
|
||||
"--release",
|
||||
"--message-format=json",
|
||||
"--target",
|
||||
&target,
|
||||
]);
|
||||
let mut child = cmd
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap_or_else(|err| panic!("failed to spawn {cmd:?}: {err}"));
|
||||
let Child { stdout, .. } = &mut child;
|
||||
let stdout = stdout.take().unwrap();
|
||||
let reader = BufReader::new(stdout);
|
||||
let mut executables = Vec::new();
|
||||
let mut compiler_messages = String::new();
|
||||
for message in Message::parse_stream(reader) {
|
||||
#[allow(clippy::collapsible_match)]
|
||||
match message.expect("valid JSON") {
|
||||
Message::CompilerArtifact(Artifact {
|
||||
executable,
|
||||
target: Target { name, .. },
|
||||
..
|
||||
}) => {
|
||||
if let Some(executable) = executable {
|
||||
executables.push((name, executable.into_std_path_buf()));
|
||||
}
|
||||
}
|
||||
Message::CompilerMessage(CompilerMessage { message, .. }) => {
|
||||
writeln!(&mut compiler_messages, "{message}").unwrap()
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
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}"),
|
||||
},
|
||||
None => panic!("{cmd:?} terminated by signal"),
|
||||
}
|
||||
|
||||
for (name, binary) in executables {
|
||||
let dst = out_dir.join(name);
|
||||
let _: u64 = fs::copy(&binary, &dst)
|
||||
.unwrap_or_else(|err| panic!("failed to copy {binary:?} to {dst:?}: {err}"));
|
||||
}
|
||||
} else {
|
||||
for (_src, dst) in c_bpf_probes {
|
||||
fs::write(&dst, []).unwrap_or_else(|err| panic!("failed to create {dst:?}: {err}"));
|
||||
}
|
||||
|
||||
let Metadata { packages, .. } = MetadataCommand::new().no_deps().exec().unwrap();
|
||||
for Package { name, targets, .. } in packages {
|
||||
if name != "integration-ebpf" {
|
||||
continue;
|
||||
}
|
||||
for Target { name, kind, .. } in targets {
|
||||
if kind != ["bin"] {
|
||||
continue;
|
||||
}
|
||||
let dst = out_dir.join(name);
|
||||
fs::write(&dst, []).unwrap_or_else(|err| panic!("failed to create {dst:?}: {err}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1 +1,20 @@
|
||||
use aya::include_bytes_aligned;
|
||||
|
||||
pub const EXT: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/ext.bpf.o"));
|
||||
pub const MAIN: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/main.bpf.o"));
|
||||
pub const MULTIMAP_BTF: &[u8] =
|
||||
include_bytes_aligned!(concat!(env!("OUT_DIR"), "/multimap-btf.bpf.o"));
|
||||
pub const TEXT_64_64_RELOC: &[u8] =
|
||||
include_bytes_aligned!(concat!(env!("OUT_DIR"), "/text_64_64_reloc.o"));
|
||||
|
||||
pub const LOG: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/log"));
|
||||
pub const MAP_TEST: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/map_test"));
|
||||
pub const NAME_TEST: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/name_test"));
|
||||
pub const PASS: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/pass"));
|
||||
pub const TEST: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/test"));
|
||||
pub const RELOCATIONS: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/relocations"));
|
||||
pub const BPF_PROBE_READ: &[u8] =
|
||||
include_bytes_aligned!(concat!(env!("OUT_DIR"), "/bpf_probe_read"));
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
@ -0,0 +1,8 @@
|
||||
mod bpf_probe_read;
|
||||
mod btf_relocations;
|
||||
mod elf;
|
||||
mod load;
|
||||
mod log;
|
||||
mod rbpf;
|
||||
mod relocations;
|
||||
mod smoke;
|
@ -1,10 +1,8 @@
|
||||
use aya::include_bytes_aligned;
|
||||
use object::{Object, ObjectSymbol};
|
||||
|
||||
#[test]
|
||||
fn test_maps() {
|
||||
let bytes = include_bytes_aligned!("../../../target/bpfel-unknown-none/release/map_test");
|
||||
let obj_file = object::File::parse(bytes).unwrap();
|
||||
let obj_file = object::File::parse(crate::MAP_TEST).unwrap();
|
||||
if obj_file.section_by_name("maps").is_none() {
|
||||
panic!("No 'maps' ELF section");
|
||||
}
|
@ -1,138 +0,0 @@
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
env,
|
||||
ffi::{OsStr, OsString},
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
|
||||
use crate::utils::{exec, workspace_root};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Architecture {
|
||||
BpfEl,
|
||||
BpfEb,
|
||||
}
|
||||
|
||||
impl std::str::FromStr for Architecture {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(match s {
|
||||
"bpfel-unknown-none" => Architecture::BpfEl,
|
||||
"bpfeb-unknown-none" => Architecture::BpfEb,
|
||||
_ => return Err("invalid target"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Architecture {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
Architecture::BpfEl => "bpfel-unknown-none",
|
||||
Architecture::BpfEb => "bpfeb-unknown-none",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct BuildEbpfOptions {
|
||||
/// Set the endianness of the BPF target
|
||||
#[clap(default_value = "bpfel-unknown-none", long)]
|
||||
pub target: Architecture,
|
||||
/// Libbpf dir, required for compiling C code
|
||||
#[clap(long, action)]
|
||||
pub libbpf_dir: PathBuf,
|
||||
}
|
||||
|
||||
pub fn build_ebpf(opts: BuildEbpfOptions) -> Result<()> {
|
||||
build_rust_ebpf(&opts)?;
|
||||
build_c_ebpf(&opts)
|
||||
}
|
||||
|
||||
fn build_rust_ebpf(opts: &BuildEbpfOptions) -> Result<()> {
|
||||
let BuildEbpfOptions {
|
||||
target,
|
||||
libbpf_dir: _,
|
||||
} = opts;
|
||||
|
||||
let mut dir = PathBuf::from(workspace_root());
|
||||
dir.push("test/integration-ebpf");
|
||||
|
||||
exec(
|
||||
Command::new("cargo")
|
||||
.current_dir(&dir)
|
||||
.args(["+nightly", "build", "--release", "--target"])
|
||||
.arg(target.to_string())
|
||||
.args(["-Z", "build-std=core"])
|
||||
.current_dir(&dir),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_libbpf_headers(libbpf_dir: &Path, include_path: &Path) -> Result<()> {
|
||||
fs::create_dir_all(include_path)?;
|
||||
let mut includedir = OsString::new();
|
||||
includedir.push("INCLUDEDIR=");
|
||||
includedir.push(include_path);
|
||||
exec(
|
||||
Command::new("make")
|
||||
.current_dir(libbpf_dir.join("src"))
|
||||
.arg(includedir)
|
||||
.arg("install_headers"),
|
||||
)
|
||||
}
|
||||
|
||||
fn build_c_ebpf(opts: &BuildEbpfOptions) -> Result<()> {
|
||||
let BuildEbpfOptions { target, libbpf_dir } = opts;
|
||||
|
||||
let mut src = PathBuf::from(workspace_root());
|
||||
src.push("test/integration-ebpf/src/bpf");
|
||||
|
||||
let mut out_path = PathBuf::from(workspace_root());
|
||||
out_path.push("target");
|
||||
out_path.push(target.to_string());
|
||||
out_path.push("release");
|
||||
|
||||
let include_path = out_path.join("include");
|
||||
get_libbpf_headers(libbpf_dir, &include_path)?;
|
||||
let files = fs::read_dir(&src).unwrap();
|
||||
for file in files {
|
||||
let p = file.unwrap().path();
|
||||
if let Some(ext) = p.extension() {
|
||||
if ext == "c" {
|
||||
let mut out = PathBuf::from(&out_path);
|
||||
out.push(p.file_name().unwrap());
|
||||
out.set_extension("o");
|
||||
compile_with_clang(&p, &out, &include_path)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Build eBPF programs with clang and libbpf headers.
|
||||
fn compile_with_clang(src: &Path, out: &Path, include_path: &Path) -> Result<()> {
|
||||
let clang: Cow<'_, _> = match env::var_os("CLANG") {
|
||||
Some(val) => val.into(),
|
||||
None => OsStr::new("/usr/bin/clang").into(),
|
||||
};
|
||||
let arch = match env::consts::ARCH {
|
||||
"x86_64" => "x86",
|
||||
"aarch64" => "arm64",
|
||||
arch => arch,
|
||||
};
|
||||
exec(
|
||||
Command::new(clang)
|
||||
.arg("-I")
|
||||
.arg(include_path)
|
||||
.args(["-g", "-O2", "-target", "bpf", "-c"])
|
||||
.arg(format!("-D__TARGET_ARCH_{arch}"))
|
||||
.arg(src)
|
||||
.arg("-o")
|
||||
.arg(out),
|
||||
)
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
use std::{cell::OnceCell, process::Command};
|
||||
|
||||
use anyhow::{bail, Context as _, Result};
|
||||
|
||||
pub fn workspace_root() -> &'static str {
|
||||
static mut WORKSPACE_ROOT: OnceCell<String> = OnceCell::new();
|
||||
unsafe { &mut WORKSPACE_ROOT }.get_or_init(|| {
|
||||
let cmd = cargo_metadata::MetadataCommand::new();
|
||||
cmd.exec().unwrap().workspace_root.to_string()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn exec(cmd: &mut Command) -> Result<()> {
|
||||
let status = cmd
|
||||
.status()
|
||||
.with_context(|| format!("failed to run {cmd:?}"))?;
|
||||
match status.code() {
|
||||
Some(code) => match code {
|
||||
0 => Ok(()),
|
||||
code => bail!("{cmd:?} exited with code {code}"),
|
||||
},
|
||||
None => bail!("{cmd:?} terminated by signal"),
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue