1#![cfg_attr(feature = "documentation", feature(get_mut_unchecked))]
2pub extern crate bindgen;
3
4use std::env;
5use std::path::{Path, PathBuf};
6use bindgen::callbacks::DeriveInfo;
7use bindgen::{EnumVariation, RustTarget, Builder, MacroTypeVariation};
8use utils::consts::*;
9use utils::toolchain::gcc::{ArmToolchain, Gcc};
10use utils::toolchain::sdk::Sdk;
11pub use bindgen_cfg as cfg;
12use rustify::rename::SharedRenamed;
13
14
15pub mod error;
16pub mod gen;
17pub mod rustify {
18 pub mod rename;
19}
20
21
22type Result<T, E = error::Error> = std::result::Result<T, E>;
23
24
25pub const SDK_VER_SUPPORTED: &str = ">=2.1.0, <3.0.0";
26
27
28pub enum Bindings {
30 Bindgen(bindgen::Bindings),
31 #[cfg(feature = "extra-codegen")]
32 Engaged(gen::Bindings),
33}
34
35
36impl Bindings {
37 #[inline(always)]
38 pub fn write_to_file<P: AsRef<Path>>(&self, path: P) -> std::io::Result<()> {
40 match self {
41 Bindings::Bindgen(this) => this.write_to_file(path),
42 #[cfg(feature = "extra-codegen")]
43 Bindings::Engaged(this) => this.write_to_file(path),
44 }
45 }
46
47 #[inline(always)]
48 pub fn write<'a>(&self, writer: Box<dyn std::io::Write + 'a>) -> std::io::Result<()> {
50 match self {
51 Bindings::Bindgen(this) => this.write(writer),
52 #[cfg(feature = "extra-codegen")]
53 Bindings::Engaged(this) => this.write(writer),
54 }
55 }
56}
57
58impl std::fmt::Display for Bindings {
59 #[inline(always)]
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 match self {
62 Bindings::Bindgen(this) => std::fmt::Display::fmt(this, f),
63 #[cfg(feature = "extra-codegen")]
64 Bindings::Engaged(this) => std::fmt::Display::fmt(this, f),
65 }
66 }
67}
68
69
70pub struct Generator {
71 pub sdk: Sdk,
73 pub version: semver::Version,
75
76 pub gcc: ArmToolchain,
78
79 pub filename: cfg::Filename,
81 pub builder: Builder,
83
84 renamed: SharedRenamed,
86
87 pub derives: cfg::Derive,
89 pub features: cfg::Features,
90}
91
92
93impl Generator {
94 pub fn new(cfg: cfg::Cfg) -> Result<Self> { create_generator(cfg) }
95
96 pub fn generate(mut self) -> Result<Bindings> {
97 if cfg!(feature = "extra-codegen") {
99 self.builder = self.builder.formatter(bindgen::Formatter::None);
100 }
101
102 let bindings = self.builder.generate()?;
104
105 #[cfg(not(feature = "extra-codegen"))]
106 return Ok(Bindings::Bindgen(bindings));
107
108
109 #[cfg(feature = "extra-codegen")]
110 gen::engage(&bindings, self.renamed, &self.features, &self.sdk, None).map(Bindings::Engaged)
111 }
112}
113
114
115fn create_generator(cfg: cfg::Cfg) -> Result<Generator, error::Error> {
116 println!("cargo::rerun-if-env-changed=TARGET");
117 let cargo_target_triple = env::var("TARGET").expect("TARGET cargo env var");
118
119 println!("cargo::rerun-if-env-changed=PROFILE");
120 let cargo_profile = env::var("PROFILE").expect("PROFILE cargo env var");
121 let is_debug = cargo_profile == "debug" || env_cargo_feature("DEBUG");
122
123 let sdk = cfg.sdk
124 .map(|p| Sdk::try_new_exact(p).or_else(|_| Sdk::try_new()))
125 .unwrap_or_else(Sdk::try_new)?;
126 let version_path = sdk.version_file();
127 let version_raw = sdk.read_version()?;
128 let version = check_sdk_version(&version_raw)?;
129 println!("cargo::rerun-if-changed={}", version_path.display());
130 let sdk_c_api = sdk.c_api();
131
132 let main_header = sdk_c_api.join("pd_api.h");
133 println!("cargo::rerun-if-changed={}", main_header.display());
134 println!("cargo::rerun-if-env-changed={SDK_ENV_VAR}");
135 println!("cargo::metadata=include={}", sdk_c_api.display());
136
137
138 let gcc = cfg.gcc
140 .map(|p| {
141 Gcc::try_from_path(p).and_then(ArmToolchain::try_new_with)
142 .or_else(|_| ArmToolchain::try_new())
143 })
144 .unwrap_or_else(ArmToolchain::try_new)?;
145 let (mut builder, renamed) = create_builder(
146 &cargo_target_triple,
147 &sdk_c_api,
148 &main_header,
149 &cfg.derive,
150 &cfg.features,
151 );
152 builder = apply_profile(builder, is_debug);
153 builder = apply_target(builder, &cargo_target_triple, &gcc);
154
155
156 let filename = cfg::Filename::new(version.to_owned(), cfg.derive)?;
157
158 Ok(Generator { sdk,
159 gcc,
160 version,
161 filename,
162 builder,
163 renamed,
164 derives: cfg.derive,
165 features: cfg.features })
166}
167
168
169fn check_sdk_version(version: &str) -> Result<semver::Version, error::Error> {
170 is_version_matches(version)
171 .map(|(ver, res, req)| {
172 if res {
173 const PKG: &str = env!("CARGO_PKG_NAME");
174 const VER: &str = env!("CARGO_PKG_VERSION");
175 println!("cargo:warning=Playdate SDK v{ver} may not be compatible with {PKG} v{VER} which hasn't been tested with it. Supported '{req}' does not matches current '{ver}'.")
176 }
177 ver
178 })
179}
180
181fn is_version_matches(version: &str) -> Result<(semver::Version, bool, semver::VersionReq), error::Error> {
182 let requirement =
183 semver::VersionReq::parse(SDK_VER_SUPPORTED).expect("Builtin supported version requirement is invalid.");
184 let version = semver::Version::parse(version.trim())?;
185 let matches = requirement.matches(&version);
186 Ok((version, matches, requirement))
187}
188
189
190pub fn env_var(name: &'static str) -> Result<String> {
191 env::var(name).map_err(|err| error::Error::Env { err, ctx: name })
192}
193
194pub fn env_cargo_feature(feature: &str) -> bool { env::var(format!("CARGO_FEATURE_{feature}")).is_ok() }
195
196
197fn create_builder(_target: &str,
198 capi: &Path,
199 header: &Path,
200 derive: &cfg::Derive,
201 features: &cfg::Features)
202 -> (Builder, SharedRenamed) {
203 let mut builder = bindgen::builder()
204 .header(format!("{}", header.display()))
205 .rust_target(RustTarget::nightly())
206
207 .allowlist_recursively(true)
209 .allowlist_type("PlaydateAPI")
210 .allowlist_type("PDSystemEvent")
211 .allowlist_type("LCDSolidColor")
212 .allowlist_type("LCDColor")
213 .allowlist_type("LCDPattern")
214 .allowlist_type("PDEventHandler")
215
216 .allowlist_var("LCD_COLUMNS")
217 .allowlist_var("LCD_ROWS")
218 .allowlist_var("LCD_ROWSIZE")
219 .allowlist_var("LCD_SCREEN_RECT")
220 .allowlist_var("SEEK_SET")
221 .allowlist_var("SEEK_CUR")
222 .allowlist_var("SEEK_END")
223 .allowlist_var("AUDIO_FRAMES_PER_CYCLE")
224 .allowlist_var("NOTE_C4")
225
226 .default_macro_constant_type(MacroTypeVariation::Unsigned)
228 .allowlist_var("LCDMakePattern")
229 .allowlist_type("LCDMakePattern")
230 .allowlist_var("LCDOpaquePattern")
231 .allowlist_type("LCDOpaquePattern")
232
233 .bitfield_enum("FileOptions")
234 .bitfield_enum("PDButtons")
235
236 .use_core()
238 .ctypes_prefix("core::ffi")
239 .size_t_is_usize(true)
240 .no_convert_floats()
241 .translate_enum_integer_types(true)
242 .array_pointers_in_arguments(true)
243 .explicit_padding(false)
244
245 .default_enum_style(EnumVariation::Rust { non_exhaustive: false })
246
247 .layout_tests(true)
248 .enable_function_attribute_detection()
249 .detect_include_paths(true)
250
251 .clang_args(&["--include-directory", &capi.display().to_string()])
252 .clang_arg("-DTARGET_EXTENSION=1")
253
254 .dynamic_link_require_all(true)
255
256 .derive_default(derive.default)
258 .derive_eq(derive.eq)
259 .derive_copy(derive.copy)
260 .derive_debug(derive.debug)
261 .derive_hash(derive.hash)
262 .derive_ord(derive.ord)
263 .derive_partialeq(derive.partialeq)
264 .derive_partialord(derive.partialord)
265
266 .must_use_type("playdate_*")
267 .must_use_type(".*")
268 .generate_comments(true);
269
270
271 builder = builder.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()));
272 if !derive.copy {
273 builder = builder.parse_callbacks(Box::new(DeriveCopyToPrimitives));
274 }
275 if derive.constparamty {
276 builder = builder.parse_callbacks(Box::new(DeriveConstParamTy));
277 }
278
279 let renamed = if features.rustify {
280 let hook = rustify::rename::RenameMap::new();
281 let renamed = hook.renamed.clone();
282 builder = builder.parse_callbacks(Box::new(hook));
283 renamed
284 } else {
285 Default::default()
286 };
287
288
289 if !derive.default {
291 builder = builder.no_default(".*");
292 }
293 if !derive.copy {
294 builder = builder.no_copy(".*");
295 }
296 if !derive.debug {
297 builder = builder.no_debug(".*");
298 }
299 if !derive.hash {
300 builder = builder.no_hash(".*");
301 }
302 if !derive.partialeq {
303 builder = builder.no_partialeq(".*");
304 }
305
306 (builder, renamed)
307}
308
309
310fn apply_profile(mut builder: Builder, debug: bool) -> Builder {
311 if debug {
313 builder = builder.clang_arg("-D_DEBUG=1").derive_debug(true);
314 } else {
315 }
318 builder
319}
320
321
322fn apply_target(mut builder: Builder, target: &str, gcc: &ArmToolchain) -> Builder {
325 builder = if DEVICE_TARGET == target {
326 let arm_eabi_include = gcc.include();
327 println!("cargo::metadata=include={}", arm_eabi_include.display());
329
330 builder.clang_arg("-DTARGET_PLAYDATE=1")
334 .blocklist_file("stdlib.h")
335 .clang_args(&["-target", DEVICE_TARGET])
336 .clang_arg("-fshort-enums")
337 .clang_args(&["--include-directory", &arm_eabi_include.display().to_string()])
338 .clang_arg(format!("-I{}", arm_eabi_include.display()))
339 } else {
340 builder.clang_arg("-DTARGET_SIMULATOR=1")
341 };
342 builder
343}
344
345
346#[derive(Debug)]
348struct DeriveCopyToPrimitives;
349impl bindgen::callbacks::ParseCallbacks for DeriveCopyToPrimitives {
350 fn add_derives(&self, info: &DeriveInfo<'_>) -> Vec<String> {
351 const TYPES: &[&str] = &[
352 "PDButtons",
353 "FileOptions",
354 "LCDBitmapDrawMode",
355 "LCDBitmapFlip",
356 "LCDSolidColor",
357 "LCDLineCapStyle",
358 "PDStringEncoding",
359 "LCDPolygonFillRule",
360 "PDLanguage",
361 "PDPeripherals",
362 "l_valtype",
363 "LuaType",
364 "json_value_type",
365 "SpriteCollisionResponseType",
366 "SoundFormat",
367 "LFOType",
368 "SoundWaveform",
369 "TwoPoleFilterType",
370 "PDSystemEvent",
371 ];
372
373 if TYPES.contains(&info.name) {
374 vec!["Copy".to_string()]
375 } else {
376 vec![]
377 }
378 }
379}
380
381
382#[derive(Debug)]
383struct DeriveConstParamTy;
385
386impl bindgen::callbacks::ParseCallbacks for DeriveConstParamTy {
387 fn add_derives(&self, info: &DeriveInfo<'_>) -> Vec<String> {
388 const TYPES: &[&str] = &[
389 "PDButtons",
390 "FileOptions",
391 "LCDBitmapDrawMode",
392 "LCDBitmapFlip",
393 "LCDSolidColor",
394 "LCDLineCapStyle",
395 "PDStringEncoding",
396 "LCDPolygonFillRule",
397 "PDLanguage",
398 "PDPeripherals",
399 "l_valtype",
400 "LuaType",
401 "json_value_type",
402 "SpriteCollisionResponseType",
403 "SoundFormat",
404 "LFOType",
405 "SoundWaveform",
406 "TwoPoleFilterType",
407 "PDSystemEvent",
408 ];
409
410 if TYPES.contains(&info.name) {
411 vec!["::core::marker::ConstParamTy".to_string()]
412 } else {
413 vec![]
414 }
415 }
416}
417
418
419pub fn rustfmt<'out>(mut rustfmt_path: Option<PathBuf>,
420 source: String,
421 config_path: Option<&Path>)
422 -> std::io::Result<String> {
423 use std::io::Write;
424 use std::process::{Command, Stdio};
425
426 rustfmt_path = rustfmt_path.or_else(|| std::env::var("RUSTFMT").map(PathBuf::from).ok());
427 #[cfg(feature = "which-rustfmt")]
428 {
429 rustfmt_path = rustfmt_path.or_else(|| which::which("rustfmt").ok());
430 }
431 let rustfmt = rustfmt_path.as_deref().unwrap_or(Path::new("rustfmt"));
432
433
434 let mut cmd = Command::new(rustfmt);
435
436 cmd.stdin(Stdio::piped()).stdout(Stdio::piped());
437
438 if let Some(path) = config_path {
439 cmd.arg("--config-path");
440 cmd.arg(path);
441 }
442
443 let mut child = cmd.spawn()?;
444 let mut child_stdin = child.stdin.take().unwrap();
445 let mut child_stdout = child.stdout.take().unwrap();
446
447 let stdin_handle = std::thread::spawn(move || {
451 let _ = child_stdin.write_all(source.as_bytes());
452 source
453 });
454
455 let mut output = vec![];
456 std::io::copy(&mut child_stdout, &mut output)?;
457
458 let status = child.wait()?;
459 let source = stdin_handle.join()
460 .expect("The thread writing to rustfmt's stdin doesn't do anything that could panic");
461
462 match String::from_utf8(output) {
463 Ok(bindings) => {
464 match status.code() {
465 Some(0) => Ok(bindings),
466 Some(2) => Err(std::io::Error::new(std::io::ErrorKind::Other, "Rustfmt parsing errors.".to_string())),
467 Some(3) => {
468 println!("cargo:warning=Rustfmt could not format some lines.");
469 Ok(bindings)
470 },
471 _ => Err(std::io::Error::new(std::io::ErrorKind::Other, "Internal rustfmt error".to_string())),
472 }
473 },
474 _ => Ok(source),
475 }
476}
477
478
479#[cfg(test)]
480mod tests {
481 #[test]
482 fn same_env_var() {
483 assert_eq!(utils::consts::SDK_ENV_VAR, bindgen_cfg::Cfg::ENV_SDK_PATH);
484 }
485
486 #[test]
487 fn version_matches() {
488 use super::is_version_matches as check;
489
490 let map = |(_, res, _)| res;
491
492 assert!(check("0.0").map(map).is_err());
493 assert!(!check("0.0.0").map(map).unwrap());
494 assert!(check("2.1.0").map(map).unwrap());
495 assert!(check("2.7.0").map(map).unwrap());
496 assert!(!check("2.7.0-beta.3").map(map).unwrap());
497 assert!(!check("3.1.0").map(map).unwrap());
498 }
499}