mruby is an open-source project which is looking forward to each contribution. Contributors agree to license their contribution(s) under MIT license.
To make it easy to review and understand your change please keep the following things in mind before submitting your pull request:
- Work on the latest possible state of mruby/master
- Create a branch which is dedicated to your change
- Test your changes before creating a pull request (
rake test) - If possible write a test case which confirms your change
- Don't mix several features or bugfixes in one pull request
- Create a meaningful commit message
- Explain your change (i.e. with a link to the issue you are fixing)
- Use mrbgem to provide non ISO features (classes, modules and methods) unless you have a special reason to implement them in the core
If you discover a security vulnerability:
- High priority security vulnerabilities (RCE): Report via email to matz@ruby.or.jp
- VM crashes from valid Ruby code: Please report as regular bug reports on our issue tracker
For detailed guidance on what qualifies as a security issue and what doesn't, see SECURITY.md.
We use prek, a fast Rust-based pre-commit hook manager.
It reads the standard .pre-commit-config.yaml format.
Install prek following the installation guide,
then install the hooks with prek install.
Now prek will run automatically on git commit!
It's usually a good idea to run the hooks against all the files when adding new hooks (usually prek
will only run on the changed files during git hooks). Use prek run --all-files to check all files.
To run a single hook use prek run --all-files <hook_id>
To update use prek autoupdate
Sometimes you might need to skip one or more hooks which can be done with the SKIP environment variable.
$ SKIP=yamllint git commit -m "foo"
For convenience, we have added prek run --all-files, prek install and prek autoupdate
to both the Makefile and the Rakefile. Run them with:
make checkorrake checkmake checkinstallorrake checkinstallmake checkupdateorrake checkupdate
To configure hooks you can modify the config file .pre-commit-config.yaml.
We use GitHub Actions to run prek on every pull request.
We have both a Dockerfile and docker-compose.yml files in the repository root.
You can run these with the command line or use
Docker Desktop.
The Docker image is running Debian bullseye with Ruby and Python installed. You can build the Docker image with:
$ docker-compose build test
So far we just have one service: test. Running the default docker-compose
command will create the Docker image, spin up a container and then build and
run all mruby tests.
The default docker-compose command is:
$ docker-compose -p mruby run test
You can also use Make or Rake to run the default docker-compose
command from above:
make composetestrake composetest
List your Docker images with:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mruby-test latest ec60f9536948 29 seconds ago 1.29GBYou can also run any custom docker-compose command which will override
the default. For example to run prek run --all-files type:
$ docker-compose -p mruby run test prek run --all-files
For convenience, you can also run prek with:
make composecheckrake composecheck
The bonus of running prek with docker-compose is that you won't need
to install prek and the hooks on your local machine.
Note limitation: currently running prek with docker-compose we
skip the check-executables-have-shebangs hook.
Two more examples of custom docker-compose commands are:
$ docker-compose -p mruby run test ls$ docker-compose -p mruby run test rake doc:api
If you want to test using a different docker-compose YAML config file you
can use the -f flag:
$ docker-compose -p mruby -f docker-compose.test.yml run test
We are using prek to run codespell
to check code for common misspellings. We have a small custom dictionary file codespell.txt.
How to style your C and Ruby code which you want to submit.
mruby has no implicit conversion protocol, so do not dispatch to_int or
to_str to convert an argument. Ask for an integer with
mrb_ensure_integer_type() in C or Integer.__ensure(obj) in Ruby, and for a
string with a plain type check. Both take the value as an argument rather than
as a receiver: a conversion written as obj.something is a dispatch, and the
argument can redefine it.
Array.new(obj), ary[obj] and "s" * obj all raise TypeError for an object
that merely defines to_int, and String#match does the same for one that
defines to_str. A method that honours the protocol is more permissive than the
tree it sits in, which is a worse inconsistency than the difference from CRuby.
Nor can the gap be closed by honouring it everywhere: convert_type() in
src/object.c dispatches without CRuby's method_missing step, so a partial
protocol only trades one surprise for another.
CRuby's NUM2LONG() and StringValue() do convert, so a faithful port of a
CRuby method will arrive with the dispatch in it. Removing it is a deliberate
difference, not a defect in the port.
The core part (parser, bytecode-interpreter, core-lib, etc.) of mruby is written in the C programming language. Please note the following hints for your C code:
mruby should be highly portable to other systems and compilers. For this it is recommended to keep your code as close as possible to the C99 standard (http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1256.pdf).
Visual C++ is also an important target for mruby (supported version is 2013 or
later). For this reason features that are not supported by Visual C++ may not
be used (e.g. %z of strftime()).
NOTE: Old GCC requires -std=gnu99 option to enable C99 support.
The dependencies to libraries should be kept to an absolute minimum. This increases the portability but makes it also easier to cut away parts of mruby on-demand.
int
main(void)
{
...
}A C function whose job is to validate an argument, determine a type, normalize
a value, or read an internal representation should do that work in C and stop
there. Do not call back into the Ruby VM from it, whether through
mrb_funcall*(), mrb_yield*(), mrb_obj_new(), or a conversion that
dispatches a user-definable method. Re-entrant VM execution can move the stack
and invalidate pointers held across the call, and a redefined method can change
the outcome of a check that was meant to be authoritative.
When a method needs both, split it: keep the check in C and express the rest in
Ruby. String#match validates its pattern in C and compiles an accepted String
in mrblib. A method that takes a block generally belongs in Ruby for the same
reason; where speed matters, define a C fast path (conventionally named with a
__ prefix) and call it from a wrapping Ruby method.
The rule is about the responsibility of the function, not about the presence of
mrb_funcall*(). Entering the VM is legitimate where dispatch is itself the
specification: convert_type() in src/object.c for the to_proc protocol,
mrb_obj_new() in src/class.c running initialize and the inherited,
included and method_missing hooks, the default proc in src/hash.c, and
the to_a and to_enum delegations in src/array.c. When you rely on such a
case, say why in a comment or in the pull request, and check that exceptions
and break propagate, that intermediate state survives re-entry, and that live
values stay rooted (see mrb_gc_arena_restore(mrb, ai); // for mrb_funcall in
src/array.c).
Parts of the standard library of mruby are written in the Ruby programming language itself. Please note the following hints for your Ruby code:
mruby is currently targeting to execute Ruby code which complies to ISO/IEC 30170:2012 (https://www.iso.org/standard/59579.html), unless there's a clear reason, e.g. the latest Ruby has changed behavior from ISO.
When a method changes what it does based on an argument's type, read that type
with Module#=== rather than asking the argument. is_a?, kind_of?, nil?,
class and respond_to? are all ordinary methods and can be redefined, so an
argument that answers them dishonestly decides which branch runs. Module#===
is mrb_obj_is_kind_of() in C and cannot be redefined.
raise TypeError, "..." if String === arg # reads the real type
raise TypeError, "..." if arg.is_a?(String) # the argument decidesThe failures are quiet rather than obvious. A guard skipped this way lets an argument reach code written on the assumption that the guard held: recursing until the stack runs out, driving a loop with an unconverted value and giving back a plausible wrong answer, or naming a class the argument does not have.
This is about type dispatch, not about duck typing. Asking an object what it can do is still how a method decides how to use it, once the type question is settled.
- YARD - YARD is a documentation generation tool for the Ruby programming language
- yard-mruby - Document mruby sources with YARD
- yard-coderay - Adds coderay syntax highlighting to YARD docs