Search code examples
crubygccmakefileffi

Compiling Ruby C API (cross-platform)


The include statements presented here corresponds to the values of RbConfig::CONFIG["rubyhdrdir"] and RbConfig::CONFIG["rubyarchhdrdir"], so I thought using RbConfig to setup the compilation will give me much better results when compiling on different platforms. I wrote the following Makefile with the prior in mind:

librb    = $(shell ruby -e 'puts RbConfig::CONFIG["rubyhdrdir"]')
archdir  = $(shell ruby -e 'puts RbConfig::CONFIG["rubyarchhdrdir"]')

CC      := gcc -std=c23
EXEC    := test
SRCS    := src/[a-z]*.c
LIBS    := -I$(librb) -I$(archdir) -Lruby
CFLAGS  := -g -Wall -Wextra -Woverflow -Og -pedantic

$(EXEC): $(SRCS)
        $(CC) -o $@ $^ $(CFLAGS) $(LIBS)
        @echo "Compiled CRuby $(shell ruby -e 'puts RbConfig::CONFIG["ruby_version"]')"

Which on my system results in:

gcc -std=c23 -o object_test src/main.c -g -Wall -Wextra -Woverflow -Og -pedantic -I/home/user/.rbenv/versions/3.2.2/include/ruby-3.2.0 -I/home/user/.rbenv/versions/3.2.2/include/ruby-3.2.0/x86_64-linux -Lruby

The ruby header files seems to be linked correctly but I still get undefined reference compilation error for ruby symbols like ruby_init and ruby_cleanup when compiling following code.

#include <ruby.h>

int main(int argc, char* argv[])
{
  ruby_init();
  //...
  return ruby_cleanup(0);
}

I know that my question may seem a duplicate of this one, but the links are broken there and it's outdated by 13 years. I am most concerned about ruby > 3.1 for my project.


Solution

  • I try to maintain docs for this process in my Ruby C API Guide.

    At minimum, the C compiler needs to find three things:

    1. The general Ruby header
    2. The platform-specific Ruby header
    3. The Ruby C lib

    Depending on how you set up Ruby and your C compiler, these could already be in standard locations where your compiler can automatically find them. At worst, you need to specify the locations of all three. Using RbConfig to find them is a great idea, you're just not doing it quite right. The correct config keys are:

    1. rubyhdrdir to be used with -I to add an include path
    2. rubyarchhdrdir to be used with -I to add an include path
    3. libdir to be used with -L to add a linker path (and -Wl,-rpath, if you need to add a runtime search path as well)

    So here's an example of a Ruby script that outputs all of the compiler options you need:

    require 'rbconfig'
    require 'shellwords'
    
    rblib = RbConfig::CONFIG['libdir']
    
    # location of ruby.h
    puts "-I#{Shellwords.escape RbConfig::CONFIG['rubyhdrdir']}"
    # location of ruby/config.h
    puts "-I#{Shellwords.escape RbConfig::CONFIG['rubyarchhdrdir']}"
    # location of libruby
    puts "-L#{Shellwords.escape rblib}"
    # add libruby location to runtime library search path
    puts "-Wl,-rpath,#{Shellwords.escape rblib}"
    # link with libruby
    puts "-lruby"