快速入门 camke, 掌握 cmake 在 c/c++ 项目构建上魅力
认识 CMake 
通过上图我们可以大致了解到: cmake 是一个跨平台项目构建工具, 通过 cmake 命令, 可以将讲简单的工程配置描述文件 CMakeFiles.txt (即指令合集) 构建成 makefile 文件,帮我们省去编写繁杂的 makefile, 然后在通过 make 命令将源代码编译成目标文件
使用 cmake 工具, 首先需要安装 cmake 到本地环境
安装好后, 接下来我们结合工程示例 https://github.com/oksep/cmake-begin  ,逐一讲解 cmake 相关概念
简单工程 源码参考: v1_basic , 项目有如下文件结构:
1 2 3 4 5 6 7 8 9 10 .
我们主要讲解 CMakeLists.txt 每个指令的含义(重点关注注释):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 cmake_minimum_required (VERSION 3.15 )set (CMAKE_CXX_STANDARD 17 )project (CALC)set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR} /bin)file (GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR} /src/*.cpp)file (GLOB HEAD_LIST ${CMAKE_CURRENT_SOURCE_DIR} /include /*.h)include_directories (${PROJECT_SOURCE_DIR} /include )add_executable (app ${SRC_LIST} )
执行 cmake . -B ./build 命令
1 2 3 4 5 build
执行构建命令 cmake --build build, 生成目标文件cd build && make:
1 2 3 4 5 6 7 [ 16%] Building CXX object CMakeFiles/app.dir/src/add.cpp.o[100% ] Linking CXX executable /Users/sep/Documents/cmake-tutorial/v1_basic/bin/app [100% ] Built target app 
执行 ./bin/app 一个简单的计算器程序就可以运行起来了:
1 2 3 4 5 a = 12, b = 4
生成静态库/动态库 源码参考: v2_gen_lib , 主要讲解 CMakeLists.txt 文件的配置(重点关注注释):
1 2 3 4 5 6 7 8 9 10 11 ...set (LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR} /lib)add_library (calc_static STATIC ${SRC_LIST} )add_library (calc_shared SHARED ${SRC_LIST} ) 
这里可以对比 v1_basic 工程中的 CMakeLists.txt 文件, 配置生成了不同的目标文件:
# 
command 
windows format 
linux format 
macOS format 
 
 
可执行文件 
add_executable 
.exe 
/ 
/ 
 
静态库文件 
add_library 
.lib 
.a 
.a 
 
动态库文件 
add_library 
.dll 
.so 
.dylib 
 
同样执行 cmake 相关命令 cmake . -B build && cmake --build build, 可以看到在 lib/  文件夹下, 输出了对应的库文件
1 2 3 lib
链接静态库 源码参考: v3_link_static_lib , 项目有如下文件结构:
1 2 3 4 5 6 7 8 ./v3_link_static_lib
使工程链接静态库, 在 CMakeLists.txt 的主要配置(重点关注注释):
1 2 3 4 5 6 7 8 9 10 11 12 ...include_directories (${PROJECT_SOURCE_DIR} /include )link_directories (${PROJECT_SOURCE_DIR} /lib)link_libraries (calc)add_executable (app ${SRC_LIST} )
执行 cmake . -B build && cmake --build build 后, libcalc.a 被打包到了可执行文件中, 运行 bin/app  静态库 libcalc.a 立即被加载到内存中
链接动态库 源码参考: v4_link_shared_lib 
文件目录结构同 v3_link_static_lib, 我们将 v2_gen_lib 生成的动态库libcalc.dylib  放在 lib/  路径下, CMakeLists.txt 主要差异在(重点关注注释):
1 2 3 4 5 6 7 8 9 10 include_directories (${PROJECT_SOURCE_DIR} /include )link_directories (${PROJECT_SOURCE_DIR} /lib)add_executable (app ${SRC_LIST} )target_link_libraries (app calc)
这里需要注意: 链接动态库, 是在 add_executable 生成 app 后再去配置的
接下来执行 cmake . -B build && cmake --build build, 运行 bin/app , 只有当真正调用到 calc 相关函数的时候, libcalc.dylib 才会被加载到内存中
子模块 源码参考: v5_nested_project 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ├── CMakeLists.txt
这里我们定义了四个子模块和一个根模块
计算器模块: calc/CMakeLists.txt 配置生成静态库 
排序模块: sort/CMakeLists.txt 配置生成动态库 
测试计算器模块: test1/CMakeLists.txt 配置测试链接静态库 
测试排序模块: test2/CMakeLists.txt 配置测试链接动态库 
根模块 
 
在根模块 CMakeLists.txt 中有如下定义(重点关注注释):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 cmake_minimum_required (VERSION 3.15 )project (test )set (CMAKE_CXX_STANDARD 17 )set (LIB_PATH ${CMAKE_SOURCE_DIR} /lib)set (EXEC_PATH ${CMAKE_SOURCE_DIR} /bin)set (HEAD_PATH ${CMAKE_SOURCE_DIR} /include )set (LIB_CALC calc)set (LIB_SORT sort)set (APPNAME1 test1)set (APPNAME2 test2)add_subdirectory (calc)add_subdirectory (sort)add_subdirectory (test1)add_subdirectory (test2)
子模块 calc 的 CMakeLists.txt 配置(重点关注注释):
1 2 3 4 5 6 7 8 9 10 11 ...file (GLOB SOURCES "src/*.cpp" )include_directories (${HEAD_PATH} )set (LIBRARY_OUTPUT_PATH ${LIB_PATH} )add_library (${LIB_CALC}  STATIC ${SOURCES} )
其它子模块: sort/test1/test2 与 calc 模块类似, 都是使用了根模块声明的字段来配置各个子模块
接下来执行 cmake . -B build && cmake --build build 有以下日志:
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 -- The C compiler identification is AppleClang 15.0.0.15000100[100% ] Linking CXX executable /Users/sep/Documents/cmake-tutorial/v5_nested_project/bin/test2 [100% ] Built target test2 
此时项目里新增了各个子模块构建出来的产物: 
1 2 3 4 5 6 7 .
可以分别执行 bin/test1 和 bin/test2 查看效果
静态库链接静态库 源码参考: v6_static_link_static_lib 
1 2 3 4 5 6 7 8 9 10 11 12 13 ├── CMakeLists.txt
这里是对 v5_nested_project 做的改造, 各个子模块间的依赖关系有:
test -> sort.a(静态库) -> calc.a(静态库)
 
各模块配置r(重点关注注释):
calc/CMakeLists.txt
1 2 3 4 5 6 7 8 9 ...include_directories (${HEAD_PATH} )set (LIBRARY_OUTPUT_PATH ${LIB_PATH} )add_library (${LIB_CALC}  STATIC ${SOURCES} )
sort/CMakeLists.txt
1 2 3 4 5 6 7 8 9 10 11 12 ...include_directories (${HEAD_PATH} )link_libraries (${LIB_CALC} )set (LIBRARY_OUTPUT_PATH ${LIB_PATH} )add_library (${LIB_SORT}  STATIC ${SOURCES} )
sort/src/insert.cpp
1 2 3 4 5 6 7 8 9 10 11 #include  <stdio.h>  #include  "calc.h"  #include  "sort.h"  void  sort_insert (int  *arr, int  len) int  s = add (1 , 2 );printf ("sort_insert call add: s = %d\n" , s);
test/CMakeLists.txt
1 2 3 4 5 6 7 8 9 10 11 12 ...include_directories (${HEAD_PATH} )link_directories (${LIB_PATH} )link_libraries (${LIB_SORT} )add_executable (test  ${SOURCES} )
 
接下来执行 cmake . -B build && cmake --build build 便可以生成: 链接了静态库 sort (sort 链接了静态库 calc) 的可执行文件 bin/test
静态库链接动态库 源码参考: v7_static_link_shared_lib 
这里是对 v6_static_link_static_lib 做的改造, 各个子模块间的依赖关系有:
test -> sort.a(静态库) -> calc.dylib(动态库)
 
配置静态库 sort 链接动态库 calc 主要有以下修改:
calc/CMakeLists.txt
1 2 3 4 5 6 7 8 include_directories (${HEAD_PATH} )set (LIBRARY_OUTPUT_PATH ${LIB_PATH} )add_library (${LIB_CALC}  SHARED ${SOURCES} )
calc/CMakeLists.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 include_directories (${HEAD_PATH} )set (LIBRARY_OUTPUT_PATH ${LIB_PATH} )link_directories (${LIB_PATH} )add_library (${LIB_SORT}  STATIC ${SOURCES} )target_link_libraries (${LIB_SORT}  ${LIB_CALC} )
 
接下来执行 cmake . -B build && cmake --build build 便可以生成: 链接了静态库 sort (sort 链接了动态库 calc) 的可执行文件 bin/test
CMake 常用指令 除了上面几个案例介绍的指令外, 可以参考 这里  和 官网  对 cmake 指令有更全面的了解
CMake 与 Ninja Ninja  也是一个构建系统, 使用 ninja 构建项目相比 makefile 可以更快, 同时也支持跨平台, Ninja 与 CMake 的关系可以参考下图:
我们可以通过 cmake 生成 .ninja  文件, 然后再用 ninja 命令执行构建生成目标文件
使用 ninja, 首先需要在本地安装:
上面的章节中所用到的命令 cmake . -B build 都是生成的 makefile 构建文件, 这其实是 cmake 工具的生成器 generators  概念
执行 cmake --help 可以查看到 cmake 的 generator 在本机系统支持生成哪些类型的构建文件
1 2 3 4 5 6 7 8 9 Generators
接下来再以 v1_basic 为案例执行命令 cmake . -B build-ninja -GNinja, 可以看到在 build-ninja  目录下有:
1 2 3 4 ├── CMakeCache.txt
以上是 cmake 生成的 ninja 相关构建文件, 我们可以 cd build-ninja && ninja 执行构建编译, 也可以执行 cmake --build build-ninja 命令来生成可执行文件 bin/app 
这里再附贴一下 makefile 与 ninja 在编译速度上更直观的对比演示:
CMake 与 vcpkg vcpkg  是 c/c++ 项目的依赖库管理工具, 可以帮助我们非常方便的管理三方库
下载 vcpkg:
1 git clone https://github.com/microsoft/vcpkg
配置环境变量:
1 2 export VCPKG_ROOT=[path-to-vcpkg]
vcpkg-smaple 以 smaple 工程  为例, 创建 vcpkg 相关配置:
vcpkg 会自动生成以下两个文件:
vcpkg-configuration.json 仓库配置 
vcpkg.json 三方库依赖配置 
 
配置 vcpkg.json 管理三方库:
1 2 3 4 5 6 7 {"dependencies" : ["cxxopts" ,"fmt" ,"range-v3" 
配置 CMakeLists.txt 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 cmake_minimum_required (VERSION 3.15 )set (CMAKE_CXX_STANDARD 17 )set (STRING  "Vcpkg toolchain file" project (fibonacci CXX)find_package (fmt REQUIRED)find_package (range-v3 REQUIRED)find_package (cxxopts REQUIRED)add_executable (fibo main.cxx)target_link_libraries (fibo
执行 vcpkg install 安装依赖库, 此时工程有以下文件:
1 2 3 4 5 6 ├── CMakeLists.txt
至此, 可以对 main.cxx 正常引用三方库进行代码编写了, 执行下 cmake --build build 试试效果 😊😊
python bindings 接下我们使用 cmake + vcpkg + pybinding11  来实现 python native bindings 的实战案例, 源码参考: python-binding 
同样配置 vcpkg.json 的依赖项:
1 2 3 4 5 {"dependencies" : ["pybind11" 
然后使用 vcpkg install 安装依赖库
在 CMakeFileLists.txt 中配置(重点参考注释):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 set ("$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" STRING  "Vcpkg toolchain file" project (example LANGUAGES CXX)set (LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR} /lib)find_package (pybind11 REQUIRED)include_directories ($pybind11_INCLUDE_DIRS)
在 example.cpp  中定义向 python 提供的 add() 方法, 声明模块宏 PYBIND11_MODULE(example, m): 
1 2 3 4 5 6 7 8 9 10 11 #include  <pybind11/pybind11.h>  namespace  py = pybind11;int  add (int  i, int  j)  return  i + j;PYBIND11_MODULE (example, m) {def ("add" , &add, "A function which adds two numbers" );
执行构建 cmake . -B build && cmake --build build, 然后进入到 lib 目录, 可以看到生成了对应的 native 库: example.cpython-311-darwin.so  
执行 python 解释器, 可以看到成功调用到了 navive 库 add() 方法并返回计算结果:
1 2 3 4 5 6 7 8 /lib > python                                                                                        py base 21 :58 :59 3.11 .5  (main, Sep 11  2023 , 08:31 :25 ) [Clang 14.0 .6  ] on darwinType  "help" , "copyright" , "credits"  or  "license"  for  more information.>>>  import  example>>>  example.add(1 , 2 )3