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

// main.c
int main(int argc, char *argv[])
{
    return 0;
}

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

# 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, make, make clean work OK.

However, after make, one might 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.

# 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 key difference is that main.o is mentioned in the Makefile, so it’s not considered as an intermediate file, but a target instead. make would auto remove intermediate files, but not targets; after all, make is supposed to build targets.

See Chains of Implicit Rules for more info.