CMake构建C++项目

使用cmake构建c++项目,学习过程的笔记。

工程主目录

cmake最低版本信息

1
cmake_minimum_required(VERSION 3.0)

设置项目信息

1
2
3
4
5
6
7
8
#设置变量
set(PACKAGE_NAME "MyProject")
set(PACKAGE_VERSION "0.0.1")
set(PACKAGE_STRING "${PACKAGE_NAME} ${PACKAGE_VERSION}")
set(PACKAGE_TARNAME "${PACKAGE_NAME}-${PACKAGE_VERSION}")
#项目名称 语言
Project(${PACKAGE_NAME} CXX)

检查OS信息

cmake会定义几个变量,可以检查os信息

1
2
3
4
5
6
7
8
9
10
#检查平台 APPLE WIN32 UNIX 等等
if(APPLE)
message(STATUS "OSX System")
endif()
if(WIN32)
message(STATUS "Windows System")
endif()
if(UNIX)
message(STATUS "UNIX System")
endif()

检查32位平台还是64位平台

CMAKE_SIZEOF_VOID_P 表示 void* 的大小,4表示32位 8表示64位

1
2
3
4
5
6
#检查64位还是32为平台
if (CMAKE_SIZEOF_VOID_P EQUAL 8)
message(STATUS "64bit OS")
else()
message(STATUS "32bit OS")
endif()

查找第三方库

cmake自己包含很多find模块,用来查找第三方库

mac下在目录/usr/local/Cellar/cmake/3.8.1/share/cmake/Modules

例如使用boost库,FindBoost.cmake调用

1
2
#REQUIRED 找不到会报错
find_package(Boost 1.64 MODULE REQUIRED)

自己编写find模块

例如我们在项目目录下有一个文件夹cmake,所有的模块,包括自定义函数都放在这个目录下。我们有一个自己的库,名字叫test

头文件 myProject/deps/LTest/include

库文件 myProject/deps/LTest/lib

1
2
3
4
5
6
7
#包含自定义的模块,将目录添加到变量CMAKE_MODULE_PATH中即可
#CMAKE_CURRENT_SOURCE_DIR 表示当前CMakeLists.txt文件所在的目录
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake" ${CMAKE_MODULE_PATH})
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
#查找Glog模块
find_package(LTest MODULE REQUIRED)

编写FindLTest.cmake

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#头文件路径下又一个lib_test.h文件
find_path(LIB_TEST_INCLUDE_DIR lib_test.h deps/LTest/include)
#lib的名字为lib_test
find_library(LIB_TEST_LIBRARY NAMES lib_test deps/LTest/lib)
#检查查找路径是否正确
include(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(
LIB_TEST DEFAULT_MSG
LIB_TEST_LIBRARY
LIB_TEST_INCLUDE_DIR
)
if(NOT LIB_TEST_FOUND)
message(STATUS "not fount lib_test")
else()
message(STATUS "fount lib_test : ${LIB_TEST_LIBRARY}")
endif(NOT LIB_TEST_FOUND)
mark_as_advanced(LIB_TEST_INCLUDE_DIR LIB_TEST_LIBRARY)

自动添加路径下源文件

单目录下查找

如果只是添加单目录下的源文件,不递归的查询子目录下的文件,可以直接使用CMake的函数aux_source_directory

添加myProject/src下的源文件 保存到变量SrcList

1
2
#添加myProject/src下的源文件
aux_source_dirctory(${PROJECT_SOURCE_DIR}/src SrcList)

目录递归查找

如果需要递归的查询文件目录及其子目录下的源文件,就需要自己编写函数

我们编写一个auto_add_sources的函数,自动添加目录下匹配的源文件

auto_add_sources函数放在myProject/CMake/CMakeFunction.cmake

编写CMakeFunction.cmake
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
#函数名 auto_add_sources
#RETURN_VALUE 返回值,要保存列表的名字
#PATTERN 匹配模式 可以是 *.cpp 或者 *.c 等等
#SOURCE_SUBDIRS 参数:是否递归“RECURSE”,要查找的目录
function(auto_add_sources RETURN_VALUE PATTERN SOURCE_SUBDIRS)
if ("${SOURCE_SUBDIRS}" STREQUAL "RECURSE")
SET(PATH ".")
#ARGC 参数个数
if (${ARGC} EQUAL 4)
#将第4个参数设置 path
list(GET ARGV 3 PATH)
message("search source files in ${PATH}")
endif ()
endif()
#如果递归
if ("${SOURCE_SUBDIRS}" STREQUAL "RECURSE")
unset(${RETURN_VALUE})
#查找所有文件,没有递归
file(GLOB SUBDIR_FILES "${PATH}/${PATTERN}")
message("search source files in ${SUBDIR_FILES}")
#查找结果添加到返回值中
list(APPEND ${RETURN_VALUE} ${SUBDIR_FILES})
#搜索子目录
file(GLOB subdirs RELATIVE ${PATH} ${PATH}/*)
#遍历子目录文件夹
foreach(DIR ${subdirs})
#如果是文件夹,继续遍历
if (IS_DIRECTORY ${PATH}/${DIR})
#排除CMakeFiles文件夹
if (NOT "${DIR}" STREQUAL "CMakeFiles")
#查找子目录所有文件,递归
file(GLOB_RECURSE SUBDIR_FILES "${PATH}/${DIR}/${PATTERN}")
message("recurse search source files in ${PATH}/${DIR}")
#保存结果到返回值
list(APPEND ${RETURN_VALUE} ${SUBDIR_FILES})
endif()
endif()
endforeach()
else()
#如果不递归
file(GLOB ${RETURN_VALUE} "${PATTERN}")
foreach (PATH ${SOURCE_SUBDIRS})
file(GLOB SUBDIR_FILES "${PATH}/${PATTERN}")
list(APPEND ${RETURN_VALUE} ${SUBDIR_FILES})
endforeach()
endif ()
#返回结果,PARENT_SCOPE 使变量 父作用域可见
set(${RETURN_VALUE} ${${RETURN_VALUE}} PARENT_SCOPE)
endfunction(auto_add_sources)
编写MyProject/CMakeLists.txt
1
2
3
4
5
#前面已经设置过CMAKE_MODULE_PATH,如果没有设置需要添加目录到CMAKE_MODULE_PATH
##set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake" ${CMAKE_MODULE_PATH})
##include_directories(${CMAKE_CURRENT_SOURCE_DIR})
include(CMakeFunction)
auto_add_sources(SrcLists "*.cpp" "RECURSE" "${PRJECT_SOURCE_DIR}/src")

预定义设置

如果代码有通用的预定义设置,可以通过cmake设置给源码,使用configure_file

1
2
3
4
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/CMake/pre-config.h.cmake
${CMAKE_CURRENT_BINARY_DIR}/config/pre-config.h
)

编译文件

利用auto_add_sources查找源文件,并编译

1
2
3
4
5
6
#查找源文件
auto_add_sources(SrcList "*.cpp" "RECURSE" ${PROJECT_SOURCE_DIR}/src)
#查找头文件
auto_add_sources(HeaderList "*.h" "RECURSE" ${PROJECT_SOURCE_DIR}/include)
#编译文件
add_library(myProject STATIC ${SrcList} ${HeaderList})

链接库

链接库需要两步

  • 包含链接库的头文件
  • 链接
1
2
3
4
5
6
target_include_directories(myProject PUBLIC
${LIB_TEST_INCLUDE_DIR}
)
target_link_libraries(myProject PUBLIC
${LIB_TEST_LIBRARY}
)

生成可执行文件

1
2
add_executable(testMyProject main.cpp)
target_link_libraries(testMyProject myProject)

添加自定义命令

在生成文件过程中,或之后,可以添加自定义命令。

例如拷贝生成文件,执行结果等等

1
2
3
# 添加自定义命令,编译完成后,运行测试
add_custom_command(
)

添加编译选项

GCC常用的编译选项

  • -o 指定生成的输出文件,缺省为a.out
  • -E 仅编译预处理,可以重定向另一个文件
  • -S 编译为汇编文件
  • -C 预编译生成obj文件
  • -Wall 打开所有警告
  • -L 指定搜索程序库的目录
  • -WL,option 传递给链接器参数
  • -O0 优化选项0~4,缺省为-O1,最高为-O3
  • -g 生成调式信息
  • -std=c++11/c++0X
  • -WL,-rpath,dir 添加依赖路径

使用CMAKE_CXX_FLAGS

这个只对C++编译有效

同理CMAKE_C_FLAGS只对C有效

1
set(CMAKE_CXX_FLAGS "-std=c++0x -wall")

使用target_compile_options

只对指定的target有效,但是不限定语言,如果限定c++

1
2
3
target_compile_options(myTarget PUBLIC
$<$<COMPILE_LANGUAGE:CXX>:-std=c++0x
)

其他方法

add_definitions

add_compile_options

target_compile_options的定制型最强

DEBUG参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
target_compile_options(myTarget PUBLIC
$<$<CONFIG:DEBUG>:
#O0优化 默认O1
$<$<BOOL:${ENABLE_DEBUG_INLINING}>:-O0>
-g # 生成调试信息
-Wall # 编译器警告
-Wextra
-pedantic # main函数的返回类型无效
-Wwrite-strings # 字符串常量到char*的转换
-Wcomment # 注解嵌套
-Wunused # 声明的变量没有使用
-Wuninitialized # 自动变量没有初始化
-Werror
>
)

RELEASE参数

1
2
3
4
5
6
target_compile_options(myTarget PUBLIC
$<$<CONFIG:RELEASE>:
#O2优化
$<$<BOOL:${NABLE_DEBUG_INLINING}>:-O2>
>
)

安装库文件

编译后可以使用install,将生成的库文件安装到指定位置。

install使用比较简单

1
2
3
4
install(TARGETS myExec mySharedLib myStaticLib
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib/static)

将会把myExec安装到/bin目录下,把myStaticLib安装到/lib/static目录下

1
2
3
4
5
6
7
8
9
10
11
12
13
# FILES版本的install命令
install(FILES files... DESTINATION <dir>
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[RENAME <name>] [OPTIONAL])
# PROGRAMS版本的install命令
install(PROGRAMS files... DESTINATION <dir>
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[RENAME <name>] [OPTIONAL])