CtrlK
BlogDocsLog inGet started
Tessl Logo

pantheon-ai/makefile-generator

Generate GNU Make build systems that define build targets, configure dependencies, set up phony targets, and implement parallel builds. Use when creating make/Makefile/.mk files, implementing compile rules, or building production-ready build automation for C/C++, Go, Python, and Java projects.

Overall
score

93%

Does it follow best practices?

Validation for skill structure

Overview
Skills
Evals
Files

makefile-structure.mdreferences/

Makefile Structure and Organization

Overview

This guide covers the organization and structure of well-designed Makefiles, including variable definitions, target organization, pattern rules, and modular design patterns.

Basic Makefile Structure

A well-organized Makefile follows this general structure:

# 1. Header and metadata
# 2. Special targets (.POSIX, .DELETE_ON_ERROR, .SUFFIXES)
# 3. User-overridable variables
# 4. Project-specific variables
# 5. .PHONY declarations
# 6. Default target (all)
# 7. Build rules
# 8. Install rules
# 9. Clean rules
# 10. Test rules
# 11. Help target

1. Header and Metadata

# Project: MyApp
# Description: Brief description of the project
# Author: Your Name
# License: MIT
# Version: 1.0.0

# Ensure POSIX compatibility (optional)
.POSIX:

# Delete target files if recipe fails
.DELETE_ON_ERROR:

# Disable built-in suffix rules
.SUFFIXES:

# Custom suffixes if needed
.SUFFIXES: .c .o .h

Special Targets Explained

  • .POSIX: Declares intent for POSIX compliance (optional, increases portability)
  • .DELETE_ON_ERROR: If a recipe fails, delete the target file (prevents corrupted builds)
  • .SUFFIXES: Clear built-in suffix rules, then optionally declare custom ones

2. Variable Organization

User-Overridable Variables (use ?=)

Variables that users should be able to override from the command line or environment:

# Compiler and tools
CC ?= gcc
CXX ?= g++
LD ?= $(CC)
AR ?= ar
RANLIB ?= ranlib
INSTALL ?= install
RM ?= rm -f
MKDIR_P ?= mkdir -p

# Compiler flags
CFLAGS ?= -Wall -Wextra -O2
CXXFLAGS ?= -Wall -Wextra -O2
CPPFLAGS ?=
LDFLAGS ?=
LDLIBS ?=

# Installation paths (GNU conventions)
PREFIX ?= /usr/local
EXEC_PREFIX ?= $(PREFIX)
BINDIR ?= $(EXEC_PREFIX)/bin
LIBDIR ?= $(EXEC_PREFIX)/lib
INCLUDEDIR ?= $(PREFIX)/include
DATAROOTDIR ?= $(PREFIX)/share
DATADIR ?= $(DATAROOTDIR)
MANDIR ?= $(DATAROOTDIR)/man

# DESTDIR for staged installations
DESTDIR ?=

Why ?= instead of =:

  • ?= only sets the variable if not already defined
  • Allows users to override: make CC=clang CFLAGS="-O3 -march=native"
  • Respects environment variables

Project-Specific Variables (use :=)

Variables internal to the Makefile that should not be overridden:

# Project configuration
PROJECT := myapp
VERSION := 1.0.0
TARGET := $(PROJECT)

# Directory structure
SRCDIR := src
INCDIR := include
BUILDDIR := build
OBJDIR := $(BUILDDIR)/obj
DEPDIR := $(BUILDDIR)/deps

# Source files (use wildcards or explicit lists)
SOURCES := $(wildcard $(SRCDIR)/*.c)
HEADERS := $(wildcard $(INCDIR)/*.h)

# Derived file lists
OBJECTS := $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o)
DEPENDS := $(OBJECTS:.o=.d)

Why := instead of =:

  • := performs immediate expansion (evaluated once)
  • = performs recursive expansion (evaluated each use)
  • := is more efficient for computed values

Variable Expansion Example

# Wrong: = causes recursive expansion
FILES = $(wildcard *.c)
# Expands every time $(FILES) is used

# Right: := evaluates once
FILES := $(wildcard *.c)
# Evaluated immediately, more efficient

3. Target Organization

.PHONY Declarations

Declare all non-file targets as .PHONY to ensure they always run:

.PHONY: all clean install uninstall test check help
.PHONY: build dist distclean format lint

Why .PHONY is critical:

  • Without .PHONY, if a file named "clean" exists, make clean won't run
  • .PHONY tells make these targets don't create files
  • Improves make performance by skipping unnecessary stat() calls

Default Target

The first target in the Makefile is the default (run when make is called without arguments):

## Build all targets
.PHONY: all
all: $(TARGET)

Best practices:

  • Name it all
  • Make it the first target after variable definitions
  • It should build everything but not install or clean

4. Build Rules

Explicit Rules

# Link the executable
$(TARGET): $(OBJECTS)
	@mkdir -p $(@D)
	$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@

Pattern Rules (Preferred)

Pattern rules use % to match multiple files:

# Compile C source files to object files
$(OBJDIR)/%.o: $(SRCDIR)/%.c
	@mkdir -p $(@D)
	$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

# Alternative without directories:
%.o: %.c
	$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

Automatic Variables

VariableMeaning
$@Target file name
$<First prerequisite
$^All prerequisites (with duplicates removed)
$+All prerequisites (with duplicates)
$?Prerequisites newer than target
$*Stem of pattern match
$(@D)Directory part of target
$(@F)File part of target

Example using automatic variables:

# Without automatic variables (verbose):
hello: hello.o utils.o
	gcc -o hello hello.o utils.o

# With automatic variables (concise):
hello: hello.o utils.o
	$(CC) -o $@ $^

5. Dependency Management

Manual Dependencies

main.o: main.c common.h
utils.o: utils.c utils.h common.h

Problems:

  • Tedious to maintain
  • Easy to get out of sync
  • Error-prone for large projects

Automatic Dependency Generation (Recommended)

# Generate dependencies automatically during compilation
$(OBJDIR)/%.o: $(SRCDIR)/%.c
	@mkdir -p $(@D)
	$(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -c $< -o $@

# Include generated dependency files
-include $(DEPENDS)

Flags explained:

  • -MMD: Generate dependency file (.d)
  • -MP: Add phony targets for headers (prevents errors if header deleted)
  • -include: Include files, ignoring errors if they don't exist yet (first build)

6. VPATH and Source Organization

VPATH for Source Directories

# Search for source files in multiple directories
VPATH = src:include:lib

# Make will search these directories for prerequisites
main.o: main.c common.h
	$(CC) -c $< -o $@

vpath Directive (More Specific)

# Search pattern-specific paths
vpath %.c src
vpath %.h include
vpath %.o build/obj

%.o: %.c
	$(CC) -c $< -o $@

When to use VPATH:

  • Multi-directory projects
  • Separating source and build directories
  • Organizing headers separately

7. Include Directives

Modular Makefiles

Split large Makefiles into smaller, focused files:

# Main Makefile
include config.mk
include rules.mk
include targets.mk

config.mk (variables):

CC := gcc
CFLAGS := -Wall -O2
PREFIX := /usr/local

rules.mk (pattern rules):

%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

targets.mk (phony targets):

.PHONY: clean
clean:
	$(RM) *.o $(TARGET)

Conditional Includes

# Include file if it exists
-include config.mk

# Include multiple files
-include $(DEPENDS)

- prefix: Suppress errors if file doesn't exist

8. Multi-Directory Projects

Non-Recursive Make (Recommended)

Single Makefile approach:

# Directory structure:
# project/
#   Makefile
#   src/
#     main.c
#     utils.c
#   lib/
#     libfoo.c

SRCDIR := src
LIBDIR := lib
BUILDDIR := build

SRC_SOURCES := $(wildcard $(SRCDIR)/*.c)
LIB_SOURCES := $(wildcard $(LIBDIR)/*.c)
ALL_SOURCES := $(SRC_SOURCES) $(LIB_SOURCES)

OBJECTS := $(ALL_SOURCES:%.c=$(BUILDDIR)/%.o)

$(TARGET): $(OBJECTS)
	$(CC) $^ -o $@

$(BUILDDIR)/%.o: %.c
	@mkdir -p $(@D)
	$(CC) $(CFLAGS) -c $< -o $@

Advantages:

  • Single make invocation
  • Accurate dependency tracking
  • Parallel builds work correctly
  • Easier to maintain

Recursive Make (Avoid if Possible)

# Top-level Makefile
SUBDIRS := src lib tests

.PHONY: all
all:
	for dir in $(SUBDIRS); do $(MAKE) -C $$dir; done

.PHONY: clean
clean:
	for dir in $(SUBDIRS); do $(MAKE) -C $$dir clean; done

Problems with recursive make:

  • Incorrect dependency tracking across directories
  • Slower (multiple make invocations)
  • Parallel builds can break
  • See: "Recursive Make Considered Harmful" paper

9. Recipe Formatting

Silent Commands

# @ prefix suppresses command echo
clean:
	@echo "Cleaning build artifacts..."
	@$(RM) *.o

# Without @:
clean:
	echo "Cleaning..."  # This line is printed
	$(RM) *.o          # This line is printed

Multi-Line Recipes

# Each line is a separate shell invocation
bad:
	cd subdir
	make all  # ERROR: cd didn't persist!

# Solution 1: Use && to chain commands
good:
	cd subdir && make all

# Solution 2: Use semicolons
good2:
	cd subdir; make all

# Solution 3: Use backslash continuation
good3:
	cd subdir && \
	make all

Error Handling

# - prefix ignores errors
clean:
	-$(RM) *.o  # Continue even if rm fails

# Without -:
clean:
	$(RM) *.o  # Make stops if rm fails

10. Complete Example

# Project: example
# Description: Example project structure

.DELETE_ON_ERROR:
.SUFFIXES:

# Variables
CC ?= gcc
CFLAGS ?= -Wall -Wextra -O2
PREFIX ?= /usr/local

PROJECT := example
VERSION := 1.0.0
SRCDIR := src
BUILDDIR := build
OBJDIR := $(BUILDDIR)/obj

SOURCES := $(wildcard $(SRCDIR)/*.c)
OBJECTS := $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o)
DEPENDS := $(OBJECTS:.o=.d)
TARGET := $(BUILDDIR)/$(PROJECT)

# Phony targets
.PHONY: all clean install test help

# Default target
all: $(TARGET)

# Build rules
$(TARGET): $(OBJECTS)
	@mkdir -p $(@D)
	@echo "  LD      $@"
	$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@

$(OBJDIR)/%.o: $(SRCDIR)/%.c
	@mkdir -p $(@D)
	@echo "  CC      $<"
	$(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -c $< -o $@

-include $(DEPENDS)

# Install
install: $(TARGET)
	install -d $(DESTDIR)$(PREFIX)/bin
	install -m 755 $(TARGET) $(DESTDIR)$(PREFIX)/bin/$(PROJECT)

# Clean
clean:
	$(RM) -r $(BUILDDIR)

# Test
test: $(TARGET)
	@echo "Running tests..."
	@$(TARGET) --test

# Help
help:
	@echo "$(PROJECT) v$(VERSION)"
	@echo ""
	@echo "Targets:"
	@echo "  all      - Build the project (default)"
	@echo "  install  - Install to PREFIX (default: /usr/local)"
	@echo "  clean    - Remove build artifacts"
	@echo "  test     - Run tests"
	@echo "  help     - Show this message"
	@echo ""
	@echo "Variables:"
	@echo "  CC=$(CC)"
	@echo "  CFLAGS=$(CFLAGS)"
	@echo "  PREFIX=$(PREFIX)"

Best Practices Summary

  1. Use .DELETE_ON_ERROR to prevent corrupted builds
  2. Declare .PHONY for all non-file targets
  3. Use ?= for user-overridable variables (CC, CFLAGS, PREFIX)
  4. Use := for project variables (SOURCES, OBJECTS)
  5. Use automatic variables ($@, $<, $^) for concise rules
  6. Generate dependencies automatically (-MMD -MP)
  7. Prefer non-recursive make over recursive make
  8. Use pattern rules (%.o: %.c) over suffix rules
  9. Create directories automatically (@mkdir -p $(@D))
  10. Document targets with ## comments for help output

References

  • GNU Make Manual - Makefile Structure
  • GNU Coding Standards - Makefile Conventions
  • Recursive Make Considered Harmful

Install with Tessl CLI

npx tessl i pantheon-ai/makefile-generator@0.1.1

references

makefile-structure.md

optimization-guide.md

patterns-guide.md

README.md

security-guide.md

targets-guide.md

variables-guide.md

SKILL.md

tile.json