Hello GNU Make
What is GNU Make?
According to the GNU Project (emphasis mine):
GNU Make is a tool which controls the generation of executables and other non-source files of a program from the program's source files.
-- gnu.org, December 2020
As the description suggests GNU make is most commonly used in the context of building software projects — but it's much more flexible than that. To understand the power and flexibility of GNU make, I prefer to approach it as a parallel task runner and dependency-graph tool.
GNU make can:
- run arbitrary shell scripts in sequence or in parallel
- specify dependencies between tasks
- determine how to run tasks in parallel for maximum efficiency
- detect changes, and efficiently re-run partial or incremental tasks
The Anatomy of a Makefile
target: dependencies ...
commands
...
Makefiles are specified in terms of "targets", "dependencies", and "commands" (terminology may vary by learning resource).
For example, this simple Makefile contains a single target, hello
with no dependencies. When run, it executes the commmand echo "Hello, GNU make!"
hello :
echo "Hello, GNU make!"
Example 1: Most Basic Makefile
Here is an example you can copy/paste into a bash terminal to follow along:
git clone https://gist.github.com/daniel-packard/35986ff5bace8335f5ff1712582f3c28 hello-gnumake
cd hello-gnumake
make hello
This clones a minimal Makefile from the following GitHub Gist.
There are a few small notes about this minimal example:
- each command for a target must be indented with a
TAB
character (spaces are not compatible) - I've prefixed the only command in this example with an
@
character - this tells GNU make to suppress the command itself from the output. The instructions above explicity name a target when invoking
make
(i.e.make hello
tellsmake
to find thehello
target and run it). However, GNU make will execute the first target found by default, so the following will work just as well:git clone https://gist.github.com/daniel-packard/35986ff5bace8335f5ff1712582f3c28 hello-gnumake cd hello-gnumake make
Example 2: Multiple Targets
The second example contains 4 targets:
default
- a target that executes no commands itself, but depends on three other targets (target1
,target2
, andtarget3
)target1
,target2
, andtarget3
- three trivial targets that only echo out a small message
git clone https://gist.github.com/daniel-packard/e19431569ade3bb10c680e2e1b111403 hello-gnumake-2
cd hello-gnumake-2
make
Running the example should result in:
Some important things to note here are:
- because I didn't specify a target, GNU make executed the first target found (
default
) - the default target had no commands, BUT it had three dependencies. GNU make will ensure all target dependencies are run before the main target
You can also invoke make with any specific target, as in:
Finally, because target1
, target2
, and target3
have no dependencies on eachother, they can theoretically be executed in parallel using the -j
command line flag.
# adding the -jN tells make it can run up to N targets in parallel, e.g.
make -j4
When make determines that tasks can be run in parallel, as is possible in this example, there is no guarantee on the order of execution. Therefore, it's important to ensure propery dependency configuration in your makefile. In example 3, we'll look at sequencing 4 tasks.
Example 3: Enforcing Dependencies/Order
In the previous example, the default
target had three dependencies (target1
, target2
, and target3
) that had no inter-dependencies on eachother. Because of this, GNU make was able to execute the tasks in parallel with the -j4
command line flag.
If it's important that target1
, target2
, and target3
are executed in a particular order, this can be achieved by specifying dependencies on the individual targets. In this example:
- the
default
target depends ontarget1
target1
depends ontarget2
target2
depends ontarget3
Because dependencies are specifed in this way, they must happen in order: target3 > target2 > target1 > default