1//! A program which generates a linux-headers installation and runs bindgen
2//! over the headers, for each supported architecture.
3
4use bindgen::{builder, EnumVariation};
5use std::collections::HashSet;
6use std::fs::File;
7use std::io::{BufRead, BufReader, Read, Write};
8use std::path::Path;
9use std::process::Command;
10use std::{env, fs};
11
12#[allow(unused_doc_comments)]
13const LINUX_VERSION: &str = "v5.17";
14
15/// Some commonly used features.
16const DEFAULT_FEATURES: &str = "\"general\", \"errno\"";
17
18fn 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
177fn 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
222fn 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
254fn 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
268fn 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
289fn 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
342fn 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