GNU Make Series: 'Make' It Simple!
by
gg582 · 2026-05-25 01:49:23 · 20 views
GNU Make Series #1
The Foundations of Automation: Build, Clean, and Variables
Note: Instructions for installing additional commands such as Clang or Pypy3 are not provided.
When developing in a Linux environment, encountering the make command is inevitable. It is a tool that remains remarkably simple and accessible, even for students or those new to programming.
Let's explore the core mechanics of make using C as our primary example.
1. The Basic Makefile
A Makefile allows you to define various targets like make build or make run. For this introduction, we will implement two essential actions:
- Building the executable.
- Cleaning up the build results.
In C, the standard way to build a program is: cc -o [executable_name] [source_file.c]. We will use this logic as our skeleton.
Step 1: Prepare the Source Files
Create a new directory and create the following two files within it.
Warning: Makefiles require Hard Tabs for indentation. Do not use spaces. Depending on your editor settings, the Tab key may insert spaces; ensure it is set to provide a literal tab character.
## Save as: Makefile
## Indentation MUST be a Hard Tab.
## Comments start with #.
# 'all' creates the executable.
all:
cc -o main main.c
# 'clean' removes the generated file.
clean:
rm -rf main
// Save as: main.c
#include <stdio.h>
int main(int argc, char **argv) {
puts("Hello World!");
return 0;
}
Step 2: Execution
Navigate to the directory in your terminal and run:
make
ls -alkh
You will see an executable named main (the name we specified after -o). You can execute it with:
./main
Understanding Permissions
When you run ls -alkh, you'll notice a string like -rwxrwxr-x. This represents the file's permission bits.
- r = 4 (Read)
- w = 2 (Write)
- x = 1 (Execute)
Permissions are grouped into three digits (Owner / Group / Others). To calculate the value, you sum the bits:
- (r+w+x) = 4 + 2 + 1 = 7
- (r+x) = 4 + 1 = 5
For example, chmod 755 myfile sets standard executable permissions.
The "Household Account" Analogy: Imagine a household shared by Josh and Emily.
- Group: The Smith Household (
r-x) - rwx: Josh (Owner/Admin)
- rwx: Emily (Partner)
- r-x: Michael (Guest)
- r-x: Sarah (Guest)
In this scenario, Josh and Emily have Write (w) permissions. They can deposit money into the household account or buy new furniture. However, guests like Michael and Sarah only have Read (r) and Execute (x) permissions. They can use the existing furniture (Execute) or look around the house (Read), but they cannot change the house's contents (Write).
Since our generated file was rwxrwxr-x, anyone in our "household" (group) can modify it, while others can only read or run it.
2. Separating with Variables
The C ecosystem has many compilers: GCC (GNU), Clang (LLVM), and TCC (Tiny C). The cc command usually defaults to whatever is installed on the system. By using variables, we can let users inject their preferred compiler.
| Name | Command |
|---|---|
| GNU C Compiler | gcc |
| LLVM Clang Compiler | clang |
| Tiny C Compiler | tcc |
Update your Makefile as follows:
## ?= allows external override, = is a hard assignment.
CC ?= gcc
all:
$(CC) -o main main.c
clean:
rm -rf main
Now, you can build using Clang without changing a single line of the file:
make clean
CC=clang make
You can verify the change by inspecting the binary:
readelf -p .comment main
Abstracting Output and Source Names
We can make the entire process modular by abstracting the file names into variables:
CC ?= gcc
OUTPUT ?= hello_world
SRC ?= main.c
all:
$(CC) -o $(OUTPUT) $(SRC)
clean:
rm -rf $(OUTPUT)
Now you can dynamically change the output name:
make OUTPUT=cyberpunk_tool
If you create a second source file, goodbye.c, you can compile it using the same Makefile:
make SRC=goodbye.c OUTPUT=test_exit
3. Application: Python
Makefile isn't just for C. It can manage any command-line workflow. For Python, you can use it to toggle between different interpreters like python3 or pypy3.
main.py
print('Hello Python')
Makefile
PY3 ?= python3
SRC ?= main.py
run:
$(PY3) $(SRC)
Usage:
make run PY3=pypy3