1 //! A program which generates a linux-headers installation and runs bindgen
2 //! over the headers, for each supported architecture.
3
4 use bindgen::{builder, EnumVariation};
5 use std::collections::HashSet;
6 use std::fs::File;
7 use std::io::{BufRead, BufReader, Read, Write};
8 use std::path::Path;
9 use std::process::Command;
10 use std::{env, fs};
11
12 #[allow(unused_doc_comments)]
13 const LINUX_VERSION: &str = "v5.17";
14
15 /// Some commonly used features.
16 const DEFAULT_FEATURES: &str = "\"general\", \"errno\"";
17
mainnull18 fn main() {
19 let mut args = env::args();
20 let _exe = args.next().unwrap();
21 let cmd = args.next();
22
23 // This is the main invocation path.
24 assert!(cmd.is_none());
25 assert!(args.next().is_none());
26
27 git_init();
28
29 let out = tempdir::TempDir::new("linux-raw-sys").unwrap();
30 let out_dir = out.path();
31 let linux_headers = out_dir.join("linux-headers");
32 let linux_include = linux_headers.join("include");
33
34 // Clean up any modules from previous builds.
35 for entry in fs::read_dir("../src").unwrap() {
36 let entry = entry.unwrap();
37 assert!(!entry.path().to_str().unwrap().ends_with("."));
38 if entry.file_type().unwrap().is_dir() {
39 fs::remove_dir_all(entry.path()).ok();
40 }
41 }
42
43 // Edit ../src/lib.rs
44 let mut src_lib_rs_in = File::open("../src/lib.rs").unwrap();
45 let mut src_lib_rs_contents = String::new();
46 src_lib_rs_in
47 .read_to_string(&mut src_lib_rs_contents)
48 .unwrap();
49 let edit_at = src_lib_rs_contents
50 .find("// The rest of this file is auto-generated!\n")
51 .unwrap();
52 src_lib_rs_contents = src_lib_rs_contents[..edit_at].to_owned();
53
54 let mut src_lib_rs = File::create("../src/lib.rs").unwrap();
55 src_lib_rs
56 .write_all(src_lib_rs_contents.as_bytes())
57 .unwrap();
58 src_lib_rs
59 .write_all("// The rest of this file is auto-generated!\n".as_bytes())
60 .unwrap();
61
62 // Edit ../Cargo.toml
63 let mut cargo_toml_in = File::open("../Cargo.toml").unwrap();
64 let mut cargo_toml_contents = String::new();
65 cargo_toml_in
66 .read_to_string(&mut cargo_toml_contents)
67 .unwrap();
68 let edit_at = cargo_toml_contents
69 .find("# The rest of this file is auto-generated!\n")
70 .unwrap();
71 cargo_toml_contents = cargo_toml_contents[..edit_at].to_owned();
72
73 // Generate Cargo.toml
74 let mut cargo_toml = File::create("../Cargo.toml").unwrap();
75 cargo_toml
76 .write_all(cargo_toml_contents.as_bytes())
77 .unwrap();
78 cargo_toml
79 .write_all("# The rest of this file is auto-generated!\n".as_bytes())
80 .unwrap();
81 writeln!(cargo_toml, "[features]").unwrap();
82
83 let mut features: HashSet<String> = HashSet::new();
84
85 let linux_version = LINUX_VERSION;
86 // Checkout a specific version of Linux.
87 git_checkout(linux_version);
88
89 let mut linux_archs = fs::read_dir(&format!("linux/arch"))
90 .unwrap()
91 .map(|entry| entry.unwrap())
92 .collect::<Vec<_>>();
93 // Sort archs list as filesystem iteration order is non-deterministic
94 linux_archs.sort_by_key(|entry| entry.file_name());
95 for linux_arch_entry in linux_archs {
96 if !linux_arch_entry.file_type().unwrap().is_dir() {
97 continue;
98 }
99 let linux_arch = linux_arch_entry.file_name().to_str().unwrap().to_owned();
100
101 let rust_arches = rust_arches(&linux_arch);
102 if rust_arches.is_empty() {
103 continue;
104 }
105
106 fs::create_dir_all(&linux_headers).unwrap();
107
108 let mut headers_made = false;
109 for rust_arch in rust_arches {
110 if !headers_made {
111 make_headers_install(&linux_arch, &linux_headers);
112 headers_made = true;
113 }
114
115 eprintln!(
116 "Generating all bindings for Linux {} architecture {}",
117 linux_version, rust_arch
118 );
119
120 let src_arch = format!("../src/{}", rust_arch);
121
122 fs::create_dir_all(&src_arch).unwrap();
123
124 let mut modules = fs::read_dir("modules")
125 .unwrap()
126 .map(|entry| entry.unwrap())
127 .collect::<Vec<_>>();
128 // Sort module list as filesystem iteration order is non-deterministic
129 modules.sort_by_key(|entry| entry.file_name());
130 for mod_entry in modules {
131 let header_name = mod_entry.path();
132 let mod_name = header_name.file_stem().unwrap().to_str().unwrap();
133 let mod_rs = format!("{}/{}.rs", src_arch, mod_name);
134
135 writeln!(src_lib_rs, "#[cfg(feature = \"{}\")]", mod_name).unwrap();
136 if *rust_arch == "x32" {
137 writeln!(src_lib_rs, "#[cfg(all(target_arch = \"x86_64\", target_pointer_width = \"32\"))]").unwrap();
138 } else if *rust_arch == "x86_64" {
139 writeln!(src_lib_rs, "#[cfg(all(target_arch = \"x86_64\", target_pointer_width = \"64\"))]").unwrap();
140 } else {
141 writeln!(src_lib_rs, "#[cfg(target_arch = \"{}\")]", rust_arch).unwrap();
142 }
143 writeln!(src_lib_rs, "#[path = \"{}/{}.rs\"]", rust_arch, mod_name).unwrap();
144 writeln!(src_lib_rs, "pub mod {};", mod_name).unwrap();
145
146 run_bindgen(
147 linux_include.to_str().unwrap(),
148 header_name.to_str().unwrap(),
149 &mod_rs,
150 mod_name,
151 rust_arch,
152 linux_version,
153 );
154
155 // Collect all unique feature names across all architectures.
156 if features.insert(mod_name.to_owned()) {
157 writeln!(cargo_toml, "{} = []", mod_name).unwrap();
158 }
159 }
160 }
161
162 fs::remove_dir_all(&linux_headers).unwrap();
163 }
164
165 writeln!(cargo_toml, "default = [\"std\", {}]", DEFAULT_FEATURES).unwrap();
166 writeln!(cargo_toml, "std = []").unwrap();
167 writeln!(cargo_toml, "no_std = []").unwrap();
168 writeln!(
169 cargo_toml,
170 "rustc-dep-of-std = [\"core\", \"compiler_builtins\", \"no_std\"]"
171 )
172 .unwrap();
173
174 eprintln!("All bindings generated!");
175 }
176
git_initnull177 fn git_init() {
178 // Clone the linux kernel source repo if necessary. Ignore exit code as it will
179 // be non-zero in case it was already cloned.
180 //
181 // Use a treeless partial clone to save disk space and clone time.
182 // See <https://github.blog/2020-12-21-get-up-to-speed-with-partial-clone-and-shallow-clone/>
183 // for more info on partial clones.
184 //
185 // Note: this is not using the official repo
186 // <git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git> but
187 // the github fork as the server of the official repo doesn't recognize
188 // filtering.
189 if !Path::new("linux/.git").exists() {
190 assert!(Command::new("git")
191 .arg("clone")
192 .arg("https://github.com/torvalds/linux.git")
193 .arg("--filter=tree:0")
194 .arg("--no-checkout")
195 .status()
196 .unwrap()
197 .success());
198 }
199
200 // Setup sparse checkout. This greatly reduces the amount of objects necessary
201 // to checkout the tree.
202 assert!(Command::new("git")
203 .arg("sparse-checkout")
204 .arg("init")
205 .current_dir("linux")
206 .status()
207 .unwrap()
208 .success());
209
210 fs::write(
211 "linux/.git/info/sparse-checkout",
212 "/*
213 !/*/
214 /include/
215 /arch/
216 /scripts/
217 /tools/",
218 )
219 .unwrap();
220 }
221
222 fn git_checkout(rev: &str) {
223 // Delete any generated files from previous versions.
224 assert!(Command::new("git")
225 .arg("clean")
226 .arg("-f")
227 .arg("-d")
228 .current_dir("linux")
229 .status()
230 .unwrap()
231 .success());
232
233 // Check out the given revision.
234 assert!(Command::new("git")
235 .arg("checkout")
236 .arg(rev)
237 .arg("-f")
238 .current_dir("linux")
239 .status()
240 .unwrap()
241 .success());
242
243 // Delete any untracked generated files from previous versions.
244 assert!(Command::new("git")
245 .arg("clean")
246 .arg("-f")
247 .arg("-d")
248 .current_dir("linux")
249 .status()
250 .unwrap()
251 .success());
252 }
253
254 fn make_headers_install(linux_arch: &str, linux_headers: &Path) {
255 assert!(Command::new("make")
256 .arg(format!("headers_install"))
257 .arg(format!("ARCH={}", linux_arch))
258 .arg(format!(
259 "INSTALL_HDR_PATH={}",
260 fs::canonicalize(&linux_headers).unwrap().to_str().unwrap()
261 ))
262 .current_dir("linux")
263 .status()
264 .unwrap()
265 .success());
266 }
267
268 fn rust_arches(linux_arch: &str) -> &[&str] {
269 match linux_arch {
270 "arm" => &["arm"],
271 "arm64" => &["aarch64"],
272 "avr32" => &["avr"],
273 // hexagon gets build errors; disable it for now
274 "hexagon" => &[],
275 "mips" => &["mips", "mips64"],
276 "powerpc" => &["powerpc", "powerpc64"],
277 "riscv" => &["riscv32", "riscv64"],
278 "s390" => &["s390x"],
279 "sparc" => &["sparc", "sparc64"],
280 "x86" => &["x86", "x86_64", "x32"],
281 "alpha" | "cris" | "h8300" | "m68k" | "microblaze" | "mn10300" | "score" | "blackfin"
282 | "frv" | "ia64" | "m32r" | "m68knommu" | "parisc" | "sh" | "um" | "xtensa"
283 | "unicore32" | "c6x" | "nios2" | "openrisc" | "csky" | "arc" | "nds32" | "metag"
284 | "tile" => &[],
285 _ => panic!("unrecognized arch: {}", linux_arch),
286 }
287 }
288
289 fn run_bindgen(
290 linux_include: &str,
291 header_name: &str,
292 mod_rs: &str,
293 mod_name: &str,
294 rust_arch: &str,
295 linux_version: &str,
296 ) {
297 let clang_target = compute_clang_target(rust_arch);
298
299 eprintln!(
300 "Generating bindings for {} on Linux {} architecture {}",
301 mod_name, linux_version, rust_arch
302 );
303
304 let mut builder = builder()
305 // The generated bindings are quite large, so use a few simple options
306 // to keep the file sizes down.
307 .rustfmt_configuration_file(Some(Path::new("bindgen-rustfmt.toml").to_owned()))
308 .layout_tests(false)
309 .generate_comments(false)
310 .default_enum_style(EnumVariation::Rust {
311 non_exhaustive: true,
312 })
313 .array_pointers_in_arguments(true)
314 .derive_debug(true)
315 .clang_arg(&format!("--target={}", clang_target))
316 .clang_arg("-DBITS_PER_LONG=(__SIZEOF_LONG__*__CHAR_BIT__)")
317 .clang_arg("-nostdinc")
318 .clang_arg("-I")
319 .clang_arg(linux_include)
320 .clang_arg("-I")
321 .clang_arg("include")
322 .blocklist_item("NULL");
323
324 // Avoid duplicating ioctl names in the `general` module.
325 if mod_name == "general" {
326 for ioctl in BufReader::new(File::open("ioctl/generated.txt").unwrap()).lines() {
327 builder = builder.blocklist_item(ioctl.unwrap());
328 }
329 }
330
331 let bindings = builder
332 .use_core()
333 .ctypes_prefix("crate::ctypes")
334 .header(header_name)
335 .generate()
336 .expect(&format!("generate bindings for {}", mod_name));
337 bindings
338 .write_to_file(mod_rs)
339 .expect(&format!("write_to_file for {}", mod_name));
340 }
341
342 fn compute_clang_target(rust_arch: &str) -> String {
343 if rust_arch == "x86" {
344 format!("i686-unknown-linux")
345 } else if rust_arch == "x32" {
346 format!("x86_64-unknown-linux-gnux32")
347 } else {
348 format!("{}-unknown-linux", rust_arch)
349 }
350 }
351