Skip to main content

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::env;
59use std::ffi::OsStr;
60use std::ffi::OsString;
61use std::path::Path;
62use std::path::PathBuf;
63
64use anyhow::anyhow;
65use anyhow::Context as _;
66use anyhow::Result;
67
68use tempfile::tempdir;
69use tempfile::TempDir;
70
71mod build;
72mod r#gen;
73mod make;
74mod metadata;
75pub mod util;
76
77#[cfg(test)]
78mod test;
79
80use build::BpfObjBuilder;
81
82
83/// `SkeletonBuilder` builds and generates a single skeleton.
84///
85/// This type is typically used from within a build scripts.
86///
87/// # Examples
88///
89/// ```no_run
90/// use libbpf_cargo::SkeletonBuilder;
91///
92/// SkeletonBuilder::new()
93///     .source("myobject.bpf.c")
94///     .clang("/opt/clang/clang")
95///     .build_and_generate("/output/path")
96///     .unwrap();
97/// ```
98#[derive(Debug)]
99pub struct SkeletonBuilder {
100    source: Option<PathBuf>,
101    obj: Option<PathBuf>,
102    clang: Option<PathBuf>,
103    clang_args: Vec<OsString>,
104    rustfmt: PathBuf,
105    dir: Option<TempDir>,
106    reference_obj: bool,
107    original_obj_name: Option<String>,
108}
109
110impl Default for SkeletonBuilder {
111    fn default() -> Self {
112        Self::new()
113    }
114}
115
116impl SkeletonBuilder {
117    /// Create a new [`SkeletonBuilder`].
118    pub fn new() -> Self {
119        Self {
120            source: None,
121            obj: None,
122            clang: None,
123            clang_args: Vec::new(),
124            rustfmt: "rustfmt".into(),
125            dir: None,
126            reference_obj: false,
127            original_obj_name: None,
128        }
129    }
130
131    /// Point the [`SkeletonBuilder`] to a source file for compilation
132    ///
133    /// Default is None
134    pub fn source<P: AsRef<Path>>(&mut self, source: P) -> &mut Self {
135        self.source = Some(source.as_ref().to_path_buf());
136        self
137    }
138
139    /// Point the [`SkeletonBuilder`] to an object file for generation
140    ///
141    /// Default is None
142    pub fn obj<P: AsRef<Path>>(&mut self, obj: P) -> &mut Self {
143        self.obj = Some(obj.as_ref().to_path_buf());
144        self
145    }
146
147    /// Specify which `clang` binary to use
148    ///
149    /// Default searches `$PATH` for `clang`
150    pub fn clang<P: AsRef<Path>>(&mut self, clang: P) -> &mut Self {
151        self.clang = Some(clang.as_ref().to_path_buf());
152        self
153    }
154
155    /// Pass additional arguments to `clang` when building BPF object file
156    ///
157    /// # Examples
158    ///
159    /// ```no_run
160    /// use libbpf_cargo::SkeletonBuilder;
161    ///
162    /// SkeletonBuilder::new()
163    ///     .source("myobject.bpf.c")
164    ///     .clang_args([
165    ///         "-DMACRO=value",
166    ///         "-I/some/include/dir",
167    ///     ])
168    ///     .build_and_generate("/output/path")
169    ///     .unwrap();
170    /// ```
171    pub fn clang_args<A, S>(&mut self, args: A) -> &mut Self
172    where
173        A: IntoIterator<Item = S>,
174        S: AsRef<OsStr>,
175    {
176        self.clang_args = args
177            .into_iter()
178            .map(|arg| arg.as_ref().to_os_string())
179            .collect();
180        self
181    }
182
183    /// Specify which `rustfmt` binary to use
184    ///
185    /// Default searches `$PATH` for `rustfmt`
186    pub fn rustfmt<P: AsRef<Path>>(&mut self, rustfmt: P) -> &mut Self {
187        self.rustfmt = rustfmt.as_ref().to_path_buf();
188        self
189    }
190
191    /// Reference the object file via `include_bytes!` instead of inlining
192    /// the raw bytes in the generated skeleton.
193    ///
194    /// When enabled, the generated skeleton uses `include_bytes!` to
195    /// reference the compiled BPF object file by path. This dramatically
196    /// reduces memory usage and build times for large object files, but
197    /// means the skeleton is no longer self-contained — the object file
198    /// must be present at its original path when rustc compiles the
199    /// skeleton.
200    ///
201    /// When no explicit [`obj`](Self::obj) path is set, the object file
202    /// is placed in `OUT_DIR` so that it persists for rustc. If `OUT_DIR`
203    /// is not set (e.g. outside a build script), an explicit `obj` path
204    /// must be provided.
205    ///
206    /// Default is `false` (inline bytes, self-contained skeleton).
207    pub fn reference_obj(&mut self, reference: bool) -> &mut Self {
208        self.reference_obj = reference;
209        self
210    }
211
212    /// Build BPF programs and generate the skeleton at path `output`
213    ///
214    /// # Notes
215    /// When used from a build script, you may be interested in
216    /// surfacing compiler warnings as part of the build. Please refer
217    /// to [`util::CargoWarningFormatter`] and its documentation for how
218    /// to go about that.
219    pub fn build_and_generate<P: AsRef<Path>>(&mut self, output: P) -> Result<()> {
220        self.build()?;
221        self.generate(output)?;
222
223        Ok(())
224    }
225
226    /// Build BPF programs without generating a skeleton.
227    ///
228    /// [`SkeletonBuilder::source`] must be set for this to succeed.
229    ///
230    /// # Notes
231    /// When used from a build script, you may be interested in
232    /// surfacing compiler warnings as part of the build. Please refer
233    /// to [`util::CargoWarningFormatter`] and its documentation for how
234    /// to go about that.
235    pub fn build(&mut self) -> Result<()> {
236        let source = self
237            .source
238            .as_ref()
239            .ok_or_else(|| anyhow!("No source file provided"))?;
240
241        let filename = source
242            .file_name()
243            .ok_or_else(|| anyhow!("Missing file name"))?
244            .to_str()
245            .ok_or_else(|| anyhow!("Invalid unicode in file name"))?;
246
247        if !filename.ends_with(".bpf.c") {
248            return Err(anyhow!(
249                "Source `{}` does not have .bpf.c suffix",
250                source.display()
251            ));
252        }
253
254        if self.obj.is_none() {
255            let name = filename.split('.').next().unwrap();
256            if self.reference_obj {
257                // Place in OUT_DIR so the .o file persists after the build
258                // script exits and is available when rustc processes
259                // include_bytes! in the generated skeleton.
260                let out_dir = env::var("OUT_DIR")
261                    .context("reference_obj requires OUT_DIR or an explicit obj path")?;
262                // Hash the source path to avoid collisions when
263                // multiple sources share the same name prefix.
264                let hash = {
265                    use std::collections::hash_map::DefaultHasher;
266                    use std::hash::Hash;
267                    use std::hash::Hasher;
268                    let mut h = DefaultHasher::new();
269                    source.hash(&mut h);
270                    h.finish()
271                };
272                let objfile = PathBuf::from(out_dir).join(format!("{name}_{hash:016x}.o"));
273                self.obj = Some(objfile);
274                // Save and use the original name for generating skel so that the identifiers in
275                // generated skel do not contain hashes that cause them to be unreliable to
276                // reference in rust code.
277                self.original_obj_name = Some(name.to_owned());
278            } else {
279                let dir = tempdir().context("failed to create temporary directory")?;
280                let objfile = dir.path().join(format!("{name}.o"));
281                self.obj = Some(objfile);
282                // Hold onto tempdir so that it doesn't get deleted early
283                self.dir = Some(dir);
284            }
285        }
286
287        let mut builder = BpfObjBuilder::default();
288        if let Some(clang) = &self.clang {
289            builder.compiler(clang);
290        }
291        builder.compiler_args(&self.clang_args);
292
293        // SANITY: Unwrap is safe here since we guarantee that obj.is_some() above.
294        builder
295            .build(source, self.obj.as_ref().unwrap())
296            .with_context(|| format!("failed to build `{}`", source.display()))
297    }
298
299    /// Generate a skeleton at path `output` without building BPF programs.
300    ///
301    /// [`SkeletonBuilder::obj`] must be set for this to succeed.
302    pub fn generate<P: AsRef<Path>>(&mut self, output: P) -> Result<()> {
303        let objfile = self.obj.as_ref().ok_or_else(|| anyhow!("No object file"))?;
304
305        r#gen::gen_single(
306            objfile,
307            r#gen::OutputDest::File(output.as_ref()),
308            Some(&self.rustfmt),
309            self.reference_obj,
310            self.original_obj_name.as_deref(),
311        )
312        .with_context(|| format!("failed to generate `{}`", objfile.display()))?;
313
314        Ok(())
315    }
316}
317
318
319/// Implementation details shared with the binary.
320///
321/// NOT PART OF PUBLIC API SURFACE!
322#[doc(hidden)]
323pub mod __private {
324    pub mod build {
325        pub use crate::build::build_project;
326    }
327    pub mod r#gen {
328        pub use crate::r#gen::generate;
329    }
330    pub mod make {
331        pub use crate::make::make;
332    }
333}