Skip to content

elct9620/beni

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

279 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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 resulting libmruby.a. Extracted from the kobako project; APIs follow 0.x semver semantics and may still evolve between minor versions.

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 three packages release in lockstep under a single version.

Package Registry Role
beni gem rubygems.org Rake tasks + DSL config that download mruby and build libmruby.a
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

Getting started

Build libmruby.a 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 pinned mruby release, builds it with mruby's untouched upstream default config, and stages vendor/mruby/build/host/lib/ with libmruby.a and its libmruby.flags.mak compile-flags sidecar — everything the crates need.

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.object_class())?;
        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.
}

With no archive discovery variable set, a host build compiles in placeholder mode: cargo check passes, no FFI surface is exported, and Mrb::open returns an error — so beni is safe to take as a transitive dependency. 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

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 the Rust channel and the wasi-sdk version move in lockstep (the wasm32-wasip1 crt1-command.o references __wasi_init_tp from Rust 1.96 onward; wasi-sdk 33's libc.a supplies that symbol) — bump the pair together, in both this repo and kobako. Host builds are unaffected by the pairing.

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 libmruby.a 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.

About

mruby vendoring and build toolchain

Topics

Resources

License

Stars

Watchers

Forks

Contributors