Why does “make” insert a prefix folder into the middle of a stem, but not into the beginning?

Summary

The issue arises when using GNU Make’s pattern rules with directory structures. The stem ($*) in a pattern rule like deps/%.d is expanded based on the target’s basename, not its full path. This leads to unexpected directory prefixes when the target is in a subdirectory.

Root Cause

  • Pattern Rule Expansion: GNU Make’s pattern rules expand the stem ($*) based on the basename of the target, not its full path.
  • Directory Handling: When a target is in a subdirectory (e.g., subfolder/file1.o), the stem becomes file1, not subfolder/file1.
  • Incorrect Dependency Path: The rule deps/%.d becomes deps/file1.d instead of deps/subfolder/file1.d because the stem (file1) is used directly.

Why This Happens in Real Systems

  • Makefile Simplicity: Pattern rules are often used to simplify Makefiles, but they can lead to unexpected behavior with directory structures.
  • Lack of Path Awareness: GNU Make’s pattern rules are not path-aware; they operate on basenames, not full paths.
  • Common Pitfall: Junior engineers often assume the stem includes directory information, leading to incorrect dependency paths.

Real-World Impact

  • Broken Builds: Incorrect dependency paths cause build failures or missing files.
  • Debugging Overhead: Engineers spend time tracing why dependencies are not generated as expected.
  • Code Duplication: Workarounds may require duplicating rules for each subdirectory, reducing maintainability.

Example or Code (if necessary and relevant)

OBJECTS := subfolder/file1.o file2.o
all: $(OBJECTS)

# Problematic rule
%.o: %.c deps/%.d
    @echo '$@: $^ ($*)'

# Corrected rule using $(dir) and $(notdir)
define generate_dep
$(1).o: $(1).c $$(subst $(dir $(1)),deps/,$(1)).d
endef
$(foreach obj,$(OBJECTS),$(eval $(call generate_dep,$(obj))))

How Senior Engineers Fix It

  • Use Directory Functions: Leverage $(dir) and $(notdir) to preserve directory structure in stems.
  • Explicit Rules: Write explicit rules for each subdirectory or use $(eval) to generate rules dynamically.
  • VPATH and Subdirectories: Use VPATH to handle files in subdirectories without altering dependency paths.

Why Juniors Miss It

  • Assumption of Path Inclusion: Juniors assume the stem includes directory information, not just the basename.
  • Lack of Makefile Depth: Limited experience with GNU Make’s pattern rules and directory handling functions.
  • Overreliance on Patterns: Juniors often overuse pattern rules without understanding their limitations.

Leave a Comment