Skip to content

Make

What is Make?

Make is a tool used for triggering actions based on the tracking of files, traditionally in a C/C++ project. The make command will look for a makefile in the current directory, named either GNUmakefile, makefile, or Makefile.

The makefile contains the rules, prerequisites, targets and commands used in a project. By defining the targets to be generated, and that targets prerequisites, Make can determine whether changes to the source files warrant a revision to the target file. Make can be used to manage dependencies - if files haven't changed since the last execution of a make command, repeating the command will have no effect. The make command will build or re-build its targets. It tracks whether relevant changes have been made recently using filesystem timestamps.

Make Syntax

Rules

make
target: prequisite
    command
target: prequisite
    command

The functional parts of a makefile are composed of block like this, called a rule. The rule defines the files, or prerequisites that are required to exist before the command contained in the rule is run. The result of a rule usually generates a target file.

Prerequisites

Prerequisites are files required by a rule to execute, whether they are existing files, or referencing other targets in the Makefile which are expected to have already ran.

Order Only Prerequisites

Prerequisites which are required for running a rule, but shouldn't require a full re-run when altered can be added as Order Only Prerequisites as follows:

make
target: prequisite | order-only-prerequisite
    command
target: prequisite | order-only-prerequisite
    command

Cases where this might be useful include directory creation. Any update to a directory would trigger an update using traditional prerequisites, due to timestamp changes when content inside the directory changes. Using this method would trigger its creation only once.

Targets

The output file to be generated by the Make rule, whether an object file or a binary file.

PHONY

A PHONY target is one that doesn't produce an output file, and is instead a placeholder name that provides a description or invocable handle for the rule.

make
.PHONY: clean
clean:
    rm -rf build
.PHONY: clean
clean:
    rm -rf build

Commands

Commands are command line operations that will run when a rule is being executed. They can be separated sequentially by line breaks, and silenced by prepending with a @. Without this, the command will appear in any command line output, followed by its print results.

Tabs vs. Spaces

Unlike other languages, Make is not agnostic about tabs and spaces. Each command line must begin with a tab, and not spaces.

Default Behavior

By calling make with no target, it will execute the first rule it finds in the Makefile. The common approach to this is the create a default first rule called all which has a prerequisite every target meant to be built.

make
.PHONY: all
all: test_program
.PHONY: all
all: test_program

Variables

Variables can be declared two ways either with = or := operators:

make
VARIABLE1 = val
VARIABLE2 := val
VARIABLE1 = val
VARIABLE2 := val

= variables are expanded recursively, such that everywhere that value is referenced, it will be updated in the file when assigned a new value. := assignments are only expanded once, and will evaluate as expected from C variable assignment.

make
greeting = $(hello)
hello = Hello
place = $(world)
world = World
greeting = $(hello)
hello = Hello
place = $(world)
world = World

echo $(greeting) $(place) -> Hello World

make
greeting := $(hello)
hello := Hello
place := $(world)
world := World
greeting := $(hello)
hello := Hello
place := $(world)
world := World

echo $(greeting) $place -> ' '

hello and world aren't defined when assigned to their variables, and will be evaluated as empty strings.

Variables are referenced in Make using the $() syntax.

A variable can also be passed in from the command line:

bash
make target VARIABLE=1
make target VARIABLE=1

These variables can be treated as optional by assigning them using the flexible operator ?= which will assign a default value to the variable if one isn't provided.

Variables can be appended using the += operator.

Automatic Variables

  • $@ represents the target which caused the rule to run
  • $^ represents the list of prerequisites, separated by spaces
  • $< represents the first in the list of prerequisites
  • $(@D) represents the directory part of the target filename
  • $(^D) represents the directory parts of the prerequisites as a list
  • $(<F) represensts the directory part of the first prerequisite
make
target: prerequisite1 prerequisite2 prerequisite3
	cc $^ -o $@
target: prerequisite1 prerequisite2 prerequisite3
	cc $^ -o $@

Second Expansion

The .SECONDEXPANSION special target is used to enable secondary expansion of prerequisites in explicit rules. The secondary expansion allows you to use automatic variables like $@, $<, and $^ in the prerequisites list. Normally, in a makefile, automatic variables are not expanded in the prerequisites list of explicit rules, but with .SECONDEXPANSION, you can achieve this.

make
.SECONDEXPANSION:

%.txt: $$(wildcard $$*.src)
    cp $< $@
.SECONDEXPANSION:

%.txt: $$(wildcard $$*.src)
    cp $< $@

Implicit Variables

Implicit variables are also made available for common commands, such as AR for archiving programs, CC for C compiling programs, and RM for removing a file. These can be useful for cross-platform makefiles.

AR
AS
CC
CXX
CPP
RM
AR
AS
CC
CXX
CPP
RM

Flags for common commands are also made available:

ARFLAGS
ASFLAGS
CFLAGS
CXXFLAGS
CPPFLAGS
LDFLAGS
LDLIBS
ARFLAGS
ASFLAGS
CFLAGS
CXXFLAGS
CPPFLAGS
LDFLAGS
LDLIBS
make
.PHONY: clean
clean:
	$(RM) target
.PHONY: clean
clean:
	$(RM) target

Implicit Rules

Many rules can be interpreted by Make itself, such as when a .o file is required as a prerequisite. Make is able to discern that should that file not exist, another file with the same prefix ending in .c should be available and will be used to build the .o file.

make
%.o: %.c
	$(CC) $(CPPFLAGS) $(CFLAGS) -c $^ -o $@
%.o: %.c
	$(CC) $(CPPFLAGS) $(CFLAGS) -c $^ -o $@

% is a Make wildcard character, so in this example any prefix of the filename ending in .o, such as filename.o will have as its prerequisite filename.c. This wildcard value can also be accessed using the operator $*. This prefix filename is known as the stem.

Note also that the default variables $(CFLAGS) and $(CPPFLAGS) are used in this rule, so when defined in the Makefile, any options to those compiler will be substituted.

Conditional Logic

Conditional blocks in Make follow the syntax

make
if #condition
#
else if #condition
#
else
#
endif
if #condition
#
else if #condition
#
else
#
endif

ifdef and ifndef

ifdef and ifndef will check whether a variable has been defined or not. They don't, however, check for the variable being an empty string.

make
ifdef VARIABLE
#
else ifndef VARIABLE
#
else
#
endif
ifdef VARIABLE
#
else ifndef VARIABLE
#
else
#
endif

ifeq and ifneq

ifeq and ifneq check a variable's value against another value. These checks can be used for testing if a variable is an empty string.

make
ifeq ($(VARIABLE), 1)
#
else ifeq ($(VARIABLE), '')
#
else
#
endif
ifeq ($(VARIABLE), 1)
#
else ifeq ($(VARIABLE), '')
#
else
#
endif

Functions

Functions in Make follow the syntax $(function_name argument1, argument2, ...) and can be defined by the user, though many are provided as Make build system functions. The full list is available in the GNU Manual.

Pattern Substitution

Replace the words matching the given pattern in text with the replacement.

$(patsubst pattern,replacement,text)

String Substitution

make
OBJS := $(SRCS:%=$(BUILD_DIR)/%.o)
DEPS := $(OBJS:.o=.d)
OBJS := $(SRCS:%=$(BUILD_DIR)/%.o)
DEPS := $(OBJS:.o=.d)

Strip Function

Remove excess whitespace characters.

$(strip string)

Filter Function

Select words in the text that match one of the pattern words.

$(filter pattern..., text)

Shell Function

Executes a shell command and returns the output.

$(shell command)

Conditional Functions

Evaluate the condition and execute the then result. Available for if, or, and and. An optional else condition can be provided.

$(if condition, then-part[,else-part])

Directory Function

Extracts the directory part from a list of filenames.

$(dir names...)

Not Directory Function

Extracts the non-directory part from a list of filenames.

$(notfir names...)

Basename Function

Extracts the filename, without the suffix of each filename.

$(basename names...)

Foreach Function

Evaluate text with var bound to each word in words, and concatenate the results.

$(foreach var,words,text)

Message Functions

warning, info, and error functions are available to trigger exit, debugging messages, or info messages.

$(info value)

$(warning value)

$(error value)

Custom Functions

Custom functions can be created by assigning any combination of GNU Make functions to a variable, and using the $(1), $(2), etc. automatic variables as the arguments.

make
function = $(foreach DIR,$(1),$(wildcard $(DIR)/*$(2)))
$(call function, param, param, ...)
function = $(foreach DIR,$(1),$(wildcard $(DIR)/*$(2)))
$(call function, param, param, ...)

Compiling

Clean

The compilation process can generate a number of temporary files, so it's always advisable to not only point output to a build folder, but to include clean rule in the Makefile that will wipe that folder for a clean rebuild.

Compile Commands

The CC compiler variable and cc command line variable is available to Mac/Linux users and points to the default C compiler. This can be made use of in Makefiles to easily call a compile command that should be okay on all systems.

make
$(CC) file1.c
$(CC) file1.c

This will generate a default a.out file as its ouput. While this can be executed ./a.out, it can be called something more descriptive or memorable using the -o output flag.

make
$(CC) file1.c -o make_test
$(CC) file1.c -o make_test

Includes can also be passed to this command using the -I flag. By default, the compiler will include the current directory it's invoked in, as well as the system header directory. The default directories can be shown with the cpp -v command.

make
$(CC) file1.c -I include/ -o make_test
$(CC) file1.c -I include/ -o make_test

Object Files

Creating the binary output file includes the intermediary step of taking the object files generated by the compiler, and linking them to addresses in memory to create a program. The intermediary object files can be generated as the primary output in a multi-step Make process using the -c flag.

make
cc -c file1.c
cc -c file1.c

This will result in a file1.o output. The functions and variables invoked in file1 will be present in the object file, but the addresses will be temporary stubs with no set value.

Linking

Linkers combine object files, and libraries to create either object files, executables, or libraries. The default linker ld for gcc and clang can be invoked for both C and C++, and outputs referenced the same was the compiler.

make
ld file1.o -o test_program
ld file1.o -o test_program

The compiler can also be used to invoke the ld linker:

**make**
cc file1.o -o test_program
cc file1.o -o test_program

Linking Libraries

When linking against libraries, the ld linker can be invoked with the -l flag, followed by the name of the library on the compilers library path:

make
ld file1.o -lc -o test_program
ld file1.o -lc -o test_program

In this case, libc was invoked by using the name of the library (all libraries begin with the lib prefix, so this can be ignored).

When the library is located in another directory, the -L flag can used to specify:

make
ld file1.o -L/home/lib/ -lcustom -o test_program
ld file1.o -L/home/lib/ -lcustom -o test_program

This will likely fail, because the ld linker requires more flags when passing libraries for compiling. Instead, using the default compiler as your interface simplifies the operation.

make
cc file1.o -L/home/lib/ -lcustom -o test_program
cc file1.o -L/home/lib/ -lcustom -o test_program

To find which directories are being searched for libraries:

make
cc -print-search-dirs
cc -print-search-dirs

A linker script can be passed , using the -T flag.

For debugging purposes, a .map file can be generated with all functions, variables, library imports, and their memory addresses and size.

make
cc file1.o -Wl,-map,program.map -o test_program
# or
ld file1.o -map program.map -o test_program
cc file1.o -Wl,-map,program.map -o test_program
# or
ld file1.o -map program.map -o test_program

Static Libraries

Denoted by the .a file extension, static libraries are files that contain functions that can be used by many applications. Within an application, linking a static library will copy the entire content of the library to the program.

Dynamic Libraries

Denoted by the .dll, .so, or .dylib file extensions, dynamic libraries are referenced by the program that it is linked to, rather than copying the entire contents of the library. Dynamic libraries are referenced using a program called the loader, which reads the library contents and stores the machine code for the required functions into memory.

Creating Static Libraries

Static libraries are creating using a program called an Archiver, ar. The benefits of compiling static libraries rather than dynamic libaries, are it can keep source code hidden, decrease coupling within the system, and reduce compilation times for files that aren't change often.

The syntax of the archiver command are as follows:

make
ar rcs library.a file1.o file2.o ...
ar rcs library.a file1.o file2.o ...

The rcs refers to the flags r, which overwrites symbols into the archive, c which creates the archive, and s which writes an index. These flags can be passed in most cases and represents a catch-all command which should suit most use-cases.

Creating Dynamic Libraries

Dynamic libaries must be created using the compiler, and are invoked using the -fPIC flag, as well as the -shared flag. The -f flag is followed by PIC for Position Independent Code, which allows the code to be references from other memory addresses.

make
cc -fPIC -c file`.c -o test_program.o
cc -shared test_program.o -o libcustom.dylib
cc -fPIC -c file`.c -o test_program.o
cc -shared test_program.o -o libcustom.dylib

The dylib file extension for MacOS will be dll for Windows, and so for Linux.

Libraries which aren't found in the default paths will need to have their locations set as the LD_LIBRARY_PATH variable.

make
LD_LIBRARY_PATH=./libraries
LD_LIBRARY_PATH=./libraries