|  | #[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<dyn Error + Sync + Send>> { | 
|  | 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 <drm/drm_fourcc.h>\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 (?P<full>DRM_FORMAT_(?P<short>[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 (?P<full>DRM_FORMAT_MOD_VENDOR_(?P<short>[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<full>(DRM|I915)_FORMAT_MOD_(?P<short>[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 <stdint.h>\n")?; | 
|  | wrapper.write_all(b"#include <drm/drm_fourcc.h>\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<Self> {{\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()) | 
|  | } | 
|  | } |