CMAKE

本文详细介绍了CMake在Linux平台下的使用流程,从编写CMakeLists.txt开始,涵盖源码安装、cmake指令、编译选项、预处理定义、编译二进制目标文件、头文件路径、共享库链接、特殊环境变量、自定义编译选项等方面,深入讲解了target_compile_options、target_compile_features的差异以及如何设置编译选项。此外,还探讨了预处理定义、编译二进制库文件、动态库版本号、使用外部库和头文件的方法,以及CMake的其他高级特性,如FetchContent、file指令、安装命令等。
摘要由CSDN通过智能技术生成


Cmake是一个跨平台的软件构建工具,主要功能是告诉编译器如何工作。
它首先允许开发者编写一种平台无关的 CMakeList.txt 文件来定制整个编译流程,然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。从而做到“Write once, run everywhere”。CMake 是一个比 GNU Make,QT 的 qmake,微软的 MS nmake,BSD Make(pmake) 更高级的编译配置工具。

总体原则

通用项目文件结构
CMake工程文件:

工程目录包含一个 CMakeLists.txt文件,是构建生成器的入口。
下层子目录的处理,使用 add subdirectory() APl, 该命令会处理下层文件夹中的CMakeLists.txt文件,

CMake脚本:

使用<script>.cmake命名的文件,可以使用命令行cmake -P <script>.cmake来指定要执行的脚本文件

CMake Module:

使用<script>.cmake 来命名,文件要在CMAKE _MODULE_PATH可达的路径,Modules 能够使用include()命令来装载

文件分布:

  • CMakeLists.txt文件与代码同路径存放,共同进行配置管理
  • CMake脚本通常放在根目录单独的文件夹中供重用
  • 内部Module,一般也放在根目录下,外部Module(不同工程),需设置CMAKE_MODULE_PATH

示例目录结构如下:

|-- CMakeLists.txt
|-- build
|-- cmake
|  -- test.cmake
|-- hello.c

顶层CMakeLists.txt内容如下:

cmake_minimum_required(VERSION 3.12.1)
project(helloworld)
include(cmake/test.cmake)

cmake/test.cmake内容如下:

add_library(hello OBJECT ${
   CMAKE_CURRENT_LIST_DIR}/hello.c)

本例中CMAKE_CURRENT_LIST_DIR的路径为BASE/cmake , 并非CMakeLists.txt所在的路径BASE

采用Modern CMake风格,优先使用基于target的命令设置编译链接选项。

target_compile_definitions
target_compile_features
target_compile_options
target_include_directories
target_link_directories
target_link_libraries
target_link_options
target_sources

Target的设计趋于“对象”,集合了编译选项,头文件依赖,源代码列表,输出目标类型等信息。也分为“构造函数”,“成员变量”,“成员方法”

  • 构造函数:
    add_executable()用于创建一个可执行的Target
    add_library()根据类型,可以创建动态库Shared, 静态库Static,对象列表Object
  • 成员变量
    COMPILE_DEEINITION INTERFACE_COMPILE_DEFINITIONS. LINK_LIBRARIESINTERFACE LINK LIBRARIES
  • 成员方法:使用对象的成员方法,来修改,设置Target的变量。 Object-Oriented是新构建系统的编写实践,而不直接用set来设置成员变量

Target的编译选项,可以传递给应用自己的Target。

  • PUBLIC
    表示相关的设置不仅作用于当前指定的target,而且会随着依赖关系进行传递
  • PRIVATE
    表示相关的设置仅作用于当前指定的target,不会随依赖关系传递
  • INTERFACE
    表示相关的设置不作用于当前指定的target,但反而会随依赖关系传递

cmake指令不区分大小写,但规范指定指令用小写字母,变量属性用大写字母。

尽量使用内置变量指定目录

  • CMAKE_SOURCE_DIR/CMAKE_CURRENT_SOURCE_DIR 指定源文件路径,
    CMAKE_BINARY_DIR/CMAKE_CURRENT_BINARY_DIR 指定目标文件路径,
    CMAKE_CURRENT_LIST_DIR 指定脚本文件路径,不可混用。
  • CMAKE_SOURCE_DIR表示代码根路径,即顶层CMakeLists.txt所在的路径。
  • CMAKE_CURRENT_SOURCE_DIR表示当前代码路径,即当前CMakeLists.txt所在的路径。
  • CMAKE_BINARY_DIR表示目标文件根路径,即执行cmake命令时所在的路径。
  • CMAKE_CURRENT_BINARY_DIR表示当前目标文件路径,即当前CMakeLists.txt所在的路径在目标文件路径下的映射。
  • CMAKE_CURRENT_LIST_DIR表示当前脚本文件路径。
  • CMAKE_CURRENT_SOURCE_DIR表示当前CMakeLists.txt所在的路径,而CMakeLists.txt和代码路径严格对应,每个目录有且仅有一个,因此CMAKE_CURRENT_SOURCE_DIR表示代码路径是准确的。而CMAKE_CURRENT_LIST_DIR是脚本文件的路径,脚本文件可能会互相调用,它的值在不同脚本文件中取值不同,而脚本文件不只CMakeLists.txt一种,并没有与代码路径一一对应,因此用来表示代码路径不准确。

在 linux 平台下使用 CMake 生成 Makefile 并编译的流程如下:

1.编写 CMake 配置/文件 CMakeLists.txt 。
2.执行命令 cmake PATH 或者 ccmake PATH 生成 Makefile(ccmake 和 cmake 的区别在于前者提供了一个交互式的界面)。其中, PATH 是 CMakeLists.txt 所在的目录。
3.使用 make 命令进行编译。

cmake源码安装

sudo apt install libssl-dev build-essential
git clone https://github.com/Kitware/CMake.git
cd CMake
./bootstrap --prefix=[install_prefix]
sudo make
sudo make install 
echo "export PATH=$PATH:[install_prefix]/bin" >> ~/.bashrc

cmake指令

编译选项

add_compile_optionstarget_compile_options 都是CMake中用于设置编译选项的命令,但它们有不同的作用域和使用方式。

add_compile_options:

add_compile_options([BEFORE] [AFTER] option1 option2 ...)

参数的说明:

  • [BEFORE] [AFTER]: 表示在已经存在的编译选项前面或后面添加新的编译选项。
  • [option1 option2 ...]: 要设置的编译选项列表。

add_compile_options 命令用于为整个项目中的所有目标添加编译选项,可以在项目的 CMakeLists.txt 文件中全局调用。这意味着,所有目标都将使用相同的编译选项。

target_compile_options:

target_compile_options(target-name [BEFORE] [AFTER]
  <INTERFACE|PUBLIC|PRIVATE> [items1...]
  [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])

参数的说明:

  • target-name: 指定要为其设置编译选项的目标名称。
  • [BEFORE] [AFTER]: 表示在已经存在的编译选项前面或后面添加新的编译选项。
  • <INTERFACE|PUBLIC|PRIVATE>: 指定编译选项的范围。PRIVATEPUBLIC项将填充<target>COMPILE OPTIONS属性。PUBLICINTERFACE项将填充的INTERFACE COMPILE OPTIONS属性。PUBLIC选项使得链接该目标的目标文件继承该目标的编译选项。
  • [items...]: 要设置的编译选项列表。
    target_compile_options 命令用于向指定的目标添加编译选项。这个命令的作用域限制在当前 CMake 文件和其导入的文件之内。这意味着,您可以针对不同的目标设置不同的编译选项。

target_compile_options和target_compile_features

target_compile_featurestarget_compile_options 都是 CMake 中用于为目标指定编译选项的命令,但它们之间有一些区别。

作用不同

target_compile_features 命令用于指定目标所需的 C++ 特性和最低特性级别,例如 cxx_std_11,在编译时告诉编译器需要启用 C++11 特性。而 target_compile_options 命令则用于向目标传递编译选项,例如 -Wall,启用所有警告。

使用方式不同

target_compile_features 命令需要指定目标名称和要启用的特性名称,如下所示:

target_compile_features(<target> <PRIVATE|PUBLIC|INTERFACE> <feature> [...])

编译器支持不同

target_compile_features 只能用于启用编译器已经支持的特性,如果指定了一个编译器无法识别的特性,将会导致错误。而target_compile_options 则可以传递所有编译器支持的选项,包括标准选项和厂商扩展选项。

综上所述,target_compile_featurestarget_compile_options 都是用于为目标指定编译选项的命令,但它们之间有一些区别,主要是作用不同、使用方式不同和编译器支持的选项范围不同。在编写 CMakeLists.txt 时,应根据实际情况选择合适的命令。

预处理定义

add_definitionstarget_compile_definitions 都可以用于向 CMake 项目添加预处理定义,但它们的作用范围不同。

add_definitions 会为所有的目标(包括可执行文件、静态库和动态库)添加预处理定义。这意味着如果您在项目中调用了 add_definitions 命令,则所有通过该项目构建的二进制文件都将使用该定义。

target_compile_definitions 则是为特定的目标(如某个可执行文件或库)设置预处理定义。这意味着您可以选择性地为一个或多个目标添加定义,而不必为整个项目添加定义。

add_definitions(-DENABLE_DEBUG  -DABC) # 为所有目标添加-DENABLE_DEBUG  -DABC 宏定义
add_executable(example example.cpp)
target_compile_definitions(example PRIVATE BAR BAZ) # 添加 BAR 和 BAZ 宏定义到 example

等效与向C/C++编译器添加-D定义,比如:add_definitions(-DENABLE_DEBUG -DABC),参数之间用空格分割。
如果你的代码中定义了#ifdef ENABLE_DEBUG #endif,这个代码块就会生效。
如果要添加其他的编译器开关,可以通过CMAKE_C_FLAGS变量和CMAKE_CXX_FLAGS变量设置。

编译二进制目标文件

编译二进制库文件

通过set(LIBRARY_OUTPUT_PATH <路径>)来指定共享库输出的位置。

add_library(name [SHARED|STATIC|MODULE|OBJECT]
[EXCLUDE_FROM_ALL] <source1 source2 ... sourceN>)

cmake系统会自动为你生成libname.X
类型有三种:

  • SHARED,动态库
  • STATIC,静态库
  • MODULE,在使用dyld的系统有效,如果不支持dyld,则被当作SHARED对待。
  • OBJECT库类型定义了编译给定源文件产生的目标文件的非归档集合(.o文件),目标文件集合可以使用$<TARGET_OBJECTS:name>语法作为其他目标的源输入。这是一个生成器表达式,可用于向其他目标提供OBJECT内容:
    add_library(archive OBJECT archive.cpp zip.cpp lzma.cpp)
    add_library(archiveExtras STATIC $<TARGET_OBJECTS:archive> extras.cpp)
    add_executable(test_exe $<TARGET_OBJECTS:archive> test.cpp)
    
    或者,可以将对象链接到其他目标:
    add_library(archive OBJECT archive.cpp zip.cpp lzma.cpp)
    add_library(archiveExtras STATIC extras.cpp)
    target_link_libraries(archiveExtras PUBLIC archive)
    add_executable(test_exe test.cpp)
    target_link_libraries(test_exe archive)
    

EXCLUDE_FROM_ALL参数的意思是这个库不会被默认构建,除非有其他的组件依赖或者手工构建。

name作为target是不能重名的,这意味着,我们无法使用add_library 同时构建同名的静态库和动态库:

add_library(hello STATIC ${
   LIBHELLO_SRC})
add_library(hello SHARED ${
   LIBHELLO_SRC})

我们想要名字相同的静态库和动态库,因为target名称是唯一的,所以,我们肯定不能通过ADD_LIBRARY指令来实现了。这时候我们需要用到另外一个指令:

set_target_properties,其基本语法是:

set_target_properties(target1 target2 ...
PROPERTIES prop1 value1
prop2 value2 ...)

这条指令可以用来设置输出的名称,对于动态库,还可以用来指定动态库版本和API版本。

add_library(hello SHARED ${
   LIBHELLO_SRC})
add_library(hello_static STATIC ${
   LIBHELLO_SRC})
set_target_properties(hello_static PROPERTIES OUTPUT_NAME "hello")

但是这样也不能同时得到libhello.so/libhello.a两个库。可以发现libhello.a已经构建完成,位于build/lib目录中,但是libhello.so去消失了。这个问题的原因是:cmake在构建一个新的target时,会尝试清理掉其他使用这个名字的库,因此,在构建libhello.a时,就会清理掉libhello.so.
为了回避这个问题,需要再次使用set_target_properties定义CLEAN_DIRECT_OUTPUT属性。

set_target_properties(hello PROPERTIES CLEAN_DIRECT_OUTPUT  1)
set_target_properties(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)

这时候,我们再次进行构建,会发现build/lib目录中同时生成了libhello.solibhello.a

动态库版本号

为了实现动态库版本号,我们仍然需要使用set_target_properties指令。
具体使用方法如下:

set_target_properties(hello PROPERTIES VERSION 1.2 SOVERSION 1)

VERSION指代动态库版本,SOVERSION指代API版本。

使用外部共享库和头文件

target_include_directories(), target_compile_definitions() and target_compile_options()命令指定二进制目标的构建规范和使用要求

引入头文件搜索路径

include_directories([AFTER|BEFORE] [SYSTEM] <dir1> <dir2> ...)

这条指令可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,如果路径中包含了空格,可以使用双引号将它括起来,默认的行为是追加到当前的头文件搜索路径的后面,你可以通过两种方式来进行控制搜索路径添加的方式:

  1. CMAKE_include_directories_BEFORE,通过set这个cmake变量为on,可以将添加的头文件搜索路径放在已有路径的前面。
  2. 通过AFTER或者BEFORE参数,也可以控制是追加还是置前。

为目标添加包含路径

target_include_directories是一个 CMake 函数,用于为指定的目标添加包含路径。

target_include_directories(<target> [SYSTEM] [AFTER|BEFORE]
  <INTERFACE|PUBLIC|PRIVATE> [items1...]
  [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])

它有两个参数:

  • 目标名称:要为其添加包含路径的目标的名称。
  • 包含路径列表:要添加的包含路径列表。
    例如,以下代码将 my_lib 目标的包含路径设置为 include 目录:
target_include_directories(my_lib PUBLIC include)

在这个例子中,我们使用了 PUBLIC 修饰符,这意味着 my_lib 的使用者也可以访问这些包含路径(例如,如果另一个库链接到 my_lib,那么这些包含路径也可用于链接到该库)。如果使用 PRIVATE 修饰符,则只有 my_lib 本身才能访问这些路径。

你可以指定多个路径,如下所示:

target_include_directories(my_app PRIVATE
    ${
   PROJECT_SOURCE_DIR}/src
    ${
   PROJECT_SOURCE_DIR}/include
    /usr/local/include
)

其中,PROJECT_SOURCE_DIR 是 CMake 中预定义的变量,表示项目根目录的路径。
总之,target_include_directories 可以帮助你轻松地设置和管理包含路径,使得编译器知道在哪里找到头文件。

include_directoriestarget_include_directories 两个 CMake 指令都用于将包含目录包含到构建中,但它们之间有一些区别。

区别如下:

include_directories 是一个全局指令,不需要指定目标,而 target_include_directories 需要指定一个目标来为其添加包含路径。
include_directories 设置的包含路径对整个项目(包括所有目标)都是可见的,而 target_include_directories 可以在特定的目标范围内设置包含路径。这意味着,在使用 target_include_directories 时,你可以精确地控制哪个目标应该具有哪些包含路径。
include_directories 在现代 CMake 中已经被弃用,因为它被认为是过度简化和不够明确。相比之下,target_include_directories 更加明确和易于管理。
综上所述,如果您在现代 CMake 项目中工作,则应该尽可能使用 target_include_directories 来明确控制包含路径。

为target添加共享库

我们现在需要完成的任务是将目标文件链接到libhello,这里我们需要引入两个新的指令
link_directoriestarget_link_library

link_directories的全部语法是:

link_directories(<directory1> <directory2> ...)

这个指令非常简单,添加非标准的共享库搜索路径,比如,在工程内部同时存在共享库和可执行二进制,在编译时就需要指定一下这些共享库的路径。

target_link_libraries的全部语法是:

target_link_libraries(<target> <library1>
<debug | optimized> <library2>
...)

这个指令可以用来为target添加需要链接的共享库,本例中是一个可执行文件,但是同样可以用于为自己编写的共享库添加共享库链接。

特殊的环境变量

CMAKE_INCLUDE_PATHCMAKE_LIBRARY_PATH是环境变量而不是cmake变量。
使用方法是要在bash中用export或者在csh中使用set命令设置或者CMAKE_INCLUDE_PATH=/home/include cmake ..等方式。

这里简单说明一下,find_path用来在指定路径中搜索文件名,比如:

find_path(myHeader NAMES hello.h PATHS /usr/include
/usr/include/hello)

这里我们没有指定路径,但是,cmake仍然可以帮我们找到hello.h存放的路径,就是因
为我们设置了环境变量CMAKE_INCLUDE_PATH
如果你不使用find_pathCMAKE_INCLUDE_PATH变量的设置是没有作用的,你不能指
望它会直接为编译器命令添加参数-I<CMAKE_INCLUDE_PATH>
以此为例,CMAKE_LIBRARY_PATH可以用在find_library中。
同样,因为这些变量直接为FIND_指令所使用,所以所有使用FIND_指令的cmake模块都会受益。

设置变量

set(<variable> [<value>] [CACHE TYPE DOCSTRING [FORCE]])
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Shilong Wang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值