Make, Ninja, Ant, Gradle, MSVC are some of the build systems. They help us to drive the compiler and other build tools. You may need to write a ‘build file’ to tell them the dependency information for them to build a software.
While this is already very convenient, the building of C/C++ projects is not standard across the platforms. Then you may need a ‘meta-build system’ to help you generate ‘build’ files for different build systems.
CMake is such a ‘meta-build system’ that can generate platform based system files by setting some flags.
See this article about the details of a buildsystem: What Is a Build System? - Scott Dorman
More information about the difference between Make and CMake: What is the difference between CMake and Make? - Quora
c++ - Difference between using Makefile and CMake to compile the code - Stack…
Make is a buildsystem - it drives the compiler and other build tools to build your project. It can also help when you have a series of instructions to run depending on what file have changed.
A Makefile consists of a series of rules:
target ...: prerequisites ...
recipe
...
You can define multiple targets for a rule, the commands will be run for every single target. $@
is an automatic variable that contains the target name:
all: f1.o f2.o
f1.o f2.o:
echo $@
# Equivalent to:
# f1.o:
# echo f1.o
# f2.o:
# echo f2.o
Variables can only be strings. You can use :=
and =
to define variables, and refer to them with ${}
or $()
:
x := dude
all:
echo $(x)
echo ${x}
# Bad practice, but works
echo $x
Both *
and %
are called wildcards in Makefile, but mean different things.
*
searches your filesystem for matching filenames. You can use it in the wildcard
function:
thing_wrong := *.o # Don't do this! '*' will not get expanded
thing_right := $(wildcard *.o)
all: one two three four
# Fails, because $(thing_wrong) is the string "*.o"
one: $(thing_wrong)
# Stays as *.o if there are no files that match this pattern :(
two: *.o
# Works as you would expect! In this case, it does nothing.
three: $(thing_right)
# Same as rule three
four: $(wildcard *.o)
Note that *
may not be directly used in variable definitions, and when it matches no files, it is left as it is (unless in the wilecard
function).
There are many automatic variables:
$@
: All the targets$^
: All the prerequisites$<
: The first prerequisite$?
: All the prerequisites newer than the target$(@D)
: The directory of the target
You can use these automatic variables to simplify your Makefile.
An example:
.PHONY: binary
binary: build/binary
build/%.o: src/%.c
mkdir -p $(@D)
g++ $^ -o $@
build/binary: build/a.o build/b.o
cat $^ > $@
Add an @
before a command to stop it from being printed:
all:
@echo: "This command will not be printed"
Each command is run independently except for this:
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`
The default shell is /bin/sh
, and you can change that by setting the SHELL
variable:
SHELL=/bin/bash
You can handle errors of commands by adding some options to these commands:
-k
: let Make continue and output all the errors together-
: supress the errors
Use $(MAKE)
to make recursively:
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
A phony target is not really the name of a file, but just a name of a recipe to be executed. When making phony targets, Make will just run its recipe without checking if the file exsists. For example, the following definition of clean will avoid clean
work not run when you have a file named clean
in your working directory:
.PHONY: clean
clean:
rm *.o
Assume that you have such a Makefile:
buid/a.o: src/a.c
mkdir -p build
g++ src/a.c -o build/a.o
build/b.o: b.c
mkdir -p build
g++ src/b.c -o build/b.o
build/binary: build/a.o build/b.o
cat build/a.o build/b.o > build/binary
You can turn it into a prettier one by using variables and functions:
.PHONY: binary
SRC_DIR := src/
SRCS := $(shell find $(SRC_DIR) -name "*.o")
OBJ_DIR := build/
OBJS := $(patsubst src/%.c,build/%.o,$(SRCS))
binary: $(OBJ_DIR)binary
$(OBJ_DIR)%.o: $(SRC_DIR)%.c
mkdir -p $(@D)
g++ $^ -o $@
$(OBJ_DIR)binary: $(OBJS)
cat $^ > $@
You can see what commands are executed with make -nB target
.
You can output some info with $(info "xxx")
to debug a Makefile.
Refer to this tutorial for more information about how to write a Makefile: Makefile Tutorial By Example
Go to GNU.org for the complete tutorial to Make: GNU make
See here for more information in practice: https://www.cs.swarthmore.edu/~newhall/unixhelp/howto_makefiles.html
You can use patsubst
or the OBJS = $(SRCS:.c=.o)
grammar to to string substitute
Look for more Makefile templates here: 几个Makefile通用模板分享!-面包板社区
Introduction of how to use gcc and make: Gcc tutorial GCC and Make - A Tutorial on how to compile, link and build C/C++ applications
CMake is a buildsystem generator. It is used to control the software compilation process using simple platform and compiler independent configuration files, and generate native makefiles and workspaces that can be used in the compiler envirionment of your choice.
If you have a cross-platform project, then CMake is a powerful tool to make your project buildsystem-independent as well.
The CMake process:
- Configure:
- Parse top level
CMakeLists.txt
- Generate cache variables
- Parse top level
- Generate Generate native build tool files
- Build Use native bulid tools to compile the sources
Refer to this tutorial for more information: An Introduction to Modern CMake · Modern CMake