Replace xtask with build scripts
Adapt https://github.com/aya-rs/aya/commit/3d463a3 and subsequent work to the template. This has worked very well for us in the main project, and our users should get the same hotness.reviewable/pr124/r1
parent
4da4bf4729
commit
51da029036
@ -1,2 +0,0 @@
|
|||||||
[alias]
|
|
||||||
xtask = "run --package xtask --"
|
|
@ -1,41 +0,0 @@
|
|||||||
use std::process::Command;
|
|
||||||
|
|
||||||
use anyhow::Context as _;
|
|
||||||
use clap::Parser;
|
|
||||||
|
|
||||||
use crate::build_ebpf::{build_ebpf, Architecture, Options as BuildOptions};
|
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
|
||||||
pub struct Options {
|
|
||||||
/// Set the endianness of the BPF target
|
|
||||||
#[clap(default_value = "bpfel-unknown-none", long)]
|
|
||||||
pub bpf_target: Architecture,
|
|
||||||
/// Build and run the release target
|
|
||||||
#[clap(long)]
|
|
||||||
pub release: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build our ebpf program and the userspace program.
|
|
||||||
pub fn build(opts: Options) -> Result<(), anyhow::Error> {
|
|
||||||
let Options {
|
|
||||||
bpf_target,
|
|
||||||
release,
|
|
||||||
} = opts;
|
|
||||||
|
|
||||||
// Build our ebpf program.
|
|
||||||
build_ebpf(BuildOptions {
|
|
||||||
target: bpf_target,
|
|
||||||
release,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Build our userspace program.
|
|
||||||
let mut cmd = Command::new("cargo");
|
|
||||||
cmd.arg("build");
|
|
||||||
if release {
|
|
||||||
cmd.arg("--release");
|
|
||||||
}
|
|
||||||
let status = cmd.status().context("failed to build userspace")?;
|
|
||||||
anyhow::ensure!(status.success(), "failed to build userspace program: {}", status);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
use std::process::Command;
|
|
||||||
|
|
||||||
use anyhow::Context as _;
|
|
||||||
use clap::Parser;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Architecture {
|
|
||||||
BpfEl,
|
|
||||||
BpfEb,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Architecture {
|
|
||||||
pub fn as_str(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Architecture::BpfEl => "bpfel-unknown-none",
|
|
||||||
Architecture::BpfEb => "bpfeb-unknown-none",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(self.as_str())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
|
||||||
pub struct Options {
|
|
||||||
/// Set the endianness of the BPF target
|
|
||||||
#[clap(default_value = "bpfel-unknown-none", long)]
|
|
||||||
pub target: Architecture,
|
|
||||||
/// Build the release target
|
|
||||||
#[clap(long)]
|
|
||||||
pub release: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build_ebpf(opts: Options) -> Result<(), anyhow::Error> {
|
|
||||||
let Options { target, release } = opts;
|
|
||||||
|
|
||||||
let mut cmd = Command::new("cargo");
|
|
||||||
cmd.current_dir("{{project-name}}-ebpf")
|
|
||||||
// Command::new creates a child process which inherits all env variables. This means env
|
|
||||||
// vars set by the cargo xtask command are also inherited. RUSTUP_TOOLCHAIN is removed so
|
|
||||||
// the rust-toolchain.toml file in the -ebpf folder is honored.
|
|
||||||
.env_remove("RUSTUP_TOOLCHAIN")
|
|
||||||
.args(["build", "--target", target.as_str()]);
|
|
||||||
|
|
||||||
if release {
|
|
||||||
cmd.arg("--release");
|
|
||||||
}
|
|
||||||
|
|
||||||
let status = cmd.status().context("failed to build bpf program")?;
|
|
||||||
anyhow::ensure!(status.success(), "failed to build bpf program: {}", status);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -0,0 +1 @@
|
|||||||
|
pub const AYA_BUILD_EBPF: &str = "AYA_BUILD_EBPF";
|
@ -1,36 +0,0 @@
|
|||||||
mod build_ebpf;
|
|
||||||
mod build;
|
|
||||||
mod run;
|
|
||||||
|
|
||||||
use std::process::exit;
|
|
||||||
|
|
||||||
use clap::Parser;
|
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
|
||||||
pub struct Options {
|
|
||||||
#[clap(subcommand)]
|
|
||||||
command: Command,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
|
||||||
enum Command {
|
|
||||||
BuildEbpf(build_ebpf::Options),
|
|
||||||
Build(build::Options),
|
|
||||||
Run(run::Options),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let opts = Options::parse();
|
|
||||||
|
|
||||||
use Command::*;
|
|
||||||
let ret = match opts.command {
|
|
||||||
BuildEbpf(opts) => build_ebpf::build_ebpf(opts),
|
|
||||||
Run(opts) => run::run(opts),
|
|
||||||
Build(opts) => build::build(opts),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(e) = ret {
|
|
||||||
eprintln!("{e:#}");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
use std::process::Command;
|
|
||||||
|
|
||||||
use anyhow::Context as _;
|
|
||||||
use clap::Parser;
|
|
||||||
|
|
||||||
use crate::{build::{build, Options as BuildOptions}, build_ebpf::Architecture};
|
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
|
||||||
pub struct Options {
|
|
||||||
/// Set the endianness of the BPF target
|
|
||||||
#[clap(default_value = "bpfel-unknown-none", long)]
|
|
||||||
pub bpf_target: Architecture,
|
|
||||||
/// Build and run the release target
|
|
||||||
#[clap(long)]
|
|
||||||
pub release: bool,
|
|
||||||
/// The command used to wrap your application
|
|
||||||
#[clap(short, long, default_value = "sudo -E")]
|
|
||||||
pub runner: String,
|
|
||||||
/// Arguments to pass to your application
|
|
||||||
#[clap(name = "args", last = true)]
|
|
||||||
pub run_args: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Build and run the project
|
|
||||||
pub fn run(opts: Options) -> Result<(), anyhow::Error> {
|
|
||||||
// Build our ebpf program and the project
|
|
||||||
build(BuildOptions{
|
|
||||||
bpf_target: opts.bpf_target,
|
|
||||||
release: opts.release,
|
|
||||||
}).context("Error while building project")?;
|
|
||||||
|
|
||||||
// profile we are building (release or debug)
|
|
||||||
let profile = if opts.release { "release" } else { "debug" };
|
|
||||||
let bin_path = format!("target/{profile}/{{project-name}}");
|
|
||||||
|
|
||||||
// arguments to pass to the application
|
|
||||||
let mut run_args: Vec<_> = opts.run_args.iter().map(String::as_str).collect();
|
|
||||||
|
|
||||||
// configure args
|
|
||||||
let mut args: Vec<_> = opts.runner.trim().split_terminator(' ').collect();
|
|
||||||
args.push(bin_path.as_str());
|
|
||||||
args.append(&mut run_args);
|
|
||||||
|
|
||||||
// run the command
|
|
||||||
let status = Command::new(args.first().expect("No first argument"))
|
|
||||||
.args(args.iter().skip(1))
|
|
||||||
.status()
|
|
||||||
.expect("failed to run the command");
|
|
||||||
|
|
||||||
if !status.success() {
|
|
||||||
anyhow::bail!("Failed to run `{}`", args.join(" "));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -0,0 +1,30 @@
|
|||||||
|
use std::env;
|
||||||
|
|
||||||
|
use which::which;
|
||||||
|
use xtask::AYA_BUILD_EBPF;
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
/// https://github.com/rust-lang/cargo/issues/12385 make their use impractical for the time being.
|
||||||
|
///
|
||||||
|
/// This file implements an imperfect solution: it causes cargo to rebuild the crate whenever the
|
||||||
|
/// mtime of `which bpf-linker` changes. Note that possibility that a new bpf-linker is added to
|
||||||
|
/// $PATH ahead of the one used as the cache key still exists. Solving this in the general case
|
||||||
|
/// would require rebuild-if-changed-env=PATH *and* rebuild-if-changed={every-directory-in-PATH}
|
||||||
|
/// which would likely mean far too much cache invalidation.
|
||||||
|
///
|
||||||
|
/// [bindeps]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html?highlight=feature#artifact-dependencies
|
||||||
|
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();
|
||||||
|
|
||||||
|
if build_ebpf {
|
||||||
|
let bpf_linker = which("bpf-linker").unwrap();
|
||||||
|
println!("cargo:rerun-if-changed={}", bpf_linker.to_str().unwrap());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
#![no_std]
|
||||||
|
|
||||||
|
// This file exists to enable the library target.
|
@ -0,0 +1,173 @@
|
|||||||
|
use std::{
|
||||||
|
env,
|
||||||
|
fs,
|
||||||
|
io::{BufRead as _, BufReader},
|
||||||
|
path::PathBuf,
|
||||||
|
process::{Child, Command, Stdio},
|
||||||
|
};
|
||||||
|
|
||||||
|
use cargo_metadata::{
|
||||||
|
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 would be better expressed as one or more [artifact-dependencies][bindeps] but issues such
|
||||||
|
/// as:
|
||||||
|
///
|
||||||
|
/// * https://github.com/rust-lang/cargo/issues/12374
|
||||||
|
/// * https://github.com/rust-lang/cargo/issues/12375
|
||||||
|
/// * https://github.com/rust-lang/cargo/issues/12385
|
||||||
|
///
|
||||||
|
/// 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
|
||||||
|
fn main() {
|
||||||
|
println!("cargo:rerun-if-env-changed={}", AYA_BUILD_EBPF);
|
||||||
|
|
||||||
|
let build_integration_bpf = 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 ebpf_package = packages
|
||||||
|
.into_iter()
|
||||||
|
.find(|Package { name, .. }| name == "{{project-name}}-ebpf")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
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)
|
||||||
|
};
|
||||||
|
|
||||||
|
if build_integration_bpf {
|
||||||
|
let arch = env::var_os("CARGO_CFG_TARGET_ARCH").unwrap();
|
||||||
|
|
||||||
|
let target = format!("{target}-unknown-none");
|
||||||
|
|
||||||
|
let Package { manifest_path, .. } = ebpf_package;
|
||||||
|
let ebpf_dir = manifest_path.parent().unwrap();
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// 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
|
||||||
|
// rest of the way.
|
||||||
|
println!("cargo:rerun-if-changed={}", ebpf_dir.as_str());
|
||||||
|
|
||||||
|
let mut cmd = Command::new("cargo");
|
||||||
|
cmd.args([
|
||||||
|
"build",
|
||||||
|
"-Z",
|
||||||
|
"build-std=core",
|
||||||
|
"--bins",
|
||||||
|
"--message-format=json",
|
||||||
|
"--release",
|
||||||
|
"--target",
|
||||||
|
&target,
|
||||||
|
]);
|
||||||
|
|
||||||
|
cmd.env("CARGO_CFG_BPF_TARGET_ARCH", arch);
|
||||||
|
|
||||||
|
// Workaround to make sure that the rust-toolchain.toml is respected.
|
||||||
|
for key in ["RUSTUP_TOOLCHAIN", "RUSTC"] {
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
println!("cargo:warning={line}");
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = child
|
||||||
|
.wait()
|
||||||
|
.unwrap_or_else(|err| panic!("failed to wait for {cmd:?}: {err}"));
|
||||||
|
assert_eq!(status.code(), Some(0), "{cmd:?} failed: {status:?}");
|
||||||
|
|
||||||
|
stderr.join().map_err(std::panic::resume_unwind).unwrap();
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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…
Reference in New Issue