1 #[cfg(not(feature = "build_bindings"))]
main()2 fn main() {
3     println!("cargo:rerun-if-changed=build.rs"); // never rerun
4 }
5 
6 #[cfg(feature = "build_bindings")]
main()7 fn main() {
8     println!("cargo:rerun-if-changed=build.rs"); // avoids double-build when we output into src
9     generate::generate().unwrap();
10 }
11 
12 #[cfg(feature = "build_bindings")]
13 mod generate {
14     use std::error::Error;
15     use std::io::Write;
16     use std::process::{Command, Stdio};
17 
18     use regex::Regex;
19     use std::env;
20     use std::fs::File;
21     use std::path::Path;
22 
23     static CONST_PREFIX: &'static str = "DRM_FOURCC_";
24 
generate() -> Result<(), Box<dyn Error + Sync + Send>>25     pub fn generate() -> Result<(), Box<dyn Error + Sync + Send>> {
26         let out_dir = env::var("OUT_DIR").unwrap();
27         let wrapper_path = Path::new(&out_dir).join("wrapper.h");
28 
29         // First get all the macros in drm_fourcc.h
30 
31         let mut cmd = Command::new("clang")
32             .arg("-E") // run pre-processor only
33             .arg("-dM") // output all macros defined
34             .arg("-") // take input from stdin
35             .stdin(Stdio::piped())
36             .stdout(Stdio::piped())
37             .spawn()?;
38 
39         {
40             let stdin = cmd.stdin.as_mut().expect("failed to open stdin");
41             stdin.write_all(b"#include <drm/drm_fourcc.h>\n")?;
42         }
43 
44         let result = cmd.wait_with_output()?;
45         let stdout = String::from_utf8(result.stdout)?;
46         if !result.status.success() {
47             panic!("Clang failed with output: {}", stdout)
48         }
49 
50         // Then get the names of the format macros
51 
52         let fmt_re = Regex::new(r"^\s*#define (?P<full>DRM_FORMAT_(?P<short>[A-Z0-9_]+)) ")?;
53         let format_names: Vec<(&str, &str)> = stdout
54             .lines()
55             .filter_map(|line| {
56                 if line.contains("DRM_FORMAT_RESERVED")
57                     || line.contains("INVALID")
58                     || line.contains("_MOD_")
59                 {
60                     return None;
61                 }
62 
63                 fmt_re.captures(line).map(|caps| {
64                     let full = caps.name("full").unwrap().as_str();
65                     let short = caps.name("short").unwrap().as_str();
66 
67                     (full, short)
68                 })
69             })
70             .collect();
71 
72         let vendor_re =
73             Regex::new(r"^\s*#define (?P<full>DRM_FORMAT_MOD_VENDOR_(?P<short>[A-Z0-9_]+)) ")?;
74         let vendor_names: Vec<(&str, &str)> = stdout
75             .lines()
76             .filter_map(|line| {
77                 if line.contains("DRM_FORMAT_MOD_VENDOR_NONE") {
78                     return None;
79                 }
80 
81                 vendor_re.captures(line).map(|caps| {
82                     let full = caps.name("full").unwrap().as_str();
83                     let short = caps.name("short").unwrap().as_str();
84 
85                     (full, short)
86                 })
87             })
88             .collect();
89 
90         let mod_re =
91             Regex::new(r"^\s*#define (?P<full>(DRM|I915)_FORMAT_MOD_(?P<short>[A-Z0-9_]+)) ")?;
92         let modifier_names: Vec<(&str, String)> = stdout
93             .lines()
94             .filter_map(|line| {
95                 if line.contains("DRM_FORMAT_MOD_NONE")
96                     || line.contains("DRM_FORMAT_MOD_RESERVED")
97                     || line.contains("VENDOR")
98                     // grrr..
99                     || line.contains("ARM_TYPE")
100                 {
101                     return None;
102                 }
103 
104                 mod_re.captures(line).map(|caps| {
105                     let full = caps.name("full").unwrap().as_str();
106                     let short = caps.name("short").unwrap().as_str();
107 
108                     (
109                         full,
110                         if full.contains("I915") {
111                             format!("I915_{}", short)
112                         } else {
113                             String::from(short)
114                         },
115                     )
116                 })
117             })
118             .collect();
119 
120         // Then create a file with a variable defined for every format macro
121 
122         let mut wrapper = File::create(&wrapper_path)?;
123 
124         wrapper.write_all(b"#include <stdint.h>\n")?;
125         wrapper.write_all(b"#include <drm/drm_fourcc.h>\n")?;
126 
127         for (full, short) in &format_names {
128             writeln!(wrapper, "uint32_t {}{} = {};\n", CONST_PREFIX, short, full)?;
129         }
130         for (full, short) in &vendor_names {
131             writeln!(wrapper, "uint8_t {}{} = {};\n", CONST_PREFIX, short, full)?;
132         }
133         for (full, short) in &modifier_names {
134             writeln!(wrapper, "uint64_t {}{} = {};\n", CONST_PREFIX, short, full)?;
135         }
136 
137         wrapper.flush()?;
138 
139         // Then generate bindings from that file
140         bindgen::builder()
141             .ctypes_prefix("crate::_fake_ctypes")
142             .header(wrapper_path.as_os_str().to_str().unwrap())
143             .whitelist_var("DRM_FOURCC_.*")
144             .generate()
145             .unwrap()
146             .write_to_file("src/consts.rs")?;
147 
148         // Then generate our enums
149         fn write_enum(
150             as_enum: &mut File,
151             name: &str,
152             repr: &str,
153             names: Vec<(&str, &str)>,
154         ) -> Result<(), std::io::Error> {
155             as_enum.write_all(b"#[derive(Copy, Clone, Eq, PartialEq, Hash)]")?;
156             as_enum.write_all(
157                 b"#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]",
158             )?;
159             writeln!(as_enum, "#[repr({})]", repr)?;
160             writeln!(as_enum, "pub enum {} {{", name)?;
161 
162             let members: Vec<(String, String)> = names
163                 .iter()
164                 .map(|(_, short)| {
165                     (
166                         enum_member_case(short),
167                         format!("consts::{}{}", CONST_PREFIX, short),
168                     )
169                 })
170                 .collect();
171 
172             for (member, value) in &members {
173                 writeln!(as_enum, "{} = {},", member, value)?;
174             }
175 
176             as_enum.write_all(b"}\n")?;
177 
178             writeln!(as_enum, "impl {} {{", name)?;
179             writeln!(
180                 as_enum,
181                 "pub(crate) fn from_{}(n: {}) -> Option<Self> {{\n",
182                 repr, repr
183             )?;
184             as_enum.write_all(b"match n {\n")?;
185 
186             for (member, value) in &members {
187                 writeln!(as_enum, "{} => Some(Self::{}),", value, member)?;
188             }
189 
190             writeln!(as_enum, "_ => None")?;
191             as_enum.write_all(b"}}}\n")?;
192 
193             Ok(())
194         }
195 
196         let as_enum_path = "src/as_enum.rs";
197         {
198             let mut as_enum = File::create(as_enum_path)?;
199 
200             as_enum.write_all(b"// Automatically generated by build.rs\n")?;
201             as_enum.write_all(b"use crate::consts;")?;
202 
203             write_enum(&mut as_enum, "DrmFourcc", "u32", format_names)?;
204 
205             as_enum.write_all(b"#[derive(Debug)]")?;
206             write_enum(&mut as_enum, "DrmVendor", "u8", vendor_names)?;
207 
208             // modifiers can overlap
209 
210             as_enum.write_all(b"#[derive(Debug, Copy, Clone)]")?;
211             as_enum.write_all(
212                 b"#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]",
213             )?;
214             as_enum.write_all(b"pub enum DrmModifier {\n")?;
215 
216             let modifier_members: Vec<(String, String)> = modifier_names
217                 .iter()
218                 .map(|(_, short)| {
219                     (
220                         enum_member_case(short),
221                         format!("consts::{}{}", CONST_PREFIX, short),
222                     )
223                 })
224                 .collect();
225             for (member, _) in &modifier_members {
226                 writeln!(as_enum, "{},", member)?;
227             }
228             as_enum.write_all(b"Unrecognized(u64)")?;
229 
230             as_enum.write_all(b"}\n")?;
231 
232             as_enum.write_all(b"impl DrmModifier {\n")?;
233             as_enum.write_all(b"pub(crate) fn from_u64(n: u64) -> Self {\n")?;
234             as_enum.write_all(b"#[allow(unreachable_patterns)]\n")?;
235             as_enum.write_all(b"match n {\n")?;
236 
237             for (member, value) in &modifier_members {
238                 writeln!(as_enum, "{} => Self::{},", value, member)?;
239             }
240             as_enum.write_all(b"x => Self::Unrecognized(x)\n")?;
241 
242             as_enum.write_all(b"}}\n")?;
243             as_enum.write_all(b"pub(crate) fn into_u64(&self) -> u64 {\n")?;
244             as_enum.write_all(b"match self {\n")?;
245 
246             for (member, value) in &modifier_members {
247                 writeln!(as_enum, "Self::{} => {},", member, value)?;
248             }
249             as_enum.write_all(b"Self::Unrecognized(x) => *x,\n")?;
250 
251             as_enum.write_all(b"}}}\n")?;
252         }
253 
254         Command::new("rustfmt").arg(as_enum_path).spawn()?.wait()?;
255 
256         Ok(())
257     }
258 
enum_member_case(s: &str) -> String259     fn enum_member_case(s: &str) -> String {
260         let (first, rest) = s.split_at(1);
261         format!("{}{}", first, rest.to_ascii_lowercase())
262     }
263 }
264