It seems like developers are afraid of using make as they link it to the painful
experience of compiling things from scratch — the dreaded ./configure && make && make install.
Part of this fear is due to the description of what make(1)
does:
The purpose of the make utility is to determine automatically which pieces of a large program need to be recompiled, and issue the commands to recompile them.
Not everyone is aware that make can be easily
used to manage tasks in your projects, so I wanted to share a brief introduction
ad how Makefiles help me automate some tasks
in my day to day activities: this brief guide will focus on using make as an
automation tool for tasks rather than a tool for compiling code.
Executing tasks…
Let’s start by simply creating a Makefile, and defining a task to run:
12
task:
date
If you run make task you will bump into the following error:
12
/tmp ᐅ make task
Makefile:2: *** missing separator. Stop.
and that’s because Makefiles use tabs to indent code. Let’s update our example by
using tabs rather than spaces and… …voila:
123
/tmp ᐅ make task
date
Fri Jun 15 08:34:15 +04 2018
What kind of sorcery is this? Well, make understood you wanted to run the section
task of your makefile, and ran the code (date) within that section in a shell,
outputting both the command and its output. If you want skip outputting the command
that’s being executed you can simply prefix it with an @:
12
task:
@date
12
/tmp ᐅ make task
Fri Jun 15 08:34:15 +04 2018
The first task in a Makefile is the default one, meaning we can run make
without any argument:
12
/tmp ᐅ make
Fri Jun 15 08:37:11 +04 2018
You can add additional tasks in your Makefile and call them with make $TASK:
1234567
task:
@date
some:
sleep 1
echo "Slept"
thing:
cal
123456789
/tmp ᐅ make thing
cal
June 2018
Su Mo Tu We Th Fr Sa
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
…in a specific order
A lot of times you will want to execute a task before the current one — think of
it as before or after hooks in your automated tests. This can be done by specifying
a list of tasks right after your task’s name:
123
task: thing some
@date
...
1234567891011121314
/tmp ᐅ make task
cal
June 2018
Su Mo Tu We Th Fr Sa
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
sleep 1
echo "Slept"
Slept
Fri Jun 15 08:40:23 +04 2018
Variables
Defining and using variables is fairly straightforward:
12345
VAR=123
print_var:
echo ${VAR}
...
123
/tmp ᐅ make print_var
echo 123
123
but watch out as your shell variables won’t work out of the box:
12
print_user:
echo $USER
123
/tmp ᐅ make print_user
echo SER
SER
as you will need to escape them with either ${VAR} or $$VAR.
Passing flags is also a bit different from what you might be used to — they’re
positioned as flags but use the same syntax as environment variables:
12
print_foo:
echo $$FOO
123456
/tmp ᐅ make print_foo
echo $FOO
/tmp ᐅ make print_foo FOO=bar
echo $FOO
bar
The shell
123456
5.3.1 Choosing the Shell
------------------------
The program used as the shell is taken from the variable `SHELL'. If
this variable is not set in your makefile, the program `/bin/sh' is
used as the shell.
Make will use sh to execute code in a task, meaning some stuff might not work
as you’re probably using some syntax that’s specific to bash — in order to switch
you can simply specify the SHELL variable (in our case
we would want to use SHELL:=/bin/bash).
As seen before, sometimes you will need to use a quirky, custom syntax to get a
regular shell command to work in make — just like variables need to be escaped
with a $$ or ${...}, you will need to use shell when using command substitution:
12
subshell:
echo $(shell echo ${USER})
123
/tmp ᐅ make subshell
echo alex
alex
Don’t believe me? Try removing the shell instruction and here’s what you’re going
to get:
123
/tmp ᐅ make subshell
echo
Conclusion
There’s so much more make can do, and so many more quirky things you might need
to find out to decrease the wps (WTF per second) when working with it :) that
doesn’t erase the fact that make is an extremely helpful took that allows us
to automate workflows with ease, without having to setup very complicated pipelines,
by writing tab-separated lines with a bunch of shell commands instead.