Notes on GNU Make
C, C++, Make
The GNU make
utility is a tool that maintains groups of programmes make
can determine which pieces of a large programme need to be compiled and/or recompiled, and generally take care of compilation and recompilation in an efficient way.
You can use make
with any programming language whose compiler can be run from the command line. Beyond compiling programmes, make
can describe any task that involves updating files automatically when other files change.
The Makefile
The Makefile
is used to describe relationships between files in the programme, and the commands necessary for updating files.
In C programmes, the executable file is updated (“linked”) from “object” files, which are in turn made by compiling source files.
Once you’ve written a suitable Makefile
, you just need to run make
in the programme root directory to perform all necessary recompilations.
If you don’t specify a Makefile
with make -f <makefile>
, make
will look for the makefiles:
- GNUmakefile
- makefile
- Makefile
in that order. It is recommended to call the makefile Makefile
.
The makefile contains one or more rules that specify how a target is produced from prerequisite files. make
updates a target if the prerequisite files that it depends upon have been modified since the target was last changed, or if the target does not exist.
Rules
A make
rule is comprised of a target specification and it’s prerequisite files, all on one line and with the target separated from the prerequisites by a colon.
After the space-separated list of prerequisites, the rule has a newline and a list of commands that should be executed required to rebuild the target from the prerequisites.
Commands may be added over multiple lines, but each line that contains a command must start with a TAB character.
Example Makefile
The following makefile has 3 targets: myProgramme
, moduleOne
& moduleTwo
:
myProgramme: moduleOne.o moduleTwo.o
gcc -o myProgramme moduleOne.o moduleTwo.o
moduleOne.o: moduleOne.c moduleOne.h standaloneHeader.h
gcc -std=gnu=gnu99 -pedantic -Wall -c moduleOne.c
moduleTwo.o: moduleTwo.c moduleTwo.h standaloneHeader.h
gcc -std=gnu=gnu99 -pedantic -Wall -c moduleTwo.c
Special Make Variables
Inside an action, special variables are available for matching filenames:
$@
: The full target name of the current target$?
: Dependencies that are newer than the current target$*
: Returns text corresponding to%
in current target$<
: Returns the name of the first dependency$^
: Name of all dependencies, space separated
This allows your makefile to be more concise:
Example Makefile 2
Example C++ makefile for the following project structure:
main.cpp
: includes grade.h & util.hutil.cpp
: includes util.hutil.h
: contains declarationsgrade.cpp
: includes grade.hgrade.h
: contains declarations
# Set a variable for flags to avoid repetition
CXXFLAGS = -W -Wall -pedantic -std=c++17 -g
CXX = g++ ${CXXFLAGS}
NAME = grades
# The target has the dependencies util.o, main.o & grade.o
$(NAME): util.o main.o grade.o
$(CXX) -o $@ $^
# For any .c file, build an object file by applying the actions on the following line.
# This is a pattern rule, a type of implicit rule. Specifies one target and one dependency.
# Causes one invocation of $(CXX) for each target.
%.o: %.c
$(CXX) -c $<
# util.c and grade.c include header files. Their object files are generated by the implicit
# rule above, but the following lines specify additional dependencies:
util.o: util.h
grade.o: grade.h
# Remove the executable and all object files
.PHONY: clean
clean:
-rm $(NAME) *.o
Note that the pattern rule (%.o: %.c
) involves matching file names - the %
can match any non-empty substring, whereas the characters match only themselves. The rule says: “Compile .c files into corresponding .o files”.
In this example, main.o
, util.o
and grade.o
will be made from main.c
, util.c
and grade.c
respectively.
Variables for Implicit Rules
CFLAGS
: Extra flags to give to the C compiler.CPPFLAGS
: Extra flags to give to the C preprocessor and programs that use it (the C and Fortran compilers).CXX
: Program for compiling C++ programs, defaultg++
.CXXFLAGS
: Extra flags to give to the C++ compiler.
Set Variables
Assign variables within a Makefile.
In a Makefile, a variable is a name assigned to a value, which is a string of text. Values are substituted into targets, prerequisites, commands etc.
# Set a variable to hold the absolute path of the Makefile
# Getting `firstword` strips off any includes
ABS_PATH := $(dir $(realpath $(firstword $(MAKEFILE_LIST))))
# Alternatively:
ABS_PATH := $(shell pwd)
See this article for a more complete description.
Variables can be either recursively expanded (defined using the =
operator) or simply expanded (defined using the :=
operator).
See this article for more information.
Example: C
# Set common parameters
CC = gcc
CFLAGS = -std=gnu99 -W -Wall -pedantic -g
# Specify necessary targets/tasks
all: maxSeq.o test-subseq.o test-subseq
# Compile maxSeq.c into maxSeq.o
maxSeq.o: maxSeq.c
$(CC) $(CFLAGS) -c maxSeq.c
# Compile test-subseq.c into test-subseq.o
test-subseq.o: test-subseq.c
$(CC) $(CFLAGS) -c test-subseq.c
# Link object files test-subseq.o and maxSeq.o into an executable test-subseq
test-subseq: test-subseq.o maxSeq.o
$(CC) $(CFLAGS) -o test-subseq test-subseq.o maxSeq.o
clean:
rm maxSeq.o test-subseq.o
Example: C++ Makefile to Create Multiple Executables
In this example, each *.cpp
file is compiled and linked into a distinct executable:
# Makefile to compile and link all C++ files in the current directory, such that each C++
# file results in a unique executable.
#
# This is useful for experimentation, testing and small-scale projects.
#
# Copyright (c) David Egan 2020
# SPDX-License-Identifier: GPL-2.0-or-later
WARNINGS = -pedantic -Wall -Wfatal-errors -Wextra -Wno-unused-parameter -Wno-unused-variable
BIN_DIR = bin
OBJS_DIR = $(BIN_DIR)/objs
CXXFLAGS = $(WARNINGS) -std=c++17 -g
LDFLAGS =
CXX = g++ ${CXXFLAGS}
# Handle all .cpp files together - allows more to be added to the project without
# changing the Makefile
SRCS = $(wildcard *.cpp)
# String substitution to generate appropriate names for executable files
EXECUTABLE_FILES = $(SRCS:%.cpp=$(BIN_DIR)/%)
# Note that the following is an alternative way of performing string substitution:
# EXECUTABLE_FILES = $(patsubst %.cpp,$(BIN_DIR)/%,$(SRCS))
# Output for debugging purposes
$(info EXECUTABLE_FILES = "$(EXECUTABLE_FILES)")
# There should be a corresponding object file for each .cpp file
OBJECT_FILES = $(SRCS:%.cpp=$(OBJS_DIR)/%.o)
.PHONY: all clean
all: $(EXECUTABLE_FILES)
# This pattern is triggered by the `all` rule. This is a builtin rule
# that builds executables from object files.
$(BIN_DIR)/%: $(OBJS_DIR)/%.o
@$(CXX) $(LDFLAGS) -o $@ $^
# Builtin rule to generate object files from .cpp files
$(OBJS_DIR)/%.o: %.cpp
$(info Building object file for "$(<)")
@mkdir -p $(@D)
@$(CXX) $(CXXFLAGS) -o $@ -c $<
# Additional dependencies
$(OBJS_DIR)/selection-sort.o: util.h
$(OBJS_DIR)/merge-sort.o: util.h
clean:
rm $(OBJS_DIR)/*.o
rm -f $(BIN_DIR)/*
Project Structure:
.
├── bin
│ ├── merge-sort
│ ├── objs
│ │ ├── merge-sort.o
│ │ └── selection-sort.o
│ └── selection-sort
├── Makefile
├── merge-sort.cpp
├── selection-sort.cpp
└── util.h
2 directories, 8 files
Output Variables
The internal info
function allows you to output variables to stdout:
$(info EXECUTABLE_FILES = "$(EXECUTABLE_FILES)")
# outputs the value of `EXECUTABLE_FILES`
String Substitution
$(patsubst pattern,replacement,text)
finds whitespace separated words in text
that match pattern
and replace these with replacement
. The pattern may contain a %
character, which acts as a wildcard matching any number of characters within a word - escape literal % characters in a pattern with a \
.
Example:
# For each "word" in $(SRCS) remove .cpp and prepend a directory path
EXECUTABLE_FILES = $(patsubst %.cpp,$(BIN_DIR)/%,$(SRCS))
You can use substitution references to get the same effect:
EXECUTABLE_FILES = $(SRCS:%.cpp=$(BIN_DIR)/%)
Substitution references substitute the value of a variable with the specified alterations, in the form:
$(VAR:a=b)
This means take the value of VAR
and replace every a
at the end of a word with b
. Wildcards can also be used, as shown in the example above.
References
comments powered by Disqus