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
  1. 命令行直接指定
    在CMake配置阶段通过-D参数显式设置:

    cmake -DCMAKE_CXX_COMPILER=/usr/bin/g++-9 ..
    

    若g+±9不在默认路径,需替换为实际安装路径。

  2. CMakeLists.txt硬编码
    在项目根目录的CMakeLists.txt中添加(需放在cmake_minimum_required之后):

    set(CMAKE_CXX_COMPILER "/usr/bin/g++-9")
    

    此方法会强制锁定编译器,可能影响跨平台兼容性。

  3. 工具链文件(推荐)
    创建独立的toolchain.cmake文件:

    set(CMAKE_CXX_COMPILER "/usr/bin/g++-9")
    set(CMAKE_C_COMPILER "/usr/bin/gcc-9")
    

    调用时通过参数加载:

    cmake -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake ..
    

    此方式更灵活且便于版本管理。

  4. 环境变量覆盖
    临时设置环境变量(适用于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配置默认版本

    1. 安装多版本GCC/G++
      首先安装需要的编译器版本(以gcc-9和gcc-11为例):

      sudo apt update
      sudo apt install gcc-9 g++-9 gcc-11 g++-11
      
    2. 注册版本到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
      
    3. 交互式切换默认版本
      运行配置命令后按提示选择版本:

      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
      

      输入对应编号即可切换。

    4. 验证当前版本
      切换后检查生效版本:

      gcc --version
      g++ --version
      

    注意事项

    • 必须同时配置gccg++以保证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_directorieslink_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所在的路径到查找路径。
解决以上报错的两种方法:

  1. 添加 set (CMAKE_PREFIX_PATH "pathtoxxx.cmake")
  2. 添加 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)

八、最佳实践
  1. 分离源码和构建目录:始终在build/目录中构建

  2. 模块化设计:每个子目录有自己的CMakeLists.txt

  3. 精确指定依赖

    • PRIVATE:仅当前目标使用
    • INTERFACE:仅依赖者使用
    • PUBLIC:当前目标及依赖者都使用
  4. 现代CMake写法

    # 旧式(避免使用)
    include_directories(include)
    link_directories(lib)
    
    # 现代(推荐)
    target_include_directories(target_name PUBLIC include)
    target_link_directories(target_name PUBLIC lib)
    
  5. 生成导出配置:便于其他CMake项目引用你的库


九、调试技巧
# 打印变量值
message(STATUS "OpenCV path: ${OpenCV_DIR}")

# 打印所有变量
get_cmake_property(_vars VARIABLES)
foreach(_var ${_vars})
    message(STATUS "${_var}=${${_var}}")
endforeach()
# 查看构建详情
cmake --build . --verbose

十、跨平台注意事项
  1. 路径分隔符:始终使用/(CMake自动转换)
  2. 库后缀处理
    set(CMAKE_DEBUG_POSTFIX "_d")  # 调试库添加后缀
    
  3. 平台检测
    if(WIN32)
        # Windows特定设置
    elseif(APPLE)
        # macOS设置
    elseif(UNIX)
        # Linux设置
    endif()
    

官方文档:https://cmake.org/cmake/help/latest/
学习资源:Modern CMake教程(https://modern-cmake-cn.github.io/)

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐