blob: 61fce854f2fa5c7fef85c4fbf7dcf14f16e8362b [file] [log] [blame]
#[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())
}
}