Moving from Make to Robo
I loooove the Make build system. It’s everywhere, pretty sane and just works out of the box with a large chunk of the different tools I…
I loooove the Make
build system. It’s everywhere, pretty sane and just works out of the box with a large chunk of the different tools I use. Additionally, I have perfected the art of generating self documenting Makfiles which makes them extremely quick to set up for new projects.
However, I’m moving away from it (at least for PHP projects).
My love for Make is … unique
Make suffers from a few problems that prevents its adoption internally at Sitewards. Instead, people roll their own build tools with a variety of bash scripts, grunt tasks or even composer post-install scripts. As much as I have tried to spread the good word of Make, it has not spread.
Honestly, this is probably because I do not use it anything like it was intended.
It is a bizarre syntax
Make is bizarre. It makes sense once you understand it, but to the new user Make is essentially nonsensical. Let’s take the following target:
.PHONY: dev.apache.shell
dev.apache.shell: ## Get a shell in the apache container
cd deploy/docker-compose && \
docker-compose exec --user=developer $(CONTAINER_APACHE_NAME) /bin/bash
It’s .. mental. Let’s unpack it:
.PHONY: dev.apache.shell
Mark this target as one that doesn’t produce any output.dev.apache.shell
The target. I use.
as a convention to namespace targets to be clear what they’re for.# Get a shell...
The documentation. This isn’t a normal make feature, but something polyfilled.cd
Changing directory. Ya’ll probably seen this before.docker-compose exec --user=developer
The command. The sane bit of Make$(CONTAINER_APACHE_NAME)
A make variable. NOT A BASH VARIABLE! A make one. Make has its own variables. To access a bash env var it’s$${CONTAINER_APACHE_NAME)
.
For colleagues who are mostly proficient with web languages this is madness.
It doesn’t support help natively
Perhaps the most frustrating part of make
is it’s so opaque. Unless we’re using the documenting polyfill earlier mentioned, one must both read and understand all the Makefile
to determine what is happening.
Help is super valuable to colleagues who do not specialise in the tasks written in the build file.
There is basically no documentation
Make is documented in the same way many Linux features are. That is, badly.
Man pages are not good documentation. They’re always there and they’re accurate, and I’m sure they’re easier to write — but they’re awful to beginners. Additionally there is no “introduction to make”; users are expected to simply know it somehow.
In summary
An opaque, slightly insane mess.
A robot that everyone knows and loves
Robo is a task runner that was released a few years ago, essentially under the radar. It makes dramatically different design decisions, which I think in retrospect are the correct ones.
It’s a less bizarre syntax
Specifically, robo uses the native PHP OO language but overloads the specification slightly to render things like documentation. Let’s take the above task, and reimplement it in the Robo syntax:
/**
* Starts a shell in the Apache container
*
* @command dev:apache:shell
*/
public function devApacheShell()
{
$this->taskExec('docker-compose')
->dir('deploy' . DIRECTORY_SEPARATOR . 'docker-compose')
->args([
'--project-name=' . self::DOCKER_COMPOSE_PROJECT_NAME,
'exec',
'--user=' . self::CONTAINER_APACHE_USER,
self::CONTAINER_APACHE_NAME,
'/bin/bash'
])
->run();
}
For those of us who are used to PHP, this is super clear. But let’s unpack it a litttle:
Starts a shell...
The doc block becomes the documentation for the CLI. Additionally, by adding a second paragraph, we can have more advanced documentation including ASCII diagrams or whatever.@command
Robo will automatically generate a command syntax based on the function name, but in this case it would bedev:apache-shell
. Instead, I likedev:apache:shell
, noted in this doc block.$this->taskExec()
Essentially this is justexec()
or “run a shell”->dir()
Change to a given workdir before running the taskargs([])
An array of arguments to pass to the process->run()
Run the task. Seems redundant, I don’t know why this is here.
This is a much, much simpler format, and does not rely on anything insane like load bearing tabs.
Help is natively supported
I cannot overstate how useful it is to allow colleagues to run:
$ robo
and have a list of options shown. Further, the ability to run:
$ robo help dev:shell:apache
and show more complex information is again, superbly useful for tasks that are not straight forward.
It comes preset with a bunch of existing functions
Perhaps the most frustrating thing at robo
is it is much, much more verbose than make
. However, a lot of this is accounted for by providing many tasks out of the box in a shorter syntax. Tasks for working with:
The filesystem
Docker
Markdown
Semver
And any number of other things. Additionally, where a task doesn’t exist, it’s trivial to fall back on exec
.
It supports arguments
Perhaps my most annoying ongoing gripe with Make is that it does not allow arguments specifically. The convention is to supply environment variables:
CONTAINER=build-css make container
but remembering, documenting and validating these arguments is a pain. Robo allows specifying arguments (including required arguments), and allows documenting those arguments:
robo build:container --container="build-css"
Additionally, because they’re all just public functions, one task can call other tasks.
Robo still has problems
That being said, in the move there are some things that I miss from make
or simply more generally:
No way to specify task dependencies
Task dependencies are specified by simply calling the public methods. This is fine, but I liked the concise syntax that make
had. Not a deal breaker, but makes the functions a bit of a mess.
No way to chunk tasks
This is perhaps the most annoying. With make
I can run multiple targets easily:
$ make foo bar # same as $ make foo && make bar
However, robo does not support task chunking in this way. It must be:
$ robo foo && robo bar
or creating a task that does both.
However, these tradeoffs are not such a high price to pay. Certainly well worth it to get understandable build files for colleagues.
In conclusion
I still like Make. I use it for the canonical task runner on my personal projects and probably will continue to do so, as it works well enough across languages.
However, using a build system that is understandable by colleagues makes life considerably easier and makes the project far more accessible and enjoyable by others.
Accordingly, while I love you make
, I’m moving on.
Thanks
Behrouz Abbasi, who’s questions made me rework the build.