libbpf_cargo/
lib.rs

1//! libbpf-cargo helps you develop and build eBPF (BPF) programs with standard rust tooling.
2//!
3//! libbpf-cargo supports two interfaces:
4//! * [`SkeletonBuilder`] API, for use with [build scripts](https://doc.rust-lang.org/cargo/reference/build-scripts.html)
5//! * `cargo-libbpf` cargo subcommand, for use with `cargo`
6//!
7//! The **build script interface is recommended** over the cargo subcommand interface because:
8//! * once set up, you cannot forget to update the generated skeletons if your source changes
9//! * build scripts are standard practice for projects that include codegen
10//! * newcomers to your project can `cargo build` and it will "just work"
11//!
12//! The following sections in this document describe the `cargo-libbpf` plugin. See the API
13//! reference for documentation on the build script interface.
14//!
15//! # Configuration
16//!
17//! cargo-libbpf consumes the following Cargo.toml configuration options:
18//!
19//! ```text
20//! [package.metadata.libbpf]
21//! prog_dir = "src/other_bpf_dir"  # default: <manifest_directory>/src/bpf
22//! target_dir = "other_target_dir" # default: <target_dir>/bpf
23//! ```
24//!
25//! * `prog_dir`: path relative to package Cargo.toml to search for bpf progs
26//! * `target_dir`: path relative to workspace target directory to place compiled bpf progs
27//!
28//! # Subcommands
29//!
30//! ## build
31//!
32//! `cargo libbpf build` compiles `<NAME>.bpf.c` C files into corresponding `<NAME>.bpf.o` ELF
33//! object files. Each object file may contain one or more BPF programs, maps, and associated
34//! metadata. The object file may then be handed over to `libbpf-rs` for loading and interaction.
35//!
36//! cargo-libbpf-build enforces a few conventions:
37//!
38//! * source file names must be in the `<NAME>.bpf.c` format
39//! * object file names will be generated in `<NAME>.bpf.o` format
40//! * there may not be any two identical `<NAME>.bpf.c` file names in any two projects in a cargo
41//!   workspace
42//!
43//! ## gen
44//!
45//! `cargo libbpf gen` generates a skeleton module for each BPF object file in the project.  Each
46//! `<NAME>.bpf.o` object file will have its own module. One `mod.rs` file is also generated. All
47//! output files are placed into `package.metadata.libbpf.prog_dir`.
48//!
49//! Be careful to run cargo-libbpf-build before running cargo-libbpf-gen. cargo-libbpf-gen reads
50//! object files from `package.metadata.libbpf.target_dir`.
51//!
52//! ## make
53//!
54//! `cargo libbpf make` sequentially runs cargo-libbpf-build, cargo-libbpf-gen, and `cargo
55//! build`. This is a convenience command so you don't forget any steps. Alternatively, you could
56//! write a Makefile for your project.
57
58use std::ffi::OsStr;
59use std::ffi::OsString;
60use std::path::Path;
61use std::path::PathBuf;
62
63use anyhow::anyhow;
64use anyhow::Context as _;
65use anyhow::Result;
66
67use tempfile::tempdir;
68use tempfile::TempDir;
69
70mod build;
71mod r#gen;
72mod make;
73mod metadata;
74pub mod util;
75
76#[cfg(test)]
77mod test;
78
79use build::BpfObjBuilder;
80
81
82/// `SkeletonBuilder` builds and generates a single skeleton.
83///
84/// This type is typically used from within a build scripts.
85///
86/// # Examples
87///
88/// ```no_run
89/// use libbpf_cargo::SkeletonBuilder;
90///
91/// SkeletonBuilder::new()
92///     .source("myobject.bpf.c")
93///     .clang("/opt/clang/clang")
94///     .build_and_generate("/output/path")
95///     .unwrap();
96/// ```
97#[derive(Debug)]
98pub struct SkeletonBuilder {
99    source: Option<PathBuf>,
100    obj: Option<PathBuf>,
101    clang: Option<PathBuf>,
102    clang_args: Vec<OsString>,
103    rustfmt: PathBuf,
104    dir: Option<TempDir>,
105}
106
107impl Default for SkeletonBuilder {
108    fn default() -> Self {
109        Self::new()
110    }
111}
112
113impl SkeletonBuilder {
114    /// Create a new [`SkeletonBuilder`].
115    pub fn new() -> Self {
116        SkeletonBuilder {
117            source: None,
118            obj: None,
119            clang: None,
120            clang_args: Vec::new(),
121            rustfmt: "rustfmt".into(),
122            dir: None,
123        }
124    }
125
126    /// Point the [`SkeletonBuilder`] to a source file for compilation
127    ///
128    /// Default is None
129    pub fn source<P: AsRef<Path>>(&mut self, source: P) -> &mut SkeletonBuilder {
130        self.source = Some(source.as_ref().to_path_buf());
131        self
132    }
133
134    /// Point the [`SkeletonBuilder`] to an object file for generation
135    ///
136    /// Default is None
137    pub fn obj<P: AsRef<Path>>(&mut self, obj: P) -> &mut SkeletonBuilder {
138        self.obj = Some(obj.as_ref().to_path_buf());
139        self
140    }
141
142    /// Specify which `clang` binary to use
143    ///
144    /// Default searches `$PATH` for `clang`
145    pub fn clang<P: AsRef<Path>>(&mut self, clang: P) -> &mut SkeletonBuilder {
146        self.clang = Some(clang.as_ref().to_path_buf());
147        self
148    }
149
150    /// Pass additional arguments to `clang` when building BPF object file
151    ///
152    /// # Examples
153    ///
154    /// ```no_run
155    /// use libbpf_cargo::SkeletonBuilder;
156    ///
157    /// SkeletonBuilder::new()
158    ///     .source("myobject.bpf.c")
159    ///     .clang_args([
160    ///         "-DMACRO=value",
161    ///         "-I/some/include/dir",
162    ///     ])
163    ///     .build_and_generate("/output/path")
164    ///     .unwrap();
165    /// ```
166    pub fn clang_args<A, S>(&mut self, args: A) -> &mut SkeletonBuilder
167    where
168        A: IntoIterator<Item = S>,
169        S: AsRef<OsStr>,
170    {
171        self.clang_args = args
172            .into_iter()
173            .map(|arg| arg.as_ref().to_os_string())
174            .collect();
175        self
176    }
177
178    /// Specify which `rustfmt` binary to use
179    ///
180    /// Default searches `$PATH` for `rustfmt`
181    pub fn rustfmt<P: AsRef<Path>>(&mut self, rustfmt: P) -> &mut SkeletonBuilder {
182        self.rustfmt = rustfmt.as_ref().to_path_buf();
183        self
184    }
185
186    /// Build BPF programs and generate the skeleton at path `output`
187    ///
188    /// # Notes
189    /// When used from a build script, you may be interested in
190    /// surfacing compiler warnings as part of the build. Please refer
191    /// to [`util::CargoWarningFormatter`] and its documentation for how
192    /// to go about that.
193    pub fn build_and_generate<P: AsRef<Path>>(&mut self, output: P) -> Result<()> {
194        self.build()?;
195        self.generate(output)?;
196
197        Ok(())
198    }
199
200    /// Build BPF programs without generating a skeleton.
201    ///
202    /// [`SkeletonBuilder::source`] must be set for this to succeed.
203    ///
204    /// # Notes
205    /// When used from a build script, you may be interested in
206    /// surfacing compiler warnings as part of the build. Please refer
207    /// to [`util::CargoWarningFormatter`] and its documentation for how
208    /// to go about that.
209    pub fn build(&mut self) -> Result<()> {
210        let source = self
211            .source
212            .as_ref()
213            .ok_or_else(|| anyhow!("No source file provided"))?;
214
215        let filename = source
216            .file_name()
217            .ok_or_else(|| anyhow!("Missing file name"))?
218            .to_str()
219            .ok_or_else(|| anyhow!("Invalid unicode in file name"))?;
220
221        if !filename.ends_with(".bpf.c") {
222            return Err(anyhow!(
223                "Source `{}` does not have .bpf.c suffix",
224                source.display()
225            ));
226        }
227
228        if self.obj.is_none() {
229            let name = filename.split('.').next().unwrap();
230            let dir = tempdir().context("failed to create temporary directory")?;
231            let objfile = dir.path().join(format!("{name}.o"));
232            self.obj = Some(objfile);
233            // Hold onto tempdir so that it doesn't get deleted early
234            self.dir = Some(dir);
235        }
236
237        let mut builder = BpfObjBuilder::default();
238        if let Some(clang) = &self.clang {
239            builder.compiler(clang);
240        }
241        builder.compiler_args(&self.clang_args);
242
243        // SANITY: Unwrap is safe here since we guarantee that obj.is_some() above.
244        builder
245            .build(source, self.obj.as_ref().unwrap())
246            .with_context(|| format!("failed to build `{}`", source.display()))
247    }
248
249    /// Generate a skeleton at path `output` without building BPF programs.
250    ///
251    /// [`SkeletonBuilder::obj`] must be set for this to succeed.
252    pub fn generate<P: AsRef<Path>>(&mut self, output: P) -> Result<()> {
253        let objfile = self.obj.as_ref().ok_or_else(|| anyhow!("No object file"))?;
254
255        r#gen::gen_single(
256            objfile,
257            r#gen::OutputDest::File(output.as_ref()),
258            Some(&self.rustfmt),
259        )
260        .with_context(|| format!("failed to generate `{}`", objfile.display()))?;
261
262        Ok(())
263    }
264}
265
266
267/// Implementation details shared with the binary.
268///
269/// NOT PART OF PUBLIC API SURFACE!
270#[doc(hidden)]
271pub mod __private {
272    pub mod build {
273        pub use crate::build::build_project;
274    }
275    pub mod r#gen {
276        pub use crate::r#gen::generate;
277    }
278    pub mod make {
279        pub use crate::make::make;
280    }
281}