Skip to content

Makefiles

Published On:
May 28, 2013
Last Updated:
Oct 3, 2024

Makefiles are a way of making a defining a source code build system to be used by the GNU command make. They take multiple file inputs, do some part or all of the code build process, and produce output(s). They are commonly used to create executables, object files, or libraries (combined object files) from source code (and sometimes there are used for completely different things that are not related to compiling code!). They come in particularly useful when dealing with large projects.

They are text files that are stored with the filename “Makefile” (or “makefile”), with no file extension (do not call it makefile.txt!). The capital M version is recommended because it persists at the top of the directory, along with other important files like the README.

Makefiles are most commonly used to compile code for Linux environments but also works in Windows. You can compile code for the native machine or cross-compile for other things such as embedded systems.

Unlike most other programming/scripting languages, Makefiles are white-space sensitive, AND have different behaviour for tabs and spaces. This can be annoying! Take care when coping makefile code from the internet, the clipboard can easily convert tabs into spaces and cause the makefile code to break.

Make can be then used by “higher-level” build programs, such as the Autoconfig and Automake tools.

If you are thinking of using Make and Makefiles for a new project, I would recommend you consider using CMake instead. CMake provides a much more modern, higher-level and powerful framework for building software.

A Simple Example

A simple, complete example of a Makefile is shown below:

make: main
gcc -o main main.c

If in Linux, opening a terminal, typing make and pressing enter while in the same directory as this makefile will compile a file called main.c with the GCC compiler and produce the output executable file main.

The primary three things a Makefile consists of are targets, prerequisites, and recipes. The above code contains two of these, main, which is the target, and gcc -o main main.c, which is the recipe. Make interprets this code as saying “You make main by running the command gcc -o main main.c in the terminal. Note that all recipes are run in the terminal, and each new recipe (which is on a new line), is run in a different instance of the terminal.

If no parameters are passed in when you call make, the first rule in the makefile will be run.

Adding Variables

Make supports variables that are defined before they are used in a rule. The value of the variable is substituted to replace the constant at run-time, similar to the C preprocessor.

You assign a value to a variable in the following ways:

VARIABLE = VALUENormal assignment of a variable. Any other variables within in it are recursively expanded when the variable is used, not when it is declared. This is the same behaviour you would expect from a variable declared in C.
VARIABLE := VALUEAny other variables within in it are expanded on declaration (instantly). This is similar behaviour to if you declared a const variable in C++.
VARIABLE ?= VALUEThis sets the variable only if it doesn’t have a value already. If the variable hasn’t already been set, the actual calculation of the value will be deferred until when it is used (like VARIABLE = VALUE).
VARIABLE += VALUEThis appends VALUE to the value that VARIABLE already has. Determining when to calculate the variable follows how it was initially set (i.e. with = or :=).

There are common names that most people use for variables, which are shown below.

CC
CFLAGS

Using wildcards in variable assignment requires special syntax, see the Wildcards section.

Wildcards

Wildcards are a great way to make automatic makefiles, i.e. makefiles that you do not need to continuously update as you create/delete code files.

One of the most common uses for a wildcard is with the “clean” rule, which is usually along the lines of:

clean:
rm -f *.o

The *.o string is called a glob. It will match any file names which end in .o. This will delete all object files in the current directory.

Wildcard expansion does not occur if you define a variable. For this reason, if you use wildcards in variable assignments, you use the following syntax instead:

objects := $(wildcard *.o)

This will replace $(wildcard *.o) with a space-separated list of all object files in the current directory, e.g. objects := object_file1.o object_file2.o.

What is also neat is you can combine the wildcard functions with other functions for string substitution and analysis. For example, you could create a list of “future” object files by scanning the directory for files using the wildcard function, and then substituting the .c for .o. In this way you can compile and link a whole directory of .c files automatically. The following example does this using the patsubst function.

objects := $(patsubst %.c,%.o,$(wildcard *.c))
foo : $(objects)
cc -o foo $(objects)

Automatic Variables

Automatic variables are another great tool to use when creating automatic makefiles. Automatic variables begin with the $ character, just like normal variables.

Here is a table some of the most useful automatic variables:

Automatic VariableComment
$@The file name of the target of the rule.
$%The target member name, when the target is an archive member.
$The name of the first prerequisite.
$?The names of all the prerequisites that are newer than the target, with spaces between them.
$^The names of all the prerequisites, even if they are not newer than the target, with spaces between them. Does not contain any order-only prerequisites!
$|The names of all of the order-only prerequisites, with spaces between them.

Automatic variables are used frequently in the compiler command that is executed when a rule is matched.

Phony Targets

By default, Makefile targets are file targets, in the sense they expect the job to create a file with the same name. Sometimes you don’t want targets to represent files, for example clean. If clean was a file target, and if by chance a file called clean existed, then Makefile would not re-run it unless it thought the dependencies were out of date.

To get around this, you can use the special PHONY keyword.

.PHONY: clean
clean:
rm -f *.o

The above code will force make to always run the clean target, even if a file called clean exists. It essentially tells make that the clean target is always out-of-date, so it will always run. The .PHONY keyword can be placed before or after the target. Common targets to be labelled as phony are clean, all, and install1.

Including One Makefile In Another

Note that this is different from Calling One Makefile From Another.

A Makefile (or a partial Makefile) can be included in another with the -include directive.

Calling One Makefile From Another

Note that this is different from Including One Makefile In Another.

You can easily call one makefile from another. This is useful when you have a project which is made up of smaller sub-projects, and each one of the sub-projects has it’s own makefile.

The recommended to run another makefile is to write:

# Run another makefile from this makefile
$(MAKE) -C ./path/to/makefile/ all

where all can be substituted for any other parameter you wish to pass to the secondary makefile. Note that this method is preferred over manually changing directory and calling make yourself, which can be done in the following manner:

subsystem:
cd subdir && $(MAKE)

The && is so that make will only be called if cd is successful. If this wasn’t there and cd was not successful, make would be called in the wrong directory.

Debugging

Make prints out some debug information to the standard output when it is run. If you want more debugging functionality, try Remake, which is a patched version of GNU Make with better error reporting, and trace execution/debugging capability.

Changing Directory

You can change directory in a Makefile using the shell cd command. You must be aware though that each separate command in a recipe is run in a separate instance of the shell. This means that a cd will not effect consecutive commands. To get around this, you will have to concatenate all your commands into a single command, using ; (and \ to effectively split a single command over multiple lines in the Makefile for readability).

all:
cd my_dir; \
echo "I'm in a different dir."; \
my_command

Be warned though that with the above method, the steps in the command won’t stop if one fails. This can lead to unexpected behaviour, such as deleting the wrong thing if say, a cd command fails first. To prevent this, a better approach is to replace ; with the && operator.

all:
cd my_dir && \
echo "I'm in a different dir." && \
my_command

As of GNU make 3.82 (release in July 2010), you can use the special .ONESHELL target to run all commands in a recipe in the same shell instance2. This removes the need to concatenate all the commands with cd together.

.ONESHELL: # Applies to all targets in the file!
all:
cd my_dir
echo "I'm in a different dir."

OS Detection

You can detect the operating system of the machine that make is being run on by inspecting the $(OS) variable.

The example below assigns a shell command to the RM variable depending on the operating system.

ifeq ($(OS),Windows_NT)
RM = del /q
else # Assume UNIX
RM = rm -f
endif

Silencing Commands

By default, make will print out the commands in a recipe before it runs them. For example:

all:
echo "Hello, World!"

Will output:

echo "Hello, World!"
"Hello, World!"

Obviously, in certain cases (like the echo command above) you do not want the command to be printed, you want just the commands output to be printed. To achieve this, you can use the @ symbol at the beginning of the command. For example:

all:
@echo "Hello, World!"

Will output what we expect:

"Hello, World!"

Further Reading

For a comprehensive reading, checkout the GNU ‘make’ manual.

Footnotes

  1. StackOverflow (2024, Mar 25). What is the purpose of .PHONY in a Makefile? [forum post]. Retrieved 2024-10-14, from https://stackoverflow.com/questions/2145590/what-is-the-purpose-of-phony-in-a-makefile.

  2. StackOverflow. How do I write the ‘cd’ command in a makefile? [forum post]. Retrieved 2024-10-03, from https://stackoverflow.com/questions/1789594/how-do-i-write-the-cd-command-in-a-makefile.

  3. Wikipedia (2024, Sep 28). Environment variable. Retrieved 2024-10-03, from https://en.wikipedia.org/wiki/Environment_variable.