make
is a simple and useful tool that controls the building of your source code. It can be used in any programming language and is particularly effective at reducing repetitive tasks. For example, if you have multiple shell tasks listed one by one, you can use make
to organize the workflow efficiently.
When you download an open-source project or join a new project as a developer, the first step is often to build the project. Nearly all open-source projects include a Makefile
to organize the build process. However, understanding and modifying a Makefile
can be challenging, especially for beginners. The official manual is long and complex, making it difficult for new users to grasp the concepts quickly. While it is possible to find Makefile
tutorials online and copy-paste code, this approach does not help in truly understanding how Makefiles
work.
This article will help you understand the core concept of make
in just 10 minutes. By the end, you will be able to create your own Makefile
to run basic tasks and understand how make works, enabling you to manage projects more efficiently and reduce repetitive tasks.
Core Concept of Makefile Dependencies
Dependency is the core concept of make
. Understanding dependencies is the most important principle in make
. In a Makefile
, every task depends on other prerequisites, which can be files, tasks, or some other form of rules. If you want to run a task, you must define the target and prerequisites. We call this a dependency. A simple Makefile
consists of “dependencies” with the following structure:
foo: bar
The above Makefile
does nothing because the bar target is undefined. A target should depend on a defined prerequisite or have a defined way of working. The following target is defined to run the echo
command:
foo:
echo "hello world" #noticed: you must use 'tab' to start this line.
In this example, foo
is the target, and there are no prerequisites. When make runs, it executes the command associated with the foo target, which in this case is echo hello world
.
Real-World Example
Let’s dive into a real-world example to see how make and Makefile
dependencies work in practice. Suppose we have a simple C project with the following files:
.
├── http.c
├── http.h
├── main.c
├── socket.c
├── socket.h
In our project, the dependency relationships are as follows:
http.c
includeshttp.h
andsocket.h
.socket.c
includessocket.h
.main.c
includessocket.h
andhttp.h
.
To build an executable file with gcc
, we must compile the source code into object files and then link them to create an executable file. Here are the rules for our Makefile:
http.o: http.c http.h socket.h
gcc -c http.c
socket.o: socket.h
gcc -c socket.c
main.o: main.c http.h socket.h
gcc -c main.c
In the above rules:
http.o
is the target, and it depends onhttp.c
,http.h
, andsocket.h
. The command to compile it isgcc -c http.c
.socket.o
is the target, and it depends onsocket.c
andsocket.h
. The command to compile it isgcc -c socket.c
.main.o
is the target, and it depends onmain.c
,http.h
, andsocket.h
. The command to compile it isgcc -c main.c
.
To link all object files into an executable file, we define the following rule:
exe: http.o socket.o main.o
gcc -o exe http.o socket.o main.o
Now, you can type make exe
to build your project. This command will execute the necessary steps to compile the object files and then link them into the exe executable.
Hope it's clear to you how Makefile Dependencies work. Experimenting with creating and modifying Makefiles will deepen your understanding and improve your ability to handle complex build processes.
If you have any questions or comments, please feel free to leave a comment below.
Note: This post was originally published on liyafu.com (One of our makers' personal blog)
Nowadays, we spend most of my time building softwares. This means less time writing. Building softwares has become my default way of online expression. Currently, we are working on Slippod, a privacy-first desktop note-taking app and TextPixie, a tool to transform text including translation and extraction.