| #[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()) |
| } |
| } |