#[cfg(not(feature = "build_bindings"))] fn main() { println!("cargo:rerun-if-changed=build.rs"); // never rerun } #[cfg(feature = "build_bindings")] fn main() { println!("cargo:rerun-if-changed=build.rs"); // avoids double-build when we output into src generate::generate().unwrap(); } #[cfg(feature = "build_bindings")] mod generate { use std::error::Error; use std::io::Write; use std::process::{Command, Stdio}; use regex::Regex; use std::env; use std::fs::File; use std::path::Path; static CONST_PREFIX: &'static str = "DRM_FOURCC_"; pub fn generate() -> Result<(), Box> { let out_dir = env::var("OUT_DIR").unwrap(); let wrapper_path = Path::new(&out_dir).join("wrapper.h"); // First get all the macros in drm_fourcc.h let mut cmd = Command::new("clang") .arg("-E") // run pre-processor only .arg("-dM") // output all macros defined .arg("-") // take input from stdin .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn()?; { let stdin = cmd.stdin.as_mut().expect("failed to open stdin"); stdin.write_all(b"#include \n")?; } let result = cmd.wait_with_output()?; let stdout = String::from_utf8(result.stdout)?; if !result.status.success() { panic!("Clang failed with output: {}", stdout) } // Then get the names of the format macros let fmt_re = Regex::new(r"^\s*#define (?PDRM_FORMAT_(?P[A-Z0-9_]+)) ")?; let format_names: Vec<(&str, &str)> = stdout .lines() .filter_map(|line| { if line.contains("DRM_FORMAT_RESERVED") || line.contains("INVALID") || line.contains("_MOD_") { return None; } fmt_re.captures(line).map(|caps| { let full = caps.name("full").unwrap().as_str(); let short = caps.name("short").unwrap().as_str(); (full, short) }) }) .collect(); let vendor_re = Regex::new(r"^\s*#define (?PDRM_FORMAT_MOD_VENDOR_(?P[A-Z0-9_]+)) ")?; let vendor_names: Vec<(&str, &str)> = stdout .lines() .filter_map(|line| { if line.contains("DRM_FORMAT_MOD_VENDOR_NONE") { return None; } vendor_re.captures(line).map(|caps| { let full = caps.name("full").unwrap().as_str(); let short = caps.name("short").unwrap().as_str(); (full, short) }) }) .collect(); let mod_re = Regex::new(r"^\s*#define (?P(DRM|I915)_FORMAT_MOD_(?P[A-Z0-9_]+)) ")?; let modifier_names: Vec<(&str, String)> = stdout .lines() .filter_map(|line| { if line.contains("DRM_FORMAT_MOD_NONE") || line.contains("DRM_FORMAT_MOD_RESERVED") || line.contains("VENDOR") // grrr.. || line.contains("ARM_TYPE") { return None; } mod_re.captures(line).map(|caps| { let full = caps.name("full").unwrap().as_str(); let short = caps.name("short").unwrap().as_str(); ( full, if full.contains("I915") { format!("I915_{}", short) } else { String::from(short) }, ) }) }) .collect(); // Then create a file with a variable defined for every format macro let mut wrapper = File::create(&wrapper_path)?; wrapper.write_all(b"#include \n")?; wrapper.write_all(b"#include \n")?; for (full, short) in &format_names { writeln!(wrapper, "uint32_t {}{} = {};\n", CONST_PREFIX, short, full)?; } for (full, short) in &vendor_names { writeln!(wrapper, "uint8_t {}{} = {};\n", CONST_PREFIX, short, full)?; } for (full, short) in &modifier_names { writeln!(wrapper, "uint64_t {}{} = {};\n", CONST_PREFIX, short, full)?; } wrapper.flush()?; // Then generate bindings from that file bindgen::builder() .ctypes_prefix("crate::_fake_ctypes") .header(wrapper_path.as_os_str().to_str().unwrap()) .whitelist_var("DRM_FOURCC_.*") .generate() .unwrap() .write_to_file("src/consts.rs")?; // Then generate our enums fn write_enum( as_enum: &mut File, name: &str, repr: &str, names: Vec<(&str, &str)>, ) -> Result<(), std::io::Error> { as_enum.write_all(b"#[derive(Copy, Clone, Eq, PartialEq, Hash)]")?; as_enum.write_all( b"#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]", )?; writeln!(as_enum, "#[repr({})]", repr)?; writeln!(as_enum, "pub enum {} {{", name)?; let members: Vec<(String, String)> = names .iter() .map(|(_, short)| { ( enum_member_case(short), format!("consts::{}{}", CONST_PREFIX, short), ) }) .collect(); for (member, value) in &members { writeln!(as_enum, "{} = {},", member, value)?; } as_enum.write_all(b"}\n")?; writeln!(as_enum, "impl {} {{", name)?; writeln!( as_enum, "pub(crate) fn from_{}(n: {}) -> Option {{\n", repr, repr )?; as_enum.write_all(b"match n {\n")?; for (member, value) in &members { writeln!(as_enum, "{} => Some(Self::{}),", value, member)?; } writeln!(as_enum, "_ => None")?; as_enum.write_all(b"}}}\n")?; Ok(()) } let as_enum_path = "src/as_enum.rs"; { let mut as_enum = File::create(as_enum_path)?; as_enum.write_all(b"// Automatically generated by build.rs\n")?; as_enum.write_all(b"use crate::consts;")?; write_enum(&mut as_enum, "DrmFourcc", "u32", format_names)?; as_enum.write_all(b"#[derive(Debug)]")?; write_enum(&mut as_enum, "DrmVendor", "u8", vendor_names)?; // modifiers can overlap as_enum.write_all(b"#[derive(Debug, Copy, Clone)]")?; as_enum.write_all( b"#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]", )?; as_enum.write_all(b"pub enum DrmModifier {\n")?; let modifier_members: Vec<(String, String)> = modifier_names .iter() .map(|(_, short)| { ( enum_member_case(short), format!("consts::{}{}", CONST_PREFIX, short), ) }) .collect(); for (member, _) in &modifier_members { writeln!(as_enum, "{},", member)?; } as_enum.write_all(b"Unrecognized(u64)")?; as_enum.write_all(b"}\n")?; as_enum.write_all(b"impl DrmModifier {\n")?; as_enum.write_all(b"pub(crate) fn from_u64(n: u64) -> Self {\n")?; as_enum.write_all(b"#[allow(unreachable_patterns)]\n")?; as_enum.write_all(b"match n {\n")?; for (member, value) in &modifier_members { writeln!(as_enum, "{} => Self::{},", value, member)?; } as_enum.write_all(b"x => Self::Unrecognized(x)\n")?; as_enum.write_all(b"}}\n")?; as_enum.write_all(b"pub(crate) fn into_u64(&self) -> u64 {\n")?; as_enum.write_all(b"match self {\n")?; for (member, value) in &modifier_members { writeln!(as_enum, "Self::{} => {},", member, value)?; } as_enum.write_all(b"Self::Unrecognized(x) => *x,\n")?; as_enum.write_all(b"}}}\n")?; } Command::new("rustfmt").arg(as_enum_path).spawn()?.wait()?; Ok(()) } fn enum_member_case(s: &str) -> String { let (first, rest) = s.split_at(1); format!("{}{}", first, rest.to_ascii_lowercase()) } }