In pursuing a gamedev project, I needed to implement a small scripting language for some logic. Lua is often the go-to choice for this, especially because of the ease of getting started. I opted for Ruby, or more specifically, MRuby. I weighed two factors when making this choice. First, I’m deeply familiar with Ruby because of my professional career, and second, MRuby has always intrigued me. That said, documentation and guides found across the internet are lacking. After much trawling the internet, I created a successful implementation compiled on Windows and macOS. I want to dive into my exploration of this project and ultimately leave you, the reader, with the same ability to compile and use MRuby yourself. This first article will cover macOS
As you read this guide, you may notice that I am using command-line tools for the respective operating systems. CMake and Makefiles are popular among C developers. It will be better to avoid abstractions to eliminate confusion. Experienced developers familiar with CMake and Makefiles will be able to make necessary changes to their setup. However, new developers starting with C or C++ can directly dive into development and simply use a .bat or a bash file to kickstart their MRuby project.
The macOS implementation
Prerequisites
- Ruby Install, hopefully, the latest version
- Git installed, as well as some basic knowledge to use it
- Clang is installed, as well as having access to clang via terminal.1
How to Compile your program on macOS
We aim to simply get the below file to compile and generate an executable. Then, we should be able to run it and have it execute the ruby code in test.rb
. Go ahead and create a file called main.c
in our project directory and insert the following code below.
|
|
Let’s try a naive compile command on main.c
and run clang main.c -o example
|
|
We are currently mimruby.h e mruby.h header file. One quick solution would be to copy the entire mruby/include/* directory to the exact location as our main.c file. This would resolve the issue, but it’s not optimal. Ideally, we should use the header files generated during the MRuby compiling process. MRuby can be configured to compile with different features, so using the generated header files will prevent potential issues in the future if we decide to exclude certain MRuby features.
Downloading and Compiling MRuby
First clone MRuby into a different directory with git clone https://github.com/mruby/mruby.git. Let’s make sure we build on a stable version, so checkout the latest stable using git checkout 3.3.0.
MRuby uses Rakefile
as the build system2. We want to ensure that the compiler we use to build MRuby is the same as the one being used in our project. Let’s do a quick edit to build_config/default.rb
and make the follow change
|
|
Now that we’ve edited this to include clang we can simply run bundle
and then rake
. This will take a few seconds. When the rake
command finishes we will have generated both the header files and static library artifacts.
Navigate to mruby/build/host/
and explore this directory.
|
|
The three things we care about the most are LEGAL
, include
and lib
.
With legal we want to keep a copy of it in our project when we decide to publish for credits. I would suggest renaming it to MRUBY_LEGAL
and keep it in a licenses directory.
If you look at the include
directory you’ll notice that we have similar header files as to mruby/include
. However there are now some extra header files for the compiled gems. There are extra features compiled into our MRuby static library.
If we had just gone with the mruby/include
header files we would not have had any access to the time
and io
gems from our C code. We reduced the opportunities to interact with Ruby from our C code. Go ahead and do a recursive copy of all the files and directories in mruby/build/host/include/*
to the same directory as our main.c
The Static Library
Let’s retry clang main.c -o example
. And notice the generated error from this command
|
|
The linker is unable to find the code related to the header file, which is causing the issue. To fix this, we must include the static library archive that was generated in our mruby directory. If we navigate to mruby/build/host/lib/, we will find the generated files.
|
|
The *.a
files are the static library files that contain the compiled code we need to link against. The .flags.mak
contains information on how the library was generated. I am weak in this area, but I think it’s an artifact for makefiles.
We want those *.a
. Technically, we only need libmruby.a
but let’s go ahead and grab both of these files and place them in the same directory as main.c
Now we need to tell clang to link those files for clang
Our command becomes clang main.c libmruby.a -o example
. Finally, we will generate an executable file in the same directory called example
.
|
|
Run example if we will see
|
|
One temptation you may face is to place your libmruby.a in a global library archive directory, such as /usr/local/lib
. However, I discourage this practice because you may have multiple mruby libraries across different systems.
The next article will explain how to perform the same steps on the MSVC environment in Windows. If you plan to use Clang or MinGW, you already have all the knowledge you need from this article.