Always build eBPF; remove xtask

reviewable/pr132/r1
Tamir Duberstein 6 months ago
parent 27e7867528
commit 451159afd2
No known key found for this signature in database

@ -1,2 +0,0 @@
[alias]
xtask = "run --package xtask --"

@ -1,7 +1,7 @@
[workspace] [workspace]
resolver = "2" resolver = "2"
members = ["xtask", "{{project-name}}", "{{project-name}}-common", "{{project-name}}-ebpf"] members = ["{{project-name}}", "{{project-name}}-common", "{{project-name}}-ebpf"]
default-members = ["xtask", "{{project-name}}", "{{project-name}}-common"] default-members = ["{{project-name}}", "{{project-name}}-common"]
[workspace.dependencies] [workspace.dependencies]
aya = { version = "0.13.0", default-features = false } aya = { version = "0.13.0", default-features = false }

@ -11,18 +11,21 @@
## Build & Run ## Build & Run
Use `cargo build`, `cargo check`, etc. as normal. Run your program with `xtask run`. Use `cargo build`, `cargo check`, etc. as normal. Run your program with:
```shell
cargo run --release --config 'target."cfg(all())".runner="sudo -E"'
```
Cargo build scripts are used to automatically build the eBPF correctly and include it in the Cargo build scripts are used to automatically build the eBPF correctly and include it in the
program. When not using `xtask run`, eBPF code generation is skipped for a faster developer program.
experience; this compromise necessitates the use of `xtask` to actually build the eBPF.
## Cross-compiling on macOS ## Cross-compiling on macOS
Cross compilation should work on both Intel and Apple Silicon Macs. Cross compilation should work on both Intel and Apple Silicon Macs.
```shell ```shell
AYA_BUILD_EBPF=true CC=${ARCH}-linux-musl-gcc cargo build --package {{project-name}} --release \ CC=${ARCH}-linux-musl-gcc cargo build --package {{project-name}} --release \
--target=${ARCH}-unknown-linux-musl \ --target=${ARCH}-unknown-linux-musl \
--config=target.${ARCH}-unknown-linux-musl.linker=\"${ARCH}-linux-musl-gcc\" --config=target.${ARCH}-unknown-linux-musl.linker=\"${ARCH}-linux-musl-gcc\"
``` ```

@ -68,7 +68,7 @@ case $OS in
if [[ "$ARCH" == "arm64" ]]; then if [[ "$ARCH" == "arm64" ]]; then
ARCH="aarch64" ARCH="aarch64"
fi fi
AYA_BUILD_EBPF=true CC=${ARCH}-linux-musl-gcc cargo build --package "${CRATE_NAME}" --release \ CC=${ARCH}-linux-musl-gcc cargo build --package "${CRATE_NAME}" --release \
--target="${ARCH}"-unknown-linux-musl \ --target="${ARCH}"-unknown-linux-musl \
--config=target."${ARCH}"-unknown-linux-musl.linker=\""${ARCH}"-linux-musl-gcc\" --config=target."${ARCH}"-unknown-linux-musl.linker=\""${ARCH}"-linux-musl-gcc\"
;; ;;
@ -85,7 +85,7 @@ case $OS in
expect <<EOF expect <<EOF
set timeout 30 ;# Increase timeout if necessary set timeout 30 ;# Increase timeout if necessary
spawn cargo xtask run spawn cargo run --release --config 'target."cfg(all())".runner="sudo -E"'
expect { expect {
-re "Waiting for Ctrl-C.*" { -re "Waiting for Ctrl-C.*" {
send -- \003 ;# Send Ctrl-C send -- \003 ;# Send Ctrl-C

@ -1,8 +0,0 @@
[package]
name = "xtask"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = { workspace = true, default-features = true }
clap = { workspace = true, default-features = true, features = ["derive"] }

@ -1 +0,0 @@
pub const AYA_BUILD_EBPF: &str = "AYA_BUILD_EBPF";

@ -1,23 +0,0 @@
mod run;
use anyhow::Result;
use clap::Parser;
#[derive(Debug, Parser)]
pub struct Options {
#[clap(subcommand)]
command: Command,
}
#[derive(Debug, Parser)]
enum Command {
Run(run::Options),
}
fn main() -> Result<()> {
let Options { command } = Parser::parse();
match command {
Command::Run(opts) => run::run(opts),
}
}

@ -1,47 +0,0 @@
use std::{ffi::OsString, process::Command};
use anyhow::{bail, Context as _, Result};
use clap::Parser;
use xtask::AYA_BUILD_EBPF;
#[derive(Debug, Parser)]
pub struct Options {
/// Build and run the release target.
#[clap(long)]
release: bool,
/// The command used to wrap your application.
#[clap(short, long, default_value = "sudo -E")]
runner: String,
/// Arguments to pass to your application.
#[clap(global = true, last = true)]
run_args: Vec<OsString>,
}
/// Build and run the project.
pub fn run(opts: Options) -> Result<()> {
let Options {
release,
runner,
run_args,
} = opts;
let mut cmd = Command::new("cargo");
cmd.env(AYA_BUILD_EBPF, "true");
cmd.args(["run", "--package", "{{project-name}}", "--config"]);
if release {
cmd.arg(format!("target.\"cfg(all())\".runner=\"{}\"", runner));
cmd.arg("--release");
} else {
cmd.arg(format!("target.\"cfg(all())\".runner=\"{}\"", runner));
}
if !run_args.is_empty() {
cmd.arg("--").args(run_args);
}
let status = cmd
.status()
.with_context(|| format!("failed to run {cmd:?}"))?;
if status.code() != Some(0) {
bail!("{cmd:?} failed: {status:?}")
}
Ok(())
}

@ -11,7 +11,6 @@ aya-log-ebpf = { workspace = true }
[build-dependencies] [build-dependencies]
which = { workspace = true } which = { workspace = true }
xtask = { path = "../xtask" }
[[bin]] [[bin]]
name = "{{ project-name }}" name = "{{ project-name }}"

@ -1,7 +1,6 @@
use std::env; use std::env;
use which::which; use which::which;
use xtask::AYA_BUILD_EBPF;
/// Building this crate has an undeclared dependency on the `bpf-linker` binary. This would be /// Building this crate has an undeclared dependency on the `bpf-linker` binary. This would be
/// better expressed by [artifact-dependencies][bindeps] but issues such as /// better expressed by [artifact-dependencies][bindeps] but issues such as
@ -15,16 +14,6 @@ use xtask::AYA_BUILD_EBPF;
/// ///
/// [bindeps]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html?highlight=feature#artifact-dependencies /// [bindeps]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html?highlight=feature#artifact-dependencies
fn main() { fn main() {
println!("cargo:rerun-if-env-changed={}", AYA_BUILD_EBPF); let bpf_linker = which("bpf-linker").unwrap();
println!("cargo:rerun-if-changed={}", bpf_linker.to_str().unwrap());
let build_ebpf = env::var(AYA_BUILD_EBPF)
.as_deref()
.map(str::parse)
.map(Result::unwrap)
.unwrap_or_default();
if build_ebpf {
let bpf_linker = which("bpf-linker").unwrap();
println!("cargo:rerun-if-changed={}", bpf_linker.to_str().unwrap());
}
} }

@ -33,7 +33,6 @@ cargo_metadata = { workspace = true }
# workflows with stable cargo; stable cargo outright refuses to load manifests that use unstable # workflows with stable cargo; stable cargo outright refuses to load manifests that use unstable
# features. # features.
{{project-name}}-ebpf = { path = "../{{project-name}}-ebpf" } {{project-name}}-ebpf = { path = "../{{project-name}}-ebpf" }
xtask = { path = "../xtask" }
[[bin]] [[bin]]
name = "{{project-name}}" name = "{{project-name}}"

@ -8,7 +8,6 @@ use std::{
use cargo_metadata::{ use cargo_metadata::{
Artifact, CompilerMessage, Message, Metadata, MetadataCommand, Package, Target, Artifact, CompilerMessage, Message, Metadata, MetadataCommand, Package, Target,
}; };
use xtask::AYA_BUILD_EBPF;
/// This crate has a runtime dependency on artifacts produced by the `{{project-name}}-ebpf` crate. /// This crate has a runtime dependency on artifacts produced by the `{{project-name}}-ebpf` crate.
/// This would be better expressed as one or more [artifact-dependencies][bindeps] but issues such /// This would be better expressed as one or more [artifact-dependencies][bindeps] but issues such
@ -20,32 +19,8 @@ use xtask::AYA_BUILD_EBPF;
/// ///
/// prevent their use for the time being. /// prevent their use for the time being.
/// ///
/// This file, along with the xtask crate, allows analysis tools such as `cargo check`, `cargo
/// clippy`, and even `cargo build` to work as users expect. Prior to this file's existence, this
/// crate's undeclared dependency on artifacts from `{{project-name}}-ebpf` would cause build (and
/// `cargo check`, and `cargo clippy`) failures until the user ran certain other commands in the
/// workspace. Conversely, those same tools (e.g. cargo test --no-run) would produce stale results
/// if run naively because they'd make use of artifacts from a previous build of
/// `{{project-name}}-ebpf`.
///
/// Note that this solution is imperfect: in particular it has to balance correctness with
/// performance; an environment variable is used to replace true builds of `{{project-name}}-ebpf`
/// with stubs to preserve the property that code generation and linking (in
/// `{{project-name}}-ebpf`) do not occur on metadata-only actions such as `cargo check` or `cargo
/// clippy` of this crate. This means that naively attempting to `cargo test --no-run` this crate
/// will produce binaries that fail at runtime because the stubs are inadequate for actually running
/// the tests.
///
/// [bindeps]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html?highlight=feature#artifact-dependencies /// [bindeps]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html?highlight=feature#artifact-dependencies
fn main() { fn main() {
println!("cargo:rerun-if-env-changed={}", AYA_BUILD_EBPF);
let build_ebpf = env::var(AYA_BUILD_EBPF)
.as_deref()
.map(str::parse)
.map(Result::unwrap)
.unwrap_or_default();
let Metadata { packages, .. } = MetadataCommand::new().no_deps().exec().unwrap(); let Metadata { packages, .. } = MetadataCommand::new().no_deps().exec().unwrap();
let ebpf_package = packages let ebpf_package = packages
.into_iter() .into_iter()
@ -64,109 +39,98 @@ fn main() {
panic!("unsupported endian={:?}", endian) panic!("unsupported endian={:?}", endian)
}; };
if build_ebpf { let arch = env::var_os("CARGO_CFG_TARGET_ARCH").unwrap();
let arch = env::var_os("CARGO_CFG_TARGET_ARCH").unwrap();
let target = format!("{target}-unknown-none");
let target = format!("{target}-unknown-none");
let Package { manifest_path, .. } = ebpf_package;
let Package { manifest_path, .. } = ebpf_package; let ebpf_dir = manifest_path.parent().unwrap();
let ebpf_dir = manifest_path.parent().unwrap();
// We have a build-dependency on `{{project-name}}-ebpf`, so cargo will automatically rebuild us
// We have a build-dependency on `{{project-name}}-ebpf`, so cargo will automatically rebuild us // if `{{project-name}}-ebpf`'s *library* target or any of its dependencies change. Since we
// if `{{project-name}}-ebpf`'s *library* target or any of its dependencies change. Since we // depend on `{{project-name}}-ebpf`'s *binary* targets, that only gets us half of the way. This
// depend on `{{project-name}}-ebpf`'s *binary* targets, that only gets us half of the way. This // stanza ensures cargo will rebuild us on changes to the binaries too, which gets us the
// stanza ensures cargo will rebuild us on changes to the binaries too, which gets us the // rest of the way.
// rest of the way. println!("cargo:rerun-if-changed={}", ebpf_dir.as_str());
println!("cargo:rerun-if-changed={}", ebpf_dir.as_str());
let mut cmd = Command::new("cargo");
let mut cmd = Command::new("cargo"); cmd.args([
cmd.args([ "build",
"build", "-Z",
"-Z", "build-std=core",
"build-std=core", "--bins",
"--bins", "--message-format=json",
"--message-format=json", "--release",
"--release", "--target",
"--target", &target,
&target, ]);
]);
cmd.env("CARGO_CFG_BPF_TARGET_ARCH", arch);
cmd.env("CARGO_CFG_BPF_TARGET_ARCH", arch);
// Workaround to make sure that the rust-toolchain.toml is respected.
// Workaround to make sure that the rust-toolchain.toml is respected. for key in ["RUSTUP_TOOLCHAIN", "RUSTC"] {
for key in ["RUSTUP_TOOLCHAIN", "RUSTC"] { cmd.env_remove(key);
cmd.env_remove(key); }
cmd.current_dir(ebpf_dir);
// Workaround for https://github.com/rust-lang/cargo/issues/6412 where cargo flocks itself.
let ebpf_target_dir = out_dir.join("{{project-name}}-ebpf");
cmd.arg("--target-dir").arg(&ebpf_target_dir);
let mut child = cmd
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap_or_else(|err| panic!("failed to spawn {cmd:?}: {err}"));
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}");
} }
cmd.current_dir(ebpf_dir); });
// Workaround for https://github.com/rust-lang/cargo/issues/6412 where cargo flocks itself. let stdout = stdout.take().unwrap();
let ebpf_target_dir = out_dir.join("{{project-name}}-ebpf"); let stdout = BufReader::new(stdout);
cmd.arg("--target-dir").arg(&ebpf_target_dir); let mut executables = Vec::new();
for message in Message::parse_stream(stdout) {
let mut child = cmd #[allow(clippy::collapsible_match)]
.stdout(Stdio::piped()) match message.expect("valid JSON") {
.stderr(Stdio::piped()) Message::CompilerArtifact(Artifact {
.spawn() executable,
.unwrap_or_else(|err| panic!("failed to spawn {cmd:?}: {err}")); target: Target { name, .. },
let Child { stdout, stderr, .. } = &mut child; ..
}) => {
// Trampoline stdout to cargo warnings. if let Some(executable) = executable {
let stderr = stderr.take().unwrap(); executables.push((name, executable.into_std_path_buf()));
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 = BufReader::new(stdout);
let mut executables = Vec::new();
for message in Message::parse_stream(stdout) {
#[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, .. }) => {
for line in message.rendered.unwrap_or_default().split('\n') {
println!("cargo:warning={line}");
}
} }
Message::TextLine(line) => { }
Message::CompilerMessage(CompilerMessage { message, .. }) => {
for line in message.rendered.unwrap_or_default().split('\n') {
println!("cargo:warning={line}"); println!("cargo:warning={line}");
} }
_ => {}
} }
Message::TextLine(line) => {
println!("cargo:warning={line}");
}
_ => {}
} }
}
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}"));
assert_eq!(status.code(), Some(0), "{cmd:?} failed: {status:?}"); assert_eq!(status.code(), Some(0), "{cmd:?} failed: {status:?}");
stderr.join().map_err(std::panic::resume_unwind).unwrap(); 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)
.unwrap_or_else(|err| panic!("failed to copy {binary:?} to {dst:?}: {err}")); .unwrap_or_else(|err| panic!("failed to copy {binary:?} to {dst:?}: {err}"));
}
} else {
let Package { targets, .. } = ebpf_package;
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}"));
}
} }
} }

Loading…
Cancel
Save