Lies, damn lies and dependencies (Yet Another Ruby Rant)

In the previous installment I have complained at how compass required a long list of OSX-specific dependencies, as well as Chrome and a (now dead) gem for reloading pages for its tests.

Well, let’s be honest: it doesn’t require them. It simply tells you it does.

The tests seem to run fine without Chrome, without livereload and most importantly without any of the Darwin-specific dependencies. So why are they still required? The answer lies in Bundler, the “nice” idea Ruby developers had to deal with dependencies.

While from one side Bundler is nice (disallows gems that you don’t enable from being loaded) it has caused all distributions quite a few headaches: not only older versions insisted in copying the content of the gem (even when coming from the system) into the project’s directory, but to work correctly, it expects all gems to provide correct gemspecs with their dependencies. You’d expect that to make them better, but that’s not always the case.

More than a few times the dependencies are set too strict (since Bundler is designed to allow relying on old and obsolete gems without clashing with newer gems requiring newer dependencies — which sounds good at first, until you understand that it might as well be a security issue), but much more commonly you have what I can only call “depend creep”.

Let’s take a step back and go back at the design of Bundler: given it seems to live in symbiosis with Rails 3, it makes sense for it to be able to specify dependencies only needed during development (webservers, logging and debug), or only during production (performance improvements) or testing (testing frameworks, mockers, and so on)… which is why it allows you to define “groups”, which you can disable when calling bundle

If it wasn’t the case that they still require you to install the dependencies! While excluding a particular group allows you to avoid loading the gems, Bundler still insists to make sure that there are no clashing dependency restriction between the various groups… which in turn means that you need to have the development and testing gems available in production as well. The documentation makes a point that the whole :platforms setting is also just an automated group handling, which means that Bundler will try to create the full dependency tree on Linux counting the Darwin and Windows gems as well, which definitely is not what we’re looking forward to.

Oh and it goes one step further: since Gems do not really have a way to explicit dependencies that are valid only for one Ruby implementation, and even if Gemfile has a way to do that, you still end up requiring gems in Ruby 1.9 (for instance) where they make no sense at all. And this brought us a number of “placeholder” gems, that when executed in Ruby 1.9 have no purpose at all.

This without counting the way the ruby-debug gem, that was already tricky due to the incompatibility between the original code and JRuby’s implementation, has now a sibling in ruby-debug19, a different gem that only works in 1.9 (why not maintain them together and simply build one or the other extension is still something I’m wondering).

In the case of compass the Gemfile was listing a number of dependencies that are used by the author during development and make no sense for us during package-testing; which means I don’t have to waste time with all of them, but just with part. Actually, almost all the dependencies are already in portage; I guess the one that is missing would be timecop — I have an ebuild for it, since I wrote it for some other gem before, but its tests fail consistently, and the last release being made over an year ago, with a lot of noise on the GitHub issues’ page, does not let me hope for the best. Honestly, though, even timecop seems to be unneeded, as I see it required but I don’t see its functions ever used.

I sure hope that most of the same is true for paperclip — I was finally able to package cocaine, since bourne was updated to mocha 0.10 (and yet it requires forcefully version 0.10.4 … which is not working under Ruby 1.9, for a regression which will be fixed in 0.10.5 — the ebuild hacks the version in the specification to make sure it does not complain as long as any 0.10 version is installed, for now), but now I’m looking forward the very long list of development dependencies, and wondering if I’ll ever be able to get it to work for me.

Now there is a good question: why am I not giving up? Because once it works, Radiant is a cool CMS; just like Typo is a cool blog engine in my opinion. I’m looking forward for Jim’s reduced dependencies (which he noted on the comments to my other post), but even as it is, it’s still the best web application for the task I could find. Would I be able to find something else? Maybe, but I’m not sure it would be the same. And even if Jim makes the clipped extension optional, I have no doubt I’ll be using it (and thus fighting with paperclip anyway): Radiant 1.0.0 is even better than the previous ones because it solves the problem with extensions’ compatibility…

Test comprehensiveness versus replicability — A Ruby Rant

I guess that “A Ruby Rant” could become a regular column on this blog, given how many of my posts over time has been “Ruby Rants”, but let’s not dig further.

I’m trying my best to package the new dependencies introduced with Radiant 1.0.0rc4 so that we can update the package in Portage (given that Radiant 0.9 is in tree already). This is proving quite difficult; even though Radiant upstream helped me out by replacing the old dependency on highline with a modern one after the issue was fixed upstream, there are a few new gems that require hours and hours of work to package.

The first big issue comes with cocaine — this gem is developed by ThoughtBot, which are the designers, one would expect highly professional development from them, but that’s by far not the case. The gem requires another ThoughtBot-developed library for testing, bourne which in turn requires mocha, but not any mocha, up to yesterday it required strictly version 0.9.5; now it requires strictly version 0.10.4, which is an improvement but still not kosher. Why this happens?

@flameeyes It hooks deep into mocha and breaks on internal changes. We’d love an internal api for mocha, but there isn’t one.

So here you can see one huge issue with Ruby development: since it’s very hard to actually make internal interfaces internal, due to monkey-patching and scope games, people will end up relying on things that they shouldn’t be relying upon. And they pretend that’s a good way to solve the issue because there is not a better way. Damn.

Okay strike one, I have put cocaine aside for now (and that was a good idea, seeing how prodding them about it have gotten them to at least update bourne’s dependency on a version that is not quite as ancient), and worked on another dependency: compass which is yet another CSS framework.

Testing this particular package has proven quite difficult, because it has a long list of extra dependencies that you won’t find listed in the gemspec at all, but just on the Gemfile. This list among others include compass-validator which is a gem only ever required by compass, which requires compass on its own (without listing it in the gemspec)… even if this sounds fishy enough it doesn’t cover how fishy that gem is.

The compass-validator gem bundles a series of prebuilt Java libraries up to W3C’s CSS Validator and executes Java at runtime. No, this has nothing to do with JRuby, and even when using JRuby it wouldn’t load the Java classes but still execute Java. Thanks to Krzysztof at least I’ve been able to get the gem to not bundle so much Java code; instead it relies on dev-java/css-validator and java-config to call into the right commands. Still nasty but at least usable.

But this is not yet a nasty problem: the Gemfile lists autotest which is a dummy gem for ZenTest instead (so why not list ZenTest directly? bah!), and then proceeds with two different ways to wrap around gems specific for OS X: autotest-fsevent is under a RUBY_PLATFORM check, while rb-fsevent is under a group. The problem is that Bundler always install all the groups, despite not loading them if you exclude them.

Okay so the gem has some specific codepaths for OS X, does that matter much? Probably not as long as you can still ignore them and run them on Linux. But there is more trouble ahead when I reach the terrific livereload gem, which is described this way:

LiveReload is a Safari/Chrome extension + a command-line tool that: 1. Applies CSS and JavaScript file changes without reloading a page. 2. Automatically reloads a page when any other file changes (html, image, server-side script, etc).

Okay so it requires Chrome at test-time.. not so surprising as another of the gems I use (best_in_place) indirectly uses selenium-webdriver, which uses Firefox. The problem is that when you go to the source repository for that gem, you’re told that it’s deprecated (compass is still actively developed!) in favour of … a graphical Mac/Windows application.

So okay I’m all to cover as much as possible with tests, but how does that help if you make it impossible for anyone else but you to actually run the tests because you tie them to one specific platform, and one that is very unlikely to be used as production server?!