Package management is a solved problem. All modern Linux distributions come with package managers (e.g. Portage, APT, RPM) that are capable of resolving dependencies and preventing conflicts. They manage every piece of software that is installed on the system, and make sure that it all works together.
Kids with Macbooks love to reinvent this wheel, ignoring the lessons of the past. Most programming languages have their own “package manager” (written in the language) which solves the easy part of package management while ignoring the hard parts. Examples of these include,
If you're on a system with a real package manager, these are all pointless. But people who don't know any better insist on using them to distribute their software. To be fair, if you want other people with Macbooks to install your stuff, you don't really have a choice. And they do provide a standard format to specify dependencies, as long as you don't try to enforce them outside of a real package manager.
The problem this causes is that you have dependencies specified in two different places. First, in the package manager. And then locally in whatever file the pseudo-package-manager uses to declare such things. We can take as an example my own mailshears package. Portage uses a file called an ebuild to declare a package's dependencies. In the ebuild for mailshears, I have,
Which states a dependency on ‘pg’ version 0.11.0 or greater. But I also ship a gemspec file containing,
s.add_runtime_dependency 'pg', '>= 0.11.0'
This is redundant for anyone with a real package manager, and can lead to conflicts when the two don't agree (and they occasionally won't). Our problem of choice is Bundler attempting to manage gems for Rails websites that happen to be running on Passenger. If you're not using Passenger, then you can stop paying attention halfway through.
Rails 3.x uses Bundler to make sure that all of the necessary gems for a website are installed. You declare your dependencies in a file called Gemfile in the root of your web application. For example,
user $ cat Gemfile
Rails then loads up Bundler to read this junk in its config/boot.rb:
require 'rubygems' # Set up gems listed in the Gemfile. ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
What's more, Passenger also integrates with Bundler. See the Passenger user guide for more information.
The end result is that if there's a problem with Bundler (which is useless for people with a real package manager!), then the Rails app is going to die. We don't need Bundler, so we don't want our Rails apps to depend on it.
Separating them is fairly easy for custom applications. First, comment out or delete the Bundler code in Rails' config/boot.rb:
# Disregard Bundler nonsense. # #require 'rubygems' # Set up gems listed in the Gemfile. #ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) #require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
Now, without Bundler loading your gems, you're going to have to do it yourself. Look in your Gemfile, and figure out which gems you're installing. We'll reuse the example above:
user $ cat Gemfile
Now, remove each line from the Gemfile, and move it into
Rails' config/application.rb, right after the line where
Bundler would have been invoked. Change
# If you have a Gemfile, require the gems listed there, including any gems # you've limited to :test, :development, or :production. Bundler.require(:default, Rails.env) if defined?(Bundler) require 'pg' require 'sqlite3'
Next, create a blank file in config/setup_load_paths.rb to prevent Passenger from invoking Bundler:
user $ echo "# Ignore Bundler." > config/setup_load_paths.rb
Finally, get rid of the old Gemfiles.
user $ rm Gemfile Gemfile.lock
Now if you restart Apache, you should be unbundled.
If you're using off-the-shelf Rails applications like Redmine, you're probably screwed here. If you attempt to deintegrate Bundler, then you're going to be stuck moving the gems from Gemfile into config/application.rb every time you update the app. It probably isn't worth the trouble.