C/C++生态工具链——编译构建工具CMake/CMakeList初探

一,CMake简介

CMake的全称是Cross-platform Make。我第一次参与Linux C++开发时使用的工具是Make,而后开始切换到CMake,一开始以为CMake是和C语言有关,原来开头的C表示它可以跨平台。

CMake的使用场景:

跨平台编译运行,交叉编译。一般基于CMakeLists.txt文件定义的编译构建规则来生成目标文件和目标库。

CMakeLists.txt样例如下:

bash 复制代码
#cmake最低版本需求
cmake_minimum_required(VERSION 3.13)
#项目名称
project(cmake_study)
#相关设置用set函数
set(CMAKE_CXX_STANDARD 11)
#生成的可执行文件的名称
add_executable(cmake_study src/main.cc)

在Linux环境使用CMake的构建和编译流程如下:

step1. 编写CMake的配置文件------CMakeLists.txt。

**step2.**执行命令 cmake PATH 或者 ccmake PATH 构建生成 Makefile配置文件。PATH为CMakeLists.txt所在的目录。

**step3.**在Makefile文件所在的路径,执行make命令进行编译。

一般使用过程如下:

bash 复制代码
$ mkdir build
$ cd build/
$ cmake ..
$ make

* 为了不让编译产生的中间文件污染项目的文件结构,专门创建build文件夹进行编译构建。

二,CMake与Make的区别

CMake并不直接参与软件的构建和编译,而是生成用于构建的Makefile等配置文件。因此在完成同样的编译任务时,CMake比Make的用法更容易,且屏蔽了Makefile中的很多复杂的语法点。

三,CMakeLists.txt语法

cmake的语法由函数名和参数构成,参数区分大小写,函数名不区分大小写(这个依据个人喜好,笔者习惯用小写,大写有点费眼睛+_+)。

(1) cmake_minimum_required

**含义:**设置项目所需的最低cmake版本以及更新策略

语法:

cmake_minimum_required(VERSION <min>...\ FATAL_ERROR)

使用样例:

cmake_minimum_required(VERSION 2.8.0)


(2) project

**含义:**设置项目的名称、版本、编程语言等信息

语法:

project(<PROJECT-NAME>

VERSION \\[.\\[.\\[.\\]\]\]

LANGUAGES \...)

使用样例:

project(Demo)


(3) set

**含义:**设置普通变量、缓存或环境变量的值

语法:

set(<variable> <value>... PARENT_SCOPE)

set(<variable> <value>... CACHE <type> <docstring> FORCE)

set(ENV{<variable>} \)

使用样例:

set(CMAKE_CXX_COMPILER D:/MinGW/bin/g++)


(4) file

**含义:**定义对文件系统的文件和路径的操作,可以结合Linux指令对文件的操作去理解。

语法:

file(READ <filename> <out-var> ...)

file({WRITE | APPEND} <filename> <content>...)

file(MAKE_DIRECTORY \...)

使用样例:

file(WRITE test.txt "Test Write\n" )


(5) option

**含义:**提供用户可以选择的布尔选项。

语法:

option(<variable> "<help_text>" value)

使用样例:

option(TEST_DEBUG "option for debug" OFF)


(6) if...elseif...endif

**含义:**这个不用详细介绍了,用法同编程语言中的控制语句

语法:

if/else(\)

使用样例:

if(WIN32)

message(STATUS "in Windows System")

elseif(UNIX)

message(STATUS "in Unix System")

endif()


(7) include_directories

**含义:**将指定目录添加到编译器的头文件搜索范围

语法:

include_directories(AFTER\|BEFORE SYSTEM dir1 dir2 ...)

使用样例:

include_directories(../src/com/include)


(8) link_directories

**含义:**添加需要链接的共享库(动态链接库)文件路径,相当于g++命令的-L参数,也相当于Linux环境变量设置LD_LIBRARY_PATH

语法:

link_directories(AFTER\|BEFORE directory1 directory2 ...)

使用样例:

link_directories(${SOURCE_DIR}/lib)


(9) aux_source_directory

**含义:**查找指定目录中的所有源文件,将结果存进指定变量名

语法:

aux_source_directory(<dir> <variable>)

使用样例:

aux_source_directory(../src DIR_SRCS)


(10) add_custom_command

**含义:**添加自定义构建规则

语法:

add_custom_command(OUTPUT output1 output2 ...

COMMAND command1 ARGS args1...

COMMAND command2 \[ARGS args2... ...])

使用样例:

add_custom_command(

TARGET ${_target}

POST_BUILD

COMMAND echo ${_command}

VERBATIM)


(11) add_compile_options

**含义:**设置编译选项

语法:

add_compile_options(<option> ...)

使用样例:

add_compile_options(-std=c++11)


(12) add_subdirectory

**含义:**将子目录添加到构建范围

语法:

add_subdirectory(source_dir binary_dir EXCLUDE_FROM_ALL)

使用样例:

add_subdirectory(utils)


(13) add_executable

**含义:**使用指定的源文件来生成目标可执行文件

语法:

add_executable(<name> WIN32 MACOSX_BUNDLE

EXCLUDE_FROM_ALL

source1 source2 ...)

使用样例:

add_executable(main main.cpp)


(14) add_dependencies

**含义:**给编译目标添加依赖的target

语法:

add_dependencies(<target> \...)

使用样例:

add_dependencies(log com_log)


(15) add_library

**含义:**添加一个库到工程中,指定这个库的源文件

语法:

add_library(<name> STATIC \| SHARED \| MODULE

EXCLUDE_FROM_ALL

\...)

使用样例:

add_library(opencv_core SHARED IMPORTED)


(16) configure_file

**含义:**将文件复制到另一个位置并修改其内容。

语法:

configure_file(<input> <output>

[NO_SOURCE_PERMISSIONS | USE_SOURCE_PERMISSIONS |

FILE_PERMISSIONS <permissions>...])

使用样例:

configure_file(CMakeLists.txt.in download/CMakeLists.txt)


(17) find_package

**含义:**查找依赖的包名

语法:

find_package(<PackageName> version EXACT QUIET MODULE

REQUIRED \[COMPONENTS components...])

使用样例:

find_package(OpenSSL REQUIRED)


(18) find_library

**含义:**查找依赖的库

语法:

find_library (<VAR> name1 path1 path2 ...)

使用样例:

find_library(LOG_LIB log)


(19) find_path

**含义:**搜索包含指定文件名的路径

语法:

find_path (<VAR> name1 path1 path2 ...)

使用样例:

find_path(_ZeroMQ_ROOT NAMES include/zmq.h)


(20) target_link_libraries

**含义:**将之前打包的库链接到生成的目标上

语法:

target_link_libraries(<target> ... <item>... ...)

使用样例:

target_link_libraries(${THREAD_LIB_NAME} pthread)


(21) target_include_directories

**含义:**指定编译生成目标时,需要使用的目录

语法:

target_include_directories(<target> SYSTEM AFTER\|BEFORE

<INTERFACE|PUBLIC|PRIVATE> items1...

\ \[items2... ...])

使用样例:

target_include_directories(hello_library

PUBLIC

${PROJECT_SOURCE_DIR}/include)


(22) target_sources

**含义:**指定构建目标或其依赖项时要使用的源文件

语法:

target_sources(<target>

<INTERFACE|PUBLIC|PRIVATE> items1...

\ \[items2... ...])

使用样例:

target_sources(main PRIVATE main.cpp)


(23) target_compile_definitions

**含义:**在编译目标文件时,指定要用到的编译选项

语法:

target_compile_definitions(<target>

<INTERFACE|PUBLIC|PRIVATE> items1...

\ \[items2... ...])

使用样例:

target_compile_definitions(${PROJECTNAME} PUBLIC ARM7)


(24) message

**含义:**编译过程添加日志消息

语法:

message(\ "message text" ...)

使用样例:

message(STATUS "sources into a library? ${LIBRARY}")


四,CMake常用的环境变量


--CMAKE_C_COMPILER

指定C编译器


--CMAKE_CXX_COMPILER

指定C++编译器


--CMAKE_BUILD_TYPE

指定构建类型,例如Debug, Release


--CMAKE_C_FLAGS

指定C编译器配置


--CMAKE_CXX_FLAGS

指定C++编译器配置


--CMAKE_INSTALL_PREFIX

指定安装的路径前缀


--CMAKE_EXE_LINKER_FLAGS

创建可执行文件时,定义链接器的配置


--CMAKE_MODULE_LINKER_FLAGS

创建模块时,定义链接器的配置


--CMAKE_BINARY_DIR

构建树顶层的完整路径


--PROJECT_BINARY_DIR

构建项目的完整路径


--CMAKE_SOURCE_DIR

源代码树顶层的完整路径


--PROJECT_SOURCE_DIR

当前项目的顶级源目录


--CMAKE_CURRENT_SOURCE_DIR

cmake 当前正在处理的源目录的完整路径


--EXECUTABLE_OUTPUT_PATH

生成的可执行文件路径


--LIBRARY_OUTPUT_PATH

生成的库路径


--BUILD_SHARED_LIBS

通过add_library构建"STATIC/SHARED"库


--CMAKE_CURRENT_LIST_FILE

当前正在处理的文件列表的完整路径


--CMAKE_CURRENT_LIST_LINE

当前正在处理的文件的行号


--CMAKE_MODULE_PATH

提供find_package搜索第三方库时使用的路径


五,开发场景中常见的CMakeList样例

场景一,简单应用

bash 复制代码
cmake_minimum_required(VERSION 3.1...3.24)

#项目声明:项目名/版本号/编码语言
project(
  ModernCMakeExample
  VERSION 1.0
  LANGUAGES C++)

#把源代码添加进构建的目标库
add_library(MyLibExample simple_lib.cpp simple_lib.hpp)

#生成可执行文件
add_executable(MyExample simple_example.cpp)

#设置链接生成的库文件的名称
target_link_libraries(MyExample PRIVATE MyLibExample)

场景二,复杂工程--基于开源项目libjsonutils

bash 复制代码
cmake_minimum_required(VERSION 3.13...3.19 FATAL_ERROR)
project(libjsonutils VERSION 1.0.0 LANGUAGES CXX)

#Make sure that custom modules like FindRapidJSON are found
list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_SOURCE_DIR}/cmake)

# Find system dependencies
set(MIN_BOOST_VERSION 1.65)
find_package(Boost ${MIN_BOOST_VERSION} REQUIRED COMPONENTS regex)

set(MIN_RapidJSON_VERSION 1.1)
find_package(RapidJSON ${MIN_RapidJSON_VERSION} REQUIRED)

# Create target and set properties
add_library(jsonutils
    src/json_utils.cpp
    src/file_utils.h
)

#Add an alias so that library can be used inside the build tree, e.g. when testing
add_library(JSONUtils::jsonutils ALIAS jsonutils)

#Set target properties
target_include_directories(jsonutils
    PUBLIC
        $<INSTALL_INTERFACE:include>
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/src
)

target_compile_features(jsonutils PRIVATE cxx_auto_type)
target_compile_options(jsonutils PRIVATE
    $<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:
    -Wall -Wextra -Wpedantic>)

target_link_libraries(jsonutils
    PUBLIC
        Boost::headers RapidJSON::RapidJSON
    PRIVATE
        Boost::regex
)

场景三,交叉编译,嵌入式场景用的比较多,通过编写toolchain.cmake指定编译时的工具链

toolchain.cmake样例

bash 复制代码
#设定目标操作系统的名称
set(CMAKE_SYSTEM_NAME Windows)

#设定编译器
set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)

#调整find命令的运行模式:在目标环境中搜索头文件和库
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)

#在宿主机环境中搜索程序
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)

学习CMake时,直接对着语法看最枯燥且收获最小。CMake由于足够灵活,带来的问题就是晦涩难懂,笔者发现身边很多经验丰富的开发者在编写CMakeList.txt时一样头疼。所以不能指望像学习脚本语言一样看完一遍便可熟悉,而是应该像查字典一样在开发中学习。可以把一些开源项目下载到自己的编译环境,查看项目中的CMakeList的写法,然后尝试自己编译和修改,可以加深对CMake用法的理解。

参考教程:

《CMake Cookbook》

https://www.hahack.com/codes/cmake/

https://doc.embedfire.com/linux/

https://github.com/Kitware/CMake

https://github.com/pabloariasal/modern-cmake-sample

相关推荐
律宏阔3 小时前
WSL Docker 端口明明空闲,但就是绑不上端口
linux·windows
律宏阔3 小时前
WSL 突然断网,无法 ping 内网或外网
linux·windows
穷人小水滴3 小时前
用容器编译 VirtualBox 虚拟机软件 (ArchLinux, podman)
linux·容器·virtualbox
乱码三千4 小时前
如何优雅地直连无公网 IP 的远程 GPU 服务器
linux·人工智能·程序员
yunwei374 小时前
使用 eBPF 跟踪 Nginx 请求
linux·后端·性能优化
yunwei374 小时前
使用 eBPF 跟踪 MySQL 查询
linux·后端·性能优化
yunwei374 小时前
eBPF 示例教程:使用 XDP 捕获 TCP 信息
linux·后端·性能优化
codigger4 小时前
ObjectSense:一门千行内核、把可靠性写进骨子里的面向对象脚本语言
开发语言·编程·编程语言
GeW4 小时前
制造业高端化,为什么必须重估Linux和数据库?
linux
GeW4 小时前
工业场景中的Linux与数据库选型:高端化转型的技术底座
linux