SPL

Make is a build system. Other build systems are

  • scons
  • cmake
  • bazel
  • ninja
  • rake
  • ant
  • maven
hello:
	echo "Hello, World"
targets: prerequisites
	command;
	command;

Make clean

it is not a special word, just good convention

some_file: 
	touch some_file
 
clean: 
	rm -f some_file

Variables

can only be strings

files := file1 file2
some_file: $(files)
	touch some_file
 
file1: 
	touch file1
 
file2: 
	touch file2
 
clean:
	rm -f file1 file2 some_file

don’t use quotes when initialising variables

reference using either $(variable) or ${variable}

Make all

The topmost target is run by default if no target specified

So make this the all target

all: target1 target2
 
target1:
 
target2:

Automatic Variables

$@: target name $?: all prerequisites newer than the target $^: all prerequisites $<: first prerequisite

f1.0 f2.0:
	echo $@ #name of target
 
# equivalent to
f1.0:
	echo f1.0
f2.0:
	echo f2.0

Wildcards

wrap wildcards in $(wildcard *.c)

Implicit Rules

Make loves c compilation. And every time it expresses its love, things get confusing. Perhaps the most confusing part of Make is the magic/automatic rules that are made. Make calls these “implicit” rules. I don’t personally agree with this design decision, and I don’t recommend using them, but they’re often used and are thus useful to know. Here’s a list of implicit rules:

  • Compiling a C program: n.o is made automatically from n.c with a command of the form $(CC) -c $(CPPFLAGS) $(CFLAGS) $^ -o $@
  • Compiling a C++ program: n.o is made automatically from n.cc or n.cpp with a command of the form $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $^ -o $@
  • Linking a single object file: n is made automatically from n.o by running the command $(CC) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@

The important variables used by implicit rules are:

  • CC: Program for compiling C programs; default cc
  • CXX: Program for compiling C++ programs; default g++
  • CFLAGS: Extra flags to give to the C compiler
  • CXXFLAGS: Extra flags to give to the C++ compiler
  • CPPFLAGS: Extra flags to give to the C preprocessor
  • LDFLAGS: Extra flags to give to compilers when they are supposed to invoke the linker

Commands and execution

Command Echoing/Silencing

Add an @ before a command to stop it from being printed
You can also run make with -s to add an @ before each line

all: 
	@echo "This make line will not be printed"
	echo "But this will"

Command Execution

Each command is run in a new shell (or at least the effect is as such)

all: 
	cd ..
	# The cd above does not affect this line, because each command is effectively run in a new shell
	echo `pwd`

	# This cd command affects the next because they are on the same line
	cd ..;echo `pwd`

	# Same as above
	cd ..; \
	echo `pwd`

Default Shell

The default shell is /bin/sh. You can change this by changing the variable SHELL:

SHELL=/bin/bash

cool:
	echo "Hello from bash"

Double dollar sign

If you want a string to have a dollar sign, you can use $$. This is how to use a shell variable in bash or sh.

Note the differences between Makefile variables and Shell variables in this next example.

make_var = I am a make variable
all:
	# Same as running "sh_var='I am a shell variable'; echo $sh_var" in the shell
	sh_var='I am a shell variable'; echo $$sh_var

	# Same as running "echo I am a make variable" in the shell
	echo $(make_var)

Error handling with -k, -i, and -

Add -k when running make to continue running even in the face of errors. Helpful if you want to see all the errors of Make at once.
Add a - before a command to suppress the error
Add -i to make to have this happen for every command.

one:
	# This error will be printed but ignored, and make will continue to run
	-false
	touch one

Interrupting or killing make

Note only: If you ctrl+c make, it will delete the newer targets it just made.

Recursive use of make

To recursively call a makefile, use the special $(MAKE) instead of make because it will pass the make flags for you and won’t itself be affected by them.

new_contents = "hello:\n\ttouch inside_file"
all:
	mkdir -p subdir
	printf $(new_contents) | sed -e 's/^ //' > subdir/makefile
	cd subdir && $(MAKE)

clean:
	rm -rf subdir

Export, environments, and recursive make

When Make starts, it automatically creates Make variables out of all the environment variables that are set when it’s executed.

# Run this with "export shell_env_var='I am an environment variable'; make"
all:
	# Print out the Shell variable
	echo $$shell_env_var

	# Print out the Make variable
	echo $(shell_env_var)

The export directive takes a variable and sets it the environment for all shell commands in all the recipes:

shell_env_var=Shell env var, created inside of Make
export shell_env_var
all:
	echo $(shell_env_var)
	echo $$shell_env_var

As such, when you run the make command inside of make, you can use the export directive to make it accessible to sub-make commands. In this example, cooly is exported such that the makefile in subdir can use it.

new_contents = "hello:\n\techo \$$(cooly)"

all:
	mkdir -p subdir
	printf $(new_contents) | sed -e 's/^ //' > subdir/makefile
	@echo "---MAKEFILE CONTENTS---"
	@cd subdir && cat makefile
	@echo "---END MAKEFILE CONTENTS---"
	cd subdir && $(MAKE)

# Note that variables and exports. They are set/affected globally.
cooly = "The subdirectory can see me!"
export cooly
# This would nullify the line above: unexport cooly

clean:
	rm -rf subdir

You need to export variables to have them run in the shell as well.

one=this will only work locally
export two=we can run subcommands with this

all: 
	@echo $(one)
	@echo $$one
	@echo $(two)
	@echo $$two

.EXPORT_ALL_VARIABLES exports all variables for you.

.EXPORT_ALL_VARIABLES:
new_contents = "hello:\n\techo \$$(cooly)"

cooly = "The subdirectory can see me!"
# This would nullify the line above: unexport cooly

all:
	mkdir -p subdir
	printf $(new_contents) | sed -e 's/^ //' > subdir/makefile
	@echo "---MAKEFILE CONTENTS---"
	@cd subdir && cat makefile
	@echo "---END MAKEFILE CONTENTS---"
	cd subdir && $(MAKE)

clean:
	rm -rf subdir

Arguments to make

There’s a nice list of options that can be run from make. Check out --dry-run, --touch, --old-file.

You can have multiple targets to make, i.e. make clean run test runs the clean goal, then run, and then test.

SHELL := /bin/bash
CC := gcc
CFLAGS := -ggdb -Wall -O3
INSTALL := sudo pacman -S 
SRC_DIR := /home/subzcuber/
BUILD_DIR := 
 
all:
	# compile the entire program
 
install:
	# compile program, and copy the executables etc where they should reside. run a simple test
 
uninstall:
	# delete all the files `install` would create
 
clean:
	# delete all files from the current dir that are normally created by building the program
 
dist:
	# create tarball for the distribution
 
check:
	# run tests
 
TARGET_EXEC := final_program
 
BUILD_DIR := ./build
SRC_DIRS := ./src
 
# Find all the C and C++ files we want to compile
# Note the single quotes around the * expressions. The shell will incorrectly expand these otherwise, but we want to send the * directly to the find command.
SRCS := $(shell find $(SRC_DIRS) -name '*.cpp' -or -name '*.c' -or -name '*.s')
 
# Prepends BUILD_DIR and appends .o to every src file
# As an example, ./your_dir/hello.cpp turns into ./build/./your_dir/hello.cpp.o
OBJS := $(SRCS:%=$(BUILD_DIR)/%.o)
 
# String substitution (suffix version without %).
# As an example, ./build/hello.cpp.o turns into ./build/hello.cpp.d
DEPS := $(OBJS:.o=.d)
 
# Every folder in ./src will need to be passed to GCC so that it can find header files
INC_DIRS := $(shell find $(SRC_DIRS) -type d)
# Add a prefix to INC_DIRS. So moduleA would become -ImoduleA. GCC understands this -I flag
INC_FLAGS := $(addprefix -I,$(INC_DIRS))
 
# The -MMD and -MP flags together generate Makefiles for us!
# These files will have .d instead of .o as the output.
CPPFLAGS := $(INC_FLAGS) -MMD -MP
 
# The final build step.
$(BUILD_DIR)/$(TARGET_EXEC): $(OBJS)
	$(CXX) $(OBJS) -o $@ $(LDFLAGS)
 
# Build step for C source
$(BUILD_DIR)/%.c.o: %.c
	mkdir -p $(dir $@)
	$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
 
# Build step for C++ source
$(BUILD_DIR)/%.cpp.o: %.cpp
	mkdir -p $(dir $@)
	$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
 
 
.PHONY: clean
clean:
	rm -r $(BUILD_DIR)
 
# Include the .d makefiles. The - at the front suppresses the errors of missing
# Makefiles. Initially, all the .d files will be missing, and we don't want those
# errors to show up.
-include $(DEPS)