README.md in rcee_system-0.1.0 vs README.md in rcee_system-0.2.0

- old
+ new

@@ -1,2 +1,91 @@ +# RCEE::System This gem is part of the Ruby C Extensions Explained project at https://github.com/flavorjones/ruby-c-extensions-explained + +## Context + +In the `isolated` gem, I mentioned that one goal of a C extension might be to optimize performance. This is the case for BCrypt. + +But there's another, more common, reason to write a C extension, which is to talk to a third-party library. Many Ruby gems use C extensions solely to integrate with a third-party library. Some examples: + +- nokogiri → libxml2, libxslt, libgumbo +- psych → libyaml +- sqlite3 → libsqlite3 +- rmagick → libMagick +- grpc → libgrpc + +These gems have a thin-ish wrapper of Ruby and C that work together to make the library's features available as idiomatic Ruby. + + +## Summary + +This gem, as well as all the following gems, will call `libyaml` as an example third-party integration, and will require that `libyaml` be installed ahead of time on the target system. + +Some real-world gems that use this "system" strategy are ruby-sqlite3 and rmagick. + + +## Details + +This gem's C code is located in `ext/system/system.c`: + +``` C +static VALUE +rb_system_extension_class_do_something(VALUE self) +{ + int major, minor, patch; + + yaml_get_version(&major, &minor, &patch); + + return rb_sprintf("libyaml version %d.%d.%d", major, minor, patch); +} +``` + +That's pretty simple, but is enough to demonstrate the integration with `libyaml` works. + +The `extconf.rb` is still simple (and similar to `isolated/ext/isolated/extconf.rb` but contains this additional block: + +``` ruby +unless find_header("yaml.h") && find_library("yaml", "yaml_get_version") + abort("\nERROR: *** could not find libyaml development environment ***\n\n") +end +``` + +`find_header` and `find_library` are `MakeMakefile` helper methods which will search your system's standard directories looking for files. If it finds them, it makes sure the compile step will be able to find `yaml.h`, and the link step will be able to find the `libyaml` library file. + +We ask `find_header` to look for the `yaml.h` header file because that's what our C code needs (see `ext/system/system.h`). We ask `find_library` to look for a library named `libyaml` and check that it has the function `yaml_get_version()` defined in it. + +(We don't need to call `find_library` for every function we intend to use; we just need to provide one function from the library so that `MakeMakefile` can verify that linking will succeed.) + +If these methods succeed, the `Makefile` recipe looks something like this. Note the include directory is added for the compile step, and the library directory and name are added to the link step. + +``` sh +# `create_makefile` recipe is something like this + +# compile phase: +gcc -c -I/path/to/ruby/include -I/path/to/libyaml/include system.c -o system.o + +# link phase: +gcc -shared \ + -L/path/to/ruby/lib -lruby \ + -L/path/to/libyaml/lib -lyaml \ + -lc -lm \ + system.o -o system.so +``` + +If you run `ldd` on the generated `system.so` you should see `libyaml` listed, something like: + +``` text + libyaml-0.so.2 => /usr/lib/x86_64-linux-gnu/libyaml-0.so.2 (0x00007f345a3dc000) +``` + +## What Can Go Wrong + +In addition to what's enumerated in `isolated`'s README ... + +If `MakeMakefile` methods fail to find the third-party library (or fail to compile and link against it), then the user will see an error message, and have to go figure out how to install `libyaml` on their system. + +If the third-party library is installed into non-standard directories by the package manager, your `extconf.rb` may need special logic. `rmagick` needs to do a lot of this. + +If the third-party library has compile-time flags to control whether features are turned on or off, then your `extconf.rb` may need to test for that with `have_func` and your C code will need to handle the case where those methods aren't implemented. + +The version of the third-party library may be older or newer than you expected, and either contain bugs or be missing new features, which also require additional code complexity.