[Linux] Make and CMake
Introduction
Linux offers various build tools.
In this post, I will discuss Make and CMake, two of the most widely used tools.
These are build automation tools commonly used in C projects.
While Automake is also frequently used for project packaging,
I have covered that in a separate post on packaging and will write more if needed.
Make
Installing make
make is a build automation tool that can be installed as follows:
$ sudo apt install build-essential # Debian
$ sudo dnf install make # Redhat
make Example
I will use a build example using the gcc compiler.
Ultimately, make executes the following command:
$ gcc -o app.out app.c
However, if most projects consisted of just one source file and one executable,
a build tool wouldn’t be necessary.
make allows you to organize numerous source files and complex build options into a single script.
The make command is executed by substituting it with commands based on the contents of the Makefile script.
Writing the gcc command as a Makefile looks like this:
main:
gcc -o app.out app.c
Here, the command line must be indented with a tab for the Makefile to recognize it as a command.
However, as you encounter various projects, you’ll realize that Makefiles are rarely this simple.
Here is an example of a file structure and a Makefile.
File-Tree:
.
|-bar.c
|-build/
|-foo.c
|-include/
|-Makefile
Makefile:
1 build_dir = $(PWD)/build
2 include_dirs = $(PWD)/include
3 srcs = $(wildcard ./*.c)
4 objs = $(patsubst %.c, $(build_dir)/%.o, $(srcs))
5
6 CC = gcc
7 CFLAGS = -g -I$(include_dirs)
8 LDFLAGS =
9 TARGET = main
10
11 # functions
12 all: $(TARGET)
13
14 $(TARGET): $(objs)
15 @$(CC) -o $@ $(objs) $(CFLAGS) $(LDFLAGS)
16
17 $(build_dir)/%.o: %.c
18 @mkdir -p $(shell dirname $@)
19 @$(CC) -c -o $@ $< $(CFLAGS) $(LDFLAGS)
20
21 clean:
22 @rm -rf $(build_dir) $(TARGET)
Let’s assume we have a project like this.
It might look complex, but it’s not difficult if you examine it piece by piece.
In reality, there are many projects much more complex than this.
Lines 1–9 define the script’s variables.
Several Makefile functions are used in the variable definition section.
The wildcard on line 3 means using Linux wildcards.
Linux wildcards include *, ?, \ , ^, etc.
Please refer to Linux wildcard for their respective meanings.
In the example, $(wildcard ./*.c) is used,
which targets all files ending in .c, following the Linux wildcard convention.
Given the file structure above, the srcs variable is defined as follows:
srcs = foo.c bar.c
Line 4 contains the following expression:
patsubst
Before discussing patsubst, let’s look at subst.
subst is short for substitution.
In other words, it replaces text.
The format is as follows:
$(subst from,to,target)
For example,
$(subst ple,PLE,apple)
The result is apPLE.
Now for patsubst.
patsubst stands for pattern substitution, meaning it replaces based on a pattern.
The format is the same, but it requires a pattern instead of plain text.
The meanings of * and % used in the example are identical.
According to the Makefile, all source files in the current directory are mapped to the build directory with their extension changed to
.o (this defines the variables; it doesn’t create the actual files yet).
Based on the srcs variable, the objs variable is defined as follows:
objs = $(patsubst %.c, $(build_dir)/%.o, foo.c bar.c)
objs = build/foo.o build/bar.o
The variable section can be simplified as follows:
1 build_dir = ./build
2 include_dirs = ./include
3 srcs = "foo.c bar.c"
4 objs = "build/foo.o build/bar.o"
5
6 CC = gcc
7 CFLAGS = -g -I./include
8 LDFLAGS =
9 TARGET = main
10
11 # functions
12 all: main
13
14 main: build/foo.o build/bar.o
15 @gcc -o $@ build/foo.o build/bar.o -g -I./include
16
17 build/foo.o build/bar.o: %.c
18 @mkdir -p $(shell dirname $@)
19 @gcc -c -o $@ $< -g -I./include
20
21 clean:
22 @rm -rf ./build main
Now for the command section.
all: main ,
main: build/foo.o build/bar.o ,
build/foo.o build/bar.o: %.c ,
The A:B format in the command section represents Target:Dependency.
On line 12, all represents the entire command to be executed in the Makefile ($ make all) and requires main.
The main target called by all is defined on line 14.
main depends on build/foo.o and build/bar.o.
build/foo.o and build/bar.o are defined on line 16.
First, the @ symbol at the beginning of each command means the command itself won’t be echoed to the terminal.
The $@ used on lines 15, 18, and 19 refers to the Target A in the A:B relationship,
and $«/span> on line 19 refers to the Dependency B.
To summarize once more:
1 build_dir = ./build
2 include_dirs = ./include
3 srcs = "foo.c bar.c"
4 objs = "build/foo.o build/bar.o"
5
6 CC = gcc
7 CFLAGS = -g -I./include
8 LDFLAGS =
9 TARGET = main
10
11 # functions
12 all: main
13
14 main: build/foo.o build/bar.o
15 @gcc -o main build/foo.o build/bar.o -g -I./include
16
17 build/foo.o build/bar.o: %.c
18 @mkdir -p build
19 @gcc -c -o build/foo.o build/bar.o foo.c bar.c -g -I./include
20
21 clean:
22 @rm -rf ./build main
dirname extracts the directory name, so it was substituted with build.
The Makefile is executed in the following order:
make -> make all -> main -> build/foo.o build/bar.o -> mkdir -p build -> gcc -c -o build/foo.o build/bar.o foo.c foo.o -g -I ./include -> gcc -o main build/foo.o build/bar.o -g -I ./include
CMake
Like make, CMake is a build automation tool.
It is much more intuitive and easier to write than make.
Regular updates provide a diverse and powerful API.
CMake scripts are written in CMakeLists.txt.
Installing CMake
Install cmake using the following command:
$ sudo apt install cmake # Debian
$ sudo yum install cmake # Redhat
CMake Example
I will use the same project structure and compiler (gcc) as in the make example.
File-Tree:
.
|-bar.c
|-build/
|-foo.c
|-include/
|-CMakeLists.txt
CMakeLists.txt:
1 cmake_minimum_required(VERSION 3.5)
2 project(projectname)
3
4 # set vars
5 set(SRC_FILES foo.c bar.c)
6
7 # print
8 message(${CMAKE_PROJECT_NAME})
9 message(${SRC_FILES})
10
11 # global options
12 add_compile_options(-g -Wall ... )
13 add_definitions(-DFLASH -DMACRO ...)
14 include_directories(include) # like compile option '-I'
15 link_directories(...) # like compile option '-L'
16 link_libraries(opencv rga samba ...) # like compile option '-l' (libopencv.so / librga.so / libsamba.so) ...
17
18 # target options
19 add_excutable(app.out ${SRC_FILES})
10 add_library(test [STATIC|SHARED|MODULE] foo.c bar.c) # output: libtest.a / libtest.so / test.cmake
21 add_dependencies(flash app.out) # "Target-to-Target" dependency. 'flash' <- 'app.out'
22
23 # specific target options
24 target_compile_options(app.out PUBLIC -g -wall)
25 target_conpile_definitions(app.out PUBLIC -DDEVMEM)
26 target_include_directories(app.out PUBLIC include driver/include)
27 target_link_libraries(app.out test) # use "-static" only use archive
28
29 # install
30 install(TARGETS app.out
31 RUNTIME_DESTINATION /usr/local/bin
32 LIBRARY_DESTINATION /usr/local/lib
33 ARCHIVE_DESTINATION /usr/share/app
34 )
Unlike Makefile, which requires writing shell commands directly, CMake has its own API.
The global options identified in the comments apply to the entire project,
while target options are limited to the target specified in the parameters.
Commonly Used CMake Variables
Below are some frequently used variables in CMakeLists.txt:
CMAKE_VERBOSE_MAKEFILE value: true/false
true: Prints build logs.
false: Does not print build logs.
CMAKE_BUILD_TYPE value: Release/Debug/RelWithDebinfo/MinSizeRel
CMAKE_C_FLAGS_{CMAKE_BUILD_TYPE} value: cflags
e.g.
set(CMAKE_C_FLAGS_RELEASE "-DCONFIG_RELEASE -03")
CMAKE_EXE_LINKER_FLAGS_{CMAKE_BUILD_TYPE} value: lflags
e.g.
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "-Wl,-whole-archive")
DESTINATION value: path default: ${CMAKE_INSTALL_PREFIX}
… More will be added.
Leave a comment