extern crate bindgen;
use std::borrow::Cow;
use std::env;
use std::error::Error;
use std::path::{PathBuf, Path};
use bindgen::SDK_PATH_ENV_VAR;
use bindgen::bindgen::callbacks::DeriveInfo;
const SDK_VER_SUPPORTED: &str = "~2.0.1"; const BINDINGS_PATH_ENV: &str = "PD_BINDINGS_PATH"; const BINDINGS_NAME_ENV: &str = "PD_BINDINGS_FILENAME";
const BINDINGS_VER_ENV: &str = "PD_SDK_VERSION"; const BINDINGS_BUILD_PREBUILT: &str = "PD_BUILD_PREBUILT";
const NO_SDK_ANYWAY: &str = "IGNORE_EXISTING_PLAYDATE_SDK";
fn main() {
println!("cargo:rerun-if-env-changed={SDK_PATH_ENV_VAR}");
let mut cfg = bindgen::cfg::Config::default();
cfg.derive.default = feature_derive_default();
cfg.derive.eq = feature_derive_eq();
cfg.derive.copy = feature_derive_copy();
cfg.derive.debug = feature_derive_debug();
cfg.derive.hash = feature_derive_hash();
cfg.derive.ord = feature_derive_ord();
cfg.derive.partialeq = feature_derive_partialeq();
cfg.derive.partialord = feature_derive_partialord();
if is_env_without_sdk() {
println!("docs.rs detected");
return use_existing_prebuilt(&cfg);
}
let mut generator = bindgen::Generator::new(cfg).expect("Couldn't create bindings generator.");
add_features_to_filename(&mut generator.filename);
if cfg!(feature = "bindings-derive-constparamty") {
generator.builder = generator.builder.parse_callbacks(Box::new(DeriveConstParamTy));
}
println!(
"cargo:rustc-env={BINDINGS_NAME_ENV}={}",
generator.filename.to_string()
);
println!("cargo:rustc-env={BINDINGS_VER_ENV}={}", generator.filename.sdk);
let out_path = if is_rebuild_prebuilt_requested() {
println!("rebuild pre-built bindings requested");
let out_dir = out_dir_prebuilt();
let out_path = out_dir.join(&generator.filename.to_string());
println!("cargo:rerun-if-changed={}", out_path.display());
println!("cargo:warning=Rebuilding `pre-built` bindings");
if !out_dir.exists() {
std::fs::create_dir_all(&out_dir).unwrap();
println!(
"cargo:warning=OUT_DIR for `pre-built` bindings created: {}",
out_dir.display()
);
}
out_path
} else {
let out_path = out_file_prebuilt(&generator.filename);
if out_path.exists() {
println!("cargo:rerun-if-changed={}", out_path.display());
println!("cargo:rustc-env={BINDINGS_PATH_ENV}={}", out_path.display());
return;
}
out_file_bounded(&generator.filename)
};
println!("cargo:rustc-env={BINDINGS_PATH_ENV}={}", out_path.display());
let bindings = generator.generate().expect("Couldn't generate bindings.");
bindings.write_to_file(&out_path)
.expect("Couldn't write bindings.");
}
#[allow(dead_code)]
fn open_bindings(path: &Path) {
let editor = std::env::var("EDITOR").map(Cow::from)
.unwrap_or_else(|_| "code".into());
let mut editor = editor.split(" ");
std::process::Command::new(editor.next().unwrap()).args(editor)
.arg(path)
.envs(env::vars())
.current_dir(env::current_dir().expect("PWD"))
.spawn()
.ok();
}
fn use_existing_prebuilt(cfg: &bindgen::cfg::Config) {
let version = get_supported_version().unwrap();
let version_raw = version.to_string();
println!("using pre-built {version_raw}");
let filename = filename(version, &cfg);
println!("cargo:rustc-env={BINDINGS_VER_ENV}={version_raw}");
println!("cargo:rustc-env={BINDINGS_NAME_ENV}={}", filename.to_string());
let out_path = out_file_prebuilt(&filename);
println!("cargo:rerun-if-changed={}", out_path.display());
println!("cargo:rustc-env={BINDINGS_PATH_ENV}={}", out_path.display());
assert!(out_path.exists(), "pre-built bindings not found.");
}
fn filename(ver: semver::Version, cfg: &bindgen::cfg::Config) -> bindgen::Filename {
let mut filename = bindgen::Filename::new(ver, &cfg.derive).unwrap();
add_features_to_filename(&mut filename);
filename
}
fn add_features_to_filename(filename: &mut bindgen::Filename) {
filename.mask.push(feature_derive_constparamty());
}
fn out_dir_prebuilt() -> PathBuf {
bindgen::env_var("CARGO_MANIFEST_DIR").map(PathBuf::from)
.map(|p| p.join("gen"))
.unwrap()
}
fn out_file_prebuilt(filename: &bindgen::Filename) -> PathBuf { out_dir_prebuilt().join(&filename.to_string()) }
fn out_file_bounded(filename: &bindgen::Filename) -> PathBuf {
bindgen::env_var("OUT_DIR").map(PathBuf::from)
.map(|p| p.join(&filename.to_string()))
.unwrap()
}
fn is_env_without_sdk() -> bool {
#![allow(unexpected_cfgs)]
cfg!(docsrs) ||
std::env::var_os("DOCS_RS").is_some() ||
env::var_os(NO_SDK_ANYWAY).filter(|s| s == "1" || s == "true")
.is_some()
}
fn is_rebuild_prebuilt_requested() -> bool {
println!("cargo:rerun-if-env-changed={BINDINGS_BUILD_PREBUILT}");
std::env::var_os(BINDINGS_BUILD_PREBUILT).is_some()
}
fn get_supported_version() -> Result<semver::Version, Box<dyn Error>> {
let requirement = semver::VersionReq::parse(SDK_VER_SUPPORTED).unwrap();
let mut major = None;
let mut minor = None;
let mut patch = None;
requirement.comparators
.iter()
.inspect(|c| {
major = Some(c.major);
minor = c.minor;
patch = c.patch;
})
.count();
let s = match (major, minor, patch) {
(Some(a), Some(b), Some(c)) => format!("{a}.{b}.{c}"),
(Some(a), Some(b), None) => format!("{a}.{b}"),
_ => return Err("Can't determine version by required semver.".into()),
};
let version = semver::Version::parse(&s)?;
Ok(version)
}
const fn feature_derive_default() -> bool { cfg!(feature = "bindings-derive-default") }
const fn feature_derive_eq() -> bool { cfg!(feature = "bindings-derive-eq") }
const fn feature_derive_copy() -> bool { cfg!(feature = "bindings-derive-copy") }
const fn feature_derive_debug() -> bool { cfg!(feature = "bindings-derive-debug") }
const fn feature_derive_hash() -> bool { cfg!(feature = "bindings-derive-hash") }
const fn feature_derive_ord() -> bool { cfg!(feature = "bindings-derive-ord") }
const fn feature_derive_partialeq() -> bool { cfg!(feature = "bindings-derive-partialeq") }
const fn feature_derive_partialord() -> bool { cfg!(feature = "bindings-derive-partialord") }
const fn feature_derive_constparamty() -> bool { cfg!(feature = "bindings-derive-constparamty") }
#[derive(Debug)]
struct DeriveConstParamTy;
impl bindgen::bindgen::callbacks::ParseCallbacks for DeriveConstParamTy {
fn add_derives(&self, info: &DeriveInfo<'_>) -> Vec<String> {
const TYPES: &[&str] = &[
"PDButtons",
"FileOptions",
"LCDBitmapDrawMode",
"LCDBitmapFlip",
"LCDSolidColor",
"LCDLineCapStyle",
"PDStringEncoding",
"LCDPolygonFillRule",
"PDLanguage",
"PDPeripherals",
"l_valtype",
"LuaType",
"json_value_type",
"SpriteCollisionResponseType",
"SoundFormat",
"LFOType",
"SoundWaveform",
"TwoPoleFilterType",
"PDSystemEvent",
];
if TYPES.contains(&info.name) {
vec!["::core::marker::ConstParamTy".to_string()]
} else {
vec![]
}
}
}