rbp: Ruby Package (manager)

rbp is a package manager for ruby. It is an alternative to rubygems as it installs dependencies locally to your application to make version management easier. Packages can also be installed globally, but the default is to keep all dependencies tidly inside your app directory.

rbp is not required at runtime which also removes the additonal loading required to make rbp operate. All packages are installed into the vendor/packages directory inside your local directory.

rbp is still in pre-pre-pre-pre-alpha

Installation

rbp can be installed directly by the following command. This will just download the latest version of rbp and install its lib and bin directories into the siteruby directory for your ruby install. Aslong as these are in the loadpath (which they should be), then rbp will just work.

To install rbp just do:

$ curl -s https://raw.github.com/adambeynon/rbp/master/setup.rb | ruby

If you use rbenv or rvm then you may need to run this command for each ruby install. To make sure rbp is working, just run:

$ rbp version

If the command cannot be found, check your shells PATH variable.

How it works

In your app directory there will be a vendor/packages directory where each dependancy is stored in its own directory by name. For example:

some_app/
 |-bin/
 |-lib/
 |-vendor/
    |-packages/
       |-init.rb
       |-rake/
       |-otest/

Installing or removing (or updating) packages will be done through the rbp command, which is simply a ruby library. rbp is only needed during development - not production. Of course, the packages directory should be added to .gitignore (or similar).

init.rb

init.rb in the packages directory is where all the magic happens. As packages are added or removed, their load paths are registered inside init.rb automatically by rbp, so when your package code runs, all the relevant load paths are setup so you can just require() away. Your packages own load path is also added to this file, so simply requiring this file will add your library to the load path as well.

Why use init.rb?

Ruby can only load files from loadpaths that are set on the $: array. Rubygems works by overriding require to perform dynamic lookup for libraries. rbp does not have any runtime component, so dependencies must be in the loadpath on running. To do this, init.rb adds all dependency load paths automatically for you. The only requirement is that init.rb is run straight away, and this is what the rbp command does.

When deploying an application, rbp will not be available - and here you have two choices. Considering deployed applications will not be used as libraries themselves, you can load init.rb in your app directly. For example, in rails you might do this in config.ru:

File.expand_path('../vendor/packages.init.rb', __FILE__)

which will run the init.rb file for you, so all dependencies will be ready to run. Also, this allows you to use a vendor version of rails itself which may be very specific to your app (2.3 vs 3.0 vs 3.1 etc).

When a dependency is not found on the loadpath, a fallback on rubygems may be used to use a global gem as the last option (probably will happen as a lot of ruby installs will load rubygems automatically).

Local development

rbp comes with a handy command irb which will open irb with the init file already added to the load path so you can just require your app dependencies directly. For example:

$ rbp irb

irb> require 'dependency_1'

To run bin files either inside your local package, or an included dependency, the exec command will setup your load paths as well. Run from the command line:

$ rbp exec bin_from_a_dependency arg1 arg2 arg3

To get all dependencies on the load path, init.rb needs to be called. rbp does this for you by wrapping common ruby commands, but any additional methods might mean having to require init.rb directly. For example, a Rakefile:

require File.expand_path('../vendor/packages/init.rb')
require 'your_lib_name'

When your lib requires its dependencies they will already be on the load path thanks to init.rb.

NOTE: init.rb also adds your actual app/packages load path into the ruby load paths as well so you don't have to.

Listing dependencies and package.yml

An apps dependencies are listed in its package.yml file, which is in the base directory. It is based off/inspired by commonjs' package.json. rbp doesn't use gemspecs because it aims to be a replacement/alternative to rubygems.

Dependencies are listed as a hash in the package.yml file and it can list either their required version numbers or a git url. Packages with versions numbers are download directly from rubygems.org server and converted from gems into packages.

Example init.rb


Note: this is created automatically by rbp - you do not need to create this yourself.

# your packages' path
path = File.expand_path('..', __FILE__)

# your packages' lib
$:.unshift File.expand_path("#{path}/../lib")

# dependency 1: `otest'
$:.unshift File.expand_path("#{path}/../packages/otest/lib")

# dependency 2: `rake'
$:.unshift File.expand_path("#{path}/../packages/rake/lib")

Installing packages globally

Some packages may want to be installed globally, mainly as a compatibility with the way rubygems does things. For example, rails generates a template directory for you, so it may not be possible to add rails as a dependency before you setup your dir structure. For this reason, global packages are supported.

Using rubygems as a 'legacy' system

For the short term, global packages in rbp will just be gem installations - rbp will just search for, and install, gems. This will allow any package that is not in the local packages directory to be loaded by rubygems itself - we could add warnings stating that you are using a global package. If you then install a different version locally, then that will be used instead, as its lib path is already on the load path before rubygems searches for an alternative.

In the longer term, the plan is to replace rubygems and global packages will be installed in a similar manner to rubygem packages, including wrapped bin files which allow this dynamic lib lookup. When local packages then need to use globally installed ones, a very smal runtime component from rvm can be loaded to assist lib lookup.

Global packages should still be used for development purposes, and the idea will be to copy all required global packages to the local directory ready for production.