All skills
akin-ozer avatar

/makefile-generator

@1b2342a

Create, generate, or scaffold Makefiles with .PHONY targets and build automation.

Use this Skill: https://skilld.dev/gh/akin-ozer/cc-devops-skills/makefile-generator

This session only. Nothing lands on disk.

docsmakefile-structure.md

≈2.8k tokens on demand. Your agent reads this file only when SKILL.md points to it.

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

Variable Meaning
$@ 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

Source: SKILL.md on GitHub

No alerts16d4 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The Makefile Generator skill is a robust and well-documented utility for scaffolding build systems across various programming languages. It adheres to security best practices, providing clear guidelines for secrets management and input validation within generated Makefiles. No malicious patterns, obfuscation, or unauthorized network activities were detected.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 1b2342a. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Steadyupdated 7 months ago

README badge

README badge for akin-ozer/cc-devops-skills/makefile-generator