Bundler and cross-platform development

The hazards of cross-platform development

Recently I helped a co-worker with getting Rails, Nginx, and Unicorn up and running in a Linux VM, using Capistrano to deploy the Rails application from the development box. While we got this to work in the end, it was not without pain. You see, we wanted the deployment to work regardless of the development platform.

The problems we ran into all revolved around Bundler, specifically that it wanted to use the entries in the Gemfile.lock file to determine what gems to install on the VM. This isn't a 'problem with Bundler'; it wants to use the Gemfile.lock to install the specific gem versions used during development, which is a desirable thing. The problem is that our stack used gems with native extensions that are different/don't exist across the platforms involved.

As background, here's the Capistrano file we started out with: deploy.rb.

Problem #1: Unicorn isn't available for Windows

Development environment, who cares what we use to handle HTTP right? Just add something like the following to the Gemfile so Unicorn won't install on Windows but will install on the VM and test using WEBrick locally:

 platforms :ruby do gem 'unicorn' end 

Wrong. If you don't install Unicorn on the development environment, it won't be added to the Gemfile.lock file and so the server never installs Unicorn since it's only looking at the Gemfile.lock entries.

The only way we were able to work around this was to add the following to the deploy.rb file used by Capistrano:

 set :bundle_flags, "--no-deployment --quiet" 

This says it's not a deployment, so install the gems based on the Gemfile rather than Gemfile.lock, and don't return all the output of this operation. Not ideal, as it completely negates Bundlers "I've got a list of all your gem versions used in development" functionality but after searching around I couldn't find a better solution.

Problem #2: .sh files not executable once deployed

This was fairly minor, but still important. The unicorn_init.sh file used to start/stop unicorn was not marked as executable once deployed to the Linux VM, so Unicorn wasn't starting at the end of deployment. We resolved this by adding a "fix permissions" task to the deploy.rb file, and then invoking it after the "deploy:finalize_update" task. Here's the code:

  desc "Fix permission"
 task :fix_permissions, :roles => [ :app, :db, :web ] do
   run "chmod +x #{release_path}/config/unicorn_init.sh"
 end

 after "deploy:finalize_update", "deploy:fix_permissions"

The real problem: native modules

No, I'm not going on a rant about how native modules are evil or that you should make sure they work on all platforms. But be aware that you're using them and that they may cause problems when someone decides to use your code on a different platform.

Note that this isn't a Ruby/Bundler specific issue, as other languages such as Node.js have similar native code issues. Any time you have to leverage OS or hardware specific functionality, you limit the portability of your code.

Is there a better way?

For working with Bundler, not that I could find. I'd be interested if anyone has recommendations on dealing with the development and deployment of Rails applications in heterogeneous environments.