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
58#![allow(clippy::let_unit_value)]
59#![warn(
60    elided_lifetimes_in_paths,
61    single_use_lifetimes,
62    clippy::absolute_paths,
63    clippy::wildcard_imports
64)]
65#![deny(unsafe_op_in_unsafe_fn)]
66
67use std::ffi::OsStr;
68use std::ffi::OsString;
69use std::path::Path;
70use std::path::PathBuf;
71
72use anyhow::anyhow;
73use anyhow::Context as _;
74use anyhow::Result;
75
76use tempfile::tempdir;
77use tempfile::TempDir;
78
79// libbpf-cargo binary is the primary consumer of the following modules. As such,
80// we do not use all the symbols. Silence any unused code warnings.
81#[allow(dead_code)]
82mod build;
83#[allow(dead_code)]
84mod gen;
85#[allow(dead_code)]
86mod make;
87#[allow(dead_code)]
88mod metadata;
89
90#[cfg(test)]
91mod test;
92
93/// `SkeletonBuilder` builds and generates a single skeleton.
94///
95/// This interface is meant to be used in build scripts.
96///
97/// # Examples
98///
99/// ```no_run
100/// use libbpf_cargo::SkeletonBuilder;
101///
102/// SkeletonBuilder::new()
103///     .source("myobject.bpf.c")
104///     .debug(true)
105///     .clang("/opt/clang/clang")
106///     .build_and_generate("/output/path")
107///     .unwrap();
108/// ```
109pub struct SkeletonBuilder {
110    debug: bool,
111    source: Option<PathBuf>,
112    obj: Option<PathBuf>,
113    clang: Option<PathBuf>,
114    clang_args: Vec<OsString>,
115    skip_clang_version_check: bool,
116    rustfmt: PathBuf,
117    dir: Option<TempDir>,
118}
119
120impl Default for SkeletonBuilder {
121    fn default() -> Self {
122        Self::new()
123    }
124}
125
126impl SkeletonBuilder {
127    pub fn new() -> Self {
128        SkeletonBuilder {
129            debug: false,
130            source: None,
131            obj: None,
132            clang: None,
133            clang_args: Vec::new(),
134            skip_clang_version_check: false,
135            rustfmt: "rustfmt".into(),
136            dir: None,
137        }
138    }
139
140    /// Point the [`SkeletonBuilder`] to a source file for compilation
141    ///
142    /// Default is None
143    pub fn source<P: AsRef<Path>>(&mut self, source: P) -> &mut SkeletonBuilder {
144        self.source = Some(source.as_ref().to_path_buf());
145        self
146    }
147
148    /// Point the [`SkeletonBuilder`] to an object file for generation
149    ///
150    /// Default is None
151    pub fn obj<P: AsRef<Path>>(&mut self, obj: P) -> &mut SkeletonBuilder {
152        self.obj = Some(obj.as_ref().to_path_buf());
153        self
154    }
155
156    /// Turn debug output on or off
157    ///
158    /// Default is off
159    pub fn debug(&mut self, debug: bool) -> &mut SkeletonBuilder {
160        self.debug = debug;
161        self
162    }
163
164    /// Specify which `clang` binary to use
165    ///
166    /// Default searches `$PATH` for `clang`
167    pub fn clang<P: AsRef<Path>>(&mut self, clang: P) -> &mut SkeletonBuilder {
168        self.clang = Some(clang.as_ref().to_path_buf());
169        self
170    }
171
172    /// Pass additional arguments to `clang` when building BPF object file
173    ///
174    /// # Examples
175    ///
176    /// ```no_run
177    /// use libbpf_cargo::SkeletonBuilder;
178    ///
179    /// SkeletonBuilder::new()
180    ///     .source("myobject.bpf.c")
181    ///     .clang_args([
182    ///         "-DMACRO=value",
183    ///         "-I/some/include/dir",
184    ///     ])
185    ///     .build_and_generate("/output/path")
186    ///     .unwrap();
187    /// ```
188    pub fn clang_args<A, S>(&mut self, args: A) -> &mut SkeletonBuilder
189    where
190        A: IntoIterator<Item = S>,
191        S: AsRef<OsStr>,
192    {
193        self.clang_args = args
194            .into_iter()
195            .map(|arg| arg.as_ref().to_os_string())
196            .collect();
197        self
198    }
199
200    /// Specify whether or not to skip clang version check
201    ///
202    /// Default is `false`
203    pub fn skip_clang_version_check(&mut self, skip: bool) -> &mut SkeletonBuilder {
204        self.skip_clang_version_check = skip;
205        self
206    }
207
208    /// Specify which `rustfmt` binary to use
209    ///
210    /// Default searches `$PATH` for `rustfmt`
211    pub fn rustfmt<P: AsRef<Path>>(&mut self, rustfmt: P) -> &mut SkeletonBuilder {
212        self.rustfmt = rustfmt.as_ref().to_path_buf();
213        self
214    }
215
216    /// Build BPF programs and generate the skeleton at path `output`
217    pub fn build_and_generate<P: AsRef<Path>>(&mut self, output: P) -> Result<()> {
218        self.build()?;
219        self.generate(output)?;
220
221        Ok(())
222    }
223
224    // Build BPF programs without generating a skeleton.
225    //
226    // [`SkeletonBuilder::source`] must be set for this to succeed.
227    pub fn build(&mut self) -> Result<()> {
228        let source = self
229            .source
230            .as_ref()
231            .ok_or_else(|| anyhow!("No source file provided"))?;
232
233        let filename = source
234            .file_name()
235            .ok_or_else(|| anyhow!("Missing file name"))?
236            .to_str()
237            .ok_or_else(|| anyhow!("Invalid unicode in file name"))?;
238
239        if !filename.ends_with(".bpf.c") {
240            return Err(anyhow!(
241                "Source `{}` does not have .bpf.c suffix",
242                source.display()
243            ));
244        }
245
246        if self.obj.is_none() {
247            let name = filename.split('.').next().unwrap();
248            let dir = tempdir().context("failed to create temporary directory")?;
249            let objfile = dir.path().join(format!("{name}.o"));
250            self.obj = Some(objfile);
251            // Hold onto tempdir so that it doesn't get deleted early
252            self.dir = Some(dir);
253        }
254
255        build::build_single(
256            self.debug,
257            source,
258            // Unwrap is safe here since we guarantee that obj.is_some() above
259            self.obj.as_ref().unwrap(),
260            self.clang.as_ref(),
261            self.skip_clang_version_check,
262            self.clang_args.clone(),
263        )
264        .with_context(|| format!("failed to build `{}`", source.display()))?;
265
266        Ok(())
267    }
268
269    // Generate a skeleton at path `output` without building BPF programs.
270    //
271    // [`SkeletonBuilder::obj`] must be set for this to succeed.
272    pub fn generate<P: AsRef<Path>>(&mut self, output: P) -> Result<()> {
273        let objfile = self.obj.as_ref().ok_or_else(|| anyhow!("No object file"))?;
274
275        gen::gen_single(
276            self.debug,
277            objfile,
278            gen::OutputDest::File(output.as_ref()),
279            Some(&self.rustfmt),
280        )
281        .with_context(|| format!("failed to generate `{}`", objfile.display()))?;
282
283        Ok(())
284    }
285}