1 // build.rs
2 
3 use std::env;
4 use std::ffi;
5 use std::fs;
6 use std::fs::read_dir;
7 use std::path;
8 use std::path::Path;
9 use std::process;
10 
11 use nix::fcntl;
12 
13 
emit_rerun_directives_for_contents(dir: &Path)14 fn emit_rerun_directives_for_contents(dir: &Path) {
15     for result in read_dir(dir).unwrap() {
16         let file = result.unwrap();
17         println!("cargo:rerun-if-changed={}", file.path().display());
18     }
19 }
20 
21 #[cfg(feature = "bindgen")]
generate_bindings(src_dir: path::PathBuf)22 fn generate_bindings(src_dir: path::PathBuf) {
23     use std::collections::HashSet;
24 
25     #[derive(Debug)]
26     struct IgnoreMacros(HashSet<&'static str>);
27 
28     impl bindgen::callbacks::ParseCallbacks for IgnoreMacros {
29         fn will_parse_macro(&self, name: &str) -> bindgen::callbacks::MacroParsingBehavior {
30             if self.0.contains(name) {
31                 bindgen::callbacks::MacroParsingBehavior::Ignore
32             } else {
33                 bindgen::callbacks::MacroParsingBehavior::Default
34             }
35         }
36     }
37 
38     let ignored_macros = IgnoreMacros(
39         vec![
40             "BTF_KIND_FUNC",
41             "BTF_KIND_FUNC_PROTO",
42             "BTF_KIND_VAR",
43             "BTF_KIND_DATASEC",
44             "BTF_KIND_FLOAT",
45             "BTF_KIND_DECL_TAG",
46             "BTF_KIND_TYPE_TAG",
47             "BTF_KIND_ENUM64",
48         ]
49         .into_iter()
50         .collect(),
51     );
52 
53     #[cfg(feature = "bindgen-source")]
54     let out_dir = &src_dir.join("src");
55     #[cfg(not(feature = "bindgen-source"))]
56     let out_dir =
57         &path::PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR should always be set"));
58 
59     bindgen::Builder::default()
60         .derive_default(true)
61         .explicit_padding(true)
62         .default_enum_style(bindgen::EnumVariation::Consts)
63         .size_t_is_usize(false)
64         .prepend_enum_name(false)
65         .layout_tests(false)
66         .generate_comments(false)
67         .emit_builtins()
68         .allowlist_function("bpf_.+")
69         .allowlist_function("btf_.+")
70         .allowlist_function("libbpf_.+")
71         .allowlist_function("perf_.+")
72         .allowlist_function("ring_buffer_.+")
73         .allowlist_function("user_ring_buffer_.+")
74         .allowlist_function("vdprintf")
75         .allowlist_type("bpf_.+")
76         .allowlist_type("btf_.+")
77         .allowlist_type("xdp_.+")
78         .allowlist_type("perf_.+")
79         .allowlist_var("BPF_.+")
80         .allowlist_var("BTF_.+")
81         .allowlist_var("XDP_.+")
82         .allowlist_var("PERF_.+")
83         .parse_callbacks(Box::new(ignored_macros))
84         .header("bindings.h")
85         .clang_arg(format!("-I{}", src_dir.join("libbpf/include").display()))
86         .clang_arg(format!(
87             "-I{}",
88             src_dir.join("libbpf/include/uapi").display()
89         ))
90         .generate()
91         .expect("Unable to generate bindings")
92         .write_to_file(out_dir.join("bindings.rs"))
93         .expect("Couldn't write bindings");
94 }
95 
96 #[cfg(not(feature = "bindgen"))]
generate_bindings(_: path::PathBuf)97 fn generate_bindings(_: path::PathBuf) {}
98 
pkg_check(pkg: &str)99 fn pkg_check(pkg: &str) {
100     if process::Command::new(pkg)
101         .stdout(process::Stdio::null())
102         .stderr(process::Stdio::null())
103         .status()
104         .is_err()
105     {
106         panic!(
107             "{} is required to compile libbpf-sys with the selected set of features",
108             pkg
109         );
110     }
111 }
112 
main()113 fn main() {
114     let src_dir = path::PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
115 
116     generate_bindings(src_dir.clone());
117 
118     let vendored_libbpf = cfg!(feature = "vendored-libbpf");
119     let vendored_libelf = cfg!(feature = "vendored-libelf");
120     let vendored_zlib = cfg!(feature = "vendored-zlib");
121     println!("Using feature vendored-libbpf={}", vendored_libbpf);
122     println!("Using feature vendored-libelf={}", vendored_libelf);
123     println!("Using feature vendored-zlib={}", vendored_zlib);
124 
125     let static_libbpf = cfg!(feature = "static-libbpf");
126     let static_libelf = cfg!(feature = "static-libelf");
127     let static_zlib = cfg!(feature = "static-zlib");
128     println!("Using feature static-libbpf={}", static_libbpf);
129     println!("Using feature static-libelf={}", static_libelf);
130     println!("Using feature static-zlib={}", static_zlib);
131 
132     if cfg!(feature = "novendor") {
133         println!("cargo:warning=the `novendor` feature of `libbpf-sys` is deprecated; build without features instead");
134         println!(
135             "cargo:rustc-link-lib={}bpf",
136             if static_libbpf { "static=" } else { "" }
137         );
138         return;
139     }
140 
141     let out_dir = path::PathBuf::from(env::var_os("OUT_DIR").unwrap());
142 
143     // check for all necessary compilation tools
144     if vendored_libelf {
145         pkg_check("autoreconf");
146         pkg_check("autopoint");
147         pkg_check("flex");
148         pkg_check("bison");
149         pkg_check("gawk");
150     }
151 
152     let (compiler, mut cflags) = if vendored_libbpf || vendored_libelf || vendored_zlib {
153         pkg_check("make");
154         pkg_check("pkg-config");
155 
156         let compiler = cc::Build::new().try_get_compiler().expect(
157             "a C compiler is required to compile libbpf-sys using the vendored copy of libbpf",
158         );
159         let cflags = compiler.cflags_env();
160         (Some(compiler), cflags)
161     } else {
162         (None, ffi::OsString::new())
163     };
164 
165     if vendored_zlib {
166         make_zlib(compiler.as_ref().unwrap(), &src_dir, &out_dir);
167         cflags.push(&format!(" -I{}/zlib/", src_dir.display()));
168     }
169 
170     if vendored_libelf {
171         make_elfutils(compiler.as_ref().unwrap(), &src_dir, &out_dir);
172         cflags.push(&format!(" -I{}/elfutils/libelf/", src_dir.display()));
173     }
174 
175     if vendored_libbpf {
176         make_libbpf(compiler.as_ref().unwrap(), &cflags, &src_dir, &out_dir);
177     }
178 
179     println!(
180         "cargo:rustc-link-search=native={}",
181         out_dir.to_string_lossy()
182     );
183     println!(
184         "cargo:rustc-link-lib={}elf",
185         if static_libelf { "static=" } else { "" }
186     );
187     println!(
188         "cargo:rustc-link-lib={}z",
189         if static_zlib { "static=" } else { "" }
190     );
191     println!(
192         "cargo:rustc-link-lib={}bpf",
193         if static_libbpf { "static=" } else { "" }
194     );
195     println!("cargo:include={}/include", out_dir.to_string_lossy());
196 
197     println!("cargo:rerun-if-env-changed=LD_LIBRARY_PATH");
198     if let Ok(ld_path) = env::var("LD_LIBRARY_PATH") {
199         for path in ld_path.split(':') {
200             if !path.is_empty() {
201                 println!("cargo:rustc-link-search=native={}", path);
202             }
203         }
204     }
205 }
206 
make_zlib(compiler: &cc::Tool, src_dir: &path::Path, out_dir: &path::Path)207 fn make_zlib(compiler: &cc::Tool, src_dir: &path::Path, out_dir: &path::Path) {
208     let src_dir = src_dir.join("zlib");
209     // lock README such that if two crates are trying to compile
210     // this at the same time (eg libbpf-rs libbpf-cargo)
211     // they wont trample each other
212     let file = std::fs::File::open(src_dir.join("README")).unwrap();
213     let _lock = fcntl::Flock::lock(file, fcntl::FlockArg::LockExclusive).unwrap();
214 
215     let status = process::Command::new("./configure")
216         .arg("--static")
217         .arg("--prefix")
218         .arg(".")
219         .arg("--libdir")
220         .arg(out_dir)
221         .env("CC", compiler.path())
222         .env("CFLAGS", compiler.cflags_env())
223         .current_dir(&src_dir)
224         .status()
225         .expect("could not execute make");
226 
227     assert!(status.success(), "make failed");
228 
229     let status = process::Command::new("make")
230         .arg("install")
231         .arg("-j")
232         .arg(&format!("{}", num_cpus()))
233         .current_dir(&src_dir)
234         .status()
235         .expect("could not execute make");
236 
237     assert!(status.success(), "make failed");
238 
239     let status = process::Command::new("make")
240         .arg("distclean")
241         .current_dir(&src_dir)
242         .status()
243         .expect("could not execute make");
244 
245     assert!(status.success(), "make failed");
246     emit_rerun_directives_for_contents(&src_dir);
247 }
248 
make_elfutils(compiler: &cc::Tool, src_dir: &path::Path, out_dir: &path::Path)249 fn make_elfutils(compiler: &cc::Tool, src_dir: &path::Path, out_dir: &path::Path) {
250     // lock README such that if two crates are trying to compile
251     // this at the same time (eg libbpf-rs libbpf-cargo)
252     // they wont trample each other
253     let file = std::fs::File::open(src_dir.join("elfutils/README")).unwrap();
254     let _lock = fcntl::Flock::lock(file, fcntl::FlockArg::LockExclusive).unwrap();
255 
256     let flags = compiler
257         .cflags_env()
258         .into_string()
259         .expect("failed to get cflags");
260     let mut cflags: String = flags
261         .split_whitespace()
262         .filter_map(|arg| {
263             if arg != "-static" {
264                 // compilation fails with -static flag
265                 Some(format!(" {arg}"))
266             } else {
267                 None
268             }
269         })
270         .collect();
271 
272     #[cfg(target_arch = "aarch64")]
273     cflags.push_str(" -Wno-error=stringop-overflow");
274     cflags.push_str(&format!(" -I{}/zlib/", src_dir.display()));
275 
276     let status = process::Command::new("autoreconf")
277         .arg("--install")
278         .arg("--force")
279         .current_dir(&src_dir.join("elfutils"))
280         .status()
281         .expect("could not execute make");
282 
283     assert!(status.success(), "make failed");
284 
285     // location of libz.a
286     let out_lib = format!("-L{}", out_dir.display());
287     let status = process::Command::new("./configure")
288         .arg("--enable-maintainer-mode")
289         .arg("--disable-debuginfod")
290         .arg("--disable-libdebuginfod")
291         .arg("--without-zstd")
292         .arg("--prefix")
293         .arg(&src_dir.join("elfutils/prefix_dir"))
294         .arg("--libdir")
295         .arg(out_dir)
296         .env("CC", compiler.path())
297         .env("CXX", compiler.path())
298         .env("CFLAGS", &cflags)
299         .env("CXXFLAGS", &cflags)
300         .env("LDFLAGS", &out_lib)
301         .current_dir(&src_dir.join("elfutils"))
302         .status()
303         .expect("could not execute make");
304 
305     assert!(status.success(), "make failed");
306 
307     let status = process::Command::new("make")
308         .arg("install")
309         .arg("-j")
310         .arg(&format!("{}", num_cpus()))
311         .arg("BUILD_STATIC_ONLY=y")
312         .current_dir(&src_dir.join("elfutils"))
313         .status()
314         .expect("could not execute make");
315 
316     assert!(status.success(), "make failed");
317 
318     let status = process::Command::new("make")
319         .arg("distclean")
320         .current_dir(&src_dir.join("elfutils"))
321         .status()
322         .expect("could not execute make");
323 
324     assert!(status.success(), "make failed");
325     emit_rerun_directives_for_contents(&src_dir.join("elfutils").join("src"));
326 }
327 
make_libbpf( compiler: &cc::Tool, cflags: &ffi::OsStr, src_dir: &path::Path, out_dir: &path::Path, )328 fn make_libbpf(
329     compiler: &cc::Tool,
330     cflags: &ffi::OsStr,
331     src_dir: &path::Path,
332     out_dir: &path::Path,
333 ) {
334     let src_dir = src_dir.join("libbpf/src");
335     // create obj_dir if it doesn't exist
336     let obj_dir = path::PathBuf::from(&out_dir.join("obj").into_os_string());
337     let _ = fs::create_dir(&obj_dir);
338 
339     let status = process::Command::new("make")
340         .arg("install")
341         .arg("-j")
342         .arg(&format!("{}", num_cpus()))
343         .env("BUILD_STATIC_ONLY", "y")
344         .env("PREFIX", "/")
345         .env("LIBDIR", "")
346         .env("OBJDIR", &obj_dir)
347         .env("DESTDIR", out_dir)
348         .env("CC", compiler.path())
349         .env("CFLAGS", cflags)
350         .current_dir(&src_dir)
351         .status()
352         .expect("could not execute make");
353 
354     assert!(status.success(), "make failed");
355 
356     let status = process::Command::new("make")
357         .arg("clean")
358         .current_dir(&src_dir)
359         .status()
360         .expect("could not execute make");
361 
362     assert!(status.success(), "make failed");
363     emit_rerun_directives_for_contents(&src_dir);
364 }
365 
num_cpus() -> usize366 fn num_cpus() -> usize {
367     std::thread::available_parallelism().map_or(1, |count| count.get())
368 }
369