mrbgems is a library manager to integrate C and Ruby extensions in an easy and
standardised way into mruby. Conventionally, each mrbgem name is prefixed by
mruby-, e.g. mruby-time for a gem that provides Time class functionality.
You have to activate mrbgems explicitly in your build configuration. To add a gem, add the following line to your build configuration file, for example:
conf.gem '/path/to/your/gem/dir'You can also use a relative path to specify a gem.
conf.gem 'examples/mrbgems/ruby_extension_example'In that case,
- if your build configuration file is in the
build_configdirectory, it's relative fromMRUBY_ROOT. - otherwise, it is relative from the directory where your build configuration is.
A remote GIT repository location for a GEM is also supported:
conf.gem :git => 'https://github.com/masuidrive/mrbgems-example.git', :branch => 'master'
conf.gem :github => 'masuidrive/mrbgems-example', :branch => 'master'
conf.gem :bitbucket => 'mruby/mrbgems-example', :branch => 'master'NOTE: :bitbucket option supports only git. Hg is unsupported in this
version.
You can specify the subdirectory of the repository with :path option:
conf.gem github: 'mruby/mruby', path: 'mrbgems/mruby-socket'To use mrbgem from mgem-list use :mgem option:
conf.gem :mgem => 'mruby-yaml'
conf.gem :mgem => 'yaml' # 'mruby-' prefix could be omittedFor specifying the commit hash to checkout use :checksum_hash option:
conf.gem mgem: 'mruby-redis', checksum_hash: '3446d19fc4a3f9697b5ddbf2a904f301c42f2f4e'If there are missing dependencies, mrbgem dependencies solver will reference mrbgem from the core or mgem-list.
Note that if more than one git-based gem has the same base name
(i.e. the default checkout directory name), it is (now) an error
UNLESS they have the same repository URL, branch name and
commit-id (i.e. checksum hash). You can bypass this by explicitly
importing your preferred version first and setting the
canonical: option to true:
conf.gem github: 'me/mruby-yaml', branch: 'my-hacked-branch', canonical: trueIf you do this, the system will (mostly) silently ignore other attempts to clone a gem with this name.
Note that this only affects cloning the gem from git. It does not resolve version conflicts. If the version as specified in the gem's rakefile is incompatible with a dependency, your build will still fail.
You can give blocks in the conf.gem call to make adjustments for
environments where the original gem does not expect them:
conf.gem core: "mruby-bin-mirb" do |g|
# For cross build to NetBSD
g.linker.libraries = %w(edit termcap)
endHowever, it should be used with caution, as it may deviate from the intent of the gem's author.
If you enable unit tests in your build with enable_test, tests will be
generated for all gems and their dependencies by default. If necessary, it is
possible to suppress tests for a specific gem like so:
conf.gem 'mruby-noisygem' do |g|
g.skip_test = true
endHowever, it is considered best practice to leave all tests enabled whenever possible. A warning message will be generated for each gem with disabled tests.
There are instances when you wish to add a collection of mrbgems into mruby at
once, or be able to substitute mrbgems based on configuration, without having to
add each gem to your build configuration file. A packaged collection of mrbgems
is called a GemBox. A GemBox is a file that contains a list of mrbgems to load
into mruby, in the same format as if you were adding them to the build config
via config.gem, but wrapped in an MRuby::GemBox object. GemBoxes are
loaded into mruby via config.gembox 'boxname'.
Below we have created a GemBox containing mruby-time and mrbgems-example:
MRuby::GemBox.new do |conf|
conf.gem "#{root}/mrbgems/mruby-time"
conf.gem :github => 'masuidrive/mrbgems-example'
endAs mentioned, the GemBox uses the same conventions as MRuby::Build. The GemBox
must be saved with a .gembox extension inside the mrbgems directory to be
picked up by mruby.
To use this example GemBox, we save it as custom.gembox inside the mrbgems
directory in mruby, and add the following to your build configuration file inside
the build block:
conf.gembox 'custom'This will cause the custom GemBox to be read in during the build process,
adding mruby-time and mrbgems-example to the build.
If you want, you can put GemBox outside the mruby directory. In that case you must specify an absolute path like below.
conf.gembox "#{ENV["HOME"]}/mygemboxes/custom"There are two GemBoxes that ship with mruby: default
and full-core. The default GemBox
contains several core components of mruby, and full-core
contains every gem found in the mrbgems directory.
The maximal GEM structure looks like this:
+- GEM_NAME <- Name of GEM
|
+- README.md <- Readme for GEM
|
+- mrbgem.rake <- GEM Specification
|
+- include/ <- Header for Ruby extension (will exported)
|
+- mrblib/ <- Source for Ruby extension
|
+- src/ <- Source for C extension
|
+- ports/<name>/ <- Platform-specific C sources (see Platform Ports)
|
+- tools/ <- Source for Executable (in C)
|
+- test/ <- Test code (Ruby)
The mrblib directory contains pure Ruby files to extend mruby. The src directory
contains C/C++ files to extend mruby. The include directory contains C/C++ header
files. The test directory contains C/C++ and pure Ruby files for testing purposes
which will be used by mrbtest. mrbgem.rake contains the specification
to compile C and Ruby files. README.md is a short description of your GEM.
The optional ports/<name>/ directories hold platform-specific C sources
selected at build time; see Platform Ports below.
mrbgems expects a specification file called mrbgem.rake inside of your
GEM directory. A typical GEM specification could look like this for example:
MRuby::Gem::Specification.new('c_and_ruby_extension_example') do |spec|
spec.license = 'MIT'
spec.author = 'mruby developers'
spec.summary = 'Example mrbgem using C and Ruby'
endThe mrbgems build process will use this specification to compile Object and Ruby
files. The compilation results will be added to lib/libmruby.a. This file exposes
the GEM functionality to tools like mruby and mirb.
The following properties can be set inside your MRuby::Gem::Specification for
information purpose:
spec.licenseorspec.licenses(A single license or a list of them under which this GEM is licensed)spec.authororspec.authors(Developer name or a list of them)spec.version(Current version)spec.description(Detailed description)spec.summary- One line short description of mrbgem.
- Printed in build summary of rake when set.
spec.homepage(Homepage)spec.requirements(External requirements as information for user)
The license and author properties are required in every GEM!
In case your GEM is depending on other GEMs please use
spec.add_dependency(gem, *requirements[, default_get_info]) like:
MRuby::Gem::Specification.new('c_and_ruby_extension_example') do |spec|
spec.license = 'MIT'
spec.author = 'mruby developers'
# Add GEM dependency mruby-parser.
# The version must be between 1.0.0 and 1.5.2 .
spec.add_dependency('mruby-parser', '>= 1.0.0', '<= 1.5.2')
# Use any version of mruby-uv from GitHub.
spec.add_dependency('mruby-uv', '>= 0.0.0', :github => 'mattn/mruby-uv')
# Use latest mruby-onig-regexp from GitHub. (version requirements can be omitted)
spec.add_dependency('mruby-onig-regexp', :github => 'mattn/mruby-onig-regexp')
# You can add extra mgems active only on test
spec.add_test_dependency('mruby-process', :github => 'iij/mruby-process')
endThe version requirements and default gem information are optional.
Version requirement supports following operators:
- '=': is equal
- '!=': is not equal
- '>': is greater
- '<': is lesser
- '>=': is equal or greater
- '<=': is equal or lesser
- '~>': is equal or greater and is lesser than the next major version
- example 1: '~> 2.2.2' means '>= 2.2.2' and '< 2.3.0'
- example 2: '~> 2.2' means '>= 2.2.0' and '< 3.0.0'
When more than one version requirements is passed, the dependency must satisfy all of it.
You can have default gem to use as dependency when it's not defined in your build configuration.
When the last argument of add_dependency call is Hash, it will be treated as default gem information.
Its format is same as argument of method MRuby::Build#gem, except that it can't be treated as path gem location.
When a special version of dependency is required,
use MRuby::Build#gem in the build configuration to override default gem.
If you have conflicting GEMs use the following method:
spec.add_conflict(gem, *requirements)- The
requirementsargument is same as inadd_dependencymethod.
- The
like following code:
MRuby::Gem::Specification.new 'some-regexp-binding' do |spec|
spec.license = 'BSD'
spec.author = 'John Doe'
spec.add_conflict 'mruby-onig-regexp', '> 0.0.0'
spec.add_conflict 'mruby-hs-regexp'
spec.add_conflict 'mruby-pcre-regexp'
spec.add_conflict 'mruby-regexp-pcre'
endIn case your GEM has more complex build requirements you can use the following options additionally inside your GEM specification:
spec.cc.flags(C compiler flags)spec.cc.defines(C compiler defines)spec.cc.include_paths(C compiler include paths)spec.linker.flags(Linker flags)spec.linker.libraries(Linker libraries)spec.linker.library_paths(Linker additional library path)spec.bins(Generate binary file)spec.rbfiles(Ruby files to compile)spec.objs(Object files to compile)spec.test_rbfiles(Ruby test files for integration into mrbtest)spec.test_objs(Object test files for integration into mrbtest)spec.test_preload(Initialization files for mrbtest)
You also can use spec.mruby.cc and spec.mruby.linker to add extra global parameters for the compiler and linker.
Your GEM can export include paths to another GEMs that depends on your GEM.
By default, /...absolute path.../{GEM_NAME}/include will be exported.
So it is recommended not to put GEM's local header files on include/.
These exports are transitive. For example: when B depends on C and A depends on B, A will get include paths exported by C.
Exported include_paths are automatically appended to GEM local include_paths by rake.
You can use spec.export_include_paths accessor if you want more complex build.
When the block argument passed to MRuby::Gem::Specification.new is executed,
the GEM build commands/tasks for the MRuby::Build instance may not yet be finalized.
In most cases, modifying the GEM build commands/tasks within the block passed to
MRuby::Gem::Specification.new is not a problem.
However, you may need to perform GEM build commands/tasks after the GEM build
commands/tasks for the MRuby::Build instance have been finalized.
In such cases, you can achieve this by passing a block argument to
MRuby::Gem::Specification#build_settings within the block passed to
MRuby::Gem::Specification.new.
spec.build_settings do
spec.cc.flags << "-any_flags"
endThe block passed to MRuby::Gem::Specification#build_settings is called in the order of the dependent GEMs,
after all setup blocks for GEMs including dependencies have been called.
NOTE: Using the build_settings method will cause GEM's all build command settings
directly written in the block passed to MRuby::Gem::Specification.new to be ignored.
A GEM announces a capability to the rest of the build by adding to
spec.build.defines, which becomes a -D on every translation unit:
spec.build.defines << "HAVE_MRUBY_IO_GEM"MRuby::Build#has_define? is how another GEM reads one back:
spec.build_settings do
spec.cc.flags << "-any_flags" if build.has_define?("MRB_UTF8_STRING")
endIt reports a define whether the build configuration asked for it or a GEM
contributed it, and matches on the name alone, so a define added as "FOO=1"
answers has_define?("FOO"). The -D belongs to the compiler flag and not to
the define, so it is no part of the name to ask under.
It has to be asked from build_settings and not from the block passed to
MRuby::Gem::Specification.new. A GEM contributes its defines when its own
block runs, and the blocks run in the order the GEMs were added, so during
that phase the answer would depend on how far down the list the caller sits.
has_define? raises there rather than hand back an answer that is right for
some GEM orders and wrong for others.
A gem may ship platform-specific C sources under ports/<name>/
subdirectories. The build configuration selects which port name(s)
are active via conf.ports, and each gem compiles the sources of
the first matching ports/<name>/ it ships:
MRuby::Build.new do |conf|
conf.toolchain
conf.ports :posix # selects ports/posix/ across all gems
endconf.ports accepts multiple names as a fallback chain. Each gem
picks the first directory in the list that exists on its side:
conf.ports :rp2040, :posix # try rp2040 per-gem, else posixHost builds auto-detect :posix or :win when conf.ports is
not set. Sources outside ports/ (i.e. src/) are always
compiled regardless of the port selection.
The selected port's ports/<name>/include/ directory is an include
path, for the gem's own sources and for every gem that depends on
it, as the gem's own include/ is. That is where a port says what
it implements: a header of feature macros, which the gem's HAL
header includes and its src/ sources read. Whether a method exists
is the port's to answer, since the port is the only thing that
knows, and src/ asks the macro rather than the platform:
/* ports/posix/include/dir_hal_features.h */
#define MRB_HAL_DIR_HAS_CHROOT
/* include/dir_hal.h */
#include "dir_hal_features.h"
#ifdef MRB_HAL_DIR_HAS_CHROOT
int mrb_hal_dir_chroot(mrb_state *mrb, const char *path);
#endifOne macro guards the prototype, the port's implementation and the
method definition in src/, so a port that declares a capability and
forgets to implement it fails to link, and one that declares nothing
owes nothing. A bundled gem that carries such a header keeps it
under ports/posix/include/ and ports/win/include/, and its README
lists what each port declares.
Only include/ is exported. A header anywhere else under
ports/<name>/ is the port's own, reached by its sources through a
relative include and by nothing else, the way a header under src/
is the gem's own; it is neither on another gem's include path nor
written into the amalgamated mruby.h.
A name a port exports is its gem's alone. The name is searched on
the include path of every gem that depends on the port's gem, where
the other dependencies' headers sit too, so the build refuses a
build in which another gem exports the same name, from its
include/ or from its port, rather than let the compiler pick
whichever it finds first. The bundled ports name the header after
the HAL header it serves, <short>_hal_features.h beside
<short>_hal.h.
The macros are the port's to define and nobody else's. A build that
wants less than the port offers uses the gem's veto define where the
gem provides one (MRB_NO_IO_POPEN for mruby-io), which the HAL
header applies after the port has spoken; a build that defines an
MRB_HAL_*_HAS_* macro itself on a port that does not implement it
gets an undefined mrb_hal_* at link time, which is the port's
answer. A build that supplies the HAL from its own sources, with no
bundled port and no provider gem, puts its own <short>_hal_features.h
on the include path with conf.cc.include_paths and declares there
what its sources implement.
A third-party gem may replace another gem's bundled port at build
time. A gem whose name matches hal-<short>-<conf> is recognized
as the external HAL provider for the target gem whose name's last
--separated segment is <short>. For example, hal-task-glib
overrides the HAL of mruby-task; hal-io-uring would override
mruby-io. The HAL provider must depend on its target so it can
#include the target's HAL header:
MRuby::Gem::Specification.new('hal-task-glib') do |spec|
spec.license = 'MIT'
spec.author = 'Your Name'
spec.summary = 'GLib HAL for mruby-task'
spec.add_dependency 'mruby-task', core: 'mruby-task'
# src/ contains the HAL implementation
endWhen a matching HAL provider gem is present in the build, the
target gem's ports/<conf.ports>/ sources are dropped from the
build automatically. The HAL provider's own sources supply the
implementation instead, avoiding duplicate symbol errors at link
time, and its include/ takes the port's include/ place as the
include path the feature header is found on, whether or not a
bundled port matched the build. Loading two gems that match the same
hal-<short>-* prefix is a build error, and so is a provider that
leaves out a header the port it replaces exports, since the target's
HAL header includes that header by name.
The naming convention is the only signal -- no spec attribute and
no HAL-specific add_dependency option is required. The ordinary
dependency on the target gem still is, since the provider includes
the target's HAL header. A gem author who wants to contribute an
additional bundled port upstream sends a PR adding
<target-gem>/ports/<name>/; a gem author who prefers to ship out
of tree publishes a hal-<short>-<conf> gem instead.
mruby can be extended with C. This is possible by using the C API to integrate C libraries into mruby.
mrbgems expects that you have implemented a C method called
mrb_YOURGEMNAME_gem_init(mrb_state). YOURGEMNAME will be replaced
by the name of your GEM. If you call your GEM c_extension_example, your
initialisation method could look like this:
void
mrb_c_extension_example_gem_init(mrb_state* mrb) {
struct RClass *class_cextension = mrb_define_module(mrb, "CExtension");
mrb_define_class_method(mrb, class_cextension, "c_method", mrb_c_method, MRB_ARGS_NONE());
}mrbgems expects that you have implemented a C method called
mrb_YOURGEMNAME_gem_final(mrb_state). YOURGEMNAME will be replaced
by the name of your GEM. If you call your GEM c_extension_example, your
finalizer method could look like this:
void
mrb_c_extension_example_gem_final(mrb_state* mrb) {
free(someone);
}+- c_extension_example/
|
+- README.md (Optional)
|
+- src/
| |
| +- example.c <- C extension source
|
+- test/
| |
| +- example.rb <- Test code for C extension
|
+- mrbgem.rake <- GEM specification
mruby can be extended with pure Ruby. It is possible to override existing
classes or add new ones in this way. Put all Ruby files into the mrblib
directory.
none
+- ruby_extension_example/
|
+- README.md (Optional)
|
+- mrblib/
| |
| +- example.rb <- Ruby extension source
|
+- test/
| |
| +- example.rb <- Test code for Ruby extension
|
+- mrbgem.rake <- GEM specification
mruby can be extended with C and Ruby at the same time. It is possible to
override existing classes or add new ones in this way. Put all Ruby files
into the mrblib directory and all C files into the src directory.
mruby codes under mrblib directory would be executed after gem init C
function is called. Make sure mruby script depends on C code and
C code doesn't depend on mruby script.
See C and Ruby example.
+- c_and_ruby_extension_example/
|
+- README.md (Optional)
|
+- mrblib/
| |
| +- example.rb <- Ruby extension source
|
+- src/
| |
| +- example.c <- C extension source
|
+- test/
| |
| +- example.rb <- Test code for C and Ruby extension
|
+- mrbgem.rake <- GEM specification
Some gems can generate executables under bin directory. Those gems are called
binary gems. Names of binary gems are conventionally prefixed by mruby-bin,
e.g. mruby-bin-mirb and mruby-bin-strip.
To specify the name of executable, you need to specify spec.bins in the
mrbgem.rake. The entry point main() should be in the C source file under
tools/<bin>/*.c where <bin> is a name of the executable. C files under the
<bin> directory are compiled and linked to the executable, but not included in
libmruby.a, whereas files under mrblib and src are.
It is strongly recommended not to include mrblib and src directories in the
binary gems, to separate normal gems and binary gems.
+- mruby-bin-example/
|
+- README.md (Optional)
|
+- bintest/
| |
| +- example.rb <- Test code for binary gem
|
+- mrbgem.rake <- Gem specification
|
+- mrblib/ <- Source for Ruby extension (Optional)
|
+- src/ <- Source for C extension (Optional)
|
+- tools/
|
+- example/ <- Executable name directory
|
+- example.c <- Source for Executable (includes main)