Make is a very complex tool, but if we ignore most of its dark corners, it could be handy.

1
2
3
4
5
// main.c
int main(int argc, char *argv[])
{
return 0;
}

This is the common Makefile one would write for the above program.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Makefile
.SUFFIXES:

all: main.out

CC=clang

SOURCES = $(wildcard *.c)

%.o: %.c
$(CC) -c $< -o $@

main.out: main.o
$(CC) $^ -o $@

Makefile: ;

.PHONY: clean
clean:
rm -f main.o main.out

Then, running make to create the final executable, and make clean to remove all generated files work OK.

However, after running make (without make clean), one may notice there’s this main.o (intermediate file) lying around in the current directory. If you find it annoying, the below Makefile could be used to remove intermediate files automatically.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Makefile
.SUFFIXES:

all: build

CC=clang

Makefile: ;

SOURCES = $(wildcard *.c)

%.o: %.c
$(CC) -c $< -o $@

%.out: %.o
$(CC) $^ -o $@

.PHONY: build
build: $(SOURCES:.c=.out)

.PHONY: clean
clean:
rm -f main.o main.out

The reason for such different behavior is that main.o is mentioned in the first Makefile, namely main.out: main.o. Therefore, it’s not considered as an intermediate file, but a target instead. Running make alone would auto remove intermediate files, but not targets; after all, make is supposed to build targets.

See Chains of Implicit Rules for more info.