Tianchi YU's Blog

What you have to know about Makefiles

As we all know, Makefiles are a powerful tool for automating the build process of software projects. They consist of rules that define how source files are compiled and linked to produce executable files or other output. The complete and detailed tutorials are spread on the internet, and there is a good reference by the official GNU. I want to mention some tiny, but useful techniques in the post, hoping it helps you to write pretty Makefiles.

1. Automatic Variables

Makefiles provide several automatic variables that simplify writing rules. For instance, $@ represents the target, $< represents the first prerequisite, and $^ represents all prerequisites. These can be particularly useful in complex build scenarios.

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

2. Phony Targets

Phony targets are those that don't represent actual files but are used for grouping and organizing tasks. They prevent issues if there are files with the same name as a target. Common examples include "clean" (to remove generated files) and "all" (to build the entire project).

.PHONY: clean all

all: my_program

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

clean:
    rm -f my_program

3. Conditional Statements

This is particularly useful when you need to support different platforms or build configurations.

ifeq ($(DEBUG), 1)
    CFLAGS += -g
endif

4. Include and Include Guard

Do you know how to split your Makefile into multiple parts, making it manageable? Yes, you can use include! Using include guards can also prevent double inclusion of the same file.

# Check if INCLUDED is not defined
ifeq ($(origin INCLUDED), undefined)
INCLUDED := 1

# Include other Makefiles here
include submakefile.mk
include another_submakefile.mk

# Rest of your Makefile content

endif

5. Recursive Make vs. Non-Recursive Make

In complex projects, you might encounter discussions about whether to use recursive make (using Makefiles in subdirectories) or a single Makefile at the top level. Both approaches have their pros and cons. We may discuss this in another post.

6. Precious Targets

In a Makefile, we could use the .PRECIOUS special target to specify that specific intermediate files produced during the build process should be considered precious, meaning they will not be deleted by the make utility even if they are not explicitly listed as targets or prerequisites in the Makefile.

.PRECIOUS: intermediate.o

all: final_target

final_target: intermediate.o
    gcc -o final_target intermediate.o

intermediate.o: source.c
    gcc -c source.c -o intermediate.o

In this example, the intermediate.o file is marked as precious using the .PRECIOUS directive. This means that even if intermediate.o is not listed as a target or prerequisite in the rules, it won't be deleted after the build process.

7. Other Useful Functions

There are bunches of functions that make Makefiles more powerful. I list some of them worthy to remember forever below:

wildcard Function

The wildcard function is used to find files that match a specified pattern or pattern list. It's particularly useful for automatically generating a list of files based on a certain pattern. Example in Makefiles:

sources := $(wildcard src/*.c)

In this example, $(wildcard src/*.c) will expand to a list of all .c files in the src directory.

patsubst Function

The patsubst function is used to perform pattern substitution on a list of words. It allows you to transform one pattern into another for each word in the list.

$(patsubst pattern,replacement,text)
objects := $(patsubst src/%.c,obj/%.o,$(sources))

In this example, $(patsubst src/%.c,obj/%.o,$(sources)) will transform each source file path from the src directory to an object file path in the obj directory.

Combining the above two functions, we present another example:

sources := $(wildcard src/*.c)
objects := $(patsubst src/%.c,obj/%.o,$(sources))

all: $(objects)

obj/%.o: src/%.c
    gcc -c $< -o $@

TO BE CONTINUED(ALWAYS)

To improve your Makefiles skills, practice makes it perfect!

#makefile