Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 6 additions & 11 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ jobs:
rustup component add rustfmt ### required for the build script to work ###
[ "${{ matrix.arch }}" = 'aarch64' ] && sudo apt update && sudo apt install -y g++-aarch64-linux-gnu || :

- name: check fails without MUJOCO_DIR
- name: build fails without MUJOCO_DIR
env:
CARGO_BUILD_TARGET: ${{ matrix.arch }}-unknown-linux-gnu
run: |
if cargo build; then
echo 'cargo check succeeded without mujoco, which is unexpected.'
echo 'cargo build succeeded without MUJOCO_DIR, which is unexpected.'
exit 1
else
echo 'cargo check failed as expected without mujoco.'
echo 'cargo build failed as expected without mujoco.'
fi

- name: install mujoco and set MUJOCO_DIR
Expand All @@ -46,17 +46,12 @@ jobs:
echo "MUJOCO_DIR=$HOME/.mujoco/mujoco-3.3.2" >> $GITHUB_ENV
echo "LD_LIBRARY_PATH=$HOME/.mujoco/mujoco-3.3.2/lib:$LD_LIBRARY_PATH" >> $GITHUB_ENV

- name: check succeeds with MUJOCO_DIR
- name: build succeeds with MUJOCO_DIR
env:
CARGO_BUILD_TARGET: ${{ matrix.arch }}-unknown-linux-gnu
run: |
if cargo build; then
echo 'cargo check succeeded with mujoco, as expected.'
else
echo 'cargo check failed with mujoco, which is unexpected.'
echo "[DEBUG] bindgen.rs content:" && cat ./src/bindgen.rs
exit 1
fi
cargo build
cargo build --features bindgen

test:
strategy:
Expand Down
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ keywords = ["mujoco", "rl", "ml", "physics", "robotics"]
categories = ["api-bindings", "science::robotics", "simulation"]

[build-dependencies]
bindgen = "0.72"
bindgen = { optional = true, version = "0.72" }

[dev-dependencies]
glfw = "0.60"

[features]
bindgen = ["dep:bindgen"] # run bindgen at build time, instead of using pre-generated bindgen.rs
101 changes: 55 additions & 46 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,66 +1,76 @@
use std::{env, io::BufRead, path::Path, process::{Command, Stdio}};
fn main() {
if option_env!("DOCS_RS").is_some() { return }

#[derive(Debug)]
struct TrimUnderscoreCallbacks;
impl bindgen::callbacks::ParseCallbacks for TrimUnderscoreCallbacks {
fn item_name(&self, item_info: bindgen::callbacks::ItemInfo) -> Option<String> {
/*
This finally oversets non-suffixed name (like `mjData`)
to/over its original name (like `mjData_`).
*/
item_info.name.strip_suffix('_').map(str::to_owned)
}
let mujoco_dir = std::env::var("MUJOCO_DIR").expect("MUJOCO_DIR environment variable is not set");
let mujoco_dir = std::path::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();

println!("cargo:rustc-link-search={mujoco_lib}");
println!("cargo:rustc-link-lib=dylib=mujoco");

#[cfg(feature = "bindgen")]
bindgen(mujoco_dir);
}

#[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> {
/*
This generates const like:
```
pub const mjNTEXROLE: mjtTextureRole = mjtTextureRole::mjNTEXROLE;
```
at module top for `mjN*` variants.
*/
original_variant_name.starts_with("mjN").then_some(bindgen::callbacks::EnumVariantCustomBehavior::Constify)
#[cfg(feature = "bindgen")]
fn bindgen(mujoco_dir: impl AsRef<std::path::Path>) {
#[derive(Debug)]
struct TrimUnderscoreCallbacks;
impl bindgen::callbacks::ParseCallbacks for TrimUnderscoreCallbacks {
fn item_name(&self, item_info: bindgen::callbacks::ItemInfo) -> Option<String> {
/*
This finally oversets non-suffixed name (like `mjData`)
to/over its original name (like `mjData_`).
*/
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> {
/*
This generates const like:
```
pub const mjNTEXROLE: mjtTextureRole = mjtTextureRole::mjNTEXROLE;
```
at module top for `mjN*` variants.
*/
original_variant_name.starts_with("mjN").then_some(bindgen::callbacks::EnumVariantCustomBehavior::Constify)
}
}
}

fn main() {
if option_env!("DOCS_RS").is_some() { return }

/*
* The hand-process step after `bindgen` generation assumes that
* The hand-processing step after `bindgen` generation requires
* `cargo fmt` (and then it's automatically applied to the
* bindgen's raw output, and the hand-processing correctly works).
* This is a **requirement** for the build script to continue.
*/
assert!(
Command::new("cargo").args(["help", "fmt"]).stdout(Stdio::null()).status().is_ok_and(|s| s.success()),
std::process::Command::new("cargo")
.args(["help", "fmt"])
.stdout(std::process::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 mujoco_dir = mujoco_dir.as_ref();
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();

let src_dir = std::path::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)
Expand Down Expand Up @@ -98,8 +108,7 @@ fn main() {

using bindgen, so we do them manually...
*/
let bindings = bindings
.lines()
let bindings = std::io::BufRead::lines(&*bindings)
.map(Result::unwrap)
.fold(Vec::with_capacity(bindings.len()), |mut new, line| {
if line.starts_with("pub struct mjt") {
Expand Down