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
target: prequisite
commandThe 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:
target: prequisite | order-only-prerequisite
commandCases 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.
.PHONY: clean
clean:
rm -rf buildCommands
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.
.PHONY: all
all: test_programVariables
Variables can be declared two ways either with = or := operators:
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.
greeting = $(hello)
hello = Hello
place = $(world)
world = Worldecho $(greeting) $(place) -> Hello World
greeting := $(hello)
hello := Hello
place := $(world)
world := Worldecho $(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:
make target VARIABLE=1These 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
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.
.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
RMFlags for common commands are also made available:
ARFLAGS
ASFLAGS
CFLAGS
CXXFLAGS
CPPFLAGS
LDFLAGS
LDLIBS.PHONY: clean
clean:
$(RM) targetImplicit 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.
%.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
if #condition
#
else if #condition
#
else
#
endififdef 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.
ifdef VARIABLE
#
else ifndef VARIABLE
#
else
#
endififeq 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.
ifeq ($(VARIABLE), 1)
#
else ifeq ($(VARIABLE), '')
#
else
#
endifFunctions
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
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.
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.
$(CC) file1.cThis 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.
$(CC) file1.c -o make_testIncludes 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.
$(CC) file1.c -I include/ -o make_testObject 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.
cc -c file1.cThis 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.
ld file1.o -o test_programThe compiler can also be used to invoke the ld linker:
cc file1.o -o test_programLinking 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:
ld file1.o -lc -o test_programIn 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:
ld file1.o -L/home/lib/ -lcustom -o test_programThis 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.
cc file1.o -L/home/lib/ -lcustom -o test_programTo find which directories are being searched for libraries:
cc -print-search-dirsA 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.
cc file1.o -Wl,-map,program.map -o test_program
# or
ld file1.o -map program.map -o test_programStatic 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:
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.
cc -fPIC -c file`.c -o test_program.o
cc -shared test_program.o -o libcustom.dylibThe 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.
LD_LIBRARY_PATH=./libraries