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)
pattern
: The pattern to search for in each word.replacement
: The replacement pattern to use.text
: The list of words to process. Example in Makefiles:
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!