This articles was published on 2012-12-10

mrbgems in HEAD

[2013-05-17] This post is obsolete due to big changes in the mruby build process. mrbgems is now an integral part of the rake build process.

Wonderful news. The work of the last three month has finally arrived in the mruby HEAD. The mrbgems patch was merged last friday and over the weekend some additional improvements were added. Also some libraries were already ported (like mattn’s mruby-md5).

Due to the reason that I saw that someone already provided an introduction to mrbgems in japanese, here a short summary from me.

Why mrbgems?

First lets clarify the question why did I start mrbgems. Most of you should know RubyGem. For the one who don’t: RubyGem is a library manager for the Ruby programming language. It can maintain versions and dependencies of source code.

Something similar would be nice for mruby for several reasons. The first one is of course, that we don’t want’t to rewrite every library over and over again. In case someone has already implemented a feature we would love to include this just into our project instead of re-writing and -integrating it. To make this possible it is kind of essential to have a standardized library structure. This is where mrbgems comes into the game. mrbgems defines a standardized way to build your own extension for mruby. Your extension is typically written in Ruby or C or both. Actually the current implementation doesn’t stop you to use whatever language you prefer to implement your extension.

Differences between RubyGem and mrbgems

One important thing to keep in mind is that RubyGem has from an implementation point of view nothing in common with mrbgems. I have chosen the name only to visualise that this is a library manager too.

So why don’t we just use RubyGem? First and foremost, RubyGem is to bloated. It has a lot of nice feature which makes our live easier with the MRI, JRuby and Rubinius. But for the embedded world most of these “advantages” are kind of disadvantages. We would need to implement a dynamic-loading functionality for example, which mruby doesn’t support by design. Something like require just doesn’t exist in mruby, so there is no way for our GEM manager to hook up into this feature. Another reason to not use RubyGem is the different C API and the reduced Ruby standard library. A normal GEM package which works fine with every 1.9 compatible Ruby interpreter is likely to never work by default with mruby.

Instead what we would love to have is no overhead at all if we don’t use mrbgems. And when we use it, the footprint should be as tiny as possible. RubyGem can’t provide that – mrbgems can.

How does it work

mrbgems is deeply integrated into the build process of the interpreter. A so called GEM in the mruby sense is actually compiled into your program. After building your mruby project you will find an archive file in every GEM folder with the naming convention: mrb-GEMNAME-lib.a. These archive files are integrated into the mruby and mirb executable. And in case you want to integrate the mruby interpreter into your own program you have to integrate these archive files too. The build process of your GEM is a black box for mruby. The only requirement is, that a make call creates this archive file in your GEM directory. A make clean is suppose to clean all build files.

To make this process a little bit more easier for common cases, I created a Makefile which you can include into your GEM Makefile. It provides lots of helpers in case you have a:

  • pure Ruby extension
  • pure C extension
  • a mixed extension with Ruby and C code

Just include mrbgems/Makefile4gem with the following helper rules:

  • gem-rb-files and gem-clean-rb-files Rules for pure Ruby extensions
  • gem-c-files and gem-clean-c-files Rules for pure C extensions
  • gem-c-and-rb-files and gem-clean-c-and-rb-files Rules for mixed Ruby and C extensions

If we take mattn’s mruby-md5 example, a GEM Makefile could look like this for a pure C extension:

GEM := mruby-md5

include $(MAKEFILE_4_GEM)

GEM_C_FILES := $(wildcard $(SRC_DIR)/*.c)
GEM_OBJECTS := $(patsubst %.c, %.o, $(GEM_C_FILES))

gem-all : $(GEM_OBJECTS) gem-c-files

gem-clean : gem-clean-c-files

This Makefile uses the C helper rules. All of this works due to the reason that the mruby build process will call your GEM Makefile with the following arguments:

make -C /Users/daniel/work_dir/mruby-md5 --no-print-directory CC='gcc' LL='gcc' CFLAGS='-I. -I/Users/daniel/work_dir/mruby/include -I/Users/daniel/work_dir/mruby/src -I/Users/daniel/work_dir/mruby-md5/include ' MRUBY_ROOT='/Users/daniel/work_dir/mruby' MAKEFILE_4_GEM='/Users/daniel/work_dir/mruby/mrbgems/Makefile4gem'

So inside of your GEM Makefile you can work with the following arguments:

  • CC
  • LL
  • CFLAGS: The global flags for the C compiler including the include directories of mruby and all other GEMs
  • MRUBY_ROOT: The root path of mruby
  • MAKEFILE_4_GEM: The helper Makefile

Inside of you C code it is essential to define an initialization function:

void
mrb_mruby_md5_gem_init(mrb_state* mrb) {
  _class_md5 = mrb_define_module(mrb, "MD5");
  mrb_define_class_method(mrb, _class_md5, "md5_hex", mrb_md5_hex, ARGS_REQ(1));
}

The naming convention of this function is mrb_GEMNAME_gem_init(mrb_state). The mruby build process will take care to call this method inside of the mruby and mirb executable.

Testing

There is also a way to automatically integrate tests of your GEMs into the mruby build process. Just create a test directory with as many Ruby files as you please. A test could look like this:

assert('MD5 Hash for old example') do
  MD5::md5_hex('お前はどこのワカメじゃ') == "43820f48a8506c8e2fae6f8558971920"
end

Just call assert with a block. In case the return value is true the test is considered as passed. Otherwise it is considered as failed.

Header Files

In case your GEM needs header files you can create a folder include. In this folder you just throw all your header files. This folder will be passed to all GEMs. So that you can integrate other GEM features into your own one.

Pure Ruby extension

For a GEM completly written in Ruby it is actually even more easier. Just create a mrblib directory and throw your Ruby files into it. The build process will handle the rest for you with the right Makefile helpers.

Further Documentation

Last but not least there is quite a bit of documentation I have written under doc/mrbgems. I also created three example projects which are showing how to implement different extensions.

Next steps

There is a lot to do still. mrbgems is still early alpha and has to be tested intensively. Specially WIN32 platforms are not yet tested due to the reason that I don’t own them. At the moment everyone who ports his mruby library to this new standard will help to improve. mattn and masuidrive gave already a lot of input just by working on a port. At the moment masuidrive is actually working on a way to include GEM data into the mrb_state for his mobiruby. In this way GEMs could communicate with each other. Building a dependency structure is another task which is still open and needed for mattn’s libuv binding. As I see also the IIJ mruby port plans already to port their work to mrbgems.