Continuous Integration means running your tests whenever you push new code. Thanks to Travis most open source projects have CI set up and the testing culture is pretty healthy in the Ruby ecosystem. But we think there is room for improvement: Most Ruby libraries have nondeterministic builds caused by how they specify their dependencies and maintainers can’t be 100% sure if their gem will work if it is installed by a user right now.

In Ruby you can’t use multiple versions of the same dependency in your code. That has led to most Rubygems maintainers supporting a very wide range of versions for each of their dependencies. Otherwise users would very quickly get into impossible to resolve version conflicts when trying to install a gem. Version requirements like these are quite common:

s.add_runtime_dependency('actionpack', '>= 4.0')
s.add_runtime_dependency('nokogiri', '>= 1.3.3')
s.add_runtime_dependency('rack', '>= 1.0.0')

These greater-than requirements mean that whenever your CI build runs, it uses the most recent version of those dependencies. The same is true when you use the twiddle-wakka:

s.add_runtime_dependency('arel', '~> 7.0')

While the version range is now bounded, Rubygems considers every release in the 7.x.x range and will install the most recent one.

Nondeterministic builds

Travis is creating a new environment every time your build runs, which means it will ask for the latest version of your dependencies that satisfies the gemspec requirements. If you specify a version range, Travis can potentially pull in a new version of that library in every new build! That is both good and bad: Good because that’s also how a new user of your library would install your library. Bad because now you have nondeterministic builds.

If you push new code regularly, your library gets tested with the new versions of your dependencies all the time. But both your change and the new version will be tested in the same build, often without you noticing. A test failure could be caused by your changes or by a bug in the dependency. Ideally you would test your change and the dependency update separately.

If you don’t push new code, your library won’t be tested at all with new versions and you make your users the first testers and rely on them to report issues when something breaks. I’ve seen this happening quite a few times, for example most recently with a wave of gems no longer supporting Ruby 1.9. Once one of your dependencies raises the required Ruby version, your library will have the same requirement as well. When would you find out? Only when you trigger a new CI build or when a user installs your library and gets a bundle install error. Ideally you would trigger a new build automatically whenever there is a new version of one of your dependencies that is within your range.

Wouldn’t it be better if you treat new versions of your dependencies the same way as new code in your library? After all your users don’t care if you broke your gem or the dependency did. They just want to use your library.

Introducing Depfu

That’s where Depfu comes in: Apart from helping you with actually upgrading dependencies regularly, we can trigger CI builds for new version that are within your specified version range. If everything works, we will delete that branch again without notifying you. If we see an error, either during bundle install or during the test run, we will let you know directly on Github. This way you can be sure your gem works with the current versions of your dependencies.

We think this is quite useful for Ruby library maintainers. Depfu is free for public repos, so give it a try or let us know how else we could help you manage dependencies in your Ruby project.