> ## Content Index
> Fetch the complete content index at: https://linuxhandbook.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Using the Make Utility and Makefiles in Linux [Guide]
- URL: https://linuxhandbook.com/using-make/
- Published: 2022-12-06T12:44:28.000Z
- Updated: 2022-12-06T12:45:17.000Z
- Description: Learn the basics of makefile and how to use the make utility for building your applications in Linux with a sample C project.
- Author: Pratham Patel
- Tags: Tutorial

This is a complete beginner's guide to using the make command in Linux.

You'll learn:

- The purpose of the make command
- Installation of the make command
- Creating and using the makefile for a sample C project

## What is the make utility?

The make utility is one of the handiest utilities for a programmer. Its primary purpose is to compile a medium-to-large software project. The make utility is so helpful and versatile that even the [Linux kernel uses it](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Makefile?ref=linuxhandbook.com)! 

To understand the usefulness of the make utility, one must first understand why it was needed in the first place.

As your software gets more extensive, you start relying more and more on external dependencies (i.e., libraries). Your code starts splitting into multiple files with God knows what is in each file. Compiling each file and linking them together sanely to produce necessary binaries becomes complicated.

> "But I can create a Bash script for that!"

Why yes, you can! More power to you! But as your project grows, you must deal with incremental rebuilds. How will you handle it generically, such that the logic stays true even when your number of files increases?

This is all handled by the make utility. So let us not reinvent the wheel and see how to install and make good use of the make utility.

## Installing the make utility

The make utility is already available in the first-party repositories of almost *all* Linux distributions.

To install make on Debian, Ubuntu, and their derivatives, use the `apt` package manager like so:

```bash
sudo apt install make
```

To install make on Fedora and RHEL-based Linux distributions, use the `dnf` package manger like so:

```bash
sudo dnf install make
```

To install make on Arch Linux and its derivatives, use the `pacman` package manager like so:

```bash
sudo pacman -Sy make
```

Now that the make utility is installed, you can proceed to understand it with examples.

## Creating a basic makefile

The make utility compiles your code based on the instructions specified in the makefile in the top level directory of your project's code repository.

Below is the directory structure of my project:

```bash
$ tree make-tutorial

make-tutorial
└── src
    ├── calculator.c
    ├── greeter.c
    ├── main.c
    └── userheader.h

1 directory, 4 files
```

Below are the contents of the `**main.c**` source file:

```main.c
#include <stdio.h>

#include "userheader.h"

int main()
{
    greeter_func();

    printf("\nAdding 5 and 10 together gives us '%d'.\n", add(5, 10));
    printf("Subtracting 10 from 32 results in '%d'.\n", sub(10, 32));
    printf("If 43 is  multiplied with 2, we get '%d'.\n", mul(43, 2));
    printf("The result of dividing any even number like 78 with 2 is a whole number like '%f'.\n", div(78, 2));

    return 0;
}
```

Next are the contents of the `**greeter.c**` source file:

```greeter.c
#include <stdio.h>

#include "userheader.h"

void greeter_func()
{
    printf("Hello, user! I hope you are ready for today's basic Mathematics class!\n");
}
```

Below are the contents of the `**calculator.c**` source file:

```calculator.c
#include <stdio.h>

#include "userheader.h"

int add(int a, int b)
{
    return (a + b);
}

int sub(int a, int b)
{
    if (a > b)
        return (a - b);
    else if (a < b)
        return (b - a);
    else return 0;
}

int mul(int a, int b)
{
    return (a * b);
}

double div(int a, int b)
{

    if (a > b)
        return ((double)a / (double)b);
    else if (a < b)
        return ((double)b / (double)a);
    else
        return 0;
}
```

Finally, below are the contents of the `**userheader.h**` header file:

```userheader.h
#ifndef USERHEADER_DOT_H
#define USERHEADER_DOT_H

void greeter_func();

int add(int a, int b);
int sub(int a, int b);
int mul(int a, int b);
double div(int a, int b);

#endif /* USERHEADER_DOT_H */
```

### Basics of a makefile

Before we create a bare-bones makefile, let us take a look at the syntax of a makefile. The basic building block of a Makefile consists of one or many "rules" and "variables".

### Rules in a makefile

Let us first take a look at rules in the makefile. A rule of makefile has the following syntax:

```Makefile
target : prerequisites
    recipe
    ...
```

- A `**target**` is the name of file that will be generated by make. These are usually object files that are later on used for linking everything together.
- A `**prerequisite**` is a file that is necessary for the target to be generated. This is where you usually specify your `.c`, `.o` and `.h` files.
- Finally, a `**recipe**` is one or many steps needed to generate the `target`.

### Macros/Variables in makefile

In C and C++, a basic language feature is variables. They allow us to store values that we might want to use in a lot of places. This helps us use the same variable name where needed. An added benefit is we only need to make one change if we need to change the value.

Similarly, a makefile can contain variables. They are sometimes referred to as *macros*. The syntax to declare a variable in a Makefile is as follows:

```Makefile
variable = value
```

A variable and the value(s) it holds are separated by an equals (`=`) sign. Multiple values are separated by spaces between each other.

In general, variables are used to store various items necessary for compilation. Let's say that you want to enable run-time buffer overflow detection and enable full ASLR for the executable; this can be achieved by storing all the compiler flags in one variable, like `CFLAGS`.

Below is a demonstration doing this:

```Makefile
CFLAGS = -D_FORTIFY_SOURCE=2 -fpie -Wl,-pie
```

We created a variable called `CFLAGS` (compiler flags) and added all of our compiler flags here.

To use our variable, we can enclose it in parentheses beginning with a dollar sign, like so:

```Makefile
gcc $(CFLAGS) -c main.c
```

The above line in our makefile will add all of our specified compiler flags and compile the `main.c` file as we require.

#### Automatic variables

The make utility has a few automatic variables to help ease repetition even further. These variables are commonly used in a rule's recipe.

Some of the automatic variables are as follows:

| Automatic variables | Meaning                                                                                                                               |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| $@                  | Name of the rule of target. Usually used to specify the output filename.                                                              |
| $<                  | Name of the first pre-requisite.                                                                                                      |
| $?                  | Names of all pre-requisites that are newer than the target. i.e. files that have been modified after the most recent code compilation |
| $^                  | Names of all pre-requisites with spaces between them.                                                                                 |

You can find the full list of the automatic variables on [GNU Make's official documentation](https://www.gnu.org/software/make/manual/html%5Fnode/Automatic-Variables.html?ref=linuxhandbook.com).

#### Implicit Variables

Like the automatic variables covered above, make also has some variables that have a set use. As I previously used the `CFLAGS` macro/variable to store compiler flags, there are other variables that have an assumed use.

This can be thought not of as "reserved keywords" but more like the "general consensus" of naming variables.

These conventional variables are as follows:

| Implicit variables | Description                                                                                                                     |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
| VPATH              | Make utility's equivalent of Bash's PATH variable. Paths are separated by the colon sign (:). This is empty by default.         |
| AS                 | This is the assembler. The default is the as assembler.                                                                         |
| CC                 | The program for compiling C files. The default is cc. (Usually, cc points to gcc.)                                              |
| CXX                | The program for compiling C++ files. The default is the g++ compiler.                                                           |
| CPP                | The program that runs the C pre-processor. The default is set to $(CC) -E.                                                      |
| LEX                | The program that turns Lexical grammars into source code. The default is lex. (**You should change this** to flex.)             |
| LINT               | The program that lints your source code. The default is lint.                                                                   |
| RM                 | The command to remove a file. The default is rm -f. (**Please pay *strong* attention to this!**)                                |
| ASFLAGS            | This contains all the flags for the assembler.                                                                                  |
| CFLAGS             | This contains all the flags for the C compiler (cc).                                                                            |
| CXXFLAGS           | This contains all the flags for the C++ compiler (g++).                                                                         |
| CPPFLAGS           | This contains all the flags for the C pre-processor.                                                                            |
| .PHONY             | Specify targets that do not resembe name of a file. An example is the "make *clean*" target; where *clean* is a value of .PHONY |

### Comments in a makefile

Comments in a makefile are like those in a shell script. They start with the pound/hash symbol (`#`) and the contents of said line (*after* the pound/hash symbol) are considered as a comment by the make utility and is ignored.

Below is an example demonstrating this:

```Makefile
CFLAGS = -D_FORTIFY_SOURCE=2 -fpie -Wl,-pie
# The '-D_FORTIFY_SOURCE=2' flag enables run-time buffer overflow detection
# The flags '-fpie -Wl,-pie' are for enabling complete address space layout randomization
```

## Initial draft of a makefile

Now that I have described the basic syntax of a makefile's elements and also the dependency tree of my simple project, let us now write a very bare-bones Makefile to compile our code and link everything together.

Let us start with setting up the `CFLAGS`, `CC` and the `VPATH` variables that are necessary for our compilation. (This is not the complete makefile. We will be building this progressively.)

```Makefile
CFLAGS = -Wall -Wextra
CC = gcc
VPATH = src
```

With that done, let us define our rules for building. I will create 3 rules, for each `.c` file. My executable binary will be called `make_tutorial` but yours can be whatever you want!

```Makefile
CFLAGS = -Wall -Wextra
CC = gcc
VPATH = src

make_tutorial : main.o calculator.o greeter.o
        $(CC) $(CFLAGS) $? -o $@

main.o : main.c
        $(CC) $(CFLAGS) -c $? -o $@

calculator.o : calculator.c
        $(CC) $(CFLAGS) -c $? -o $@

greeter.o : greeter.c
        $(CC) $(CFLAGS) -c $? -o $@
```

As you can see, I am compiling all the `.c` files into object files (`.o`) and linking them together at the end.

When we run the `make` command, it will start with the first rule (`make_tutorial`). This rule is to create a final executable binary of the same name. It has 3 prerequisite object files for each `.c` files.

Each consecutive rule after the `make_tutorial` rule is creating an object file from the source file of same the name. I can understand how complex this feels. So let us break down each of these automatic and implicit variables and understand what they mean.

- `$(CC)`: Calls the GNU C Compiler (`gcc`).
- `$(CFLAGS)`: An implicit variable to pass in our compiler flags like `-Wall`, etc.
- `$?`: Names of all prerequisite files that are newer than the target. In the rule for `main.o`, `$?` will expand to `main.c` *IF* `main.c` has been modified after `main.o` had been generated.
- `$@`: This is the target name. I am using this to omit typing the rule name twice. In rule for `main.o`, `$@` expands to `main.o`.

Finally, the options `-c` and `-o` are `gcc`'s options for compiling/assembling source files *without linking* and specifying an output file name respectively. You can check this by running the `man 1 gcc` command in your terminal.

Now let's try and run this makefile and hope it works on first try!

```bash
$ make
gcc -Wall -Wextra -c src/main.c -o main.o
gcc -Wall -Wextra -c src/calculator.c -o calculator.o
gcc -Wall -Wextra -c src/greeter.c -o greeter.o
gcc -Wall -Wextra main.o calculator.o greeter.o -o make_tutorial
```

If you look closely, each step of compilation contains all the flags we specified in the `CFLAGS` implicit variable. We can also see that the source files were automatically sourced from the "src" directory. This occurred automatically because we specified "src" in the `VPATH` implicit variable.

Let's try and run the `make_tutorial` binary and verify if everything works as intended.

```bash
$ ./make_tutorial
Hello, user! I hope you are ready for today's basic Mathematics class!

Adding 5 and 10 together gives us '15'.
Subtracting 10 from 32 results in '22'.
If 43 is  multiplied with 2, we get '86'.
The result of dividing any even number like 78 with 2 is a whole number like '39.000000'.
```

[via GIPHY](https://giphy.com/gifs/maudit-maudit-frankenstein-boris-karloff-tze1mGedykiuk?ref=linuxhandbook.com)

## Improving the makefile

"What is there to improve?"  
Let us run the `ls` command you can see that for yourself ;)

```bash
$ ls --group-directories-first -1
src
calculator.o
greeter.o
main.o
Makefile
make_tutorial
```

Do you see the build artifacts (object files)? Yeah, they can clutter things up for the worse. Let's use our build directory and reduce this clutter.

Below is the modified makefile:

```makefile
CFLAGS = -Wall -Wextra
CC = gcc
VPATH = src:build

make_tutorial : main.o calculator.o greeter.o
        $(CC) $(CFLAGS) $? -o $@

build/main.o : main.c
        mkdir build
        $(CC) $(CFLAGS) -c $? -o $@

build/calculator.o : calculator.c
        $(CC) $(CFLAGS) -c $? -o $@

build/greeter.o : greeter.c
        $(CC) $(CFLAGS) -c $? -o $@
```

Here, I have made one simple change: I added the `build/` string before each rule that generates an object file. This will put each object file inside the "build" directory. I also added "build" to the `VPATH` variable.

If you look closely, our first compilation target is `make_tutorial`. But it will not be the target that is pedantically the first. The first target whose recipe runs is `main.o` (or rather `build/main.o`). Therefore, I added the "mkdir build" command as a recipe in the `main.o` target.

If I were to not create the "build" directory, I would get the following error:

```bash
$ make
gcc -Wall -Wextra -c src/main.c -o build/main.o
Assembler messages:
Fatal error: can't create build/main.o: No such file or directory
make: *** [Makefile:12: build/main.o] Error 1
```

Now that we have modified our makefile, let us remove the current build artifacts along with the compiled binary and rerun the make utility.

```bah
$ rm -v *.o make_tutorial
removed 'calculator.o'
removed 'greeter.o'
removed 'main.o'
removed 'make_tutorial'

$ make
mkdir build
gcc -Wall -Wextra -c src/main.c -o build/main.o
gcc -Wall -Wextra -c src/calculator.c -o build/calculator.o
gcc -Wall -Wextra -c src/greeter.c -o build/greeter.o
gcc -Wall -Wextra build/main.o build/calculator.o build/greeter.o -o make_tutorial
```

This compiled perfectly! If you look closely, we had already specified the "build" directory in the `VPATH` variable, making it possible for the make utility to search for our object files inside the "build" directory.

Our source and header files were automatically found from the "src" directory and the build artifacts (object files) were kept inside and linked from the "build" directory, just as we intended.

### Adding .PHONY targets

We can take this improvement one step further. Let's add the "make *clean*" and "make *run*" targets.

Below is our final makefile:

```Makefile
CFLAGS = -Wall -Wextra
CC = gcc
VPATH = src:build

build/bin/make_tutorial : main.o calculator.o greeter.o
        mkdir build/bin
        $(CC) $(CFLAGS) $? -o $@

build/main.o : main.c
        mkdir build
        $(CC) $(CFLAGS) -c $? -o $@

build/calculator.o : calculator.c
        $(CC) $(CFLAGS) -c $? -o $@

build/greeter.o : greeter.c
        $(CC) $(CFLAGS) -c $? -o $@

.PHONY = clean
clean :
        rm -rvf build

.PHONY = run
run: make_tutorial
        ./build/bin/make_tutorial
```

Everything about the build targets is the same, except for a change where I specify that I want the `make_tutorial` binary executable file placed inside the `build/bin/` directory.

Then, I set `.PHONY` variable to `clean`, to specify that `clean` is not a file that the make utility needs to worry about. It is... phony. Under the `clean` target, I specify what must be removed to "clean everything".

I do the same for the `run` target. If you are a Rust developer you will like this pattern. Like the `cargo run` command, I use the `make run` command to run the compiled binary.

For us to run the `make_tutorial` binary, it must exist. So I added it to the prerequisite for the `run` target.

Let's run `make clean` first and then run `make run` directly!

```bash
$ make clean
rm -rvf build
removed 'build/greeter.o'
removed 'build/main.o'
removed 'build/calculator.o'
removed 'build/bin/make_tutorial'
removed directory 'build/bin'
removed directory 'build'

$ make run
mkdir build
gcc -Wall -Wextra -c src/main.c -o build/main.o
gcc -Wall -Wextra -c src/calculator.c -o build/calculator.o
gcc -Wall -Wextra -c src/greeter.c -o build/greeter.o
mkdir build/bin
gcc -Wall -Wextra build/main.o build/calculator.o build/greeter.o -o build/bin/make_tutorial
./build/bin/make_tutorial
Hello, user! I hope you are ready for today's basic Mathematics class!

Adding 5 and 10 together gives us '15'.
Subtracting 10 from 32 results in '22'.
If 43 is  multiplied with 2, we get '86'.
The result of dividing any even number like 78 with 2 is a whole number like '39.000000'.
```

As you see here, we did not run the `make` command to compile our project first. Upon running the `make run`, compilation was taken care of. Let's understand how it happened.

Upon running the `make run` command, the make utility first looks at the `run` target. A prerequisite for the `run` target is our binary file that we compile. So our `make_tutorial` binary file gets compiled first.

The `make_tutorial` has its own prerequisites which are placed inside the `build/` directory. Once those object files are compiled, our `make_tutorial` binary is compiled; finally, the Make utility returns back to the `run` target and the binary file `./build/bin/make_tutorial` is executed.

such elegance much wow

## Conclusion

This article covers the basics of a makefile, a file that the make utility depends on, to simplify compilation of your software repository. This is done by starting from a basic Makefile and building it as our needs grow.