CMake 和 CMakeLists.txt 使用
文章目录
CMake 和 CMakeLists.txt 使用
一、CMake 简介
CMake 是一个跨平台的自动化构建系统生成工具。它使用配置文件(CMakeLists.txt
)描述构建过程,生成标准构建文件(如 Makefile 或 Visual Studio 项目)。
二、基础项目结构
my_project/
├── CMakeLists.txt # 根配置文件
├── include/ # 头文件目录
│ └── utils.h
├── src/ # 源代码目录
│ ├── main.cpp
│ └── utils.cpp
└── build/ # 构建目录(建议)
三、核心 CMakeLists.txt 语法
1. 基础配置
cmake_minimum_required(VERSION 3.10) # 最低版本要求
project(MyProject # 项目名称
VERSION 1.0 # 版本号
LANGUAGES CXX) # 语言 (C/CXX/ASM)
# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
指定编译器,如g+±9
-
命令行直接指定
在CMake配置阶段通过-D
参数显式设置:cmake -DCMAKE_CXX_COMPILER=/usr/bin/g++-9 ..
若g+±9不在默认路径,需替换为实际安装路径。
-
CMakeLists.txt硬编码
在项目根目录的CMakeLists.txt
中添加(需放在cmake_minimum_required
之后):set(CMAKE_CXX_COMPILER "/usr/bin/g++-9")
此方法会强制锁定编译器,可能影响跨平台兼容性。
-
工具链文件(推荐)
创建独立的toolchain.cmake
文件:set(CMAKE_CXX_COMPILER "/usr/bin/g++-9") set(CMAKE_C_COMPILER "/usr/bin/gcc-9")
调用时通过参数加载:
cmake -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake ..
此方式更灵活且便于版本管理。
-
环境变量覆盖
临时设置环境变量(适用于Linux/macOS):export CXX=/usr/bin/g++-9 export CC=/usr/bin/gcc-9 cmake ..
验证方法
配置完成后,可通过以下命令检查实际使用的编译器:cmake --build . --verbose
或查看CMakeCache.txt中的
CMAKE_CXX_COMPILER
变量值。
-
推荐同时指定C++标准(如
set(CMAKE_CXX_STANDARD 11)
)以确保兼容性 -
若存在多版本编译器,建议用
update-alternatives
配置默认版本-
安装多版本GCC/G++
首先安装需要的编译器版本(以gcc-9和gcc-11为例):sudo apt update sudo apt install gcc-9 g++-9 gcc-11 g++-11
-
注册版本到alternatives系统
为每个版本设置优先级(数值越大优先级越高):sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 90 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 110 sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 90 sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-11 110
-
交互式切换默认版本
运行配置命令后按提示选择版本:sudo update-alternatives --config gcc sudo update-alternatives --config g++
终端会显示类似选项:
Selection Path Priority Status ------------------------------------------------------------ 0 /usr/bin/gcc-11 110 auto mode 1 /usr/bin/gcc-9 90 manual mode * 2 /usr/bin/gcc-11 110 manual mode
输入对应编号即可切换。
-
验证当前版本
切换后检查生效版本:gcc --version g++ --version
注意事项:
- 必须同时配置
gcc
和g++
以保证C/C++编译器版本一致 - 若遇到
g++ is a slave of gcc
错误,需先配置gcc
再配置g++
- 通过
--display
参数可查看当前配置详情:update-alternatives --display gcc
如需删除某个版本选项,使用:
sudo update-alternatives --remove gcc /usr/bin/gcc-9
-
2. 添加可执行文件
add_executable(my_app src/main.cpp src/utils.cpp)
3. 添加头文件目录
target_include_directories(my_app
PRIVATE
${PROJECT_SOURCE_DIR}/include # 仅当前目标使用
PUBLIC # 依赖本目标的其他目标也会继承
INTERFACE
)
4. 添加静态库
add_library(my_lib STATIC src/utils.cpp)
target_include_directories(my_lib PUBLIC include)
5. 链接库文件
add_executable(my_app src/main.cpp)
target_link_libraries(my_app PRIVATE my_lib) # PUBLIC/INTERFACE
四、高级功能
1. 条件语句
if(UNIX AND NOT APPLE)
set(LINUX TRUE)
endif()
if(WIN32)
add_definitions(-DWINDOWS_PLATFORM)
endif()
2. 包含子目录
add_subdirectory(submodule) # 包含子目录中的 CMakeLists.txt
3. 查找外部库
find_package(OpenCV REQUIRED)
target_link_libraries(my_app PRIVATE ${OpenCV_LIBS})
4. 生成配置文件
configure_file(
config.h.in # 输入模板
config.h # 输出文件
)
5. 安装规则
install(TARGETS my_app
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib/static)
install(DIRECTORY include/ DESTINATION include)
Cmake中的包头和库的指定
cmake提供更简单的方式来解决编译依赖问题,通常遇到编译时,需要指定包来进行编译,可通过如下几种方式来指定参与链接的包
1、通过find_package查找包
######## 1) find_package自动查找
在以下环境变量中查找cmake文件
<package>_DIR
CMAKE_PREFIX_PATH
CMAKE_FRAMEWORK_PATH
CMAKE_APPBUNDLE_PATH
PATH
1\通常ROS环境下:CMAKE_PREFIX_PATH
为/opt/ros/<melodic>
,
因此ROS环境下find_package()
首先会在/opt/ros/<melodic>
下寻找,如/opt/ros/melodic/share/<packages>
能找到关于集成在ros下的package的Config.cmake
文件。
2\对于根目录PATH下的查找:
cmake会自动去/usr/(lib/<arch>|lib|share)/cmake/<name>*/
寻找模块,这使得绝大部分我们直接通过apt-get安装的库可以被找到。find_package()
会在~/.cmake/packages/
或/usr/local/share/
中的各个包目录中查找,寻找<库名字的大写>Config.cmake
或者 <库名字的小写>-config.cmake
(比如库Opencv
,它会查找/usr/local/share/OpenCV
中的OpenCVConfig.cmake或opencv-config.cmake)。
常用两种Config.cmake系统查找路径:/usr/local/share/<package>
/usr/local/lib/cmake/<package>
以上来自:https://www.jianshu.com/p/243ff97bbbc6
######## 2)package_DIR设定包的位置
比较重要的是<package>_DIR
。我们可以在调用cmake时将这个目录传给cmake。由于其优先级最高,因此cmake会优先从该目录中寻找,当我们不想安装某些自编译的包时,可以通过这种方法来指定find_package的指定寻找路径。
如:
set(G2O_DIR ${PROJECT_SOURCE_DIR}/lib/g2o/build)
find_package(G2O REQUIRED)
include_directories(${G2O_INCLUDE_DIRS})
link_directories(${G2O_LIBRARY_DIRS})
link_libraries(${G2O_LIBRARIES})
find_package根据package名称寻找指定的package的.cmake文件,并对应赋值包对应的头文件地址(如${OpenCV_INCLUDE_DIRS}
)及库(如${OpenCV_LIBRARIES}
)${OpenCV_LIBRARIES}
不是库的存放地址,而是-lxxx,即用pkg-config opencv –libs打印出的内容,如:-L/usr/local/lib -lopencv_dnn -lopencv_highgui -lopencv_ml -lopencv_objdetect -lopencv_shape -lopencv_stitching -lopencv_superres -lopencv_videostab -lopencv_calib3d -lopencv_videoio -lopencv_imgcodecs -lopencv_features2d -lopencv_video -lopencv_photo -lopencv_imgproc -lopencv_flann -lopencv_viz -lopencv_core
2、设定包的头文件及库
也可以通过cmakelist的set()
重设${xxxINCLUDE_DIRS}
和${xxx_LIBRARIES}
,以及自定义变量,用法,set(变量名 变量值)
,如:
set(LIBS ${OpenCV_LIBS}
${PROJECT_SOURCE_DIR}/../../../Thirdparty/g2o/lib/libg2o.so
-lboost_system)
3、使用pkg-config
在cmakelist中指定包
set(ENV{PKG_CONFIG_PATH} /packname.pc_path)
find_package(PkgConfig)
pkg_search_module(MyDepName REQUIRED packname)
之后可以使用${MyDepName_LIBRARIES}
和${MyDepName_INCLUDE_DIRS}
来指定包的头文件及库
https://www.jianshu.com/p/eb6b06463da9
为编译指定全部头文件及库地址以及库名
通过include_directories
和link_directories
指定额外的头文件及库文件路径。分别对应g++的-I和-L操作。link_libraries
添加需要链接的库文件路径,注意这里是全路径,如:
LINK_LIBRARIES("/opt/MATLAB/R2012a/bin/glnxa64/libeng.so")
通过target_link_libraries
指定链接库,即执行编译中对应的-lxxx
操作
add_executable(GNtest src/test.cpp)
target_link_libraries(GNtest ${catkin_LIBRARIES} ${OpenCV_LIBRARIES} ${PCL_LIBRARIES} -lgtsam)
遇到问题
Q1:Could not find a package configuration file
By not providing "FindNLOPT.cmake" in CMAKE_MODULE_PATH this project has
asked CMake to find a package configuration file provided by "NLOPT", but
CMake did not find one.
Could not find a package configuration file provided by "NLOPT" with any of
the following names:
NLOPTConfig.cmake
nlopt-config.cmake
Add the installation prefix of "NLOPT" to CMAKE_PREFIX_PATH or set
"NLOPT_DIR" to a directory containing one of the above files. If "NLOPT"
provides a separate development package or SDK, be sure it has been
installed.
R1:首先请确保是否是因为缺包引起的。
(1)是的话安装对应包,不是则找到xxx.cmake的存放位置
在CMakelist.txt中添加xxx.cmake所在的路径到查找路径。
解决以上报错的两种方法:
- 添加
set (CMAKE_PREFIX_PATH "pathtoxxx.cmake")
- 添加
list(APPEND CMAKE_FIND_ROOT_PATH "pathtoxxx.cmake")
如果直接xxx.cmake直接在项目目录下,添加list(APPEND CMAKE_FIND_ROOT_PATH ${PROJECT_SOURCE_DIR})
即可。
CMAKE_SOURCE_DIR does indeed refer to the folder where the top-level CMakeLists.txt is defined.
PROJECT_SOURCE_DIR refers to the folder of the CMakeLists.txt containing the most recent project() command.
(2)若包已安装,但是没有对应的xxx.cmake文件,find_package找不到xxx.cmake文件。
则可以利用pkg-config 找到对应的库。
在要编译的包的CMakeList.txt中对应修改,以下以fcl包为例。
find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBFCL_PC REQUIRED fcl)
## find *absolute* paths to LIBFCL_INCLUDE_DIRS and LIBFCL_LIBRARIES
find_path(LIBFCL_INCLUDE_DIRS fcl/config.h HINTS ${LIBFCL_PC_INCLUDE_DIR} ${LIBFCL_PC_INCLUDE_DIRS})
find_library(LIBFCL_LIBRARIES fcl HINTS ${LIBFCL_PC_LIBRARY_DIRS})
include_directories(SYSTEM ${catkin_INCLUDE_DIRS}
...
${LIBFCL_INCLUDE_DIRS}
)
catkin_package(
...
DEPENDS
Boost
EIGEN3
LIBFCL
)
opencv solve test:cvsolve.cpp
##include <iostream>
##include <opencv/cv.h>
##include <opencv2/core/core.hpp>
using namespace std;
int main()
{
cv::Mat A = (cv::Mat_<float>(2, 2) << 1,2,3,4);
cv::Mat B = (cv::Mat_<float>(2, 1) <<5,11);
cv::Mat C;
cout << "A" << endl << A << endl;
cout << "B" << endl << B << endl;
cv::solve(A, B, C, CV_LU);
cout << "X" << endl << C << endl;
return 0;
}
编译命令:要主动链上opencv 否则会报未定义的错误。
g++ `pkg-config --libs --cflags opencv` -ldl xx.cpp
##下边这个命令常用
g++ opencv_test.cpp `pkg-config --cflags --libs opencv`
以上命令会自动找到并连接opencv的库。而不用在手动指定。
pkg-config 用于获得某一个库/模块的所有编译相关的信息。寻找对应的xxx.pc文件,来获取包及库的相关信息。
ldl: 链接动态库
如果你的程序中使用dlopen、dlsym、dlclose、dlerror 显示加载动态库,需要设置链接选项 -ldl
输出结果:
cmake用法:
add_subdirectory 添加下级编译目录,下级有CMakelist.txt的目录
add_definitions 添加可配置的宏定义:
##eg:如下示例可置宏定义ADVANCED_SENSING为1或0,进而可以再代码终用##ifdef打开或关闭对应的代码。
option(ADVANCED_SENSING "ADVANCED_SENSING set" ON)
if (ADVANCED_SENSING)
add_definitions(-DADVANCED_SENSING)
endif()
cmake . -DADVANCED_SENSING=0
add_dependencies:添加编译依赖顺序,检查上级编译是否已经编译,否则先编译依赖上级
五、完整示例
1. 根目录 CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(MyApp VERSION 1.0 LANGUAGES CXX)
# 添加子目录
add_subdirectory(src)
add_subdirectory(tests) # 测试目录
2. src/CMakeLists.txt
# 创建库
add_library(core_lib STATIC utils.cpp)
target_include_directories(core_lib PUBLIC ../include)
# 创建可执行文件
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE core_lib)
# 安装规则
install(TARGETS my_app DESTINATION bin)
3. tests/CMakeLists.txt
find_package(GTest REQUIRED)
add_executable(run_tests test_utils.cpp)
target_link_libraries(run_tests PRIVATE core_lib GTest::gtest_main)
add_test(NAME AllTests COMMAND run_tests)
六、构建流程(命令行)
# 创建构建目录
mkdir build && cd build
# 生成构建系统
cmake .. -DCMAKE_BUILD_TYPE=Release # 或 Debug
# 编译项目
cmake --build . --parallel 4
# 运行测试
ctest -V
# 安装到系统
cmake --install . --prefix "/usr/local"
七、常用变量
变量名 | 描述 |
---|---|
CMAKE_SOURCE_DIR |
顶层CMakeLists.txt所在目录 |
PROJECT_SOURCE_DIR |
当前项目源目录 |
CMAKE_BINARY_DIR |
构建目录(build/) |
CMAKE_CURRENT_SOURCE_DIR |
当前处理的CMakeLists.txt目录 |
CMAKE_INSTALL_PREFIX |
安装路径前缀(默认为/usr/local) |
八、最佳实践
-
分离源码和构建目录:始终在
build/
目录中构建 -
模块化设计:每个子目录有自己的CMakeLists.txt
-
精确指定依赖:
PRIVATE
:仅当前目标使用INTERFACE
:仅依赖者使用PUBLIC
:当前目标及依赖者都使用
-
现代CMake写法:
# 旧式(避免使用) include_directories(include) link_directories(lib) # 现代(推荐) target_include_directories(target_name PUBLIC include) target_link_directories(target_name PUBLIC lib)
-
生成导出配置:便于其他CMake项目引用你的库
九、调试技巧
# 打印变量值
message(STATUS "OpenCV path: ${OpenCV_DIR}")
# 打印所有变量
get_cmake_property(_vars VARIABLES)
foreach(_var ${_vars})
message(STATUS "${_var}=${${_var}}")
endforeach()
# 查看构建详情
cmake --build . --verbose
十、跨平台注意事项
- 路径分隔符:始终使用
/
(CMake自动转换) - 库后缀处理:
set(CMAKE_DEBUG_POSTFIX "_d") # 调试库添加后缀
- 平台检测:
if(WIN32) # Windows特定设置 elseif(APPLE) # macOS设置 elseif(UNIX) # Linux设置 endif()
官方文档:https://cmake.org/cmake/help/latest/
学习资源:Modern CMake教程(https://modern-cmake-cn.github.io/)
更多推荐
所有评论(0)