use std::{env, io::BufRead, path::Path, process::{Command, Stdio}};
#[derive(Debug)]
struct TrimUnderscoreCallbacks;
impl bindgen::callbacks::ParseCallbacks for TrimUnderscoreCallbacks {
fn item_name(&self, item_info: bindgen::callbacks::ItemInfo) -> Option<String> {
item_info.name.strip_suffix('_').map(str::to_owned)
}
}
#[derive(Debug)]
struct MakeMjnConstantsCallbacks;
impl bindgen::callbacks::ParseCallbacks for MakeMjnConstantsCallbacks {
fn enum_variant_behavior(
&self,
_enum_name: Option<&str>,
original_variant_name: &str,
_variant_value: bindgen::callbacks::EnumVariantValue,
) -> Option<bindgen::callbacks::EnumVariantCustomBehavior> {
original_variant_name.starts_with("mjN").then_some(bindgen::callbacks::EnumVariantCustomBehavior::Constify)
}
}
fn main() {
if option_env!("DOCS_RS").is_some() { return }
assert!(
Command::new("cargo").args(["help", "fmt"]).stdout(Stdio::null()).status().is_ok_and(|s| s.success()),
"`cargo fmt` is not available; This build script can't continue without it."
);
let src_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
let bindgen_h = src_dir.join("bindgen.h").to_str().unwrap().to_owned();
let bindgen_rs = src_dir.join("bindgen.rs").to_str().unwrap().to_owned();
println!("cargo:rerun-if-changed={bindgen_h}");
let mujoco_dir = std::env::var("MUJOCO_DIR").expect("MUJOCO_DIR environment variable is not set");
let mujoco_dir = Path::new(&mujoco_dir).canonicalize().expect("MUJOCO_DIR is not a valid path");
let mujoco_lib = mujoco_dir.join("lib").to_str().unwrap().to_owned();
let mujoco_include = mujoco_dir.join("include").to_str().unwrap().to_owned();
let mujoco_include_mujoco = mujoco_dir.join("include").join("mujoco").to_str().unwrap().to_owned();
println!("cargo:rustc-link-search={mujoco_lib}");
println!("cargo:rustc-link-lib=dylib=mujoco");
let mut bindings = Vec::new();
bindgen::builder()
.header(bindgen_h)
.clang_args([format!("-I{mujoco_include}"), format!("-I{mujoco_include_mujoco}")])
.use_core()
.raw_line("#![allow(unused, non_camel_case_types, non_snake_case, non_upper_case_globals)]")
.respect_cxx_access_specs(false)
.default_visibility(bindgen::FieldVisibilityKind::PublicCrate)
.newtype_enum("_?mjt.+[^Bit]")
.bitfield_enum("_?mjt.+Bit_")
.allowlist_type("_?mj.*")
.allowlist_function("_?mj.*")
.allowlist_var("_?mj.*")
.no_copy("mj(Model|Data|Spec|vScene|rContext)_")
.size_t_is_usize(true)
.array_pointers_in_arguments(true)
.merge_extern_blocks(true)
.prepend_enum_name(false)
.layout_tests(false)
.parse_callbacks(Box::new(TrimUnderscoreCallbacks))
.parse_callbacks(Box::new(MakeMjnConstantsCallbacks))
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate().expect("Failed to generate bindings")
.write(Box::new(&mut bindings)).expect("Failed to write bindings to file");
let bindings = bindings
.lines()
.map(Result::unwrap)
.fold(Vec::with_capacity(bindings.len()), |mut new, line| {
if line.starts_with("pub struct mjt") {
new.push(line.replace("(pub", "(pub(crate)"));
} else if line.starts_with("pub const mjN") {
let mut line = line.split(' ');
let _ = line.next(); let _ = line.next(); let name = line.next().unwrap().strip_suffix(':').unwrap();
let _ty = line.next().unwrap();
let _ = line.next(); let value = line.next().unwrap().strip_suffix(";").unwrap();
new.push(if value.contains("::") {
format!("pub const {name}: usize = {value}.0 as usize;")
} else {
format!("pub const {name}: usize = {value};")
});
} else if line.starts_with("pub const mjMAX") {
let mut line = line.split(' ');
let _ = line.next(); let _ = line.next(); let name = line.next().unwrap().strip_suffix(':').unwrap();
let ty = line.next().unwrap();
let _ = line.next(); let value = line.next().unwrap().strip_suffix(";").unwrap();
new.push(if ty.starts_with('f') {
format!("pub const {name}: {ty} = {value};")
} else {
format!("pub const {name}: usize = {value};")
});
} else if line.starts_with(" pub const mjN") {
new.push(line.replace("pub ", ""));
} else if line.starts_with(" pub const mj") {
let after_mj_prefix = line.split_once('_').map_or(&*line, |(_, rest)| rest);
new.push(if after_mj_prefix.starts_with("2D") {
format!(" pub const {}", after_mj_prefix.replace("2D", "D2"))
} else if after_mj_prefix.chars().next().unwrap().is_ascii_digit() {
format!(" pub const X{after_mj_prefix}")
} else {
format!(" pub const {after_mj_prefix}")
});
} else {
new.push(line);
}
new
});
std::fs::write(&bindgen_rs, bindings.join("\n")).expect("Failed to write bindings to file");
}