If you want to run or update a task when certain files are updated, the make utility can be useful. The make utility requires a file, Makefile (or makefile), which defines the set of tasks to be executed. You may have used make to compile a program from source code. Most open source projects use make to compile a final executable binary, which can then be installed using make install.
In this article, we will explore make and makefile using basic and advanced examples. Before you begin, make sure that make is installed on your system.
Basic examples
Let’s start by printing the classic “Hello World” on the terminal. Create an empty myproject directory containing a Makefile with this content
: say_hello: echo “Hello World”
Now run the file by typing make inside the myproject directory. The result will be:
$ make echo “Hello World” Hello World
In the example above, say_hello behaves like a function name, as in any programming language. This is called the goal. Prerequisites or dependencies follow the target. For the sake of simplicity, we have not defined any prerequisites in this example. The echo command “Hello World” is called the recipe. The recipe uses prerequisites to make a goal. The goal, prerequisites and recipes together make a rule.
To summarize, below is the syntax of a typical rule
: target: prerequisites <TAB>
recipe As an example, a target might be a binary file that depends on prerequisites (source files). On the other hand, a prerequisite can also be a target that depends on other dependencies
: final_target: sub_target final_target.c Recipe_to_create_final_target sub_target: sub_target.c Recipe_to_create_sub_target
The destination doesn’t need to be a file; it could be just a name for the recipe, as in our example. We call these “false targets.”
Going back to the previous example, when make was executed, the entire echo of the “Hello World” command was displayed, followed by the output of the actual command. We often don’t want that. To suppress the echo of the actual command, we need to start echo with @
: say_hello: @echo “Hello World”
Now try to run make again. The output should show only this
: $ make Hello World
Let’s add a few more fake destinations: generate and clean up the Makefile
: say_hello: @echo “Hello World” generate: @echo “Creating empty text files…” touch file-{1..10}.txt clean: @echo “Clean-up…” rm *.txt
If we try to run make after the changes, only the target say_hello will be executed. This is because only the first destination in the makefile is the default destination. Often called the default goal, this is why you’ll see everything as the first goal in most projects. It is everyone’s responsibility to call for other targets. We can override this behavior by using a special false target called . DEFAULT_GOAL.
Let’s include that at the beginning of our makefile:
. DEFAULT_GOAL := generate
This will run the target generate as default:
$ make Creating empty text files… Tap File-{1..10}.txt As the
name suggests, the fake target. DEFAULT_GOAL can only run one target at a time. This is why most makefiles include them all as one destination that you can call as many destinations as needed.
Let’s include the false target all and remove . DEFAULT_GOAL
: all: say_hello generate say_hello: @echo “Hello World” generate: @echo “Creating empty text files…” touch file-{1..10}.txt clean: @echo “Clean-up…” rm *.txt
Before running make, let’s include another special fake target, . PHONY, where we define all targets that are not files. make will run your recipe regardless of whether a file with that name exists or what its last modified time is. Here is the full makefile:
. PHONY: all say_hello generate clean all: say_hello generate say_hello: @echo “Hello World” generate: @echo “Creating empty text files…” touch file-{1..10}.txt clean: @echo “Clean-up…” rm *.txt The make
must call say_hello and generate: $ make
Hello World Creating empty text files… Tap File-{1..10}.txt
It is a good practice not to call clean in all or put it as the first objective. clean should be called manually when cleaning is needed as the first argument to do: $ do clean
Cleaning… rm *.txt Now that you have an idea of how a basic makefile works and how to write a simple makefile
, let’s look at some more advanced examples
.
Advanced examples
Variables
In the example above, most of the target values and prerequisites are hard-coded, but in real projects, these are replaced with variables and patterns
.
The easiest way to define a variable in a makefile is to use the = operator. For example
, to assign the gcc command to a CC variable: CC = gcc
This is also called a recursive expanded variable, and is used in a rule as shown below
: hello: hello.c ${CC} hello.c -o hello
As you might have guessed, the recipe expands as follows when passed to the terminal:
gcc hello.c -o hello
Both ${CC} and $(CC) are valid references for calling gcc. But if one tries to reassign a variable to oneself, it will cause an infinite loop. Let’s check this
: CC = gcc CC = ${CC} all: @echo ${CC}
Running make will result in
: $ make Makefile:8: *** The recursive variable ‘CC’ refers to itself (eventually). Stop.
To avoid this scenario, we can use the := operator (this is also called the simply expanded variable). We should have no problem running the
makefile below: CC := gcc CC := ${CC} all: @echo ${CC}Patterns and functions The
following makefile can compile all C programs using variables, patterns, and functions. Let’s explore it line by line
: # Usage: # do # compile all binary # clean # delete ALL binaries and objects. PHONY = all clean CC = gcc # compiler to use LINKERFLAG = -lm SRCS := $(wildcard *.c) BINS := $(SRCS:%.c=%) all: ${BINS} %: %.o @echo “Checking..” ${CC} ${LINKERFLAG} $< -o $@ %.o: %.c @echo “Creating object..” ${CC} -c $< clean: @echo “Cleaning…” rm -rvf *.o ${BINS}
-
Lines beginning with # are comments
-
line. PHONY = all clean defines false all and clean targets.
-
The LINKERFLAG variable defines the flags to be used with gcc in a recipe
-
SRCS := $(wildcard *.c): $(wildcard pattern) is one of the functions for file names. In this case, all files with the .c extension will be stored in a variable SRCS.
-
BINS := $(SRCS:%.c=%): This is called the substitution reference. In this case, if SRCS has values ‘foo.c bar.c’, BINS will have ‘foo bar’.
-
The false destination calls all values in ${BINS} as individual destinations
-
Rule:
%.o @echo “Checking..” ${CC} ${LINKERFLAG} $< -o $@
Let’s look at an example to understand this rule. Suppose foo is one of the values in ${BINS}. Then % will match foo (% can match any target name). Below is the rule in its expanded form:
foo: foo.o @echo “Checking..” gcc -lm foo.o -o foo As shown, % is replaced by foo. $<is
replaced by foo.o. $< is modeled to match the prerequisites and $@ matches the target. This rule will be called for each value in ${BINS
-
o: %.c @echo “Creating object..” ${CC} -c $<
Each prerequisite of the preceding rule is considered a target for this rule. Below is the rule in its expanded form
: foo.o: foo.c @echo “Object Creation..” gcc -c foo.c
-
Finally, we remove all binary and object files in target clean.
.
.
Line all: ${BINS}:
.
} Rule: %.
Below is the
rewrite of the previous makefile file, assuming it is placed in the directory that has a single foo.c file
: # Usage: # make # compile all binary # make clean # remove ALL binaries and objects. PHONY = all clean CC = gcc # compiler to use LINKERFLAG = -lm SRCS := foo.c BINS := foo all: foo foo: foo.o @echo “Checking..” gcc -lm foo.o -o foo foo.o: foo.c @echo “Creating object..” gcc -c foo.c clean: @echo “Cleaning…” rm -rvf foo.o foo
For more information on makefiles, see the GNU Make manual, which provides a complete reference and examples
.
You can also read our Introduction to GNU Autotools to learn how to automate the generation of a makefile for your coding project.