Search code examples
rubyruby-c-extensionextconf.rbruby-native-extensions

ruby native extension: undefined symbol


I'm attempting to create a ruby native extension, but when I run rake which uses ext/example_project/extconf.rb to build my project and run my tests under test/, I get the following error when the tests are run:

./home/jbuesking/.rbenv/versions/2.3.0/bin/ruby: symbol lookup error: 
/home/jbuesking/repositories/example_project/lib/example_project/example_project.so: undefined symbol: some_function

I'm pretty sure my files are not being linked correctly and that I need to alter my extconf.rb and/or Rakefile in some way, but I'm not sure how.

I've created a simple repository that demonstrates the issue over on GitHub. It'll fail with the same error if you clone it and run rake from the projects root.

Some additional information:

  • I used the ruby gem hoe to create the project via sow example_project
  • The failing function is attempting to call a function defined in the subdirectory ext/example_project/c_example_project. My actual project uses a git submodule from the ext/example_project directory, which in turn sets up the submodule as a subdirectory. The submodule is a c project with a flattened structure (all files in the root directory). Note: That wording may be confusing, but the key point is that there's a nested c project defined at ext/example_project/c_example_project which has methods I'm trying to call.

Let me know if any clarification is needed, and I'll do my best to provide it.


Solution

  • So, there are some interesting issues you have here. By default, mkmf doesn't actually support specifying multiple directories for building sources.

    There is a workaround, as seen here (Takehiro Kubo's comment about setting objs):

    https://www.ruby-forum.com/topic/4224640

    Basically, you construct the $objs global in your extconf.rb file yourself.

    Using your github code, here's what I added to the extconf.rb and got to work

    extconf.rb

    globs = [".", "c_example_project"].map do |directory|
      File.join(File.dirname(__FILE__), directory)
    end.join(",")
    
    $objs = Dir.glob("{#{globs}}/*.c").map do |file|
      File.join(File.dirname(file), "#{File.basename(file, ".c")}.o")
    end
    

    Notice I'm actually constructing an absolute path to each of the c sources, for some reason rake-compiler was freaking out if we were just globbing with {.,c_example_project}/*.c, presumably since it's running the extconf.rb file from another directory.

    In addition, your tests/c extensions have a few errors in them. Making the following change in example_project.c fixes the test failure:

     static VALUE example_project_c_code_function()
     {
    -    return some_function();
    +    VALUE _string = rb_str_new2(some_function());
    +    int _enc = rb_enc_find_index("UTF-8");
    +    rb_enc_associate_index(_string, _enc);
    +    return _string;
     }
    

    Explanation

    Basically even though you're checking the c_example_project.h header in your extconf.rb, you're not actually generating the object file where some_function is defined. So, when linking the final dynamic library that ruby loads up, there's no definition for some_function and you get your error.