CMakeLists.txt配置笔记
引言
CMake通过CMakeLists.txt配置项目的构建系统,配合使用cmake命令行工具生成构建系统并执行编译、测试,相比于手动编写构建系统(如Makefile)要高效许多。
1.基础配置
基础配置是对所在的需要编译的项目进行的说明,包括项目名及版本说明,编译器的设置,全局宏定义和include目录添加。
1.1.设置项目版本和生成version.h
首先,项目一般需要设置一个版本号方便进行版本的发布,也可以根据版本对问题或者特性进行追溯和记录。(如果只是个人使用,以后不会修改可以不添加版本号)
通过project
命令配置项目信息,如下:
project(CMakeTemplate VERSION 1.0.0 LANGUAGES C CXX)
第一个字段是项目名称,之后通过VERSION指定版本号,格式为major.minor.patch.tweak,并且CMake会将对应的值分别赋值给以下变量(如果没有设置,则为空字符串):
PROJECT_VERSION, <PROJECT-NAME>_VERSION
PROJECT_VERSION_MAJOR, <PROJECT-NAME>_VERSION_MAJOR
PROJECT_VERSION_MINOR, <PROJECT-NAME>_VERSION_MINOR
PROJECT_VERSION_PATCH, <PROJECT-NAME>_VERSION_PATCH
PROJECT_VERSION_TWEAK, <PROJECT-NAME>_VERSION_TWEAK
现在,可以使用configure_file
命令配置自动生成版本头文件version.h,将头文件版本号定义成对应的宏,或者定义成接口,方便在代码运行的时候了解当前的版本号。
示例:
configure_file(src/c/cmake_template_version.h.in "${PROJECT_SOURCE_DIR}/src/c/cmake_template_version.h")
如果cmake_template_version.h.in内容如下:
#define CMAKE_TEMPLATE_VERSION_MAJOR @CMakeTemplate_VERSION_MAJOR@
#define CMAKE_TEMPLATE_VERSION_MINOR @CMakeTemplate_VERSION_MINOR@
#define CMAKE_TEMPLATE_VERSION_PATCH @CMakeTemplate_VERSION_PATCH@
在使用cmake配置构建系统后,将会自动生成对应文件:cmake_template_version.h,这时@<var-name>@
将会被替换为对应的值:
#define CMAKE_TEMPLATE_VERSION_MAJOR 1 // @CMakeTemplate_VERSION_MAJOR@被替换为1
#define CMAKE_TEMPLATE_VERSION_MINOR 0
#define CMAKE_TEMPLATE_VERSION_PATCH 0
1.2.指定语言版本
如果在不同机器上进行编译,声明指定语言版本是一个好的选择,示例:
set(CMAKE_C_STANDARD 99)// C使用c99标准
set(CMAKE_CXX_STANDARD 11)// C++使用c++11标准
注:CMAKE_、_CMAKE或者以下划线开头后面加上任意CMake命令的变量名是CMAKE的内置变量,通过修改这些变量的值来配置CMake的构建
1.3.配置编译选项
使用add_compile_options
命令为所有编译器配置编译选项,同时对多个编译器生效,示例:
add_compile_options(-Wall -Wextra -pedantic -Werror)// 对所有编译器配置编译选项
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pipe -std=c99")// 设置变量CMAKE_C_FLAGS可以配置c编译器的编译选项
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe -std=c++11")// 设置变量CMAKE_CXX_FLAGS配置针对c++编译器的编译选项
1.4.配置编译类型
通过设置变量CMAKE_BUILD_TYPE
来配置编译类型,可设置为:Debug、Release等,比如:
set(CMAKE_BUILD_TYPE Debug)
同样可以在执行cmake命令时使用参数-D来指定:
cmake -B build -DCMAKE_BUILD_TYPE=Debug
1.4.1.Debug编译类型
设置编译类型为Debug
,则对于c编译器,CMake会检查是否有针对此编译类型的编译选项CMAKE_C_FLAGS_DEBUG
,如果有,则将它的配置内容加到CMAKE_C_FLAGS
中。
可以针对不同的编译类型设置不同的编译选项,示例:对于Debug
版本,开启调试信息,不进行代码优化
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g -O0")// c
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0")// c++
1.4.2.Release编译类型
对于Release
版本,不包含调试信息,同时优化等级设置为2,只用对上面示例进行对应修改:
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O2")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
1.5.添加全局宏定义
使用add_definitions
命令添加全局的宏定义,在源码中就可以通过判断不同的宏定义实现相应的代码逻辑。
add_definitions(-DDEBUG -DREAL_COOL_ENGINEER)
1.6.添加include目录
使用include_directories
来来设置头文件的搜索目录,可以指定路径,也可以使用当前二进制目录路径(二进制目录是指编译生成的可执行文件、库文件等输出文件所在的路径)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
include_directories(src/c)
2.编译目标文件
通常,编译目标(target)的类型一般有静态库、动态库和可执行文件,这时需要确定编译目标所需要的源文件和确定链接的时候需要依赖的额外的库。
以开源项目(cmake-template)来演示。项目的目录结构如下:
./cmake-template
├── CMakeLists.txt
├── src
│ └── c
│ ├── cmake_template_version.h
│ ├── cmake_template_version.h.in
│ ├── main.c
│ └── math
│ ├── add.c
│ ├── add.h
│ ├── minus.c
│ └── minus.h
└── test
└── c
├── test_add.c
└── test_minus.
项目的构建任务为:
1.将math目录编译成静态库,命名为math
2.编译main.c为可执行文件demo,依赖math静态库
3.编译test目录下的测试程序,可以通过命令执行所有的测试
4.支持通过命令将编译产物安装及打包
2.1.编译静态库
首先,需要将项目目录路径src/c/math下的源文件编译为静态库,这时要使用set
命令或者file
命令来进行设置获取编译此静态库需要的文件列表
file(GLOB_RECURSE MATH_LIB_SRC
src/c/math/*.c)
add_library(math STATIC ${MATH_LIB_SRC})
使用file
命令获取src/c/math目录下所有的*.c 文件,然后通过add_library
命令编译名为math的静态库,库的类型是第二个参数STATIC
指定的(如果指定为SHARED
则编译的就是动态链接库)。
2.2.编译可执行文件
使用add_executable
来往构建系统中添加一个可执行构建目标,同样需要指定编译需要的源文件。
但是对于可执行文件来说,有时候还会依赖其他的库,则需要使用target_link_libraries
命令来声明构建此可执行文件需要链接的库(例如涉及opencv,opengl时)。
在示例项目中,main.c就使用了src/c/math下实现的一些函数接口,所以依赖于前面构建的math静态库,可以在CMakeLists.txt中添加以下内容:
// 说明编译可执行文件demo需要的源文件(可以指定多个源文件,此处只是以单个文件作为示例)
add_executable(demo src/c/main.c)
// 表明对math库存在依赖。
target_link_libraries(demo math)
此时可以在项目的根目录下执行构建和编译命令,并执行demo:
➜ # cmake -B cmake-build
➜ # cmake --build cmake-build
➜ # ./cmake-build/demo
Hello CMake!
10 + 24 = 34
40 - 96 = -56
3.安装和打包
3.1.安装
指定当前项目在执行安装时,需要安装什么内容
1.通过install
命令来说明需要安装的内容及目标路径;
2.通过设置CMAKE_INSTALL_PREFIX
变量说明安装的路径;
3.CMake3.15往后的版本可以使用cmake --install --prefix <install-path>
覆盖指定安装路径。
示例,将math和demo两个目标按文件类型安装
install(TARGETS math demo
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
1.TARGETS
参数指定需要安装的目标列表
2.RUNTIME DESTINATION
、LIBRARY DESTINATION
、ARCHIVE DESTINATION
分别指定可执行文件、库文件、归档文件分别应该安装到安装目录下个哪个子目录。
这时,如果CMAKE_INSTALL_PREFIX
为/usr/local
,则math库安装在/usr/local/lib/
,同理demo可执行文件在/usr/local/bin
注:CMAKE_INSTALL_PREFIX在不同的系统上有不同的默认值,使用的时候最好显式指定路径。
同时,还可以使用install
命令安装头文件:
file(GLOB_RECURSE MATH_LIB_HEADERS src/c/math/*.h)
install(FILES ${MATH_LIB_HEADERS} DESTINATION include/math)
如果将安装到当前项目的output文件夹下,可以执行:
➜ # cmake -B cmake-build -DCMAKE_INSTALL_PREFIX=./output
➜ # cmake --build cmake-build
➜ # cd cmake-build && make install && cd -
Install the project...
-- Install configuration: ""
-- Installing: .../cmake-template/output/lib/libmath.a
-- Installing: .../gitee/cmake-template/output/bin/demo
-- Installing: .../gitee/cmake-template/output/include/math/add.h
-- Installing: .../gitee/cmake-template/output/include/math/minus.h
可以看到安装了前面install
命令指定要安装的文件,并且不同类型的目标文件安装到不同子目录。
3.2.打包
要使用打包功能,需要执行include(CPack)
启用相关的功能,在执行构建编译之后使用cpack
命令行工具进行打包安装;对于make
工具,也可以使用命令make package
打包的内容就是install
命令安装的内容,关键需要设置的变量有:
变量 | 说明 |
---|---|
CPACK_GENERATOR | 打包使用的压缩工具,比如"ZIP" |
CPACK_OUTPUT_FILE_PREFIX | 打包安装的路径前缀 |
CPACK_INSTALL_PREFIX | 打包压缩包的内部目录前缀 |
CPACK_PACKAGE_FILE_NAME | 打包压缩包的名称,由CPACK_PACKAGE_NAME、CPACK_PACKAGE_VERSION、CPACK_SYSTEM_NAME三部分构成 |
示例:
include(CPack)
set(CPACK_GENERATOR "ZIP")
set(CPACK_PACKAGE_NAME "CMakeTemplate")
set(CPACK_SET_DESTDIR ON)
set(CPACK_INSTALL_PREFIX "")
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
假如:
CPACK_OUTPUT_FILE_PREFIX
设置为/usr/local/package;
CPACK_INSTALL_PREFIX
设置为real/cool/engineer;
CPACK_PACKAGE_FILE_NAME
设置为CMakeTemplate-1.0.0;
那么执行打包文件的生成路径为:
/usr/local/package/CMakeTemplate-1.0.0.zip
解压这个包得到的目标文件则会位于路径下:
/usr/local/package/real/cool/engineer/
重新执行构建,使用cpack
命令执行打包:
➜ # cmake -B cmake-build -DCPACK_OUTPUT_FILE_PREFIX=`pwd`/output
➜ # cmake --build cmake-build
➜ # cd cmake-build && cpack && cd -
CPack: Create package using ZIP
CPack: Install projects
CPack: - Run preinstall target for: CMakeTemplate
CPack: - Install project: CMakeTemplate
CPack: Create package
CPack: - package: /Users/Farmer/gitee/cmake-template/output/CMakeTemplate-1.0.0-Darwin.zip generated.
cpack
有一些参数是可以覆盖CMakeLists.txt设置的参数的,比如这里的-G
参数就会覆盖变量CPACK_GENERATOR
,具体细节可使用cpack --help
查看。
4.测试
CMake的测试功能使用起来有几个步骤:
1.CMakeLists.txt中通过命令enable_testing()
或者include(CTest)
来启用测试功能;
2.使用add_test
命令添加测试样例,指定测试的名称和测试命令、参数;
3.构建编译完成后使用ctest
命令行工具运行测试。
为了控制是否开启测试,可使用option
命令设置一个开关,在开关打开时才进行测试,比如:
option(CMAKE_TEMPLATE_ENABLE_TEST "Whether to enable unit tests" ON)
if (CMAKE_TEMPLATE_ENABLE_TEST)
message(STATUS "Unit tests enabled")
enable_testing()
endif()
5.CMakeLists.txt示例
cmake_minimum_required(VERSION 3.12)
project(CMakeTemplate VERSION 1.0.0 LANGUAGES C CXX DESCRIPTION "A cmake template project")
##--------------------- Version file ---------------------------------------##
configure_file(src/c/cmake_template_version.h.in "${PROJECT_SOURCE_DIR}/src/c/cmake_template_version.h")
# Specified the language standard
set(CMAKE_C_STANDARD 99)
set(CMAKE_CXX_STANDARD 11)
##--------------------- Compile Options ------------------------------------##
# Configure compile options
add_compile_options(-Wall -Wextra -pedantic -Werror)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pipe -std=c99")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe -std=c++11")
# Set build type
# set(CMAKE_BUILD_TYPE Debug) # Use `cmake -DCMAKE_BUILD_TYPE=Debug` more better
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
# Compile options for Debug variant
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g -O0")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0")
# Compile options for Release variant
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O2")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
message(STATUS "Compile options for c: ${CMAKE_C_FLAGS}")
message(STATUS "Compile options for c++: ${CMAKE_CXX_FLAGS}")
##--------------------- Global Macros --------------------------------------##
add_definitions(-DDEBUG -DREAL_COOL_ENGINEER)
##--------------------- Include directories --------------------------------##
include_directories(src/c)
##--------------------- Source files ---------------------------------------##
file(GLOB_RECURSE MATH_LIB_SRC
src/c/*.c
)
##--------------------- Build target ---------------------------------------##
option(USE_IMPORTED_LIB "Use pre compiled lib" OFF)
if (USE_IMPORTED_LIB)
# add_library(math STATIC IMPORTED)
# set_property(TARGET math PROPERTY IMPORTED_LOCATION "./lib/libmath.a")
find_library(LIB_MATH_DEBUG mathd HINTS "./lib")
find_library(LIB_MATH_RELEASE math HINTS "./lib")
add_library(math STATIC IMPORTED GLOBAL)
set_target_properties(math PROPERTIES
IMPORTED_LOCATION "${LIB_MATH_RELEASE}"
IMPORTED_LOCATION_DEBUG "${LIB_MATH_DEBUG}"
IMPORTED_CONFIGURATIONS "RELEASE;DEBUG"
)
add_subdirectory(src/c/nn)
else()
# Build math lib
add_subdirectory(src/c/math)
add_subdirectory(src/c/nn)
endif()
# Merge library
if (APPLE)
set(MERGE_CMD libtool -static -o)
add_custom_command(OUTPUT libmerge.a
COMMAND libtool -static -o libmerge.a $<TARGET_FILE:math> $<TARGET_FILE:nn>
DEPENDS math nn)
else()
add_custom_command(OUTPUT libmerge.a
COMMAND ar crsT libmerge.a $<TARGET_FILE:math> $<TARGET_FILE:nn>
DEPENDS math nn)
endif()
add_custom_target(_merge ALL DEPENDS libmerge.a)
add_library(merge STATIC IMPORTED GLOBAL)
set_target_properties(merge PROPERTIES
IMPORTED_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/libmerge.a
)
# Build demo executable
add_executable(demo src/c/main.c)
target_link_libraries(demo PRIVATE merge)
##--------------------- Build unit tests -----------------------------------##
option(CMAKE_TEMPLATE_ENABLE_TEST "Whether to enable unit tests" ON)
if (CMAKE_TEMPLATE_ENABLE_TEST)
message(STATUS "Unit tests enabled")
enable_testing()
add_subdirectory(third_party/googletest-release-1.10.0 EXCLUDE_FROM_ALL)
include_directories(third_party/googletest-release-1.10.0/googletest/include)
add_executable(test_add test/c/test_add.cc)
add_executable(test_minus test/c/test_minus.cc)
add_executable(test_gtest_demo test/c/test_gtest_demo.cc)
target_link_libraries(test_add math gtest gtest_main)
target_link_libraries(test_minus math gtest gtest_main)
target_link_libraries(test_gtest_demo math gtest gtest_main)
add_test(NAME test_add COMMAND test_add)
add_test(NAME test_minus COMMAND test_minus)
add_test(NAME test_gtest_demo COMMAND test_gtest_demo)
endif()
##--------------------- Install and Package target -------------------------##
# Install
if (NOT USE_IMPORTED_LIB)
install(TARGETS math nn demo
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
PUBLIC_HEADER DESTINATION include)
file(GLOB_RECURSE MATH_LIB_HEADERS src/c/math/*.h)
install(FILES ${MATH_LIB_HEADERS} DESTINATION include/math)
endif()
# Package, These variables should set before including CPack module
set(CPACK_GENERATOR "ZIP")
set(CPACK_SET_DESTDIR ON) # 支持指定安装目录
set(CPACK_INSTALL_PREFIX "RealCoolEngineer")
include(CPack)
本文转载整理作为笔记记录,转载于:https://zhuanlan.zhihu.com/p/371257515