CMakeLists.txt写法

完整样例

一份CMakeLists.txt完整的样例和解释说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
cmake_minimum_required(VERSION 3.13)
project(ust_demo CXX C)

# 通过上面的PROJECT设置后自动就有了PROJECT_NAME变量
message(STATUS PROJECT_NAME=${PROJECT_NAME})
# 指向顶级CMakeLists.txt所在绝对路径
message(STATUS CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR})
# CMAKE_CURRENT_SOURCE_DIR变量是指向当前CMakeLists.txt所在的绝对路径
message(STATUS CMAKE_CURRENT_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR})
# 指向最后一次调用project命令时CMakeLists.txt所在的绝对路径
message(STATUS PROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR})
# 只要单个CMakeLists.txt时上面三个变量没有不同,当有多级目录多个CMakeLists.txt通过add_subdirectory相互包含时会有区别

# 在cmake命令中加参数 -DCMAKE_BUILD_TYPE=Debug 来指定编译Debug版本,否则默认编译Release版本
IF (CMAKE_BUILD_TYPE MATCHES "Debug" OR CMAKE_BUILD_TYPE MATCHES "DEBUG")
set(CMAKE_BUILD_TYPE "Debug")
# 指定可执行文件输出路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/out/Debug)
file(MAKE_DIRECTORY ${EXECUTABLE_OUTPUT_PATH})
ELSE ()
set(CMAKE_BUILD_TYPE "Release")
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/out/Release)
file(MAKE_DIRECTORY ${EXECUTABLE_OUTPUT_PATH})
ENDIF ()
# 指定存放动态链接库的路径
# 生成可执行文件后可以把out文件夹下的文件整体复制到其他环境下运行,为了保证动态链接库能找到需要做两件事:
# 1.把.so文件复制到out/libs目录下
# 2.链接时通过-rpath选项指定运行时查找链接库的路径(这一步如果不指定也可以在运行前通过LD_LIBRARY_PATH环境变量指定)
set(LIBRARY_OUTPUT_PATH ${EXECUTABLE_OUTPUT_PATH}/libs)
file(MAKE_DIRECTORY ${LIBRARY_OUTPUT_PATH})

# 设置编译选项
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17")

set(CMAKE_C_FLAGS_DEBUG " -std=c99 -g -ggdb -O0 -Wall -Wno-unused-function -fpic -fPIC -D_DEBUG")
set(CMAKE_CXX_FLAGS_DEBUG " -std=c++17 -g -ggdb -O0 -Wall -Wno-unused-function -fpic -fPIC -D_DEBUG")

set(CMAKE_C_FLAGS_RELEASE " -std=c99 -O3 -Wall -Wno-unused-function -Wno-unused-result -Wno-unknown-pragmas -Wno-format-security -fpic -fPIC")
set(CMAKE_CXX_FLAGS_RELEASE " -std=c++17 -O3 -Wall -Wno-unused-function -Wno-unused-result -Wno-unknown-pragmas -Wno-format-security -fpic -fPIC")

# 添加头文件目录
set(INCLUDE_DIR ./include)
include_directories(${INCLUDE_DIR})

# 添加库文件目录
set(LIB_DIR lib/linux.x64)
link_directories(${LIB_DIR})

# 将src目录下所有源文件加入 SRC_LIST
aux_source_directory(./src SRC_LIST)
# # 也可以通过 FILE(GLOB_RECURSE <var> <parterns>) 把匹配指定模式的文件加入变量中
# 区别是这样同时会查找src子目录下的.cpp和.c文件
# file(GLOB_RECURSE SRC_LIST "src/*.cpp" "src/*.c")
# # 也可以通过set明确地逐个文件加入源文件列表
# set(SRC_LIST
# src/main.cpp
# )

# 指定生成可执行文件要编译的源文件
add_executable(${PROJECT_NAME}
${SRC_LIST}
)

# 指定生成.so动态库要编译的源文件
#add_library(${PROJECT_NAME} SHARED
# ${SRC_LIST}
# )
# 指定生成.a静态库要编译的源文件
#add_library(${PROJECT_NAME}
# ${SRC_LIST}
# )

# 将指定的.so文件的绝对路径加入 LIB_LIST
# file(GLOB_RECURSE LIB_LIST "${LIB_DIR}/*.so")

# 同样也可以通过set明确地逐个文件加入库文件列表
set(LIB_LIST
libHSSecuTradeApi.so
t2sdk
)

file(COPY ${LIB_DIR}/libHSSecuTradeApi.so DESTINATION ${LIBRARY_OUTPUT_PATH})
file(COPY ${LIB_DIR}/libt2sdk.so DESTINATION ${LIBRARY_OUTPUT_PATH})

# 指定指定生成PROJECT_NAME要连接的动态链接库
# 这里链接库的路径可以是绝对路径
# 也可以只写文件名将在通过link_directories指定的包含目录以及环境变量LIBRARY_PATH指定的目录下去查找
# 名为libxxx.so的文件可以只写xxx
target_link_libraries(${PROJECT_NAME}
${LIB_LIST}
pthread
-Wl,-rpath,'$ORIGIN'/libs
)
# -Wl 的意思是把后面参数传给链接器,多个参数用逗号分割,这里实际传给链接器的参数是 -rpath '$ORIGIN'/libs
# 其中$ORIGIN表示运行时可执行文件的位置,'$ORIGIN'/libs也就是可执行文件同目录的libs文件夹。
# 不能指定成./libs因为这会被误认为是链接时的相对路径,而不是运行时的。

# 指定PROJECT_NAME需要用到的头文件路径
# 只在目标是库文件时有意义,此项目是一个子项目时,其他项目链接这个项目生成的库时会自动添加此处指定的INCLUDE路径
target_include_directories(${PROJECT_NAME} PUBLIC
include
../include
)

# 指定PROJECT_NAME需要用到的库文件路径
# 只在目标是库文件时有意义,此项目是一个子项目时,其他项目链接这个项目生成的库时会自动添加此处指定的LINK路径(需要CMake 3.13)
target_link_directories(${PROJECT_NAME} PUBLIC ${LIB_DIR})

#file(MAKE_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)
## 指定可执行文件输出路径
#set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
#file(MAKE_DIRECTORY ${PROJECT_SOURCE_DIR}/bin/libs)
## 指定库文件输出路径
#set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin/libs)

## 递归处理含子文件下的CMakeLists.txt,第二个参数是指定其中间文件路径相对当前文件的路径,如果不指定则会把中间文件生成在子文件夹下
#ADD_SUBDIRECTORY(subproj subproj)

简洁模板

简洁版,可以基于这个改写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
cmake_minimum_required(VERSION 3.10)
project(demo CXX C)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17")

set(CMAKE_C_FLAGS_DEBUG " -std=c99 -g -ggdb -O0 -Wall -Wno-unused-function -fpic -fPIC -D_DEBUG")
set(CMAKE_CXX_FLAGS_DEBUG " -std=c++17 -g -ggdb -O0 -Wall -Wno-unused-function -fpic -fPIC -D_DEBUG")

set(CMAKE_C_FLAGS_RELEASE " -std=c99 -O3 -Wall -Wno-unused-function -fpic -fPIC")
set(CMAKE_CXX_FLAGS_RELEASE " -std=c++17 -O3 -Wall -Wno-unused-function -fpic -fPIC")

include_directories(
include
)
link_directories(
lib
)

aux_source_directory(src SRC_LIST)
add_executable(${PROJECT_NAME}
${SRC_LIST}
)

target_link_libraries(${PROJECT_NAME}
pthread
)

使用方式

cmake命令生成Makefile时会生成一些中间文件和一些缓存文件,我一般会在根CMakefile.txt的同级目录下新建build目录用来构建,通常的步骤是

1
2
3
4
mkdir build
cd build
cmake ..
make

cmake生成Makefile时是增量处理的,如果想重新生成,需要先删除CMakeCache.txt

make clean可以清除目标文件以保证再次make时是重新生成的。

在Windows上如果生成想用minGW的Makefile需要使用-G"Unix Makefiles"参数,即

1
2
cmake -G"Unix Makefiles" ..
make

记得用minGW/bin/mingw32-make.exe 复制一份make.exe,并把minGW/bin添加到PATH

构建后自动复制文件

有时候我们想将依赖的头文件(构建.so时需要依赖)或库文件复制到指定路径

可以使用如下命令

1
2
3
4
5
# 复制指定目录下的文件到指定的输出目录(包含子目录)
add_custom_command(TARGET <项目名> POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
<源路径> <目标路径>
)
1
2
3
4
5
6
7
8
9
# 复制指定目录下的*.h到指定的输出目录(不包含子目录)
file(GLOB HEADER_FILES "${CMAKE_CURRENT_SOURCE_DIR}/include/*.h")
foreach(HEADER_FILE ${HEADER_FILES})
add_custom_command(TARGET amaquote POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${HEADER_FILE}
${OUTPUT_PATH}/include
)
endforeach()

执行Shell命令并保存输出结果到变量中

1
2
3
4
5
EXECUTE_PROCESS(COMMAND <Shell命令>
TIMEOUT 5
OUTPUT_VARIABLE <变量名>
OUTPUT_STRIP_TRAILING_WHITESPACE
)

样例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
EXECUTE_PROCESS(COMMAND sh ${CMAKE_SOURCE_DIR}/is_abi_gcc4.sh
TIMEOUT 5
OUTPUT_VARIABLE IS_ABI_GCC4
OUTPUT_STRIP_TRAILING_WHITESPACE
)
IF (IS_ABI_GCC4 STREQUAL "1")
message(STATUS "Use gcc4-compatible libs")
SET(ABI_VERSION gcc4-compatible)
SET(LIB_DIR utils/lib/linux.x64/abi-gcc4-compatible)
ELSE()
message(STATUS "Use cxx11 libs")
SET(ABI_VERSION cxx11)
SET(LIB_DIR utils/lib/linux.x64/abi-cxx11)
ENDIF ()

写在COMMAND后面的命令支持带参数但不支持管道,如果需要用到管道可以写在外部的.sh中再进行调用

1
2
# is_abi_gcc4.sh
g++ -v -x 2>&1|grep gcc4-compatible|wc -l