Hello GNU Make

by daniel packard

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:

  1. each command for a target must be indented with a TAB character (spaces are not compatible)
  2. I've prefixed the only command in this example with an @ character - this tells GNU make to suppress the command itself from the output.
  3. The instructions above explicity name a target when invoking make (i.e. make hello tells make to find the hello 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:

  1. default - a target that executes no commands itself, but depends on three other targets (target1, target2, and target3)
  2. target1, target2, and target3 - 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:

image

Some important things to note here are:

  1. because I didn't specify a target, GNU make executed the first target found (default)
  2. 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:

image

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

image

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 on target1
  • target1 depends on target2
  • target2 depends on target3

Because dependencies are specifed in this way, they must happen in order: target3 > target2 > target1 > default

Software Development GNU Make, C, c++