POSIX finally standardized .PHONY. Only took thirty years of everyone using it.
Published by RodHat

The Open Group published POSIX.1-2024 earlier this year — IEEE Std 1003.1-2024, Issue 8,
the first major revision since 2017 — and the coverage mostly focused on the shell and
AWK updates. Those are real. What I want to talk about is the make section, because it
is the most quietly significant part of the update and approximately nobody wrote about it.
POSIX make has been an embarrassment for a long time. Not wrong — the original
specification documented what make looked like circa 1988: suffix-based inference rules,
macro expansion, the .SUFFIXES and .PRECIOUS and .DEFAULT special targets, basic
recipe syntax. Then the standard more or less stopped. The world kept using make, make
kept gaining features, and the POSIX spec sat in amber while every production Makefile
in the world accumulated extension syntax that was portable in practice but nowhere in the
standard. POSIX make became the document you cited in arguments and never actually used
for compliance.
The .PHONY situation
.PHONY declares that a target is a command name, not a filename. The purpose is simple
enough to fit in one sentence: it makes make clean run the clean recipe even if
someone has created a file called clean in the current directory, which would otherwise
convince make that the target is already current and skip the recipe silently.
Without .PHONY, every Makefile with an all: or install: target has a latent bug
waiting for the one environment where a file with that name exists. That environment is
rarer than a disk full on your build server but not so rare that nobody has hit it. And
“the build says nothing to do when there’s clearly something to do and no error message
anywhere” is exactly the class of failure that burns an hour before someone thinks to
check whether a stale artifact with the target name is sitting in the tree.
BSD make has had .PHONY since the early 90s. GNU make has had it since at least
3.77, which shipped in 1997. pmake, NetBSD make, OpenBSD make — all of them.
Every make implementation anyone uses on a non-embedded UNIX system has had .PHONY
for somewhere between 25 and 35 years. It was a de facto POSIX feature for three decades
before POSIX.1-2024 made it an actual POSIX feature.
POSIX.1-2024 made it an actual POSIX feature.
What else changed
The .PHONY standardization is the emblematic change but not the only one. POSIX.1-2024
also specifies double-colon rules — the :: syntax that lets you attach multiple
independent sets of prerequisites and recipes to the same target, each evaluated and
run independently. GNU make and BSD make have both supported this for decades but
with differences in edge-case behavior that made relying on it genuinely non-portable.
The new spec gives it defined semantics.
The inference rule system also gets clarification. The old spec left enough ambiguity
around how .SUFFIXES modifications interact with the default suffix list that
implementations could and did disagree on ordering. The new spec is tighter. This is the
boring kind of correctness that only matters when you’re writing a make from scratch or
debugging why two compliant implementations produce different results on the same input —
but it matters for those people and for the test suites that will eventually enforce it.
What didn’t change: pattern rules. The % syntax — %.o: %.c — that GNU make
uses for generic pattern-based inference is not in POSIX.1-2024. The suffix rule mechanism
($(CC) -c $< -o $@ attached to a .c.o: rule) is still how POSIX make does
generalized compilation. I have opinions about this. I will keep them to myself for now.
Why it took thirty years is not actually a mystery
The POSIX standard is maintained by the Austin Group, a joint working group of the IEEE
Computer Society and The Open Group. It operates on committee time: proposals, working
group discussion, formal balloting, appeals, revision. Getting consensus across BSD
vendors, Linux distributors, proprietary UNIX licensees, and embedded systems vendors
that all ship make implementations with their own extension histories is not fast.
The make changes weren’t languishing because nobody cared. They were in the Austin
Group’s issue tracker for years, debated, refined, and eventually accepted. That process
is slow and sometimes maddening, but it’s the process that produces standards rather
than the preferences of whichever vendor has the loudest voice this year.
There’s also a legitimate reason not to rush: if you standardize .PHONY in 1998 when
BSD make and GNU make still disagree on edge cases — and there were edge cases, around
how .PHONY interacts with inference rules and what happens when a .PHONY target appears
as a prerequisite of another target — you’ve locked in one interpretation and made one
major implementation wrong. Better to let the implementations converge on behavior and
then write down what they converged on. Slower, more correct.
The practical consequence of that caution is that anyone writing strictly portable
Makefiles from 1997 to 2024 was working without a net. The implementations had
converged but the spec hadn’t, which meant “portable” meant “tested against at least
GNU and BSD make and known to work on both” rather than “verified against the spec.”
Most people just used GNU make and called it a day. Which is fine until you’re on
a system that doesn’t have GNU make in the default base, which is every BSD system
I have ever administered.
What it means in practice
If you’re on a BSD system, this changes almost nothing operationally. FreeBSD make
and OpenBSD make have been doing the right thing for decades; POSIX.1-2024 is the
standard catching up to what the implementations already agreed on. The behavior you
rely on today is the behavior the spec now endorses.
If you’re writing Makefiles that need to run on BSD base make, GNU make, and any
other POSIX-compliant implementation, you now have an actual standard to target rather
than the intersection of whatever extensions happen to be in both GNU and BSD
implementations. That intersection was large enough in practice but it was always an
empirical claim, not a specified one.
The real value is in tooling. Linters, static analyzers, and make compliance test
suites can now work from a current specification rather than POSIX 2017 plus a list of
known extensions. That’s infrastructure work — invisible until it isn’t.
Same pattern as the Git SHA-256 story: the technical reality existed before the formal standard, the standard is the certification that the community agreed. Both took longer than they should have and both landed correctly when they did. The portability tax is always real — running on more than one implementation always costs something — and a well-maintained spec is one of the few tools that makes it cheaper over time.
.PHONY is in POSIX now. I have been using it since before most of you had root on
anything. Thirty years late. Correct. Filed under: better than never, barely.
Sources
- POSIX.1-2024 — The Open Group Base Specifications Issue 8 — The Open Group
- POSIX.1-2024 make utility specification — The Open Group