Understanding .PHONY in Makefiles by Actually Trying It

Oct 18, 2025

2 min
💬

Why does make skip a command when a file shares the target's name? I tested the role of .PHONY hands-on and wrote up what I found.

1. Overview

.PHONY is a declaration in a Makefile that tells make a given target is not an actual file but a name meant to be run. Imperative targets like clean, all, and install usually don't produce files, so without .PHONY they can collide with files of the same name or trigger unnecessary rule searches.

If a file named clean already exists, make considers it up to date and never runs the clean command.

.PHONY is what solves this problem. The key points to verify are:

  • Why .PHONY is needed and what problem it solves
  • How make behaves differently with and without .PHONY
  • Patterns for using .PHONY in real projects

2. What is a phony target? (.PHONY)

In a Makefile, a target is a rule for building some file.

MAKEFILE
hello: hello.c
	cc hello.c -o hello

Here hello is the name of a file that actually gets created. make runs cc when that file is missing or when the source (hello.c) is newer. But things break down once you add a "command that doesn't produce a file."

MAKEFILE
clean:
	rm *.o

If a clean file happens to exist in the directory, make decides "clean is already up to date" and never runs rm *.o. This is where .PHONY comes in.

MAKEFILE
.PHONY: clean
clean:
	rm *.o

With this declaration, clean is treated as a command name (label) rather than a file, so it always runs.

NOTE

.PHONY is a declaration that tells make: "this target is not a real file — it's a command."

3. Testing .PHONY

Let's create two files for the test.

hello.c

C
#include <stdio.h>
 
int main(void) {
    puts("Hello, .PHONY!");
    return 0;
}

Makefile

View the full test Makefile
MAKEFILE
CC      ?= cc
CFLAGS  ?= -O2 -Wall
 
ifeq ($(USE_PHONY),1)
.PHONY: all run clean
endif
 
all: hello
 
hello: hello.o
	$(CC) $(CFLAGS) -o $@ $^
 
hello.o: hello.c
	$(CC) $(CFLAGS) -c $< -o $@
 
run: hello
	@./hello
 
clean:
	@echo "[clean] removing artifacts..."
	@rm -f hello hello.o
 
## 'clean'이라는 실제 파일을 만들어 충돌 테스트
touch-clean-file:
	@echo "This is a file named 'clean'" > clean

3.1. Basic behavior test

The commands below work as expected.

Bash
make clean
## → [clean] removing artifacts...
 
make all
## → cc -O2 -Wall -c hello.c -o hello.o
## → cc -O2 -Wall -o hello hello.o
 
make run
## → Hello, .PHONY!

3.2. The collision without .PHONY

Bash
## 'clean' 파일 생성
make touch-clean-file
 
make clean
## → make: 'clean'은(는) 이미 업데이트되었습니다.

make clean should delete hello and hello.o, but nothing gets removed.

3.3. Running with .PHONY enabled

Bash
make USE_PHONY=1 clean
## → [clean] removing artifacts...

Once .PHONY is enabled, make clean always runs even when a clean file exists.

TIP

This test let me confirm firsthand that .PHONY tells make "this target is a command, not a real file."

4. When .PHONY really pays off

Imagine a parallel build environment that builds several directories at once.

MAKEFILE
SUBDIRS = foo bar baz
 
subdirs:
	for dir in $(SUBDIRS); do \
		$(MAKE) -C $$dir; \
	done

Here the for loop runs sequentially in a single shell, so parallel builds (make -j) are impossible, and even if one directory errors out, the overall build keeps going. .PHONY lets us improve this.

MAKEFILE
SUBDIRS = foo bar baz
.PHONY: subdirs $(SUBDIRS)
 
subdirs: $(SUBDIRS)
 
$(SUBDIRS):
	$(MAKE) -C $@

The key move is splitting $(SUBDIRS) out as prerequisites of subdirs. Now make treats each directory as an independent target, making make -j parallel builds possible. Here .PHONY guarantees that even if directories named foo, bar, or baz exist, they are never judged "already up to date."

5. Wrapping up

.PHONY plays the following roles in a Makefile.

RoleDescription
Prevents filename collisionsThe command always runs even when a file of the same name exists
Supports command-only targetsLets you write command targets with no real file, like clean, install, test, run
Improves performanceSkips implicit rule search, reducing unnecessary checks

TIP

For targets that don't produce a real file, it's worth declaring .PHONY out of habit. Beyond preventing collisions, it also cuts down on make's unnecessary searching.

6. References