Do you have really heavy Rakefiles?
I do. My day job includes working in a Ruby-based repository with a lot of test, build, and release tooling, living in an enormous Rakefile. Actually, an enormous main Rakefile, plus lots of additional Rakefiles in dozens of subdirectories. Combined, it’s well over seven thousand lines of Rakefile—and that’s actually quite a bit smaller than it has been in the past.
Rakefiles are the ugly junk closets of our Ruby projects. Our app might have nicely factored classes, short methods, and excellent test coverage for our application code, but that seldom extends to our Rakefiles. Is your Rakefile covered by your code health tools? Do you have tests for your Rake tasks? Didn’t think so.
And running Rake tasks is another adventure. Simple cases such as
rake test work great. But what if you need to pass in flags or arguments to a task? Yes, you can do it with Rake. There are several ways in fact. But it’s not pretty.
Rake is an incredibly useful tool that has served the community really well for many years. But I’ve always felt like it was not quite right, that its design didn’t quite match up. And that’s not surprising, because… it doesn’t.
Rake was written as a replacement for the unix make tool. As a make tool, it excels, covering all the essential aspects of file-based dependencies and builds. And the Rakefile format is, to a Rubyist, much nicer to work with than Makefile.
But it is still a make replacement, designed for building projects in languages like C with file-based compilation dependencies. Where you have to recompile object files because source and header files changed.
We generally don’t do that sort of thing in Ruby.
Indeed I think the only time Ruby devs compile C regularly is when we install a gem with C extensions. And for that, most gems use mkmf to create, yes, a Makefile. That’s right, we generally don’t even use Rake to build our C extensions. Maybe we should. But I digress.
When was the last time you wrote a Rakefile with a FileTask? Maybe you have, but most of us seldom if ever use Rake’s primary features. We don’t use Rake for what it’s designed for.
Instead, we mostly use Rake to write scripts. Scripts to run our tests, build assets, initiate deployments, and so forth.
Now when it comes to writing and using scripts, there are a few capabilities I find important.
I might need to pass arguments or flags to the script. For example, a test script might use arguments to configure the test environment or select which tests to run. Rake does have a syntax for arguments, but I’d like to use normal unix arguments and flags. It would be much nicer to say
run-test --integration --env=production
Online help for my scripts is also very important. I’m not going to remember the usage for all my scripts, and I don’t want to have to open the source every time. Rake provides a way to set a short description for each task, but it’s not well suited for long or complex content, or especially for documenting arguments.
I want my implementation to be organized, testable, and maintainable , just like application code. Large scripts should be broken into smaller methods. There should be principled ways to share code and data, and to break big projects into multiple files. Yes, all this is technically possible with Rake. It’s just Ruby after all. But, let’s face it: the Rakefile format doeesn’t exactly encourage good software practice.
Support for typical script-y features would be nice. Terminal features such as input, progress meters, and styled text. Shell completion. Rich file system manipulation and process control features. Not a lot of this is provided by Rake, and while you can bring in some additional gems, it’s not common practice.
But there’s one thing I like about Rake that I want to keep: the Rakefile. Or something like it. The ability to define scripts in a file in the current directory, with a simple DSL. That’s why I’ve been using Rake all this time, despite its limitations. I suspect that’s why we all still use it.
So is there an alternative?
Actually, there are a few. Thor is probably best known as a framework for building command line applications in your own gems. But it also can read “Thorfiles” in the current directory, and run them as tasks using the
thor executable. Your tasks then have access to all the features of Thor, including normal unix-style command line options and flags, and automatic help.
I’ve also been experimenting with a similar tool myself, called Toys. Like Thor, Toys reads its scripts from files in the current directory, and it supports unix-style command line options and flags, and automatically generates help screens. But it goes further, providing features that I’ve found helpful to manage the complex projects I work on. It provides better code organization, integrates with tab completion and the
did_you_mean gem, includes templates for generating common tasks, provides helpers for building terminal apps and controlling subprocesses, has built-in logging, and a whole lot more. Toys can also read an existing Rakefile, making it easy to migrate.
I’ve been using Toys to manage workflow scripts at my day job for several years already. And recently I’ve started replacing the Rakefiles for my open-source Ruby projects. Both usability and maintainability have improved significantly.
The Rails team also decided to move on from Rake for Rails apps. Several versions ago, the Rake tasks in new Rails apps were deprecated in favor of
bin/rails, a custom command line tool.
So we already have some good alternatives. I think it’s just a matter of the Ruby community at large trying them out.
Rake has served the community well for many years, and for some uses it’s still very appropriate. Do you need to orchestrate building files in your project—whether that’s C source, assets, codegen, or other tasks with file-based dependencies? Then Rake will continue to be your friend.
But for many, maybe most, of your project tasks—running tests, deploying code, performing admin tasks, etc.—it may make more sense to use a tool that is optimized for writing scripts. For these cases, maybe it is indeed time to replace Rake.
If you agree, consider checking out Toys. I’d love to hear whether it works for you, and how it could be better. And if you think Rake should continue to be the tool of choice, I’d also love to hear your thoughts.