1 Comment

Using Bundler as a Repository Manager

Many version controls systems today have some form of “external” or “submodule” feature that’s basically just a way to embed one repository (or a piece of one) in another. After working with a few of these systems in a variety of scenarios, I’ve come to the conclusion that they are more trouble than they’re worth. But I didn’t know of any good alternatives… until now.

I’ve discovered that Bundler is very useful as a “repository manager.” It’s not just useful for Ruby projects; you don’t even need to use gems! You can keep all projects/libraries as simple repositories (with no externals) wherever you want, and have Bundler keep track of things for you. (A similar but slightly different method is also very useful for managing tools. See: Stealing Ruby Gems)

Step One

For each repository that is depended on by other repositories, put a .gemspec file and a tiny ruby script in the root. (The Ruby file can relocated.) They should look something like this:

# my_awesome_lib.gemspec
Gem::Specification.new do |gem|
  gem.email = "job.vranish@atomicembedded.com"
  gem.authors = ["Job Vranish"]
  gem.summary = %Q{An awesome library}
  gem.description = %Q{A fake gem to make bundler to what we want}

  files  = Dir['*'].reject {|f| File.directory? f}

  test_files = []

  gem.require_path = '.'   # <-- make sure this includes the path of your ruby file (relative to your .gemspec)
  gem.executables   = []
  gem.files         = files # <-- at a minimum this should include your ruby script
                            # but if you want to eventually package up your library in a gem
                            # (not a bad idea) this needs to include all the files you want to 
                            # include in the gem.
  gem.test_files    = test_files
  gem.name = "my_awesome_lib"
  gem.version = "1.0.0"

  gem.add_dependency 'bundler'

The Ruby script is simply a little helper file that we can use to get the path to our library, whether it's in some local directory, an installed gem, or an installed gem fetched from a git repo.

# my_awesome_lib.rb
class My_Awesome_Lib
  # this little method uses the magic __FILE__ value to generate a path
  # relative to this file
  def self.location
        ".")) # <-- change this appropriately if this file not at project root

Step Two

In a project that depends on these libraries, add a Gemfile similar to the following:

# Gemfile
source "http://rubygems.org"

gem "my_awesome_lib",       :path => "../my_awesome_lib"

# or to use directly from a git repository:
# gem "my_awesome_lib", :git => "git://path/to/git/repo/my_awesome_lib.git", :branch => "1.0"

# or just use as a gem (perhaps from your very own gem source):
# gem "my_awesome_lib", ">= 1.0.0"

Bundler gives you a lot of control over where your "gem" can come from. You can have it pull from a local directory, a git repository, or a regular gem. See Bundler's page on gemfiles for more information.

Step Three

In your project, add a Ruby script like this to get the path of your library (no matter where it is):

# my_awesome_lib_path.rb
require "rubygems"
require "bundler/setup"
require 'my_awesome_lib'

# print out the path to our awesome library
puts My_Awesome_Lib.location

If you're using Rake or Ceedling or another Ruby-based build system, you can just require my_awesome_lib directly and use My_Awesome_Lib.location where ever a path to your library is needed.

Step Four

On the command line, go to where you have the Gemfile and type: bundle install.

And that's it! You should now be able to use my_awesome_lib_path.rb (or similar) to get the path to your library (for use in your include paths in your build system or whatever).

If you want to change where you're pulling your library from, you just have to change the Gemfile.