Replace xtask builds 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. Note that xtask is still used for running, as it is in the main project.reviewable/pr125/r1
parent
4da4bf4729
commit
5811d6ff56
@ -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,55 +1,47 @@
|
||||
use std::process::Command;
|
||||
use std::{ffi::OsString, process::Command};
|
||||
|
||||
use anyhow::Context as _;
|
||||
use anyhow::{bail, Context as _, Result};
|
||||
use clap::Parser;
|
||||
|
||||
use crate::{build::{build, Options as BuildOptions}, build_ebpf::Architecture};
|
||||
use xtask::AYA_BUILD_EBPF;
|
||||
|
||||
#[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
|
||||
/// Build and run the release target.
|
||||
#[clap(long)]
|
||||
pub release: bool,
|
||||
/// The command used to wrap your application
|
||||
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>,
|
||||
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<(), 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))
|
||||
/// 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()
|
||||
.expect("failed to run the command");
|
||||
|
||||
if !status.success() {
|
||||
anyhow::bail!("Failed to run `{}`", args.join(" "));
|
||||
.with_context(|| format!("failed to run {cmd:?}"))?;
|
||||
if status.code() != Some(0) {
|
||||
bail!("{cmd:?} failed: {status:?}")
|
||||
}
|
||||
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,172 @@
|
||||
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_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 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_ebpf {
|
||||
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