Project

beni

0.0
The project is in a healthy, maintained state
Vendors mruby and builds libmruby.a through a Rake task library, with generated build configs for customization. Aims to grow into a full mruby dependency manager.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

~> 13.0
 Project Readme

Beni

beni gives Rust developers a magnus-like experience for mruby: a Ruby gem manages the mruby build chain, and Rust crates expose a safe, typed API over the archive it builds. Extracted from the kobako project; APIs follow 0.x semver semantics and may still evolve between minor versions.

beni gem ──rake beni:build──▶ archive + sidecar ──▶ beni-sys ──▶ beni crate

Warning

The beni crate does not yet cover the full mruby C API. Anything missing stays reachable through the unsafe beni::sys escape hatch — issue reports for the gaps you hit are welcome.

Packages

All four packages release in lockstep under a single version.

Package Registry Role
beni gem rubygems.org Rake tasks + DSL config that download mruby and build the archive
beni-sys crate crates.io bindgen FFI surface over the mruby C API
beni crate crates.io safe typed wrapper over beni-sys, aligned with magnus idioms
beni-macros crate crates.io the beni crate's wrap, TypedData, and InlineStruct macros, used through beni

Getting started

Build the mruby archive with the gem

Add beni to your Gemfile and install the task library in your Rakefile:

require "beni/tasks"

Beni::Tasks.new
rake beni:build

This downloads the default mruby release, builds it with mruby's untouched upstream default config, and stages vendor/mruby/build/host/lib/ with the archive and its libmruby.flags.mak compile-flags sidecar — everything the crates need. The archive's own file name follows the toolchain that built it (libmruby.a, libmruby.lib under MSVC), and the sidecar names it.

To tune the build, declare a config path and generate the seed:

Beni::Tasks.new do
  build_config "build_config/mruby.rb"
end

rake beni:config writes a self-contained copy of the upstream default config to that path. The file is yours to edit — add targets, gems, or defines; beni never rewrites it.

Embed mruby from Rust

Add the beni crate to your Cargo.toml, then point archive discovery at the vendor tree the gem staged:

BENI_VENDOR_DIR=$PWD/vendor cargo build

A crate ships its Ruby surface as a Gem and installs it during interpreter setup:

use beni::{method, Error, Gem, Module, Mrb, Value};

fn answer(_mrb: &Mrb, _self: Value) -> i32 {
    42
}

struct WidgetGem;

impl Gem for WidgetGem {
    fn init(mrb: &Mrb) -> Result<(), Error> {
        let widget = mrb.define_class(c"Widget", mrb.class_object())?;
        widget.define_method(mrb, c"answer", method!(answer, 0))?;
        Ok(())
    }
}

fn main() {
    let mrb = Mrb::open().expect("mruby interpreter");
    mrb.init_gem::<WidgetGem>().expect("Widget surface");
    // Widget#answer now returns 42 to any Ruby code the interpreter runs.
}

An interpreter is carried between threads but never reached from two at once. Mrb is Send and not Sync: handing one to another thread compiles, while sharing it needs a Mutex of your own. A handle stays on the thread that made it; wrap it in beni::value::Opaque to carry it along, and unwrap it there with Mrb::get_inner.

Every build needs the archive: with no discovery variable set the build fails naming the ones it looked for, rather than handing you a crate whose every call aborts later. Any C API the typed wrapper does not cover stays reachable through the unsafe beni::sys escape hatch.

Cross-compile for wasm32-wasip1

Declare a wasi target referencing the wasi-sdk toolchain:

Beni::Tasks.new do
  build_config "build_config/mruby.rb"

  target :host
  target :wasi do
    toolchain "wasi-sdk"
  end
end

and append the cross build to the generated config:

MRuby::CrossBuild.new("wasi") do |conf|
  conf.toolchain :wasi
end

conf.toolchain :wasi resolves to the wasi toolchain file beni:vendor:setup stages into the mruby tree whenever wasi-sdk is selected — the cross-compile settings ship with beni and update with it. After rake beni:build, name the staged archive and the wasi-sdk root explicitly for the cargo side (a cross-compiled cargo target never reads the vendor tree on its own):

MRUBY_LIB_DIR=$PWD/vendor/mruby/build/wasi/lib \
WASI_SDK_PATH=$PWD/vendor/wasi-sdk \
cargo build --target wasm32-wasip1

Prism Source

An mruby release whose compiler gem parses with Prism keeps Prism in a git submodule, which the release tarball leaves out. Declare the prism toolchain with that submodule's commit; the definition alone selects it, and beni:vendor:setup stages it into the mruby tree:

Beni::Tasks.new do
  version "<mruby release>"

  toolchain "prism" do
    version "<ruby/prism commit>"
    sha256 "…"
  end
end

The commit is the one the release records for mrbgems/mruby-compiler/lib/prism.

Toolchain

beni targets plain mruby and is not bound to WebAssembly. rust-toolchain.toml keeps wasm32-wasip1 only as a build-verification target for downstream wasi consumers (kobako). For that target two versions move in lockstep, in both this repo and kobako; host builds are unaffected.

Version Lockstep reason
Rust channel crt1-command.o references __wasi_init_tp from 1.96 on
wasi-sdk 33 its libc.a supplies __wasi_init_tp

Development

After checking out the repo, run bin/setup to install dependencies, then bundle exec rake for the default gate (tests + RuboCop + Steep). The repo dogfoods its own gem: the Rakefile wires Beni::Tasks with the validation config build_config/mruby.rb (host + wasi targets), and a repo-local rake chain verifies the crates compile against a real archive on both the host target and wasm32-wasip1:

bundle exec rake rust:verify   # beni:build + check/test (host) + check (wasm32)

Behavior contracts live in SPEC.md — the source of truth the implementation follows.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/elct9620/beni.

License

The gem and crates are available as open source under the terms of the Apache License 2.0.