Rip & Gem Dependencies

; updated

Update: my changes have been merged into rip by Chris. Nice!

The good stuff is here, my ADD-addled friends

I’ve just spent a bit of time kicking the tyres of Rip, an alternative package manager by Chris Wanstrath.

If you’ve done any Rails development recently, and tried to keep a vendor directory of gems in order, you’ll know the pain that almost certainly inspired the Rip project.

While there are already some alternatives (bundler, dependencies), I like the elegant and lo-fi approach that Rip takes.

Environments

Rip lets you partition the dependencies you install into seperate ‘environments’. When an environment is active, the installed versions are the only ones that running code will be able to load. So, if you have one project that needs a specific version of soup, for example, you can install that version in an environment for that project.

In your default environment, you can continue to upgrade/downgrade/play about with the gems and libraries that are installed, but as long as you activate the other, known-good environment when you are working with soup, you can be sure that none of the new versions will interfere. Each environment is like a sandbox.

Unsurprisingly, it’s a lot like the cheap branching that git brings - you can play around as much as you like, but always get your environment back to working state just by typing rip use <environment>.

The elegance of Rip

I said above that I thought Rip was elegant, and that’s because these environments are just subdirectories which are added to the $RUBYLIB environment variable. This becomes part of the $LOAD_PATH for your Ruby process. Regular require statements will work as expected, and you don’t even need to require "rubygems" (which, it seems, we shouldn’t be doing anyway).

All Rip really does is try to ensure that the ‘active’ environment is injected into the $RUBYLIB; switching environments is really just moving a symlink; and the rest of the code deals with nuts and bolts of actually downloading libraries of code from various sources. Very simple, as most good things are.

All in all, when it works it’s a very pleasant development tool to use, and you could do worse than to spend a few minutes checking it out.

Gem dependencies

Rip works with git repositories, directories on the local filesystem, or over HTTP, and has rudimentary support for install rubygems too.

However, the current version (0.0.3) doesn’t install any gem dependencies. So, if I want to play about with monk, which I did want to do today, Rip is only going to download the monk gem itself, and not any of its dependencies. It’s actually going to be a bit of a pain to even find out what the dependencies are.

If I’m going really start putting Rip through its paces, I need to be able to quickly and reliably install the same libraries that I can install using normal Rubygems, including their dependencies.

Time to stop whining

So, today I rolled up my sleeves and taught Rip how to download gem dependencies too.

It wasn’t quite as easy as I hoped - it took me a bit of head-scratching to really figure out the relationship between Packages, the PackageManager, the Installer and the two GemPackages, but thanks to a bit of help from Gabriel Horner’s previous work, I have it working in a reasonably stable way.

Here’s my fork of rip installing Rails:

➜ $ rip install rails Searching gems.github.com for rails... ERROR: Could not find rails in any repository Searching gems.rubyforge.org for rails... Searching gems.github.com for rake... ERROR: Could not find rake in any repository Searching gems.rubyforge.org for rake... Searching gems.github.com for activesupport... ERROR: Could not find activesupport in any repository Searching gems.rubyforge.org for activesupport... Searching gems.github.com for activerecord... ERROR: Could not find activerecord in any repository Searching gems.rubyforge.org for activerecord... Searching gems.github.com for actionpack... ERROR: Could not find actionpack in any repository Searching gems.rubyforge.org for actionpack... Searching gems.github.com for actionmailer... ERROR: Could not find actionmailer in any repository Searching gems.rubyforge.org for actionmailer... Searching gems.github.com for activeresource... ERROR: Could not find activeresource in any repository Searching gems.rubyforge.org for activeresource... Successfully installed rake (0.8.7) Successfully installed activesupport (2.3.3) Successfully installed activerecord (2.3.3) Searching gems.github.com for rack... ERROR: Could not find rack in any repository Searching gems.rubyforge.org for rack... Successfully installed rack (1.0.0) Successfully installed actionpack (2.3.3) Successfully installed actionmailer (2.3.3) Successfully installed activeresource (2.3.3) Successfully installed rails (2.3.3) ➜ $

Next steps

I’m sure my approach could be improved. Here’s the issue on github, if you’ve got any feedback, or would like to see efforts here continue.

I’d also quite like to have Rip respect the sources (and their ordering) as defined in .gemrc, but that’s just my preference.

Now, to finally start playing with monk

(oh, btw, here’s rip installing monk; I don’t know why the dependencies gem needs wycats-thor…)

➜ $ rip install monk Searching gems.github.com for monk... ERROR: Could not find monk in any repository Searching gems.rubyforge.org for monk... Searching gems.github.com for thor... ERROR: Could not find thor in any repository Searching gems.rubyforge.org for thor... Searching gems.github.com for dependencies... ERROR: Could not find dependencies in any repository Searching gems.rubyforge.org for dependencies... Successfully installed thor (0.11.5) Searching gems.github.com for wycats-thor... Successfully installed wycats-thor (0.11.5) Successfully installed dependencies (0.0.6) Successfully installed monk (0.0.5) ➜ $

Another update, a few days later

You can see the gem dependency changes in the rip source now. It now delegates as much as it can to the gem binary, which means it will respect the gem sources you already have installed (rubyforge, github, gemcutter and anything else).

Older versions of gems now install properly too

I had a bit of fun figuring out some kinks in the dependency identification for specific versions of rubygems. Normally, you can find out the dependencies of a gem, without installing it, by running

$ gem dependency <gem> --remote

However, this doesn’t seem to work for many gems, particularly ones that aren’t the most recent. For example:

$ gem dependency rails --remote
Gem rails-2.3.3
  rake (>= 0.8.3, runtime)
  activesupport (= 2.3.3, runtime)
  activerecord (= 2.3.3, runtime)
  actionpack (= 2.3.3, runtime)
  actionmailer (= 2.3.3, runtime)
  activeresource (= 2.3.3, runtime)

But when I try requesting the dependencies for a specific version:

$ gem dependency rails --version="2.2.2" --remote
No gems found matching rails (= 2.2.2)

No dice. The simple solution we’ve adopted is to download the gem at the specific version requested, and then read the dependencies from the gem itself, using gem specification to produce a YAML version of its gemspec:

$ gem fetch rails --version="2.2.2"
Downloaded rails-2.2.2
$ gem specification rails-2.2.2.gem
--- !ruby/object:Gem::Specification
name: rails
version: !ruby/object:Gem::Version
  version: 2.2.2
platform: ruby
authors:
- David Heinemeier Hansson
autorequire:
bindir: bin
cert_chain: []

date: 2008-11-20 23:00:00 +00:00
default_executable: rails
dependencies:
- !ruby/object:Gem::Dependency
  name: rake
  type: :runtime
  version_requirement:
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - ">="
      - !ruby/object:Gem::Version
        version: 0.8.3
    version:
(etc)

File conflicts

One of the selling points of rip is that dependency issues are resolved when you are building the environment, and not when your application actually runs. As part of this, rip will now [warn you when one library is trying to overwrite a file that was already installed by another package][fil-conflicts]. This means that two packages can’t both try to install a file called node.rb (although they can install package_a/node.rb and package_b\node.rb, so nicely namespaced code will work just fine).

However, this means that my monkrb example doesn’t work anymore, because of the wycats-thor dependency:

➜ $ rip install monk Successfully installed thor (0.11.5) Some files from wycats-thor (0.11.5) conflict with those already installed by thor: lib/thor/actions/create_file.rb lib/thor/actions/directory.rb lib/thor/actions/empty_directory.rb lib/thor/actions/file_manipulation.rb lib/thor/actions/inject_into_file.rb lib/thor/actions.rb lib/thor/base.rb lib/thor/core_ext/hash_with_indifferent_access.rb lib/thor/core_ext/ordered_hash.rb lib/thor/error.rb lib/thor/group.rb lib/thor/invocation.rb lib/thor/parser/argument.rb lib/thor/parser/arguments.rb lib/thor/parser/option.rb lib/thor/parser/options.rb lib/thor/parser.rb lib/thor/rake_compat.rb lib/thor/runner.rb lib/thor/shell/basic.rb lib/thor/shell/color.rb lib/thor/shell.rb lib/thor/task.rb lib/thor/util.rb lib/thor.rb bin/rake2thor bin/thor rip: installation failed ➜ $

The solution here isn’t really clear. I think that the dependencies gem shouldn’t really depend on a user-specific version of thor, and there’s some kind of irony there. One way to proceed might be to add a new flag to rip install, something like rip install --no-dependencies which only installs the package in question and ignores its dependencies.

Anyway, please do kick the tires around, join the mailing list and contribute!

interblah.net - Rip & Gem Dependencies

Rip & Gem Dependencies

; updated

Update: my changes have been merged into rip by Chris. Nice!

The good stuff is here, my ADD-addled friends

I’ve just spent a bit of time kicking the tyres of Rip, an alternative package manager by Chris Wanstrath.

If you’ve done any Rails development recently, and tried to keep a vendor directory of gems in order, you’ll know the pain that almost certainly inspired the Rip project.

While there are already some alternatives (bundler, dependencies), I like the elegant and lo-fi approach that Rip takes.

Environments

Rip lets you partition the dependencies you install into seperate ‘environments’. When an environment is active, the installed versions are the only ones that running code will be able to load. So, if you have one project that needs a specific version of soup, for example, you can install that version in an environment for that project.

In your default environment, you can continue to upgrade/downgrade/play about with the gems and libraries that are installed, but as long as you activate the other, known-good environment when you are working with soup, you can be sure that none of the new versions will interfere. Each environment is like a sandbox.

Unsurprisingly, it’s a lot like the cheap branching that git brings - you can play around as much as you like, but always get your environment back to working state just by typing rip use <environment>.

The elegance of Rip

I said above that I thought Rip was elegant, and that’s because these environments are just subdirectories which are added to the $RUBYLIB environment variable. This becomes part of the $LOAD_PATH for your Ruby process. Regular require statements will work as expected, and you don’t even need to require "rubygems" (which, it seems, we shouldn’t be doing anyway).

All Rip really does is try to ensure that the ‘active’ environment is injected into the $RUBYLIB; switching environments is really just moving a symlink; and the rest of the code deals with nuts and bolts of actually downloading libraries of code from various sources. Very simple, as most good things are.

All in all, when it works it’s a very pleasant development tool to use, and you could do worse than to spend a few minutes checking it out.

Gem dependencies

Rip works with git repositories, directories on the local filesystem, or over HTTP, and has rudimentary support for install rubygems too.

However, the current version (0.0.3) doesn’t install any gem dependencies. So, if I want to play about with monk, which I did want to do today, Rip is only going to download the monk gem itself, and not any of its dependencies. It’s actually going to be a bit of a pain to even find out what the dependencies are.

If I’m going really start putting Rip through its paces, I need to be able to quickly and reliably install the same libraries that I can install using normal Rubygems, including their dependencies.

Time to stop whining

So, today I rolled up my sleeves and taught Rip how to download gem dependencies too.

It wasn’t quite as easy as I hoped - it took me a bit of head-scratching to really figure out the relationship between Packages, the PackageManager, the Installer and the two GemPackages, but thanks to a bit of help from Gabriel Horner’s previous work, I have it working in a reasonably stable way.

Here’s my fork of rip installing Rails:

➜ $ rip install rails Searching gems.github.com for rails... ERROR: Could not find rails in any repository Searching gems.rubyforge.org for rails... Searching gems.github.com for rake... ERROR: Could not find rake in any repository Searching gems.rubyforge.org for rake... Searching gems.github.com for activesupport... ERROR: Could not find activesupport in any repository Searching gems.rubyforge.org for activesupport... Searching gems.github.com for activerecord... ERROR: Could not find activerecord in any repository Searching gems.rubyforge.org for activerecord... Searching gems.github.com for actionpack... ERROR: Could not find actionpack in any repository Searching gems.rubyforge.org for actionpack... Searching gems.github.com for actionmailer... ERROR: Could not find actionmailer in any repository Searching gems.rubyforge.org for actionmailer... Searching gems.github.com for activeresource... ERROR: Could not find activeresource in any repository Searching gems.rubyforge.org for activeresource... Successfully installed rake (0.8.7) Successfully installed activesupport (2.3.3) Successfully installed activerecord (2.3.3) Searching gems.github.com for rack... ERROR: Could not find rack in any repository Searching gems.rubyforge.org for rack... Successfully installed rack (1.0.0) Successfully installed actionpack (2.3.3) Successfully installed actionmailer (2.3.3) Successfully installed activeresource (2.3.3) Successfully installed rails (2.3.3) ➜ $

Next steps

I’m sure my approach could be improved. Here’s the issue on github, if you’ve got any feedback, or would like to see efforts here continue.

I’d also quite like to have Rip respect the sources (and their ordering) as defined in .gemrc, but that’s just my preference.

Now, to finally start playing with monk

(oh, btw, here’s rip installing monk; I don’t know why the dependencies gem needs wycats-thor…)

➜ $ rip install monk Searching gems.github.com for monk... ERROR: Could not find monk in any repository Searching gems.rubyforge.org for monk... Searching gems.github.com for thor... ERROR: Could not find thor in any repository Searching gems.rubyforge.org for thor... Searching gems.github.com for dependencies... ERROR: Could not find dependencies in any repository Searching gems.rubyforge.org for dependencies... Successfully installed thor (0.11.5) Searching gems.github.com for wycats-thor... Successfully installed wycats-thor (0.11.5) Successfully installed dependencies (0.0.6) Successfully installed monk (0.0.5) ➜ $

Another update, a few days later

You can see the gem dependency changes in the rip source now. It now delegates as much as it can to the gem binary, which means it will respect the gem sources you already have installed (rubyforge, github, gemcutter and anything else).

Older versions of gems now install properly too

I had a bit of fun figuring out some kinks in the dependency identification for specific versions of rubygems. Normally, you can find out the dependencies of a gem, without installing it, by running

$ gem dependency <gem> --remote

However, this doesn’t seem to work for many gems, particularly ones that aren’t the most recent. For example:

$ gem dependency rails --remote
Gem rails-2.3.3
  rake (>= 0.8.3, runtime)
  activesupport (= 2.3.3, runtime)
  activerecord (= 2.3.3, runtime)
  actionpack (= 2.3.3, runtime)
  actionmailer (= 2.3.3, runtime)
  activeresource (= 2.3.3, runtime)

But when I try requesting the dependencies for a specific version:

$ gem dependency rails --version="2.2.2" --remote
No gems found matching rails (= 2.2.2)

No dice. The simple solution we’ve adopted is to download the gem at the specific version requested, and then read the dependencies from the gem itself, using gem specification to produce a YAML version of its gemspec:

$ gem fetch rails --version="2.2.2"
Downloaded rails-2.2.2
$ gem specification rails-2.2.2.gem
--- !ruby/object:Gem::Specification
name: rails
version: !ruby/object:Gem::Version
  version: 2.2.2
platform: ruby
authors:
- David Heinemeier Hansson
autorequire:
bindir: bin
cert_chain: []

date: 2008-11-20 23:00:00 +00:00
default_executable: rails
dependencies:
- !ruby/object:Gem::Dependency
  name: rake
  type: :runtime
  version_requirement:
  version_requirements: !ruby/object:Gem::Requirement
    requirements:
    - - ">="
      - !ruby/object:Gem::Version
        version: 0.8.3
    version:
(etc)

File conflicts

One of the selling points of rip is that dependency issues are resolved when you are building the environment, and not when your application actually runs. As part of this, rip will now [warn you when one library is trying to overwrite a file that was already installed by another package][fil-conflicts]. This means that two packages can’t both try to install a file called node.rb (although they can install package_a/node.rb and package_b\node.rb, so nicely namespaced code will work just fine).

However, this means that my monkrb example doesn’t work anymore, because of the wycats-thor dependency:

➜ $ rip install monk Successfully installed thor (0.11.5) Some files from wycats-thor (0.11.5) conflict with those already installed by thor: lib/thor/actions/create_file.rb lib/thor/actions/directory.rb lib/thor/actions/empty_directory.rb lib/thor/actions/file_manipulation.rb lib/thor/actions/inject_into_file.rb lib/thor/actions.rb lib/thor/base.rb lib/thor/core_ext/hash_with_indifferent_access.rb lib/thor/core_ext/ordered_hash.rb lib/thor/error.rb lib/thor/group.rb lib/thor/invocation.rb lib/thor/parser/argument.rb lib/thor/parser/arguments.rb lib/thor/parser/option.rb lib/thor/parser/options.rb lib/thor/parser.rb lib/thor/rake_compat.rb lib/thor/runner.rb lib/thor/shell/basic.rb lib/thor/shell/color.rb lib/thor/shell.rb lib/thor/task.rb lib/thor/util.rb lib/thor.rb bin/rake2thor bin/thor rip: installation failed ➜ $

The solution here isn’t really clear. I think that the dependencies gem shouldn’t really depend on a user-specific version of thor, and there’s some kind of irony there. One way to proceed might be to add a new flag to rip install, something like rip install --no-dependencies which only installs the package in question and ignores its dependencies.

Anyway, please do kick the tires around, join the mailing list and contribute!

interblah.net - Some thoughts about Ruby Manor

Some thoughts about Ruby Manor

As you might be aware, I’ve been involved in the running of a couple of Ruby conferences over the past few years. I wanted to try and codify some of my feelings about the conference’s goals, and how it might proceed in the future.

While Ruby Manor is by no means my thing1, as one of the original instigators I had a few rough goals that I wanted to achieve. In a nutshell, I wanted to demonstrate that it is possible to run a conference which is equal to any of the “big” confs in terms of technical quality, usefulness and general atmosphere, but without the bloat which has become a typical part of the conference experience.

I’m talking about fluffy inspiration from the same pool of “big name” keynote speakers peddling keynotes without any real insight. I’m talking about low quality food and swag (t-shirts, lanyards, glossy schedules) that we’ve all inadvertently paid for in our ticket prices. I’m talking about cheaper tickets to boot - can it really cost hundreds of pounds per person to run these events?

So that’s what we tried to do in 2008, and again in 2009.

Some time later…

Hereafter I can only speak for myself. Everything that follows is my own rambling opinion, and I don’t speak for “Ruby Manor” as a thing when I say any of the following2. I am on occasion vitriolic, but I hope that doesn’t distract from the fundamental points I’m trying to make.

So.

I know that a lot of people enjoyed the conferences we ran, and the praise is something I personally treasure. However, after the euphoria of the first event, and then the hard work of the repeat, I was left thinking about my original goals again.

We had imagined that, with a minimal amount of structure, the community could be inspired to come together and suggest, plan and refine the presentations and structure of the day. However, after the second event it was clear that it still required a significant injection of energy from the “organisers” to keep the process going. While a not-insubstantial portion of the attendees did participate in shaping the proposals, the majority of people engaged either in a minimal way (“yes that sounds interesting”), or not at all.

This left me wondering about some of these goals. I have a handful of hypotheses about why participation didn’t feel as strong as I hoped.

Hypothesis A: Confusion

Perhaps people we not sure or confident about how they could engage with the process. Admittedly, it was very free-form (a mailing-list), and so there was no apparent structure unless you read the introductory posts and some of the content to get your bearings.

We chose email because… well, everyone uses it, so the barrier to entry should be low. We hoped it would be the simplest way both for us to get something rolling quickly, and the simplest way for people to start engaging. It was also an easy way to keep people informed about progress and new developments.

However, perhaps the “blank page” of an email was more of a hurdle to jump than we imagined.

Hypothesis B: Laziness

My somewhat-pessimistic hypothesis is that people are generally lazy, and so it was easy to allow or assume that other people would contribute and drive the process.

Having seen the twitter hyperbole surrounding most conferences (ours included), I now suspect that a lot of people enjoy going to conferences partially because they can just buy a ticket and turn up without being required to invest much time or energy, and get the same conference “buzz” without expending much effort.

Of course, people are busy – we’ve all got jobs and deliverables, and volunteering becomes a good intention that is rapidly superseded by more tangible priorities.

Hypothesis C: The Quo is Good Enough

Perhaps people don’t really care about the presentation content, or the food, and are only going to be a part of the mild hysteria that sets in when you’re on a tech jolly3 away from home? They clearly don’t mind spending the money, as most established Ruby conferences continue to sell out4 with ticket prices of hundreds of dollars/pounds, often before they’ve even outlined the schedule or themes. I think that’s pretty strong evidence that people either:

So if people don’t care about the price of the ticket, and they don’t really mind about the exact nature of the content, what is the point of running Ruby Manor in the way we do? Does it serve any purpose beyond being just another conference with a slightly quirky aspect?

A + B + C == ?

In reality I think there’s a bit of truth in all three hypotheses.

And it would be remiss not to suggest that perhaps the fault is my own - perhaps I didn’t do a good enough job of communicating these goals; you’re not psychic, after all.

But either way, the question I am asking myself now is this: what is my motivation for running the next Ruby Manor?

Ruby Manor has been misinterpreted as a “unconference”, a distinction which I personally believe is fairly meaningless beyond reinforcing traditional conference organisation as the status quo. Personally I think that “unconferences” are better at achieving the fundamental goals of a conference than these other ones are - meeting people and getting new ideas. Unconferences strongly encourage participation, whereas “real” conferences cater to consumers.

I had originally hoped our execution of our idea would act as a strong, definite statement both to potential attendees and existing “real” conference organisers that you don’t need t-shirts or lunch boxes, or the same speakers peddling the same presentations from conference to conference, pontificating from an altar.

I believed that the “real” conference world needed (and was ready) to be disrupted. I had hoped it catalyse active questioning by attendees about why tickets cost hundreds of pounds, and at the very least conferences should declare clearly whether or not they are for-profit, and if not then make the finances transparent. I hope hoped that conference attendees would demand a stronger influence on the content of the conferences themselves.

But now I’m not sure if that’s what people really care about.

Now before I lose myself in pure ranting, it’s obviously true that people can do whatever they want, and in reality there is room in the world for lots of different kinds of conference. My point is, I suppose, this: surely this cannot be as good as it gets?

What next?

For the best part of two years I’ve been mulling this over.

I think we’re going to run another Ruby Manor, but I would really love it if more people were thinking about how to make these days better, simpler, and more useful.

As I said at the start, Ruby Manor does not belong to me, but as long as I am involved, I’m always going to push this agenda. Perhaps not everyone is ready to leave the safe world of traditional conferences, but some people are, and those are the people that I think can help us figure out what might be better than the status quo.

Conclusion

I want to explore the Space of Possible Conferences, and every aspect should be challenged, every pointless indulgence stripped away. I want it to be simpler, more honest, and less prone to hyperbole, so that the community actually becomes stronger, not just increasingly bloated, flabby and self-congratulatory.

And I would love to know what you think.

Given the absence of commenting on interblah, but more particularly in the spirit of this essay, I hope that you use whatever motivation or momentum that might be available to you to blog or tweet what you think.

  1. Ruby Manor is organised by a group, and it’s very much owned by the community. I would be delighted if people in the community wanted to take it forward. 

  2. I cannot make this clear enough - these are only my own, sightly-raw feelings about how the events went, and how the community is at the moment. 

  3. Apparently plenty of people are happy to pay thousands of pounds to go to conferences on tropical islands, full of leisure activities like photo walks and so on. cough BizConf cough. I don’t begrudge anyone a holiday, but it’s fairly indulgent as a conference. 

  4. The Scottish Ruby Conference has sold out incredibly quickly for two years running now; even Ruby Manor 2 sold out incredibly quickly, before we really had a chance to get momentum behind the community organisation of it.