Make your own make
Introduction
One of my favorite features of Cargo is that it is not a general purpose build tool. This allows Cargo to really excel at the task of building Rust code, without usual Turing tarpit of build configuration files. I have yet to see a complicated Cargo.toml file!
However, once a software project grows, it’s almost inevitable that it will require some tasks besides building Rust code. For example, you might need to integrate several languages together, or to setup some elaborate testing for non-code aspects of your project, like checking the licenses, or to establish an elaborate release procedure.
For such use-cases, a general purpose task automation solution is needed. In this blog post I want to describe one possible approach, which leans heavily on Cargo’s built-in functionality.
Existing Solutions
The simplest way to automate something is to write a shell script. However there are few experts in the arcane art of shell scripting, and shell scripts are inherently platform dependent.
The same goes for make, with its many annoyingly similar flavors.
Two tools which significantly improve on the ease of use and ergonomics are just and cargo make. Alas, they still mostly rely on the shell to actually execute the tasks.
Reinventing the Wheel
Obligatory XKCD 927:
An obvious idea is to use Rust for task automation. Originally, I have proposed creating a special Cargo subcommand to execute build tasks, implemented as Rust programs, in this thread. However, since then I realized that there are built-in tools in Cargo which allow one to get a pretty ergonomic solution. Namely, the combination of workspaces, aliases and ability to define binaries seems to do the trick.
Elements of the Solution
If you just want a working example, see this commit.
A typical Rust project looks like this
Suppose that we want to add a couple of tasks, like generating some code from
some specification in the RON format, or
grepping the source code for TODO
marks.
First, create a special tools
package:
The tools/Cargo.toml
might look like this:
Then, we add a
[workspace]
to the parent package:
We need this section because tools
is not a dependency of frobnicator
, so it
won’t be picked up automatically.
Then, we write code to accomplish the tasks in tools/src/bin/gen.rs
and
tools/src/bin/todo.rs
.
Finally, we add frobnicator/.cargo/config
with the following contents:
VoilĂ ! Now, running cargo gen
or cargo todo
will execute the tasks!
Discussion on /r/rust.