目录
前言
以下内容为我在工作中使用cmake的一些理解及经验分享,原文为个人对cmake的一些使用笔记,目前对其进行一些整理。从背景、工作方式、特点、应用情况以及常见的几个用法,这几个维度去分享cmake的一些经验。由于文章为较早之前的总结文档,若有参考到其他一些文章的内容,请联系我添加相关链接。
出现背景
-
编译C/C++有好几种 Make 工具,例如 GNU Make ,QT 的 qmake ,微软的 MS nmake,BSD Make(pmake),Makepp,等等。这些 Make 工具遵循着不同的规范和标准,所执行的 Makefile 格式也千差万别。
-
问题:如果软件想跨平台,必须要保证能够在不同平台编译。而如果使用上面的 Make 工具,就得为每一种标准写一次 Makefile ,这将是一个灾难。
-
CMake就是针对上面问题所设计的工具,提供统一的工程构建规范,再进行Makefile的构建。(除了cmake,像AutoConfig也是常见的工程构建工具,比如常见到的执行configure文件进行工程构建)
工作方式
-
首先开发者编写一种平台无关的
CMakeList.txt
文件来定制整个编译流程 -
然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。
特点
-
开放源代码, 使用类 BSD 许可发布。
-
跨平台, 并可生成 native 编译配置文件, 在 Linux/Unix 平台, 生成 makefile, 在苹果平台, 可以生成 xcode, 在 Windows 平台, 可以生成 MSVC 的工程文件。
-
能够管理大型项目, KDE4 就是最好的证明。
-
简化编译构建过程和编译过程。 CMake 的工具链非常简单: cmake+make。
-
可扩展, 可以为 cmake 编写特定功能的模块, 扩充 cmake 功能。
应用情况
-
CMake 是一个比make 更高级的编译配置工具,统一了构建方式。
-
使用 CMake 作为项目架构系统的知名开源项目有 VTK、ITK、KDE、OpenCV、OSG 等,包括OpenGL的开源项目glfw。
编辑器推荐
- CLion,对cmake工程有较为不错的支持,同时跨平台,配置可多平台共用(个人常用,主要使用系统为ubuntu)。
- Visual Studio,debug功能非常强悍,可通过交叉编译解决跨平台问题,适用于使用windows开发的场景。
- Visual Studio Code,文本编辑器的基本优点均有,可配置性高,主要还是启动快,快速完成编译,可搭配CLion与vs一起使用。缺点是跳转略差。
个人见解
由于目前工作内容,难免会使用到嵌入式的编译工具链,交叉编译就成为了C++工程构建中必须考虑的点。事实上,目前的C/C++工程构建工具还是挺多的,选择cmake,主要是考虑到易用性、跨平台这两大方面,同时业内使用的较多,整体支持也不错。我这边也有参与基于docker的交叉编译工具开发,因此,对统一build(构建)工具的重要性感受颇深。
在编译一些第三方库时,有使用统一构建工具的,能带来极大的编译,目前很多开源库都逐步开始支持cmake编译,比如curl。也有一些使用了其他构建工具,如AutoConfig。也有不少只使用GNU Make,一个Makefile文件,拥有较高的灵活度,传入参数也不够稳定,因此,只能针对不同库编写不同的脚本,没法做到统一的交叉编译。
总的来说,cmake是目前较为流行C/C++构建工具,提供了不少优秀的特性,可以在工程构建中带来极大的便利,也非常推荐使用。个人习惯来说:
- 单文件,不想打开IDE,只想做些简单的验证,一般可以直接考虑使用g++/gcc
- 多文件构建工程,一般直接使用cmake了,很少会考虑直接编写makefile
基本用法
这边同时总结了一些cmake的基本用法,可供参考,更多细节直接百度/google一下,或者直接查看官网文档。
注意:
项目名称_SOURCE_DIR指代工程目录,如/home/myname/mycpp/
项目名称_BINARY_DIR指代编译路径,如果使用内部构建(在项目根目录使用cmake构建),这项目名称一样;如果使用外部构建(新建一个build目录,使用cmake ..构建),则/home/myname/mycpp/build/
查看细节可以是make VERBOSE=1去编译
CMakelists.txt如果做了修改,最好是清空build路径再进行重新cmake
project
声明项目名称为mycpp
project(mycpp)
使用项目名称
${PROJECT_NAME}
链接:https://cmake.org/cmake/help/latest/command/project.html
project(<PROJECT-NAME> [<language-name>...])
project(<PROJECT-NAME>
[VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
[DESCRIPTION <project-description-string>]
[HOMEPAGE_URL <url-string>]
[LANGUAGES <language-name>...])
add_library
添加生成一个库, SHARED动态库,STATIC静态库,MODULE在使用dyld的系统有效,否则当做为SHARED对待
ADD_LIBRARY(libname # 库名,不需要写后缀
[SHARED|STATIC|MODULE] # 库类型
[EXCLUDE_FROM_ALL] # 这个库不会被默认构建,除非有其他的组件依赖或者手工构建
source1 source2 ... sourceN) # 依赖的cpp文件
添加一个动态库编译,会生成一个login_shared.so文件,但是编译过程中不会去检查引用错误,只检查基本的语法错误
add_library(login_shared SHARED ./network/Login.cpp)
添加一个静态库编译,会生成一个login_shared.a文件
add_library(login_shared STATIC ./network/Login.cpp)
SET_TARGET_PROPERTIES可以生成一个名字相同的动态库和静态库,同时project可以指定版本号
add_executable
第一个参数为生成的可执行文件名称,可以随意定义,例子中使用了项目名称
add_executable(${PROJECT_NAME}
src/main.cpp
src/network/Login.cpp
)
如果不想每次都手动添加cpp文件,可借助file指令进行操作
file(GLOB_RECURSE PROJECT_SOURCES ${PROJECT_SOURCE_DIR}/src "*.cpp")
add_executable(${PROJECT_NAME} ${PROJECT_SOURCES})
add_subdirectory
把src编译出来的二进制文件放到bin目录中,如果在build里面cmake ..,则可执行文件中在build/bin中
ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
add_subdirectory(src bin)
include_directories
添加头文件目录,如CMakelists.txt同一目录下的include文件夹
include_directories(
./include
)
install
添加安装的目标路径
install(TARGETS ${PROJECT_NAME}
DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/output
)
参数中的 TARGETS 后面跟的就是我们通过 ADD_EXECUTABLE 或者 ADD_LIBRARY 定义的目标文件,可能是可执行二进制、动态库、静态库。
目标类型也就相对应的有三种,ARCHIVE 特指静态库,LIBRARY 特指动态库,RUNTIME特指可执行目标二进制。
INSTALL(TARGETS targets...
[[ARCHIVE|LIBRARY|RUNTIME]
[DESTINATION <dir>]
[PERMISSIONS permissions...]
[CONFIGURATIONS
[Debug|Release|...]]
[COMPONENT <component>]
[OPTIONAL]
] [...]
)
如
INSTALL(TARGETS myrun mylib mystaticlib
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION libstatic
)
安装
make DESTDIR=/install/directory install
添加第三方库
以curl库和自己写的一个简单的hello库,输出一个hello world添加为例子
首先把头文件目录添加进来,可以直接使用具体路径
include_directories(
${CURL_INCLUDE_DIR}
/src/local/hello # 该目录下有hello.h
)
链接第三方库,特别注意:target的可执行文件/库,其add_executable指令必须在同一个文件,报错
target_link_libraries(<target> ... <item>... ...)
add_executable(
${PROJECT_NAME}
main.cpp
network/Login.cpp
)
target_link_libraries(${PROJECT_NAME} curl hello) # 不写后缀默认为so动态库,静态库添加.a后缀即可
find_library查找yaml-cpp和curl库,并分别赋值给YAML_LIBRARY和CURL_LIBRARY
find_library(YAML_LIBRARY yaml-cpp)
find_library(CURL_LIBRARY curl)
target_link_libraries(${PROJECT_NAME} ${CURL_LIBRARY} ${YAML_LIBRARY})
target_link_libraries主要是链接库的作用,因此如果是编译库的话,不一定需要使用,一般是在编译可执行文件的路径中使用,将其链接进可执行文件中。
添加系统库直接使用target_link_libraries写库名就可以,不需要find_library
target_link_libraries(
${PROJECT_NAME}
pthread
rt
)
添加自定义路径第三方库/自己的库,如在当前目录下的lib目前有自己要用的库文件
find_library(IOT_SDK_LIB NAME iot_sdk PATHS ${CMAKE_CURRENT_SOURCE_DIR}/lib)
find_library(IOT_HAL_LIB NAME iot_hal P正常模式切换到光强模式,有一定概率出现,2s时间后频率依然不够稳定(雷达本身)
雷达接收到启动指令时,有较低几率出现,雷达没有转,却发送了启动成功的指令(雷达本身)ATHS ${CMAKE_CURRENT_SOURCE_DIR}/lib)
find_library(IOT_TLS_LIB NAME iot_tls PATHS ${CMAKE_CURRENT_SOURCE_DIR}/lib)
target_link_libraries(
${PROJECT_NAME}
${IOT_SDK_LIB}
${IOT_HAL_LIB}
${IOT_TLS_LIB}
pthreadW
rt
)
注意:
-
不在同一个目录的话,cmake的时候就会报错
如果target_link_libraries编写有问题的话,会报出大量引用错误
option
-
编译脚本传入参数 传入一个cmake option TEST_DEBUG
cmake -DTEST_DEBUG=ON .
cmake --build .
-
CMake脚本接收option cmake 脚本定义TEST_DEBUG 默认关闭OFF
project(test)
option(TEST_DEBUG "option for debug" OFF)
if (TEST_DEBUG)
add_definitions(-DTEST_DEBUG)
endif()
...