CMake 使用笔记
前言
目前有几种主流使用方案:
- VS使用vcxprj文件进行源代码管理,nmake编译
- QT4,5使用QMAKE(.pro)文件进行管理
- Keil使用uvprojx文件进行管理
- Linux下一般使用make(makefile)进行管理
CMake即跨平台和跨方案的类似make方案,使用CMakeLists.txt进行编译内容管理。
和他对应的是国产的xmake, 更简单易用,但是不够国际化,正式项目以Cmake为主
目前,VS,QT, Clion,VSCode等各大IDE都默认支持CMake构建,所以,以后的项目如果可以使用CMake是一个比较好的开放方案。
CMake 组成
cmake
是命令行,cmake-gui
是windows下的GUI实现, ccmake
是交互式的命令行GUI工具。
CMake是一个构建生成器,提供了强大的领域特定语言(DSL)来描述构建系统应该实现的功能。这是CMake的主要优势之一,它允许使用相同的CMake脚本集生成平台原生构建系统。CMake软件工具集,使开发人员可以完全控制给定项目的生命周期:
- CMake是描述如何在所有主要硬件和操作系统上配置、构建和安装项目,无论是构建可执行文件、库,还是两者都要构建。
- CTest定义测试、测试套件,并设置应该如何执行。
- CPack为打包需求提供了DSL。
- CDash将项目的测试结果在面板中展示。
CMake支持c/c++/java等语言的项目。
一般 CMake 的使用步骤:
- 编写源码
- 编写 CMakeLists.txt
- 使用 cmake 命令生成 Makefile/project 文件
- 使用 make/nmake 等命令编译
- 运行生成的可执行文件
CMake 的所有的语句都写在 CMakeLists.txt 文件中,且必须要是这个文件名,CMake 命令会根据 CMakeLists.txt 中的语法构建编译规则,生成 Makefile 文件。可以在 CMakeLists.txt 中包含其他的 cmake 文件,被包含的文件名可以自定义,一般以 .cmake 结尾。
常用指令
project 关键字
可以用来指定工程名和支持的语言,默认支持所有语言,如
# 指定工程名字,并支持所有语言(建议使用)
project(hello)
# 指定工程名字,并支持 C++ 语言
project(hello CXX)
# 指定工程名字,并支持 C/C++ 语言
project(hello C CXX)
cmake_minimum_required 关键字
定义 CMake 最低支持的版本,低于次版本则会报错,如最低支持3.5以上的版本
cmake_minimum_required(VERSION 3.5)
set 关键字
用于显示指定变量,类似于变量赋值。如 set(src_list main.cc) 表示 src_list 变量包含了 main.cc 文件。多个文件使用空格分开,如
set(src_list main.cc)
set(src_list main.cc util.cc test.cc)
还可以设置其他信息,如 C++ 支持的版本
set(CMAKE_CXX_STANDARD 11) # C++11
set(CMAKE_CXX_STANDARD 14) # C++14
message 关键字
向终端用户输出自定义信息,主要包含 3 种信息:
SEND_ERROR
生产错误,生成过程被跳过;STATUS
输出前缀为 – 的信息;FATAL_ERROR
立即终止所有 cmake 过程。
message(STATUS "binary dir ${hello_BINARY_DIR}")
message(STATUS "source dir ${hello_SOURCE_DIR}")
# 输出内容,会有 -- 前缀
-- binary dir /home/mayw/tmp/cmake_test
-- source dir /home/mayw/tmp/cmake_test
aux_source_directory 关键字
查找目录中的所有源文件,并赋值给指定变量,如
# 查找当前目录下的所有源文件,将名称保存到 src_list 变量中,可以使用 ${src_list} 进行引用
aux_source_directory(. src_list)
这样便可以不用逐个添加源文件了,详细信息可以参考官网 aux_source_directory 。
include_directories 关键字
添加头文件目录,相当于 gcc/g++ 命令的 -I 参数的功能,如
# 类似 gcc/g++ 中的 -I 选项
include_directories(../include)
也可以添加一个 CPLUS_INCLUDE_PATH
环境变量,并将目录追加到环境变量中,但 CMake 一般不这样用。
include 关键字
包含其他的 cmake 文件,被包含的文件一般以 cmake结尾,如
include(../cmake/common.cmake)
add_definitions 关键字
添加定义信息,类似于 gcc/g++ 中的 CFLAGS 和 宏定义,如
add_definitions(-g -Wall -O3)
# -D开表示宏定义
if(MSVC)
add_definitions(-D_WIN32_WINNT=0x600)
endif()
link_directories 关键字
添加库文件目录,相当于 gcc/g++ 命令的 -L 参数的功能,如
# 类似 gcc/g++ 中的 -L 选项
include_directories(../lib ../lib64)
也可以添加一个 LD_LIBRARY_PATH
环境变量,并将目录追加到环境变量中。Linux 中一般出现找不到库文件的问题,就会追加路径到次环境变量中。
target_link_libraries 关键字
链接库文件到程序中,相当于 gcc/g++ 命令的 -l 参数的功能,如
# 类似 gcc/g++ 中的 -l 选项
target_link_libraries(hello mylib)
一般都是与 add_executable
配合使用,紧跟其后。
add_executable 关键字
指定生成的可执行文件,如
set(src_list main.cc)
add_executable(hello ${src_list})
# 生成可执行文件 hello,源文件读取变量 src_list 里的内容,也就是 main.cc,等价add_executable(hello main.cc)
# 若要链接 libmylib.so,可以添加
target_link_libraries(hello mylib)
注意:工程名 hello 和生成的可执行文件 hello 没有任何关系,可以相同,也可以不相同。
add_library 关键字
与add_executable
类似,指定生成的库文件。
set(lib_src add.cpp)
add_library(add STATIC ${lib_src})
- add :库文件名称,Linux 上生成库文件会自动加上前后缀,如当前的静态库文件名称为 libadd.a;
- STATIC :静态库,动态库为 SHARED;
- ${lib_src} :构造库文件所需的源码文件。
add_subdirectory 关键字
add_subdirectory (source_dir [binary_dir] [EXCLUDE_FROM_ALL])
添加一个子目录并构建该子目录。
CMake 语法的基本原则
- 变量使用
${}
方式取值,但是在if
控制语句中是直接使用变量名; - 指令参数使用括号,参数之间使用空格或分号分开,如
add_executable(hello main.cc util.cc)
- 指令是大小写无关的,即
add_executable
和ADD_EXECUTABLE
是一样的,但 参数和变量是大小写相关的。google 规范中全部使用小写指令和变量,可视项目情况而定,与项目保持一致就行。
语法注意事项
set(src_list main.cc)
可以写成set(src_list "main.cc")
,这两种写法是一样的,但如果文件名中有空格、中文或其他特殊字符,则必须要加双引号;add_executable(hello main.cc)
后缀名可以不写,CMake 会自动去找 .c 和 .cpp 的源文件文件,但最好明确指定源文件的后缀名。
Demo
# 设置最低版本3.5,否则发出致命错误
cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
# 工程名称project01, 语言CXX代表C++
project(project01 CXX)
# 添加可执行文件hello-world, 源代码main.cpp
# 源代码可以多个,使用空格或者;分割
add_executable(hello-world main.cpp)
message(STATUS "==> cmake file end <==")
NOTE: CMake中,C++是默认的编程语言。不过,我们还是建议使用LANGUAGES
选项在project
命令中显式地声明项目的语言。
构建:
$ mkdir -p build
$ cd build
$ cmake ..
-- Building for: Visual Studio 17 2022
-- Selecting Windows SDK version 10.0.19041.0 to target Windows 10.0.19044.
-- The CXX compiler identification is MSVC 19.34.31937.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: C:/Program Files/Microsoft Visual Studio/2022/Professional/VC/Tools/MSVC/14.34.31933/bin/Hostx64/x64/cl.exe - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- ==> cmake file end <==
-- Configuring done
-- Generating done
-- Build files have been written to: C:/xxxxx/build
$ cmake --build .
MSBuild version 17.4.1+9a89d02ff for .NET Framework
Checking Build System
Building Custom Rule C:/xxxxx/c1/CMakeLists.txt
main.cpp
hello-world.vcxproj -> C:\xxxxx\c1\build\Debug\hello-world.exe
Building Custom Rule C:/XinDoc/codedb/testPrj/c1/CMakeLists.txt
这个构建命令等价于cmake -H . -B build
,-H
表示当前目录中搜索根CMakeLists.txt
文件。-B build
告诉CMake在一个名为build
的目录中生成所有的文件。
-G Ninja
用来切换生成器,也可以通过GUI设定,完整列表如下:
The following generators are available on this platform (* marks default):
* Visual Studio 17 2022 = Generates Visual Studio 2022 project files.
Use -A option to specify architecture.
Visual Studio 16 2019 = Generates Visual Studio 2019 project files.
Use -A option to specify architecture.
Visual Studio 15 2017 [arch] = Generates Visual Studio 2017 project files.
Optional [arch] can be "Win64" or "ARM".
Visual Studio 14 2015 [arch] = Generates Visual Studio 2015 project files.
Optional [arch] can be "Win64" or "ARM".
Visual Studio 12 2013 [arch] = Generates Visual Studio 2013 project files.
Optional [arch] can be "Win64" or "ARM".
Visual Studio 11 2012 [arch] = Deprecated. Generates Visual Studio 2012
project files. Optional [arch] can be
"Win64" or "ARM".
Visual Studio 9 2008 [arch] = Generates Visual Studio 2008 project files.
Optional [arch] can be "Win64" or "IA64".
Borland Makefiles = Generates Borland makefiles.
NMake Makefiles = Generates NMake makefiles.
NMake Makefiles JOM = Generates JOM makefiles.
MSYS Makefiles = Generates MSYS makefiles.
MinGW Makefiles = Generates a make file for use with
mingw32-make.
Green Hills MULTI = Generates Green Hills MULTI files
(experimental, work-in-progress).
Unix Makefiles = Generates standard UNIX makefiles.
Ninja = Generates build.ninja files.
Ninja Multi-Config = Generates build-<Config>.ninja files.
Watcom WMake = Generates Watcom WMake makefiles.
CodeBlocks - MinGW Makefiles = Generates CodeBlocks project files.
CodeBlocks - NMake Makefiles = Generates CodeBlocks project files.
CodeBlocks - NMake Makefiles JOM
= Generates CodeBlocks project files.
CodeBlocks - Ninja = Generates CodeBlocks project files.
CodeBlocks - Unix Makefiles = Generates CodeBlocks project files.
CodeLite - MinGW Makefiles = Generates CodeLite project files.
CodeLite - NMake Makefiles = Generates CodeLite project files.
CodeLite - Ninja = Generates CodeLite project files.
CodeLite - Unix Makefiles = Generates CodeLite project files.
Eclipse CDT4 - NMake Makefiles
= Generates Eclipse CDT 4.0 project files.
Eclipse CDT4 - MinGW Makefiles
= Generates Eclipse CDT 4.0 project files.
Eclipse CDT4 - Ninja = Generates Eclipse CDT 4.0 project files.
Eclipse CDT4 - Unix Makefiles= Generates Eclipse CDT 4.0 project files.
Kate - MinGW Makefiles = Generates Kate project files.
Kate - NMake Makefiles = Generates Kate project files.
Kate - Ninja = Generates Kate project files.
Kate - Unix Makefiles = Generates Kate project files.
Sublime Text 2 - MinGW Makefiles
= Generates Sublime Text 2 project files.
Sublime Text 2 - NMake Makefiles
= Generates Sublime Text 2 project files.
Sublime Text 2 - Ninja = Generates Sublime Text 2 project files.
Sublime Text 2 - Unix Makefiles
= Generates Sublime Text 2 project files.
-A <platform-name>
提供target,对VS可选内容为Win32/x64/ARM/ARM64
Win32, Release版本的指令:
cd <root>
cmake -S . -B build -A Win32 # 在build目录,生成32位的Debug;Release;MinSizeRel;RelWithDebInfo 配置版本
cmake --build build --config Release # 使用build目录里的配置信息,编译选项为Release的程序版本
一个综合例子
假设有一个项目,tool1目录提供了一个static库的源代码,tool2提供了一个DLL/so的源代码,app提供了主程序的源代码,comm提供了一些公用库和文件,大概结构如下:
proj02
comm # 公用代码
comm.h
comm.cpp
version.h
version.c
tool1 # static library
tool1.h
tool1.cpp
CMakeList.txt
tool2 # dll/so library
tool2.h
tool2.cpp
CMakeLists.txt
main.h
main.cpp
CMakeLists.txt
proj02 对应 CMakeLists.txt
cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
project(proj02 C CXX)
message(STATUS "prepare path")
# 自定义lib和exe输出的路径
# set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/Output)
# set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/Output)
message(STATUS "begin build")
# tool1 static lib
add_subdirectory(tool1) # call tool1 CMakeLists.txt
# tool2 dll lib
add_subdirectory(tool2) # call tool2 CMakeLists.txt
# app build
message(STATUS "begin ${PROJECT_NAME}")
# app code
aux_source_directory(. app_src)
# 手动追加源代码
list(APPEND app_src ../comm/comm.cpp ../comm/version.c)
message(STATUS "${PROJECT_NAME} code list ${app_src}")
# exe
add_executable(${
PROJECT_NAME} ${
app_src})
# header
target_include_directories(${
PROJECT_NAME} PUBLIC ./comm ./tool1 ./tool2)
# link
# target_link_directories(${PROJECT_NAME} PUBLIC ${CMAKE_BINARY_DIR}/Output/Release)
target_link_libraries(${
PROJECT_NAME} tool1 tool2)
# post-build action
# copy tool2.dll to exe folder
add_custom_command(TARGET ${
PROJECT_NAME} POST_BUILD
COMMAND ${
CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:tool2>
$<TARGET_FILE_DIR:proj02>)
message(STATUS "build end")
tool1 对应 CMakeLists.txt
cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
project(tool1 C CXX)
message(STATUS "try build ${PROJECT_NAME}")
# add_definitions(-D_DLL_EXPORT=1) # dll export
# tool1 code
aux_source_directory(. tool1_src)
# 手动追加源代码
list(APPEND tool1_src ../comm/comm.cpp ../comm/version.c)
message(STATUS "${PROJECT_NAME} code list ${tool1_src}")
# static lib
add_library(${
PROJECT_NAME} STATIC ${
tool1_src})
# header info
# include_directories(../comm)
target_include_directories(${
PROJECT_NAME} PUBLIC ../comm)
tool2 对应 CMakeLists.txt
cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
project(tool2 C CXX)
message(STATUS "try build ${PROJECT_NAME}")
# tool2 code
aux_source_directory(. tool2_src)
# 手动追加源代码
list(APPEND tool2_src ../comm/comm.cpp ../comm/version.c)
message(STATUS "${PROJECT_NAME} code list ${tool2_src}")
# windows 下必须开启才有lib文件生成
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
# dll
add_library(${
PROJECT_NAME} SHARED ${
tool2_src})
# header info
# include_directories(../comm)
target_include_directories(${
PROJECT_NAME} PUBLIC ../comm)
编译指令如下:
cmake -S . -B build -A Win32
cmake --build build --config Release
# 输出内容在 ./build/Release里面,包含了exe和dll.
# tool1和tool2的库输出文件在对应文件夹下的Release文件夹里面。
代码内容(参考):
// main.h
#include <cstdlib>
#include <iostream>
void app_test();
// main.cpp
#include "main.h"
#include "tool1.h"
#include "tool2.h"
#include "comm.h"
int main() {
app_test();
tool1_test();
tool2_test();
comm_test();
int a = 0;
std::cin >> a;
return EXIT_SUCCESS;
}
void app_test(){
std::cout << "Hello, CMake world!" << std::endl;
}
// comm/comm.h
#ifndef DLL_EXPORT
#define API __declspec(dllexport)
#else
#define API __declspec(dllimport)
#endif
void comm_test();
// comm/comm.cpp
#include <iostream>
#include "comm.h"
void comm_test(){
std::cout << "Hello, comm test!" << std::endl;
}
// comm/version.h
#define APP_VERSION 1.0
// comm/version.c
#include "version.h"
// tool1/tool1.h
#include "comm.h"
extern "C" API void tool1_test();
// tool1/tool1.cpp
#include <iostream>
#include "tool1.h"
void tool1_test(){
std::cout << "Hello, tool1 test!" << std::endl;
}
// tool2/tool2.h
#include "comm.h"
extern "C" API void tool2_test();
// tool2/tool2.cpp
#include <iostream>
#include "tool2.h"
void tool2_test(){
std::cout << "Hello, tool2 test!" << std::endl;
}
这个是一个基础使用的exe依赖static lib和shared lib的综合例子,虽然短小但是已经涵盖了一般使用的大部分需求,更复杂和深入的后续探索。
更多技巧
add_custom_command
find
find_package
...
后续继续学习(qt, boost, 嵌入式)
Reference
- Keil环境如何使用CMake https://segmentfault.com/a/1190000019934297
- Clion/CMake开发STM32 https://www.freesion.com/article/7529133907/ https://ost.51cto.com/posts/13464
- CMake别人的Note https://blog.csdn.net/myw31415926/article/details/127852177
- cmake系列教程 https://blog.csdn.net/myw31415926/category_12119774.html