TĐ

Lecture 13 – Makefiles, Dependency Management & Yak-Shaving

Dependency Parables

  • Two opening stories illustrate the concept of “dependencies” before any code appears.
    • Yak-shaving fable
    • Goal: wash car ➔ need hose ➔ toll bridge ➔ neighbour’s toll-pass ➔ must return neighbour’s cushion ➔ cushion stuffed with yak hair ➔ end up shaving a yak at the zoo.
    • Moral: ask whether every dependency (step) is necessary; sometimes you should “just pay the toll.”
    • “There’s a Hole in My Bucket” (Henry & Liza)
    • Sequential complaints and fixes:
      • Hole in bucket ➔ fix with straw
      • Straw too long ➔ cut with axe
      • Axe too blunt ➔ sharpen with stone
      • Stone too dry ➔ wet with water
    • Demonstrates cyclic or chained dependencies.
  • Connection to software: every #include, module import or library link is a dependency.
    • Excessive or circular dependencies create needless work, delay and complexity.

What Make Is For

  • make = Unix utility that automates compilation by analysing file timestamps and declared dependencies.
  • Mandatory tooling for C/C++ projects at Adelaide Uni; foundational to more modern build systems (e.g. CMake, Meson, Bazel, Ninja).
  • Two reasons to recompile a file:
    1. The file itself changed (newer timestamp).
    2. A prerequisite it relies on changed.
  • Without make:
    • Naïve manual compile:
    • Example: gcc file1.c file2.c … file20.c -o My_Exe
    • Risks: forget files (file14), typos (fi1e17.c) → broken builds.
    • Lazy wildcard compile: gcc *.c -o My_Exe
    • Works only if every *.c truly belongs to this executable.
    • Fails when multiple programs share directories or files.
    • Full rebuilds waste time. Even projects with 20 files begin to exhibit noticeable delays; million-line codebases can take hours or days to build from scratch.

Core Mechanics of Make

  • Operates through a text file named exactly Makefile (no extension) in the working directory.
    • Alternative: invoke with make -f CustomFile to use a differently named file.
  • Command make without arguments executes the first target in the Makefile (the “default”).
  • Each rule has three parts:
    1. Target (red in slides) – the file to build.
    2. Prerequisites / Dependencies (blue) – files the target relies on.
    3. Recipe / Command (green) – shell commands to produce the target.
  • Whitespace matters: the recipe line must start with a real , not spaces.

Minimal Example

my_exe: my_code.c my_code.h
    gcc my_code.c -o my_exe
  • Meaning:
    • Re-build my_exe if it does not exist OR if my_code.c or my_code.h is newer.
    • Invoked by make (if it’s the first rule) or make my_exe.

Utility Targets

  • Recipes can run any shell commands; they need not produce the file named by the target.
  • Conventional example:
clean:
    rm *.o my_exe
  • Used to delete intermediates; invoked with make clean.

Layered / Recursive Dependencies

Illustrative Makefile:

clean:
    rm *.o my_exe

my_exe: driver.o quack.o
    gcc driver.o quack.o -o my_exe

driver.o: driver.cpp
    gcc -c driver.cpp

quack.o: quack.cpp quack.h
    gcc -c quack.cpp

Process from scratch:

  1. make my_exe finds driver.o & quack.o missing ⇒ descends into their rules.
  2. gcc -c compiles each source into object files (*.o).
  3. Final link step produces my_exe.
    Rebuild scenario:
  • Change only quack.hquack.o is out-of-date ➔ only quack.o and my_exe are rebuilt; driver.o is untouched. Saves compilation time.

Visual Dependency Trees

  • Hierarchical graph: leaf nodes are headers / source; roots are executables.
  • Altering one file triggers rebuilds only along paths that reference it.
  • Shared dependencies (a header used in two sub-trees) cause multiple branches to rebuild.
  • Upgrading a standard header like stdio.h would cascade everywhere, exemplifying large rebuild costs.

Custom Makefile Names

  • Syntax: make -f <my_weird_make_file> <targets>.
    • Example: make -f QuackMake my_exe.

Built-in Shortcuts & Symbols

  • Provide terse recipes but can obscure clarity.
  1. @ → the target name.
  2. ^ → the full list of prerequisites.
  3. < → the first prerequisite.
  4. * → shell-style wildcard in prerequisite list.
  5. % → pattern wildcard inside target name.

Examples:

my_exe: file1.c file2.c file3.c
    gcc $^ -o $@

%.o: %.c
    gcc -c $<
  • First rule links three object files into an executable using @/^.
  • Second rule is a template: any .o depends on a same-named .c; compiles with only the first prereq via <.

Multiple targets, same rule:

alpha beta: common.h
    @echo Building $@
  • Recipe runs twice, once for alpha, once for beta.

Macros / Variables

  • Define once, reuse everywhere.
CC = gcc
CFLAGS = -Wall -O2

%.o: %.c
    $(CC) $(CFLAGS) -c $<
  • Later switching to clang requires editing only the variable.
  • Syntax $(NAME) or ${NAME} (both legal, mildly confusing).

Practical Guidance & Caveats

  • Make’s historical evolution = “hack-on-hack,” originally written by a student intern; still standard but replaced in industry by higher-level generators.
  • IDEs & build systems auto-write Makefiles; understanding them lets you debug when auto-generation fails.
  • Danger zones:
    • Over-relying on wildcards (*, %) may inadvertently compile or link unwanted files.
    • Incorrect timestamps (e.g. extracted archives, version-control checkout with old dates) can trick make into skipping necessary rebuilds.
    • Cyclic dependencies can cause infinite rebuild loops or ambiguous rules.

Philosophical / Ethical Reflection

  • Yak-shaving metaphor warns against needless work created by poor planning of dependencies—applies to life and software.
  • Always weigh cost vs. benefit: sometimes re-architecting (paying the toll) is cheaper than stringing more abstractions.
  • Clean, minimal dependency graphs aid maintainability, shorten build times, and reduce energy consumption—an ecological plus.

Numerical & Statistical Nuggets

  • Small student programs (
  • Header change causing global rebuild demonstrates highly nonlinear compile cost: O(N) files touched vs. O(1) for isolated modules.

Commands & Flags To Memorise

  • make – build default (first) target.
  • make <target> – build specific target.
  • make -f File – use alternative makefile.
  • gcc -c file.c – compile to object without linking.
  • gcc a.o b.o -o exe – link objects into executable.

Study Checklist

  • Explain difference between target, prerequisite, recipe.
  • Demonstrate writing a clean target.
  • Show how make decides whether to rebuild.
  • Use @, ^, < correctly in context.
  • Recognise pitfalls of wildcards and auto-generated rules.
  • Describe object files and linking.
  • Recount yak-shaving story as a mnemonic for dependency minimisation.
  • Compare make to modern tools (CMake, Ninja).