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