Skip to content
VLSI Mentor

Makefile · Part I · Module 2 · Rules and Dependencies

Default Targets and Execution Order

Two questions close this module. Which target does Make build when somebody just types make — and in what order is it allowed to do the work? The first has a surprising answer that depends on where a rule sits in the file. The second matters more: the order you observe is not the order Make promises, and the difference stays invisible until the day a build runs in parallel.

Intermediate22 min readMakefileGNU MakePart IDefault GoalExecution Order

Part I · Module 2 · Chapter 2.4 · Default Targets and Execution Order

1. Two Different Questions

People new to Make tend to run these two questions together, and they have different answers:

  • Which target gets built? Make builds the goal — either the one you named on the command line, or, if you named none, a default it picks for you.
  • In what order does the work happen? Make derives that from the dependency graph, and what it guarantees is narrower than what you will usually observe.

The first question is a small piece of knowledge with a sharp edge. The second is where real flows break. Sections 2 to 5 cover the goal; sections 6 to 10 cover the order.

2. With No Target, Make Builds the First One

Type make with no arguments and Make builds the first eligible target in the file. Not the one called all, not the most important-looking one — the first one.

Here is why that is worth a whole section:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile
clean:
	@echo "  [clean] removing everything"

all: report.txt
	@echo "  [all] done"

report.txt:
	@echo "  [report] building"
	@echo data > report.txt

A reader skimming that file would say it builds a report. Run it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
make
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
  [clean] removing everything

A bare make deleted things. Nothing is wrong with Make — clean is simply the first rule in the file, so it is the default goal. But a new engineer who clones the repository, types make to see what happens, and wipes their working files has been handed a trap by whoever wrote the Makefile.

This is the single most common way a Makefile surprises a newcomer, and it costs nothing to prevent.

3. Moving a Rule Changes What make Does

The dependency on position is literal. Take the identical three rules and move all to the top:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile — same rules, reordered
all: report.txt
	@echo "  [all] done"

clean:
	@echo "  [clean] removing everything"

report.txt:
	@echo "  [report] building"
	@echo data > report.txt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
make
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
  [report] building
  [all] done

Same rules, same recipes, different behaviour — because one rule moved. That is a fragile property for a file several people edit: somebody adding a new rule at the top of a Makefile, or sorting the rules alphabetically, silently changes what a bare make does.

Note also what "eligible" excludes. A target whose name begins with a period — .PHONY, .DELETE_ON_ERROR, .DEFAULT_GOAL — is a special directive and is never chosen as the default goal:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile
.PHONY: all

all:
	@echo "  [all] ran as the default"
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
  [all] ran as the default

The .PHONY line is first in the file, and all is still the default. So the rule is precisely: the first target that is an ordinary target.

4. Declaring the Default Deliberately

GNU Make lets you state the default goal instead of inheriting it from the layout:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile
.DEFAULT_GOAL := all

clean:
	@echo "  [clean] removing everything"

all: report.txt
	@echo "  [all] done"

report.txt:
	@echo "  [report] building"
	@echo data > report.txt

clean is still the first rule in the file. Run a bare make:

Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
  [report] building
  [all] done

The declaration wins. .DEFAULT_GOAL is a Make directive, not a general lesson about variables — Module 3 covers variables properly, and the := here is simply the form this directive takes. Put it at the top of the file and the default stops depending on where anybody adds a rule.

Two habits, either of which solves §2's trap:

  • Put the intended default first, by convention all, and keep it there.
  • Or declare .DEFAULT_GOAL := all at the top, which survives reordering.

Doing both is common and costs nothing. What you should not do is leave a destructive target like clean sitting at the top of the file.

5. The Conventional all Target

all is not special to Make — it is a convention. It is an aggregate: a phony target whose prerequisites are the things a complete build produces, and whose own recipe does little or nothing.

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile
.PHONY: all clean
.DEFAULT_GOAL := all

all: ports.rpt blocks.rpt
	@echo "  [all] both reports present"

ports.rpt: ports.txt
	@echo "  [ports.rpt]"
	@wc -l < ports.txt | tr -d ' ' > ports.rpt

blocks.rpt: blocks.txt
	@echo "  [blocks.rpt]"
	@wc -l < blocks.txt | tr -d ' ' > blocks.rpt

clean:
	@echo "  [clean]"
	@rm -f ports.rpt blocks.rpt

With ports.txt and blocks.txt in place, run a bare make from clean, then again, then after touching one input:

Azvya Education Pvt. Ltd.VLSI Mentor
Actual output — three runs
  [ports.rpt]
  [blocks.rpt]
  [all] both reports present
  [all] both reports present
  [blocks.rpt]
  [all] both reports present

Three things to read out of that.

Run 1 built both reports, then all. Run 2 built neither — both file targets were up to date — but [all] still printed, because all is phony and a phony target always runs (Chapter 2.2). Run 3 rebuilt only the report whose input changed.

So all gives you one command that means "bring everything up to date", while the real skipping still happens underneath, per file target. That is the whole value of the convention.

Two independent chains converging, flowing left to right. Top chain: ports.txt, a hand-written input, feeds ports.rpt, a generated report. Bottom chain: blocks.txt feeds blocks.rpt. Both generated reports feed the aggregate target named all, which is the default goal. The two chains do not touch each other.ports.txtyou write thisports.rptgeneratedalldefault goal, phonyblocks.txtyou write thisblocks.rptgenerated12
Figure 1 — the shape of an aggregate goal. Two independent chains run from the hand-written inputs on the left to the generated reports, and both reports are prerequisites of all. Make guarantees that both chains finish before all's own recipe runs; it guarantees nothing about which of the two chains is worked on first.

That last sentence of the caption is the bridge to the rest of the chapter.

6. Textual Order Is Not Execution Order

A Makefile is a set of rules, and Chapter 1.4 showed that Make reads the whole file before building anything. So the order rules appear in has exactly one effect — which target is the default (§2) — and no effect at all on the order recipes run.

The order recipes run comes from the graph. In §5's Makefile, ports.rpt's rule is written above blocks.rpt's, and that is not why it ran first; it ran first because of how Make walked the prerequisite list. Reorder the two rules in the file and the build behaves identically.

What Make does guarantee is this, and it is worth memorising in one line:

Every prerequisite of a target is brought up to date before that target's own recipe runs.

That is a statement about completion, not about sequence. It says ports.rpt and blocks.rpt are both finished before all's recipe starts. It says nothing about ports.rpt versus blocks.rpt.

7. Independent Prerequisites Have No Guaranteed Order

Two prerequisites are independent when neither is reachable from the other in the graph. In §5, ports.rpt and blocks.rpt are independent: nothing connects them.

Watch what Make does with three independent prerequisites:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile
.PHONY: all a b c

all: a b c
	@echo "  [all]"

a:
	@echo "  [a]"
b:
	@echo "  [b]"
c:
	@echo "  [c]"

Run it normally:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
make
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
  [a]
  [b]
  [c]
  [all]

Left to right, which is what a serial Make does and what four consecutive runs produced. It is tempting to conclude that the prerequisite list is a sequence. Now run the same Makefile with two jobs allowed at once, four times:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
make -j4
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output — four consecutive attempts
  [a]   [c]   [b]   [all]
  [a]   [b]   [c]   [all]
  [a]   [b]   [c]   [all]
  [a]   [b]   [c]   [all]

One of the four runs put [c] before [b]. Three agreed with the serial order and one did not, which is the most dangerous possible result: an assumption that holds almost always is an assumption that will fail in production rather than in testing.

Notice what did not change: [all] is last in every run. The guarantee from §6 held every time; only the unguaranteed part moved.

8. A Prerequisite Is a Requirement, Not a Sequence

Here is the mistake that §7 sets up, written the way it actually appears in real Makefiles. An engineer wants a setup step to run before a build step, and reaches for the prerequisite list because it looks like a to-do list:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile — wrong: the list is being used as a sequence
.PHONY: flow setup build

flow: setup build
	@echo "  [flow] done"

setup:
	@echo "  [setup] creating stamp"
	@sleep 0.3
	@echo ready > stamp.txt

build:
	@echo "  [build] reading stamp"
	@cat stamp.txt

The intent is clear: setup writes stamp.txt, then build reads it. Run it serially and the intent appears to be satisfied:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
make
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
  [setup] creating stamp
  [build] reading stamp
ready
  [flow] done

It works. It has probably worked for a year. Now run it with two jobs:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
make -j2
Azvya Education Pvt. Ltd.VLSI Mentor
Representative output — this failed on all four attempts
  [setup] creating stamp
  [build] reading stamp
cat: stamp.txt: No such file or directory
make: *** [build] Error 1
make: *** Waiting for unfinished jobs....

It fails every time. setup and build are independent as far as the graph is concerned — neither is a prerequisite of the other — so Make is free to start them together. It does, build reaches for a file setup has not finished writing, and the build breaks.

The Makefile never said build needs stamp.txt. It said flow needs both of them, which is a different claim. Writing them in a particular order inside one list expressed a hope, not a requirement.

9. Expressing a Real Ordering Requirement

If one step genuinely needs another's output, say so with a dependency edge. The requirement in §8 is not "run setup first" — it is "build needs stamp.txt". Write that:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile — correct: the requirement is a dependency
.PHONY: flow build

flow: build
	@echo "  [flow] done"

stamp.txt:
	@echo "  [setup] creating stamp"
	@sleep 0.3
	@echo ready > stamp.txt

build: stamp.txt
	@echo "  [build] reading stamp"
	@cat stamp.txt

Two changes. The setup step became a file target named after what it produces, and build names that file as its prerequisite. Now the graph contains the fact that used to live only in the author's head.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
make -j2
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output — all four attempts succeeded
  [setup] creating stamp
  [build] reading stamp
ready
  [flow] done

Four parallel attempts, four correct runs. Make cannot start build early now, because the guarantee from §6 applies: stamp.txt is a prerequisite of build, so it is complete before build's recipe begins.

The general principle, and it is the most useful sentence in this chapter:

If step B needs something step A produces, make that thing a prerequisite of B. Do not rely on the order of a list, and do not rely on having observed the right order.

This also explains why the §8 version could not be fixed by reordering the list or by any amount of care in writing it. The information Make needed was absent, and a list has no way to supply it.

10. Checking the Plan Before Running It

When you are unsure what a goal will do — especially on a Makefile you did not write — ask Make to print its plan. Chapter 1.4 introduced make -n; it is at its most useful here:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
make -n flow
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output, on §9's corrected Makefile
echo "  [setup] creating stamp"
sleep 0.3
echo ready > stamp.txt
echo "  [build] reading stamp"
cat stamp.txt
echo "  [flow] done"

That is the serial plan, in order, including the @-suppressed lines. It answers "which goal, and what will it run" in one command without touching a file.

Two limits to keep in mind. It shows the serial plan, so it will not reveal the §8 bug — a dry run is not a parallelism test. And as Chapter 1.4 showed, a recipe line prefixed with + executes even under -n, so it is not a sandbox.

11. An RTL Flow With Deliberate Goals

Putting the chapter together on a compile-and-simulate flow. This version is portable and was executed, standing in for a simulator:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile — portable stand-in, executed as shown
.PHONY: all run clean
.DEFAULT_GOAL := all

all: build/run.log

build/sim.img: rtl/defines.vh rtl/alu.v rtl/tb_top.v
	@echo "  [compile]"
	@mkdir -p build
	@cat rtl/defines.vh rtl/alu.v rtl/tb_top.v > build/sim.img

build/run.log: build/sim.img
	@echo "  [run]"
	@wc -l < build/sim.img | tr -d ' ' > build/run.log

run: build/run.log
	@echo "  [run target] log says:"
	@cat build/run.log

clean:
	@echo "  [clean]"
	@rm -rf build

Four invocations, each asking a different question:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
make                      # the declared default goal
make run                  # an explicit phony goal
make build/sim.img        # an explicit artifact goal
# edit rtl/defines.vh, then:
make
Azvya Education Pvt. Ltd.VLSI Mentor
Representative output
  [compile]
  [run]
  [run target] log says:
5
make: 'build/sim.img' is up to date.
  [compile]
  [run]

Reading those four in order: the default goal produced the log; run found the log current and only printed it; asking for the artifact directly reported it up to date; and an edit to the shared defines file rebuilt the chain, because Chapter 2.3 declared it as a prerequisite.

Three deliberate choices in that file, each one this module's subject:

  • .DEFAULT_GOAL := all so a bare make is defined by intent rather than by all happening to be first.
  • run is phony and build/run.log is not. The action a person asks for and the artifact it produces have different names, which keeps both honest (Chapter 2.2).
  • Every step that needs another step's output names that output. run depends on build/run.log, which depends on build/sim.img. There is no list-order dependence anywhere, so make -j is safe.

The simulator version is illustrative — vcs is not installed here and was not run:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile — illustrative; vcs was not executed
.PHONY: all run clean
.DEFAULT_GOAL := all

all: build/run.log

build/simv: rtl/defines.vh rtl/alu.v rtl/tb_top.v
	mkdir -p build
	vcs -full64 -sverilog rtl/alu.v rtl/tb_top.v -o build/simv

build/run.log: build/simv
	./build/simv -l build/run.log

run: build/run.log
	@echo "see build/run.log"

Tuning how many jobs to run, and the reliability questions that come with a genuinely parallel flow, are Chapter 8.2. Everything needed to make a flow safe under parallelism is in this chapter: express every real requirement as a dependency edge.

12. Common Mistakes

Leaving clean as the first rule. A bare make then deletes things (§2). Put all first, or declare .DEFAULT_GOAL.

Assuming all is special to Make. It is only a convention. Nothing happens automatically because a target is called all.

Believing rule order controls execution. It controls the default goal and nothing else (§6).

Reading a prerequisite list as a sequence. It is a set of requirements. Serial Make happens to walk it left to right; that is not a promise (§7, §8).

Adding a prerequisite to force "run this first". If the step produces something, depend on the thing it produces (§9). If it produces nothing, there is no requirement for Make to enforce.

Treating make -n as proof. It prints the serial plan, so it cannot expose an ordering bug that only appears in parallel (§10).

Testing only serially. A flow that is never run with -j has never been tested for the §8 bug, and a build server will eventually run it that way.

13. Check Your Understanding

Question 1. A repository's Makefile starts with a docs: rule, then all:, then clean:. A new engineer clones it and types make. What happens, and what are the two one-line fixes?

Question 2. A Makefile has .PHONY: all on line 1 and install: as the first ordinary rule, followed by all:. Which target does a bare make build? Explain using the word "eligible".

Question 3. report: compile simulate is intended to compile then simulate. It has worked for a year and fails on a new build server. Give the mechanism and the correct Makefile change.

Question 4. In §5's Makefile, make prints [all] both reports present even on a run where neither report was rebuilt. Why — and what would have to change for [all] to be skipped?

14. Exercise

Here is a flow with a default-goal problem and an ordering problem.

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile — as given
.PHONY: clean package build

clean:
	@echo "  [clean] removing out/"
	@rm -rf out

package: build
	@echo "  [package] archiving"
	@cat out/payload.txt > out/package.txt

build:
	@echo "  [build] writing payload"
	@mkdir -p out
	@sleep 0.2
	@echo payload > out/payload.txt

Answer before running, then verify:

  1. What does a bare make do?
  2. Does make package work serially?
  3. Does make -j2 package work reliably? Why?
  4. Rewrite the Makefile correctly.

15. Remember

  • A bare make builds the first eligible target — the first ordinary target in the file. Targets beginning with a period are never eligible.
  • Moving a rule can change what make does. Put the intended default first, or declare .DEFAULT_GOAL := all, or both.
  • Never leave a destructive target at the top of a Makefile.
  • all is a convention, not a Make feature — a phony aggregate whose prerequisites are everything a full build produces.
  • Rule order in the file affects only the default goal, never the order recipes run.
  • The one guarantee: every prerequisite of a target is up to date before that target's recipe runs. That is about completion, not sequence.
  • Independent prerequisites have no guaranteed order. Serial Make walks them left to right; parallel Make need not, and was measured not to.
  • A prerequisite list is a set of requirements, not a sequence. Using it as a sequence produces a flow that passes every serial test and fails under make -j.
  • Express a real requirement as a dependency edge. If B needs what A produces, make that output a prerequisite of B — then the order is enforced rather than hoped for.
  • make -n shows the serial plan. It answers "which goal and what will run", and cannot reveal a parallel ordering bug.

That completes Module 2. You can now read a multi-target Makefile and say what will run, what will not, why a target rebuilds, why one is skipped, when .PHONY belongs, how the default goal is chosen, and which parts of the execution order are actually promised. Every Makefile so far has spelled out each filename by hand, and that is the next constraint to remove: Module 3 introduces variables, the automatic variables that let a recipe refer to its own target and prerequisites, and the functions that build a file list instead of repeating one.

Where this fits

Part of the Makefile curriculum.