2

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.

JesseBuesking
  • 6,496
  • 4
  • 44
  • 89
  • Just a follow up, if there's something better than `hoe` for doing this, please let me know. I was [using a tutorial](https://tenderlovemaking.com/2009/12/18/writing-ruby-c-extensions-part-1.html) as a launchpoint, but it's 7 years old so maybe something better has come along since then! – JesseBuesking Nov 01 '16 at 17:35

3 Answers3

4

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.

photoionized
  • 5,092
  • 20
  • 23
  • Awesome! Yeah, I thought the issue was that the file's weren't being compiled and linked correctly, I just had no idea how to make that work. Thank you for figuring out what needed to be done! – JesseBuesking Nov 09 '16 at 03:23
  • Truly great. Definitely useful information. Though, I think the path generated in the sample code is not absolute paths, $objs is an Enumerator, not an array. – kojix2 Dec 18 '21 at 23:10
0

I don't have any experience with building native extensions, but from mkmf source code it looks like you can only specify one source directory. I moved both files from c_example_project to the parent directory and everything was linked properly. I think that is how you should do it. All common gems (like pg, nokogiri etc) have such code structure, all *.c and *.h files are in one directory.

You can always create Makefile yourself, but that would require too much work to maintain.

PS. Your project compiled successfully, but you there is segmentation fault, because you should return proper ruby string object in some_function and not pointer to a string.

Michał Młoźniak
  • 5,466
  • 2
  • 22
  • 38
  • I was hoping for a solution that did not involve moving the files up a directory, as that means I can no longer use the git submodule and instead have to rely on copying files manually... if no one else leaves a better answer I'll accept yours, although I'm hoping there's a better solution. – JesseBuesking Nov 07 '16 at 01:03
0

photoionized has a great answer, but $obj can be an array instead of an Enumerator.It seems to be okay to simply use an absolute path.

$objs = Dir.glob([".c", "libfoobar/*.c"], base: __dir__)
           .map { |f| File.expand_path(f, __dir__) }
           .map { |f| f.sub(/\.c$/, ".o") }
kojix2
  • 806
  • 7
  • 18