missing runtime library path breaks execjs and coffescript in production setting
When I deployed a Rails site the other day, I was greeted with a colorful yet stern error page from my web server (nginx/passenger):
Some folks say adding a dependency to therubyracer -- a second server side JS implementation -- is an easy way to solve this, even though the application already uses the libv8 gem. However, this additional dependency can create other problems (like libv8 and therubyracer requiring mutually exclusive versions of v8 in their latest version -- ugh!), especially considering the machine dependent nature of the optimized implementations, which can really muck up testing and deployment across different types of systems.
Better to figure out what's broken. Maybe it's something simple, easy to fix.
Turns out it is: Some gems, especially ones calling none-ruby code via ffi, or running external processes altogether to do their bidding, need to have access to the right libs and binaries at runtime. If they don't have the right binary or lib paths, they just won't work, and they sometimes fail in annoyingly obscure ways. Here are some very common ones:
- Pygments.rb - just refuses to highlight, raising an exception instead
Setting your runtime paths (PATH and LD_LIBRARY_PATH under FreeBSD to include /usr/local/lib, for instance), will fix that. You can do that at several level, I prefer to do it at the Rails config level by appending things to ENV['PATH']/ENV['LIBRARY_PATH']. Here is what I have in my application.rb:
# apply local envars and extend PATHs if necessary env_yml = File.expand_path('../env.yml', __FILE__) if File.exist? env_yml YAML.load(File.open(env_yml)).each do |key, value| name = key.to_s if name[-7..-1] == "_extras" path_name = name[0..-8] path = (ENV[path_name] || "").split(File::PATH_SEPARATOR) path_extras = value.split(File::PATH_SEPARATOR) path_extras.each do |extra_path| if File.exist?(extra_path) && ! path.include?( extra_path ) path << extra_path end end ENV[path_name] = path.join(File::PATH_SEPARATOR) else ENV[key.to_s] = value end end end
Make sure this goes almost at the top, after the require 'rails/all' but before the Bundler.require
Then, I have this in my local envar settings (config/env.yml):
DB_DEVELOPMENT_PASSWORD: ... DB_PRODUCTION_PASSWORD: ... GITHUB_API_ID: '...' GITHUB_API_SECRET: '...' LINKEDIN_API_ID: '...' LINKEDIN_API_SECRET: '...' PATH_extras: /usr/local/bin LD_LIBRARY_PATH_extras: /usr/local/lib:/usr/lib:/lib
You could also adjust your Passenger or Nginx settings if you prefer, but the way I see it, this is a matter between bundler and what my app's required gems need on this local machine, much like the other app specific config.