CMake笔记(简易教程)
CMake笔记
概述(需要提前了解的知识)
一个c/c++程序从代码到生成二进制文件,需要经历的几个关键步骤:预编译(预处理)、编译、汇编、连接 【编译链接的几个步骤】
-
编译器:目前市面常见的编译器有msvc(微软)、gcc(gnu compiler collection)、clang。
其中,msvc被集成在微软的visual studio当中,用于windows平台下的c++开发;gcc则是gnu/linux的编译工具集合,除了使用在gnu/linux下,也会平移到了windows系统中的MinGW中。而clang则是被主要用于macOS的开发。 -
Visual Studio(集成开发环境IDE:编辑器、编译器msvc、nmake、调试器wingdb),比较傻瓜的进行完整的编译流程,不需要开发者干预。但是,mvsc对c++的标准做了一定的自适配,有一些c++的特性、语法、api做了适当性的调整,可能会导致代码的跨平台兼容性差。
-
windows平台的Visal Studio替代方案:vscode(编辑器)、 MinGW(编译器、调试器)、cmake(编译工具)
-
vcode优点:开源、免费、插件丰富、可定制化极强、小巧;缺点:配置略复杂,部分插件需要科学上网
-
MinGW优点:对c++ 标准特性的支持比msvc高(msvc在某些C++特性上有一定的改动);缺点:编译流程基本靠命令行进行,对开发人员有一定门槛
-
cmake优点:跨平台、支持多种编译器:vc++、gcc、clang等,利于开发者掌握代码模块间的关联性;缺点:官方文档比较杂,没有优秀的官方范例,很多配置项有多种配置方式,一些配置项互相影响导致结果与官方文档说明的功能有差异
CMake
CMake是一个跨平台的c/c++ 构建系统,同时支持主流的c++编译器:gcc/g++、msvc、clang等。
通过一个CMakeLists.txt文件管控整个c++ 项目的代码结构、三方依赖、编译生成方式等,抹平了不同操作系统和编译器的差异。是目前主流的c++ 构建系统工具。
CMakeLists.txt文件
-
CMakeLists.txt文件是CMake进行编译链接的配置文件,保存了项目信息、代码模块关联性、链接库生成配置、可执行文件生成配置、头文件引用配置等,一般由开发者根据项目内容手动编写,保存在项目的根目录下
-
CMakeLists.txt主要结构:
- [项目信息] 一般包括CMake最低版本要求、项名称、版本信息等(可省略)
- [系统变量定义] 主要包括编译器编译参数,和CMake系统变量的定义(非必须)
- [自定义变量选项] 复杂项目中,可能需要手动设置一些宏或者模块选择,可通过自定义变量选项来进行配置(可省略)
- [子项目配置] 多模块开发涉及(非必须)
- [头文件、库文件引用配置] 应用第三方库或多模块开发涉及,非必须
- [编译] 一般add_executable或者add_library即可
- [链接] 链接库(动态库、静态库)连接至上一步生成的产物上
常用语法总览
语法 | 解释 | 其他注意事项 |
---|---|---|
project | 工程名 | 非必须 |
cmake_minimum_required | cmake最低版本需求 | 非必须 |
${} | 引用变量等 | |
add_executable | 编译可执行文件 | 参数1:目标名称 参数2:源文件列表 |
add_library | 编译库文件 | 参数1:目标名称 参数2:STATIC SHARED动态静态 参数3:源文件列表 |
include_directories | 设置头文件目录 | 一般引用第三方库时使用 |
link_directories | 设置库文件目录 | 一般引用第三方库时使用 |
message | 输出文本信息 | 调试输出信息 |
add_compile_options | 设置编译选项 | gcc、g++选项(有些选项可能无效)。 需要使用set(CMAKE_CXX_FLAGS …)来指定 |
set | 设置选项 | 设置CMake系统变量值 |
aux_source_directory | 批量引用源文件 | 搜寻目录下的全部源文件 |
options | 自定义选项 | |
if(),elseif(),else(),endif() | 条件分支 | |
target_link_options | 目标链接选项 | 指定链接器的选项,有时候一些编译器的链接器需要专用的链接参数 |
SET常用选项
选项 | 解释 | 参数 |
---|---|---|
CMAKE_BUILD_TYPE | 编译模式 | “Release”,“Debug” 。PS:微软msvc编译器下,该选项无效,需要有单独的参数进行指定,参见后文 |
EXECUTABLE_OUTPUT_PATH | 设置可执行文件输出目录 | |
CMAKE_ARCHIVE_OUTPUT_DIRECTORY | 设置静态库模块导入文件dll.a(lib)输出目录 | |
CMAKE_RUNTIME_OUTPUT_DIRECTORY | 设置链接库输出目录 | |
CMAKE_LIBRARY_OUTPUT_DIRECTORY | 设置链接库输出目录 | |
LIBRARY_OUTPUT_PATH | 设置静态库、动态库生成路径 | |
CMAKE_INSTALL_PREFIX | 设置make install的安装目录 | 绝对路径 |
BUILD_SHARED_LIBS | 动态库生成开关 | ON/OFF |
CMAKE_CXX_FLAGS | 设置编译选项 | 编译选项 |
PS:链接库输出选项
- CMAKE_ARCHIVE_OUTPUT_DIRECTORY,CMAKE_RUNTIME_OUTPUT_DIRECTORY, CMAKE_LIBRARY_OUTPUT_DIRECTORY, LIBRARY_OUTPUT_PATH 在Linux和Windows下以及不同的编译器的产生的效果不同,可能与CMake版本或者系统差异有关,需要酌情调用,或者全部调用**
- 调用add_compile_options来设置编译选项可能会不生效(因编译器而定)这时候可以使用CMAKE_CXX_FLAGS变量来设置
命令行常用选项
选项 | 说明 | 其他注意事项 |
---|---|---|
-version | 显示cmake版本信息 | |
-help | 显示cmake命令行帮助信息 | |
-G | 选择编译器 | MinGW编译器一般是"MinGW Makefiles" |
-D[选项] | 命令行临时修改选项,[选项]参见SET常用选项 | 如果命令行和CMakeLists.txt里有同样的选项,那么命令行选项设置会失效 |
–build | 在当前目录下执行编译 | |
–target install | 与–build同时使用,执行安装 | 需要提前设置CMAKE_INSTALL_PREFIX |
–parallel | 并行编译开关 | 与–build参数同时调用 |
范例:
1. 范例1(windows msvc)
# 使用VisualStudio 2017版本进行编译
# CMakeLists.txt与build目录同级
mkdir build
cd build
cmake .. -G "Visual Studio 2017 15 Win64" -DCMAKE_INSTALL_PREFIX=d:\package
cmake --build . --parallel --target install --config Release
说明:
cmake … -G “Visual Studio 2017 15 Win64” -DCMAKE_INSTALL_PREFIX=d:\package
- cmake … 从上层目录寻找MakeLists.txt
- -G “Visual Studio 2017 15 Win64” 指明使用vs2017(msvc15) x64 编译器进行编译
- -DCMAKE_INSTALL_PREFIX=d:\package 项目最终安装在d:\package目录下
cmake --build . --parallel --target install --config Release
- –build . 从当前目录开始编译
- –parallel 并行编译(多线程编译)
- –target install 编译完以后进行安装,安装至CMAKE_INSTALL_PREFIX指定的目录
- –config Release 微软msvc编译器需要在编译时指定Debug或Release
2. 范例2(Windows gnu/c++)
#!/bin/bash
# CMakeLists.txt与build目录同级
mkdir build
cd build
cmake .. -G "MinGW Makefiles" -DCMAKE_INSTALL_PREFIX=d:\package -DCMAKE_BUILD_TYPE=Release
cmake --build . --parallel --target install
说明:
cmake … -G “MinGW Makefiles” -DCMAKE_INSTALL_PREFIX=d:\package
- cmake … 从上层目录寻找MakeLists.txt
- -G “MinGW Makefiles” 指明使用MinGW的g++进行编译
- -DCMAKE_INSTALL_PREFIX=d:\package 项目最终安装在d:\package目录下
- -DCMAKE_BUILD_TYPE=Release 使用Release模式进行编译
cmake --build . --parallel --target install
- –build . 从当前目录开始编译
- –parallel 并行编译(多线程编译)
- –target install 编译完以后进行安装,安装至CMAKE_INSTALL_PREFIX指定的目录
- –config Release 微软msvc编译器需要在编译时指定Debug或Release
详细解析
[项目信息]
project([NAME])项目名称
插入字符串即可\
cmake_minimum_required(VERSION 3.0)cmake需求版本
最低版本号,根据所使用的cmake版本可支持的特性来决定,本篇所涉及的特性使用3.0以上即可
[变量定义]
set([NAME] [VALUE])设定变量
一般复杂项目时,部分字符串、文件名会反复调用,定义变量保存,方便编写,使用${NAME}可得到变量值
message(${NAME})输出内容
在cmake准备阶段,message会输出指定变量或者宏的值,方便开发者确认详细配置内容\
[自定义配置选项]
option([NAME] [DESCRIPTION] [DEFAULT_VALUE])设置自定义选项
自定义执行选项,可在cmake执行阶段,通过命令行改变选项,一般配合if()else()进行编译分支
NAME-选项名称
DESCRIPTION-描述字符串
DEFAULT_VALUE-默认值ON/OFF或者字符串\
if([表达式])…elseif([表达式])…else()…endif() 条件分支
关系运算符 | 解释 |
---|---|
AND | 与 |
NOT | 非 |
OR | 或 |
EXIST | 目录、文件是否存在 |
EQUAL | 变量等于 |
LESS | 变量小于 |
GREATER | 变量大于 |
STREQUAL | 字符串等于 |
STRELESS | 字符串小于 |
STREGREATER | 字符串大于 |
[子项目配置]
add_subdirectory([DIR])添加子项目
运用在多模块项目中,主模块CMakeLists.txt中应用其他模块,DIR目录为绝对路径,该目录下必须要有对应的源文件和CMakeLists.txt\
[头文件、库文件引用配置]
include_directories([DIR])添加头文件目录
一般用在引用第三方模块\
link_directories([DIR])添加链接库目录
一般用在引用第三方模块\
[编译]
add_executable([OUT] [SOURCE FILE LIST])编译可执行文件
- OUT-可执行文件名
- SOURCE FILE LIST-编译OUT需要的源文件列表
add_library([OUT] [TYPE] [SOURCE FILE LIST])编译链接库
- OUT-编译输出的链接库名称,PS:cmake采用Linux命名规则,链接库会自动添加"lib"前缀
- TYPE-链接库类型:STATIC-静态库,SHARED-动态库;如果不指定,则默认静态库
- SOURCE FILE LIST-编译OUT需要的源文件列表
aux_source_directory([DIR] [NAME])查找目录中所有源文件
在调用add_executable或add_library时,需要列出全部的源文件。大项目中,源文件如果很多,一个一个添加很复杂,可以使用aux_source_directory自动扫描[DIR]下的全部源文件,赋值给[NAME]变量,再通过NAME引用
aux_source_directory(${CMAKE_SOURCE_DIR}/src CPPS)//扫描CMakeLists.txt所在目录下src目录下的全部源文件,定义为CPPSadd_executable(out ${CPPS}) //使用CPPS来编译可执行文件out
[链接]
target_link_libraries([OUT] [LIBS])链接库文件
将LIBS的全部库文件链接到[OUT]可执行文件、库文件中
- target_link_libraries必须写在add_executable和add_library之后
- cmake采用的是Linux命名规则,所以指定LIBS时,如果只写库名,会在库名前加lib,默认优先查找静态库,再查找动态库
- LIBS可以指定完整的库文件名
- 不建议在一条target_link_libraries中混合使用2,3中的静态、动态、自动查找方式,否则可能会导致未知错误
-Wl,-rpath选项的用法
设置二进制文件bin的动态库加载路径
## 指定bin的链接器参数
set_target_properties(bin PROPERTIES LINK_FLAGS "-Wl,-rpath=./")
target_link_options(bin PRIVATE -Wl,-rpath=$ORIGIN)## 在使用"./"时,又是不一定生效,所以需要使用ld链接器的参数$ORIGIN来指定当前路径
-------------------- --------------------
Cmake 命令
以下以编译第三方开源库为例
构建编译系统 win/msvc
#一般来说,需要开发者在CMakeLists.txt所在同级目录创建一个构建系统目录
mkdir build
cd build# cmake .. 指定CMakeLists.txt所在路径
# -G选项可以指定编译器与32位或64位,可以使用"make -G"命令列举当前cmake版本支持的全部编译器版本
# -DCMAKE_INSTALL_PREFIX 指定编译的二进制文件、头文件路径安装目录
# -DBUILD_SHARED_LIBS=OFF 禁制编译动态库,只生成静态库
cmake .. -G "Visual Studio 15 2017 Win64" -DCMAKE_INSTALL_PREFIX=/d/library -DBUILD_SHARED_LIBS=OFF# --build . 对当前目录下的构建系统,进行编译(根据所选的编译器)
# --target install 编译完成后,进行安装,安装路径为上一步DCMAKE_INSTALL_PREFIX所指定的路径
# --config=release 指定编译类型为release模式(或debug)
cmake --build . --target install --config=release
构建编译系统 win/mingw
#一般来说,需要开发者在CMakeLists.txt所在同级目录创建一个构建系统目录
mkdir build
cd build# cmake .. 指定CMakeLists.txt所在路径
# -G选项可以指定编译器与32位或64位,可以使用"make -G"命令列举当前cmake版本支持的全部编译器版本
# -DCMAKE_INSTALL_PREFIX 指定编译的二进制文件、头文件路径安装目录
# -DBUILD_SHARED_LIBS=OFF 禁制编译动态库,只生成静态库
cmake .. -G "MinGW Makefiles" -DCMAKE_INSTALL_PREFIX=/d/library -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release# --build . 对当前目录下的构建系统,进行编译(根据所选的编译器)
# --target install 编译完成后,进行安装,安装路径为上一步DCMAKE_INSTALL_PREFIX所指定的路径
cmake --build . --target install
构建编译系统 linux/默认配置
#一般来说,需要开发者在CMakeLists.txt所在同级目录创建一个构建系统目录
mkdir build
cd build# cmake .. 指定CMakeLists.txt所在路径
# -DCMAKE_INSTALL_PREFIX 指定编译的二进制文件、头文件路径安装目录
# -DBUILD_SHARED_LIBS=OFF 禁制编译动态库,只生成静态库
cmake .. -DCMAKE_INSTALL_PREFIX=/d/library -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release# --build . 对当前目录下的构建系统,进行编译(根据所选的编译器)
# --target install 编译完成后,进行安装,安装路径为上一步DCMAKE_INSTALL_PREFIX所指定的路径
cmake --build . --target install
PS:
msvc(visual studio)与其他编译器不同。设置debug/release模式时,msvc是在编译步骤(cmake --build .)使用–config参数来指定debug/release。
而其他编译器,则是在生成构建系统时(cmake …),使用-DCMAKE_BUILD_TYPE来指定debug/release
CMake与交叉编译
由于每个交叉编译链的差异,以及个人部署的环境不同,所以CMake的交叉编译环境,需要根据系统差异进行反复测试才能调试通过
以下为官方文档,且较为通用的配置
ps:在较高版本的cmak下,建议将设置交叉编译工具的命令放置在project()之前,否则cmake可能仍旧会使用系统默认的编译器
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)set(ARMROOT "/home/ubuntu/arm-linux-gnueabi/")//设置编译链的主目录(非必须,如果编译出错时,可以尝试加上)
set(CMAKE_SYSROOT ${ARMROOT})//设置C编译器绝对路径
set(CMAKE_C_COMPILER ${ARMROOT}/bin/arm-linux-gnueabi-gcc)
//设置C++编译器绝对路径
set(CMAKE_CXX_COMPILER ${ARMROOT}/bin/arm-linux-gnueabi-g++)set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)//project(xxxx)
....
....
....