<!-- summary: About the Build-time Library Manager -->

# mrbgems

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.

## Usage

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:

```ruby
conf.gem '/path/to/your/gem/dir'
```

You can also use a relative path to specify a gem.

```ruby
conf.gem 'examples/mrbgems/ruby_extension_example'
```

In that case,

- if your build configuration file is in the `build_config` directory, it's
  relative from `MRUBY_ROOT`.
- otherwise, it is relative from the directory where your build configuration is.

A remote GIT repository location for a GEM is also supported:

```ruby
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:

```ruby
conf.gem github: 'mruby/mruby', path: 'mrbgems/mruby-socket'
```

To use mrbgem from [mgem-list](https://github.com/mruby/mgem-list) use `:mgem` option:

```ruby
conf.gem :mgem => 'mruby-yaml'
conf.gem :mgem => 'yaml' # 'mruby-' prefix could be omitted
```

For specifying the commit hash to checkout use `:checksum_hash` option:

```ruby
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`:

```ruby
conf.gem github: 'me/mruby-yaml', branch: 'my-hacked-branch', canonical: true
```

If 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.

### Tweak the gem in your build configuration file

You can give blocks in the `conf.gem` call to make adjustments for
environments where the original gem does not expect them:

```ruby
conf.gem core: "mruby-bin-mirb" do |g|
  # For cross build to NetBSD
  g.linker.libraries = %w(edit termcap)
end
```

However, it should be used with caution, as it may deviate from the intent
of the gem's author.

### Gem Testing

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:

```ruby
conf.gem 'mruby-noisygem' do |g|
  g.skip_test = true
end
```

However, it is considered best practice to leave all tests enabled whenever
possible. A warning message will be generated for each gem with disabled tests.

## GemBox

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`:

```ruby
MRuby::GemBox.new do |conf|
  conf.gem "#{root}/mrbgems/mruby-time"
  conf.gem :github => 'masuidrive/mrbgems-example'
end
```

As 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:

```ruby
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.

```ruby
conf.gembox "#{ENV["HOME"]}/mygemboxes/custom"
```

There are two GemBoxes that ship with mruby: [default](../../mrbgems/default.gembox)
and [full-core](../../mrbgems/full-core.gembox). The [default](../../mrbgems/default.gembox) GemBox
contains several core components of mruby, and [full-core](../../mrbgems/full-core.gembox)
contains every gem found in the `mrbgems` directory.

## GEM Structure

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](#platform-ports-ports) below.

## Build process

mrbgems expects a specification file called `mrbgem.rake` inside of your
GEM directory. A typical GEM specification could look like this for example:

```ruby
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'
end
```

The 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.license` or `spec.licenses` (A single license or a list of them under which this GEM is licensed)
- `spec.author` or `spec.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:

```ruby
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')
end
```

The 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 `requirements` argument is same as in `add_dependency` method.

like following code:

```ruby
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'
end
```

In 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.

### include_paths and dependency

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.

### Settings for GEM build commands/tasks

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`.

```ruby
spec.build_settings do
  spec.cc.flags << "-any_flags"
end
```

**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.

## Platform Ports (ports/)

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:

```ruby
MRuby::Build.new do |conf|
  conf.toolchain
  conf.ports :posix     # selects ports/posix/ across all gems
end
```

`conf.ports` accepts multiple names as a fallback chain. Each gem
picks the first directory in the list that exists on its side:

```ruby
conf.ports :rp2040, :posix    # try rp2040 per-gem, else posix
```

Host 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.

### External HAL Provider Gems

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:

```ruby
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
end
```

When 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. Loading two gems that match the same `hal-<short>-*`
prefix is a build error.

The naming convention is the only signal -- no spec attribute,
no `add_dependency` flag is required. 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.

## C Extension

mruby can be extended with C. This is possible by using the C API to
integrate C libraries into mruby.

### Preconditions

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:

```c
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());
}
```

### Finalize

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:

```c
void
mrb_c_extension_example_gem_final(mrb_state* mrb) {
  free(someone);
}
```

### Example

```
+- c_extension_example/
    |
    +- README.md        (Optional)
    |
    +- src/
    |   |
    |   +- example.c    <- C extension source
    |
    +- test/
    |   |
    |   +- example.rb   <- Test code for C extension
    |
    +- mrbgem.rake      <- GEM specification
```

## Ruby Extension

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.

### Preconditions

none

### Example

```
+- ruby_extension_example/
    |
    +- README.md        (Optional)
    |
    +- mrblib/
    |   |
    |   +- example.rb   <- Ruby extension source
    |
    +- test/
    |   |
    |   +- example.rb   <- Test code for Ruby extension
    |
    +- mrbgem.rake      <- GEM specification
```

## C and Ruby Extension

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_.

### Preconditions

See C and Ruby example.

### 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
```

## Binary gems

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.

### Example

```
+- 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)
```
