<= Home
Localizing Gems (vendor everything) with Native Extensions
Localizing all libraries used by a rails app is a good strategy to building robust systems. Localizing or 'vendor everything' mitigates version conflicts between applications and simplifies deployments as we move from one environment to another. It drastically increases the portability of our application and transparency of what we have dependencies on. Additionally it enables something I consider very important,checkout development, which essentially means a developer can check out the code from scm for the first time and be running the development environment...no lengthy "how to setup development environment" documents.
We localize all our dependencies (i.e. gems) in the enterprise environment since by nature we internally run our rails apps in a "shared hosting" environment and would frequently run into the above issues if we didn't localize. It is trival to localize pure ruby gems and there are even other gems to help us make it stupid easy to 'vendor everything', see http://gemsonrails.rubyforge.org/. Personally I don't find it that hard to just use the "gem unpack command" instead. But what about localizing gems that have native extensions...
Localizing Hpricot
If your rails app is doing any extensive XML processing you should avoid REXML as its terribly slow. Hpricot proves to be a nice XML parser for ruby but it depends on some c libraries. Since we localize everything we are going to need to figure out how to freeze the native extensions to our rails app. The tricky part of native extensions is they are unique to each OS/architecture. To set the stage we have to run our apps on several architectures:
- Windows
- Mac OSX Intel
- 32 Bit RedHat
- 64 Bit RedHat
1. Vendoring the gem
This is the easiest part, assuming you have the hpricot gem installed on you development box, run from the command line:
cd vendor gem unpack hpricot
You should now have the hpricot gem source in your vendor folder.
2. Building the native extension by hand
We need to understand the anatomy of a gem to do this next bit. Generally gems with native extensions will have a ext folder where the c source code and such live. When you typically install a gem the install process will compile these c source code and generate our ruby extension targeted for our particular OS Architecture and put the compiled library in under the lib folder of our gem...for example on mac the installed hpricot gem has a i686-linux folder with the compiled library under the hprict lib folder. But up until now we have only unpacked our gem to the vendor folder the native extension has not been built so our hprioct's lib folder will only have ruby source.
To build the native extension by hand we need to navigate the the C source code in the ext folder, create the make file, and then run the makefile to compile.
cd vendor/hpricot-0.6/ext/hpricot_scan ruby extconf.rb make
3. Moving the compiled library to the lib folder
If everything went smoothly above, then you will see a new file under the ext/hpricot_scan folder. On macs this is hpricot_scan.bundle. When you install a gem with "gem install hpriot" this file is moved for us to the lib folder, but because we are building by hand we need to move it. We want to move it to a folder under the vendor/hpricot/lib folder target for the architecture we just built. I do this by getting the OS platform as ruby sees it.
ruby -e "puts RUBY_PLATFORM"
On my mac, the output is "i686-darwin8.9.1" so I create a i686-darwin8.9.1 folder and put the hpricot_scan.bundle in this folder. I'll repeat step 2 (compiling the source files) above and create folders and copy the compiled library for each OS architecture (mac, various linux flavors, etc) I plan to run my rails app on. This might be a little time consuming if your app needs to run on 3 or 4 environments but once the libraries are built we can reuse this localized gem for all other rails projects. For our current project I have serveral folders with their own compiled library under the froozen hpricot's lib folder: i486-linux, i686-darwin8.9.1, i686-linux, x86_64-linux.
4. Configuring Rails
At this point we have our localized gem ready but we need to tell rails about it. In our envionrment.rb file we want to add:
config.load_paths << "#{RAILS_ROOT}/vendor/hpricot-0.6/lib"
config.load_paths << "#{RAILS_ROOT}/vendor/hpricot-0.6/lib/#{RUBY_PLATFORM}"
This first puts the froozen hpricot in our load paths so we load up the localized gem in vendor. The second line is so we load up the right library for the right OS platform we are running on.
Let me know if you have any issues/questions: mdeiters@gmail.com