3.5 Makefile
1. What is make
?
make
is a tool which controls the generation of executables and other non-source files of a program from the program’s source files.
2. What is Makefile
?
A makefile is a file containing a set of directives used by a make build automation tool to generate a target/goal.
3. Makefile structure
A makefile consists of a set of rules. Each rule consists of a target, a list of prerequisites, and a series of commands to be executed.
The makefile consists of rules
. Each rule consists of target
, prerequisites
and commands
:
The
target
is usually the name of a file that is generated by a program; examples of targets are executable or object files. A target can also be the name of an action to carry out, such asclean
(see Phony Targets).The
prerequisites
are files that are used as inputs to create the target. A target often depends on several files.The
commands
are a series of steps typically used to convert the prerequisites into the target.
A simple makefile consists of rules
with the following shape:
target: prerequisites
command
command
command
The target is the name of the file that is generated by the command. The prerequisites are files that are used as input to create the target. The commands are a series of steps typically used to convert the prerequisites into the target.
4. Hello World
Make sure that you have make
installed on your system. If not, install it using the following command:
sudo apt-get install build-essential
Now, let’s create a simple Makefile that prints “Hello, World!” when you run make. Here’s the content of the Makefile
:
hello:
@echo "Hello, World!"
Save the content above in a file named Makefile
(it’s case-sensitive; it should be exactly Makefile unless you want to specify the makefile name every time you run make).
Now open a terminal, navigate to the directory where your Makefile is located, and simply type make
. The output should be “Hello, World!”.
Note
Explanation
The word hello
followed by a colon :
specifies a “target”. A target is essentially a label for a set of commands that are executed when you run make <target>.
@echo "Hello, World!"
: This line is a command that the make utility will execute. The @
symbol prevents the command itself from being printed to the terminal before it’s executed, so only the command’s output (“Hello, World!”) will be shown. Without the @
, you would see both the command and its output.
Indentation matters: The commands to be executed for a target must be indented by a Tab
, not spaces.
Comments start with #
, and they are ignored by make.
To run the target hello, you’d just execute make hello
or simply make
(if hello is the first target in the Makefile, it becomes the default target).
5. Variables and Arguments
Variables are used to make makefiles more flexible and reusable. Variables can be defined in the makefile, passed as arguments to make
, or inherited from the environment.
Variables are defined in the makefile using VAR = value
or VAR := value
. The difference between the two is that VAR = value
is a recursive
variable, while VAR := value
is a simple
variable. Recursive variables are more flexible than simple variables, but simple variables are more predictable.
Recursive variables are defined by VAR = value
. The value of a recursive variable is only expanded when it is used. This means that you can define a recursive variable at the end of the makefile, and use it in variable definitions above that line.
Simple variables are defined by VAR := value
. The value of a simple variable is expanded immediately. This means that you can’t define a simple variable at the end of the makefile and use it in variable definitions above that line.
Passing arguments directly to Makefile targets isn’t straightforward because make wasn’t designed to accept arguments in the way that a normal shell script does. However, there are workarounds to accomplish this.
One common way is to use make variables. These can be set on the command line when you invoke make.
# This is a comment
# The target is "hello" and the command to execute is below it
hello:
@echo "Hello, $(name)!"
To run this with your name, you would enter something like:
make hello name=Ion
The output would be “Hello, Ion!”.
Note
Explanation
$(name)
: This is a variable in make syntax. When you run make hello name=Ion
, make assigns the value “Ion” to name, and then substitutes $(name)
with “Ion” when it executes the echo command.
You can also specify a default value for the variable within the Makefile:
name=World
hello:
@echo "Hello, $(name)!"
With this version, if you just run make hello
without specifying name, it will default to “World”. If you run make hello name=Ion
, it will override the default value with “Ion”.
6. PHONY Targets
The .PHONY
target in a Makefile is used to specify that a target name does not represent a file. In make, by default, the targets are file names. When you define a target, make checks the modification time of the files that the target depends on (if any), and the modification time of the target file itself, to decide whether it needs to execute the commands under that target.
However, sometimes you have targets that don’t produce any files or where the name of the target doesn’t correspond to a file. In those cases, you use .PHONY
to tell make that the target is not a file name.
.PHONY: hello clean
hello:
@echo "Hello, World!"
clean:
@rm -f *.log
Note
Explanation
.PHONY: hello clean
: This line tells make that hello and clean are phony targets, which means they don’t represent files. Therefore, make will always execute the commands under these targets, even if there’s a file named hello or clean in the directory.
Why is .PHONY useful?
Always Execute: When a target is phony, make will always execute its commands, regardless of file states.
Avoid File Name Conflict: Imagine you have a file named clean in your directory. If you didn’t specify clean as a phony target, make clean would do nothing, thinking that the target clean is up-to-date because a file with the same name exists.
Clarifies Intent: By using
.PHONY
, you make it explicit that the target doesn’t produce a file with the target’s name. This makes the Makefile easier to understand.
You can place .PHONY
either at the top of your Makefile to declare all phony targets at once, or right before each phony target for better readability and maintainability, like so:
.PHONY: hello
hello:
@echo "Hello, World!"
.PHONY: clean
clean:
@rm -f *.log