A while back I made a makefile which could compile virtually any simple project in c++. Recently, this need appeared again so I created the makefile again.

This source code is released into the public domain. Basically, you may use it in any way you like, but read the unlicense notice at the beginning of the file for more precise information.

Requirements

The project should have this makefile above src folder, and it creates a build folder for intermediate compilation files and exe folder for the final executables. It uses grep, g++, mkdir, and dirname which should be accessible on every unix system.

How it works

Rough idea is to identify executables by searching for int main. Other files are compiled into object files in ./build/ folder. Next, we compile the executables into the ./exe/ folder (quite ineffectively linking all the object files). As a sideproduct when building we create dependency definition files in the ./build/ folder which have information on mutual dependencies of the files – all of these are included as rules at the end of the makefile, so only files which are affected by a change are recompiled.

That’s it. Use make to build all, or make exe/a to compile only the executable created from a.cpp.

MAKEFLAGS += --no-builtin-rules
MAKEFLAGS += --no-builtin-variables

CC           = g++
LOADLIBES    = -lm
CXXFLAGS     = --std=c++17 -Wall -pedantic -g -O5 -ffast-math
DEPFLAGS     = -MT $@ -MMD -MP -MF ./build/$*.d

MAIN_FILES   = $(shell grep -lr 'int main' './src/' | grep 'cpp$$')
OTHER_FILES  = $(shell grep -Lr 'int main' './src/' | grep 'cpp$$')

SOURCE_FILES = $(OTHER_FILES) $(MAIN_FILES)
EXE_FILES    = $(MAIN_FILES:./src/%.cpp=./exe/%)
OBJECT_FILES = $(OTHER_FILES:./src/%.cpp=./build/%.o)
HEADER_FILES = $(OTHER_FILES:.cpp=.hpp)
DEPENDENCIES = $(SOURCE_FILES:./src/%.cpp=./build/%.d)

compile: $(EXE_FILES)

./build/%.o: ./src/%.cpp ./build/%.d
	@mkdir -p `dirname $@`
	$(CC) $(CXXFLAGS) $(DEPFLAGS) -c -o $@ $<

$(EXE_FILES): ./exe/%: ./src/%.cpp $(OBJECT_FILES)
	@mkdir -p `dirname $@`
	$(CC) $(CXXFLAGS) -o $@ $< $(OBJECT_FILES)

.PHONY: clean
clean:
	@echo "don't change this rule, as it may be dangerous if small bugs are introduced"
	rm -rf build/ exe/

$(DEPENDENCIES):
include $(wildcard $(DEPENDENCIES))

Code explanation

As the make tool has by default many rules it behaves quite unpredicably, if we don’t know the rules by heart. First two lines disable all builtin rules.

MAKEFLAGS += --no-builtin-rules
MAKEFLAGS += --no-builtin-variables

Next, we define the compiler in CC and some compilation flags in CXXFLAGS; also DEPFLAGS is setup to guide creation of dependency.

CC           = g++
LOADLIBES    = -lm
CXXFLAGS     = --std=c++17 -Wall -pedantic -g -O5 -ffast-math
DEPFLAGS     = -MT $@ -MMD -MP -MF ./build/$*.d

Find source files which are executalbe and ones which are not.

MAIN_FILES   = $(shell grep -lr 'int main' './src/' | grep 'cpp$$')
OTHER_FILES  = $(shell grep -Lr 'int main' './src/' | grep 'cpp$$')

Rename the source files to get filenames for necessary object, executable, and dependency files.

SOURCE_FILES = $(OTHER_FILES) $(MAIN_FILES)
EXE_FILES    = $(MAIN_FILES:./src/%.cpp=./exe/%)
OBJECT_FILES = $(OTHER_FILES:./src/%.cpp=./build/%.o)
HEADER_FILES = $(OTHER_FILES:.cpp=.hpp)
DEPENDENCIES = $(SOURCE_FILES:./src/%.cpp=./build/%.d)

Define the main goal when make is run – compile everything.

compile: $(EXE_FILES)

To get the object files, we need the .cpp source and its dependencies .d to be up-to-date; if those changed, then recompile it.

./build/%.o: ./src/%.cpp ./build/%.d
	@mkdir -p `dirname $@`
	$(CC) $(CXXFLAGS) $(DEPFLAGS) -c -o $@ $<

To get the executable we need its .cpp source and also all object files ready. Then, again, build it.

$(EXE_FILES): ./exe/%: ./src/%.cpp $(OBJECT_FILES)
	@mkdir -p `dirname $@`
	$(CC) $(CXXFLAGS) -o $@ $< $(OBJECT_FILES)

To clean, we simply remove the only two folders which got poluted.

clean:
	rm -rf build/ exe/

Finally, as sources files are dependent on each other we include those dependencies into this makefile as rules. These are created thanks to the -MMD flag of g++ as a sideproduct of .o compilation.

$(DEPENDENCIES):
include $(wildcard $(DEPENDENCIES))