CMake教程(三):添加库的使用要求

摘要:本文介绍了如何通过现代 CMake 语法设置目标的使用要求,以便更好地管理库和可执行文件的编译、链接及包含路径等属性。使用要求命令如 target_compile_definitions()target_include_directories()target_link_libraries() 等,允许精确控制编译器和链接器的行为。文章通过重构一个 CMake 项目示例,展示了如何设置库的包含目录、使用 INTERFACE 库来定义编译选项以及链接多个库。此外,还详细解释了 target_include_directories() 的使用场景,包括如何为目标设置私有、公共或接口包含目录。

目标参数的使用要求(Usage Requirements)允许更好地控制库或可执行文件的链接和 include 行,同时也可以更好地控制 CMake 中目标的传递属性。使用要求指的是某个目标(如库或可执行文件)在被其他目标使用时,需要满足的条件或要求,比如包含路径、编译选项、链接库等。这些要求定义了目标对外暴露的接口,以及在链接其他目标时如何传递这些属性。举一个例子,例如我们制造一个物理部件供别人使用,使用要求就是这个物理部件的使用说明书,告诉客户应该怎么使用这个物理部件。

利用使用要求的主要命令是:

  • target_compile_definitions():用于向指定的目标(库、可执行文件等)添加编译选项。这可以使我们对特定的目标定制某些编译选项,这些选项不会影响到其它目标。例如:
 add_library(MyLibrary STATIC mylibrary.cpp)
 target_compile_options(MyLibrary PRIVATE -Wall -Wextra -Werror)

以上命令只向 MyLibrary 这个库添加私有编译选项。

  • target_include_directories():用于向指定的目标添加包含目录,这些目录是编译器在搜索头文件时应该查找的路径。
  • target_link_directories():原本用于向指定的目标(target)添加链接器搜索库的路径(linker search directories),会影响到链接器全局的搜索路径列表。但在 CMake 3.12 之后的版本,更推荐使用 target_link_libraries()
  • target_link_options():为目标(可执行文件、动态链接库和模块库(module library))的链接步骤添加选项。
  • target_precompile_headers():添加需要进行预编译的头文件列表
  • target_sources():向指定的目标添加源文件。这使得我们可以在定义目标之后,仍然能够向其中添加或修改源文件列表,而无需重新定义整个目标。这对于构建系统需要动态地包含排除源文件时特别有用。

本节我们使用现代 CMake 语法重构 CMake教程(二):创建并使用库

在上一节中,当可执行文件使用 MathFunctions 库时,顶层的 CMake 文件需要包含这个库所在的目录。本节我们在自定义的库中定义它自己的使用要求,以便在必要时将其传递给其他目标。简言之,就是在 MathFunctions 子目录下的 CMake 文件当中指定包含目录。这样使用 MathFunctions 库的目标只需要链接这个库,而不需要再添加 include 目录。

本节所用的材料是官网提供的源代码目录下的 Step3

下面的两个练习,将要完成的功能:

  1. 生成 MathFunctions 库的 CMake 文件指定 include 路径。这样使用这个库的目标,在使用 target_include_directories() 是就不用再指定 MathFunctions 的目录。
  2. 使用 INTERFACE 库指定生成目标时所使用的 C++ 标准。这样就可以为每个目标单独设置编译器编译时的选项。

1. 添加库的使用要求

按照如下步骤修改相应的文件:

  1. MathFunctions/CMakeLists.txt
target_include_directories(MathFunctions
                           INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
                           )

${CMAKE_CURRENT_SOURCE_DIR} 是指当前被处理的源目录的路径。
2. 顶层 CMakeLists.txt

target_include_directories(Tutorial PUBLIC
                           "${PROJECT_BINARY_DIR}"
                           )

可以看到,此时可以不用再加上"${PROJECT_SOURCE_DIR}/MathFunctions"

2. 使用 INTERFACE 库设置 C++ 标准

按照如下步骤修改相应的文件:

  1. 修改顶层的 CMakeLists.txt 文件:
add_library(tutorial_compiler_flags INTERFACE)
target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)

target_link_libraries(Tutorial PUBLIC MathFunctions tutorial_compiler_flags)

删除之前的代码:

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

这里把 tutorial_compiler_flags 设置成 INTERFACE 库。SqrtLibraryMathFunctions 库也需要链接 tutorial_compiler_flags。这样做的一个好处就是单独把编译器的选项设置摘取出来,每个库可以使用它自己的 CMake 文件设置的编译器选项设置。

  1. 修改 MathFunctions/CMakeLists.txt
 target_link_libraries(SqrtLibrary PUBLIC tutorial_compiler_flags)
 
 target_link_libraries(MathFunctions PUBLIC tutorial_compiler_flags)

放上结果:
在这里插入图片描述
下面给出顶层和 MathFunctions 目录下的 CMake 文件。

  1. 顶层 CMakeLists.txt
cmake_minimum_required(VERSION 3.10)

# set the project name and version
project(Tutorial VERSION 1.0)

# TODO 4: Replace the following code by:
# * Creating an interface library called tutorial_compiler_flags
#   Hint: use add_library() with the INTERFACE signature
# * Add compiler feature cxx_std_11 to tutorial_compiler_flags
#   Hint: Use target_compile_features()
add_library(tutorial_compiler_flags INTERFACE)
target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)
# specify the C++ standard
# set(CMAKE_CXX_STANDARD 11)
# set(CMAKE_CXX_STANDARD_REQUIRED True)

# configure a header file to pass some of the CMake settings
# to the source code
configure_file(TutorialConfig.h.in TutorialConfig.h)

# TODO 2: Remove EXTRA_INCLUDES list

# add the MathFunctions library
add_subdirectory(MathFunctions)
# list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions")

# add the executable
add_executable(Tutorial tutorial.cxx)

# TODO 5: Link Tutorial to tutorial_compiler_flags

target_link_libraries(Tutorial PUBLIC MathFunctions tutorial_compiler_flags)

# TODO 3: Remove use of EXTRA_INCLUDES

# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
target_include_directories(Tutorial PUBLIC
                           "${PROJECT_BINARY_DIR}"
                           )

  1. MathFunctions/CMakeLists.txt
add_library(MathFunctions MathFunctions.cxx)

# TODO 1: 声明所有链接 MathFunctions 库的目标都需要 include 当前源目录,但 MathFunctions 自身不需要
# Hint: Use target_include_directories with the INTERFACE keyword
target_include_directories(MathFunctions 
                          INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}")
# should we use our own math functions
option(USE_MYMATH "Use tutorial provided math implementation" ON)
if (USE_MYMATH)
  target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
  # library that just does sqrt
  add_library(SqrtLibrary STATIC
              mysqrt.cxx
              )
  # TODO 6: Link SqrtLibrary to tutorial_compiler_flags
  target_link_libraries(SqrtLibrary PUBLIC tutorial_compiler_flags)
  target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
endif()

# TODO 7: Link MathFunctions to tutorial_compiler_flags
target_link_libraries(MathFunctions PUBLIC tutorial_compiler_flags)

3. target_include_directories()指令用法的详细说明

3.1 target_include_directories()

用于向指定的目标添加包含目录,这些目录是编译器在搜索头文件时应该查找的路径。
基本语法:

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

参数说明
1. target: 目标名称,即你想为哪个目标(如一个库或可执行文件)设置包含路径。
2. SYSTEM: 表示将路径标记为系统包含路径,通常用于避免编译时对这些路径中的头文件发出警告(如编译器对系统库发出的警告)。
3. BEFORE: 表示把这些包含路径放在其他默认包含路径之前。
4. <INTERFACE|PUBLIC|PRIVATE>:
INTERFACE: 路径只会添加到链接此目标的其他目标的包含路径中(即“消费者”),不会对目标自身生效。主要用于头文件只对外暴露但自己不需要的库。
PUBLIC: 路径既会添加到目标自身的包含路径中,也会添加到依赖此目标的其他目标中。
PRIVATE: 路径只会添加到目标自身的包含路径中,不会影响依赖此目标的其他目标。例如,目标 B 以 PRIVATE 方式包含了一个目录 path,而目标 A 以某种方式引入了目标B,此时目标 A 是无法索引到目录 path 的。

3.2 PUBLIC, PRIVATEINTERFACE 的作用

3.2.1 设置私有包含目录

target_include_directories(MyTarget PRIVATE /path/to/include)

MyTarget 在编译时可以使用 /path/to/include 作为包含路径,但依赖 MyTarget 的其他目标不能使用该路径。

3.2.2 设置公共包含目录

target_include_directories(MyTarget PUBLIC /path/to/include)

这样,MyTarget 和依赖 MyTarget 的其他目标都可以使用 /path/to/include 作为包含路径。

3.2.3 设置接口包含目录

target_include_directories(MyTarget SYSTEM PUBLIC /usr/local/include)

此时,/path/to/include 只会在依赖 MyTarget 的目标中可见,而 MyTarget 本身在编译时不会使用这个路径。
使用场景
INTERFACE:适合头文件只对外暴露,但自身编译不需要的库。例如,头文件是库接口的一部分,但库的源代码不需要该路径/。
PUBLIC:适合头文件既供目标自己使用,也供依赖此目标的其他目标使用。
PRIVATE:适合头文件只在目标内部使用,不希望其他依赖此目标的代码看到这些头文件。

各位道友,码字不易,如有收获,记得一键三连啊。看完觉得有帮助的道友,可以关注我的公众号,尽可能分享一些高质量的个人成长类读书笔记、生活随笔以及职场经验等等。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值