tartarus's bolg tartarus's bolg
  • Linux and Unix Guide
  • CMake
  • gcc
  • gdb
  • bash
  • GNU Make
  • DDCA-ETH
  • CS106L
  • CS144
  • NJU PA
  • NJU OS(jyy)
  • C
  • C++
  • Python
  • reveal-md
  • LaTex
  • Paper Reading
  • TBD
  • Linux and Unix Guide
  • CMake
  • gcc
  • gdb
  • bash
  • GNU Make
  • DDCA-ETH
  • CS106L
  • CS144
  • NJU PA
  • NJU OS(jyy)
  • C
  • C++
  • Python
  • reveal-md
  • LaTex
  • Paper Reading
  • TBD
  • pdb

  • make

  • cmake

    • Introduction
    • Basic Intro
    • Basic 01-hello-cmake
    • Basic 02-hello-headers
    • Basic 03-static-library
      • 介绍
      • 概念解释
        • 创建一个静态库(Static Library)
        • 指定Including Directories
        • 解析targetincludedirectories
        • 解析targetlinklibraries
        • 链接库文件
      • 构建本例
    • Basic 04-shared-library
    • Basic 05-installing
    • Basic 06-build-type
    • Basic 07-complie-flags
    • Basic 08-third-party-library
    • Basic 09-compiling-with-clang
    • Basic 10-building-with-ninjia
    • Basic 11-cpp-standard
    • Intermediate sub-projects
    • Intermediate static-analysis
    • packge-management 04-conan
    • packge-management 05-vcpkg
    • Offical Tutorial(未完成)
  • Linux and Unix

  • Basic_Software
  • cmake
tartarus
2023-04-15
目录

Basic 03-static-library

# 介绍

本节主要展示一个 hello world 的例子,创建并链接一个静态库。这是一个简化的例子,展示了库和可执行二进制文件在同一个 build 文件夹中。它们会作为 02-sub-projects 章节的子项目出现。

本例的目录🌲:

.
├── CMakeLists.txt  # 包含了本项目运行所需要的CMake命令
├── include          
│   └── static
│       └── Hello.h # main.cpp, Hello.cpp包含的头文件
└── src
    ├── Hello.cpp   # Hello.h头文件对应的cpp文件
    └── main.cpp
1
2
3
4
5
6
7
8

CMakeLists.txt:

cmake_minimum_required(VERSION 3.5)

project(hello_library)

############################################################
# Create a library
############################################################

#Generate the static library from the library sources
add_library(hello_library STATIC 
    src/Hello.cpp
)

target_include_directories(hello_library
    PUBLIC 
        ${PROJECT_SOURCE_DIR}/include
)


############################################################
# Create an executable
############################################################

# Add an executable with the above sources
add_executable(hello_binary 
    src/main.cpp
)

# link the new hello_library target with the hello_binary target
target_link_libraries( hello_binary
    PRIVATE 
        hello_library
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 概念解释

# 创建一个静态库 (Static Library)

add_library() 函数使用源文件创建库,需要指定:

  • 静态库的目标名 (注意该目标对应的库会默认以 “目标名.xxx” 的方式来命名,静态库 “lib 目标名.a”, 共享库 “lib 目标名.so”)
  • 库类型 什么是静态库?什么是动态库?见共享库和静态库的区别
  • 构成该库的源文件
add_library(hello_library STATIC # 生成libhello_library.a
    src/Hello.cpp
)
1
2
3

使用建议

我们直接将 main 中被包含的源文件由 add_library 制作为库 (比如这个例子中的 libhello_library.a ),然后在 main 的目标上添加链接库属性 使用add_link_libraries ,这是现代 CMake 的推荐做法,而不使用之前 Basic 01-hello-cmake 和 Basic 02-hello-headers 中使用 add_execute 函数添加 main 的头文件对应的源文件供链接使用。

# 指定 Including Directories

在这个例子中我们使用 target_include_directoris() 函数将声明我们的目标包含 include 目录。

target_include_directories(hello_library 
    PUBLIC
        ${PROJECT_SOURCE_DIR}/include
)
1
2
3
4

注意

hello_library 是项目的名称,在这里还是 library target 的名称,当然两者可以不同,他们之间没有仍和联系

因为是可见性为 PUBLIC,根据继承关系 (什么是继承关系可以看我下面的讲解 PUBLIC VS INTERFACE VS PRIVATE),会使得:

  1. 编译 target 对应的目标库 hello_library.a 时会使用这个包含路径
  2. 编译任何的其他目标,并且这些目标链接了这个库 (比如这个例子中 target_link_libraries(helllo_binary, hello_library) ) 时也会包含这个路径 ${PROJECT_SOURCE_DIR}/include 。

简单解释一下 target_include_directories 就是:
PRIVATE - the directory is added to this target’s include directories
INTERFACE - the directory is added to the include directories for any targets that link this library.
PUBLIC - As above, it is included in this library and also any targets that link this library.
(emmm.... 感觉翻译过来就变味了,所以留了原文在这里)

使用建议:
对于公共的头文件,建议把它放在 ./include 文件夹下定义的文件夹中。因为在通过给 target_include_directories 传递包含目录时,我们经常传递包含目录的根目录 ( ./include ) 给目标,因此在 cpp 的源文件中包含头文件时,你必须要从 ./include 下面的目录开始包含。
比如这个例子中,我们的源文件的头文件格式都是:

#include "static/Hello.h"
1

即从 static 开始,这样做的好处在于:可以防止发生你的头文件名重名。

PUBLIC VS INTERFACE VS PRIVATE:
(关于这里,在 Google 游荡了一天下午,终于算是有点理解了。cmake 真的好复杂啊!其实是我菜😭)

建议直接读 blog:CMake: Public VS Private VS Interface (opens new window) 和 https://kubasejdak.com/modern-cmake-is-like-inheritance (opens new window), 读完其中一个你就懂了。(如果不懂就多读几遍,建议读第一个,我感觉更容易理解些)

要理解上面提到的三者,首先要理解 Modern CMake 是由什么组成的?

Modern CMake = targets + properties

  • target: targets 是 cmake 中最基本的单元,它是一个构建流程的构建目标。

笔记

最常见的 target 有可执行目标文件目标 (executabel object target) 和库目标 (library target)。


# 如何创建target
# target为myExecutable对应的可执行文件名为myExecutable
add_executable(myExecutable
    main.cpp
)

# target为libA,对应的库为libA.a
add_library(libA
    sourceA.cpp
)

add_library(libB
    sourceB.cpp
    sourceB_impl.cpp
)

add_library(libC
    sourceC.cpp
)

# 如何关联不同target
target_link_libraries(myExecutable libA)
target_link_libraries(libA libB)
target_link_libraries(libB libC)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

为了构建 myExecutabel 需要和 libA 链接,为了构建 libA 需要和 libB 链接,为了构建 libB,需要和 libC 链接。
propeties: 每个 target 都有自己的 properties, 最常见的有:

  • compilation flags
  • linking flags
  • preprocessor flags
  • C/C++ standard
  • include directories
    在 cmake 中可以使用以下函数构建:
propeties setting method
include directories target_include_directories
preprocessor flags target_compile_definitions
compilation flags target_compile_options
linking flags target_link_options
C++ standard set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED True)

Modern CMake 的 target_xxxx_xxxx 和 target_xxxx_xxxx 中还引入了可见性: PUBLIC , PRIVATE and INTERFACE .

  • PRIVATE property is only for the internal usage by the property owner,
  • PUBLIC property is for both internal usage by the property owner and for other targets that use it (link with it, inherit from it)
  • INTERFACE property is only for usage by other libraries.
    上面的这三句话中的 other targets 是继承关系中的子对象 (类似于 CPP 中类的继承关系)

接下来就可以解析可见性 PUBLIC , PRIVATE and INTERFACE 为什么像是继承关系了,以 target_include_directories 和 target_link_libraries 为例:

# 解析 target_include_directories

在 CMake 中任何的 target 在预处理阶段,预处理器都会根据 INCLUDE_DIRECTORIES 和 INTERFACE_INCLUDE_DIRECTORIES 两个目标的属性来决定搜索哪些头文件进行预处理。

这就很自然的会让人联想到,target 的 INCLUDE_DIRECTORIES 和 INTERFACE_INCLUDE_DIRECTORIES 是怎么来的呢?谁让 target 有了这两个属性呢?

答案是:target_include_directories

target_include_directories 根据可见性 <PUBLIC|PRIVATE|INTERFACE> 指定了目标两个属性 ( INCLUDE_DIRECTORIES 和 INTERFACE_INCLUDE_DIRECTORIES ).

  • INCLUDE_DIRECTORIES 将被用于生成当前target
  • INTERFACE_INCLUDE_DIRECTORIES 将被用于生成 "其他" 目标,并且这些 "其他" 目标依赖于当前target。
    通过这些配置,使得不同目标的继承关系之间变的清楚明了。

下面是对 target_include_directories 中可见性 PUBLIC,PRIVATE 和 INTERFACE 的解析:

  1. PUBLIC
target_include_directories(target1
  PUBLIC 
  [items1...]
)

target_link_libraries(target2 
  PRIVATE 
  target1
)
1
2
3
4
5
6
7
8
9

注意

target_link_libraries 可以帮助我们把 target1 的 INTERFACE_INCLUDE_DIRECTORIES 属性传递给 target2 INCLUDE_DIRECTORIES

所有的包含路径 (items1) 将被用当前目标 (target1) 和依赖于当前目标 (target1) 的其他目标 (target2)。

换句话说会把这些路径添加到当前目标 (target1) 的 INCLUDE_DIRECTORIES 和 INTERFACE_INCLUDE_DIRECTORIES 两个属性中。

并且 target2 获取 target1 的 INTERFACE_INCLUDE_DIRECTORIES 属性,将路径添加到自己的 INCLUDE_DIRECTORIES 属性中。

  1. PRIVATE
target_include_directories(target1
  PRIVATE 
  [items1...]
)

target_link_libraries(target2 
  PRIVATE 
  target1
)
1
2
3
4
5
6
7
8
9

所有的包含路径 (items1) 将只被用来编译生成当前目标 (target1) 所需要的源文件。

换句话说会把这些路径添加到当前目标 (target1) 的 INCLUDE_DIRECTORIES 属性中,不会添加到 INTERFACE_INCLUDE_DIRECTORIES 属性中。

所以 target2 根据 target1 的 INTERFACE_INCLUDE_DIRECTORIES 属性,就不能将路径 (items1) 添加到自己的 INCLUDE_DIRECTORIES 属性中。

  1. INTERFACE
target_include_directories(target1
  INTERFACE 
  [items1...]
)

target_link_libraries(target2 
  PRIVATE 
  target1
)
1
2
3
4
5
6
7
8
9

所有的包含路径 (items1) 只会添加到依赖于当前目标 (target1) 的其他目标 (target2) 的包含路径中。

换句话说会把这些路径 (items1) 添加到当前目标 (target1) 的 INTERFACE_INCLUDE_DIRECTORIES 属性中,不会添加到 INCLUDE_DIRECTORIES 属性中。

所以在编译 target1 的源文件时,target1 对应的源文件无法获取路径 (items1)
(别忘了有两种方法指定生成 target 对应的二进制文件或者库需要的源文件,它们分别是 add_executabel and add_library() )

但是 target2 根据 target1 的 INTERFACE_INCLUDE_DIRECTORIES 属性,可以将路径添加到自己的 INCLUDE_DIRECTORIES 属性中。

我们可以在本节的 CMakeList.txt 中证明这一点是正确的,比如我们可以修改本节的 CMakeLists:

target_include_directories(hello_library
    PRIVATE 
        ${PROJECT_SOURCE_DIR}/include
)
1
2
3
4

我们把包含目录的可见性改为 PRIVATE,那么:

target_link_libraries(hello_binary
    PRIVATE 
        hello_library
)
1
2
3
4

中的可执行目标 hello_binary 无法继承来自于 hello_libray 中的包含路径,因为目标 hello_library 的 INTERFACE_INCLUDE_DIRECTORIES 属性中没有包含路径 {PROJECT_SOURCE_DIR}/include 。

这会导致使用 CMake 构建完 Makefile 后,使用 GNU Make 构建时会产生 main.cpp 无法找到头文件的路径👀,在我的 linux 上运行的到如下错误:

$ mkdir build

$ cd build

$ cmake ..

$ make
Scanning dependencies of target hello_lib
[ 25%] Building CXX object CMakeFiles/hello_lib.dir/src/Hello.cpp.o
[ 50%] Linking CXX static library libhello_lib.a
[ 50%] Built target hello_lib
Scanning dependencies of target hello_binary
[ 75%] Building CXX object CMakeFiles/hello_binary.dir/src/main.cpp.o
/home/tartarus/cmake/cmake-examples/01-basic/C-static-library/src/main.cpp:1:10: fatal error: static/Hello.h: No such file or directory
    1 | #include "static/Hello.h"
      |          ^~~~~~~~~~~~~~~~
compilation terminated.
make[2]: *** [CMakeFiles/hello_binary.dir/build.make:63: CMakeFiles/hello_binary.dir/src/main.cpp.o] Error 1
make[1]: *** [CMakeFiles/Makefile2:78: CMakeFiles/hello_binary.dir/all] Error 2
make: *** [Makefile:84: all] Error 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 解析 target_link_libraries

target_link_libraries(destination_target 
  <PRIVATE | PUBLIC | INTERFACE>
 source_target 
)

1
2
3
4
5

target_link_libraries 的两个作用:

  • target_link_libraries 是不同 target 之间,包含路径的传递桥梁 (source_target 的 INTERFACE_INCLUDE_DIRECTORIES 属性会传递给 destination_target 的 INCLUDE_DIRECTORIES 属性)。
    示例见解析 target_include_directories

  • 在链接阶段,我们也需要确定给出的 source_target 是否需要被链接。
    我也分别解释一下三个可见性的作用:

  1. PUBLIC: source_target 的目标文件在链接阶段会和 destination_target 一起进行链接,并且如果其他目标依赖于 destination_target,那么也会提供 source_target 供他们链接使用。
  2. PRIVATE: source_target 的目标文件在链接阶段会和 destination_target 一起进行链接。但是如果其他目标依赖于 destination_target,不会提供 source_target 供他们链接使用。
  3. INTERFACE: 如果其他目标依赖于 destination_target,那么也会提供 source_target 供他们链接使用,但是 source_target 的目标文件在链接阶段不会和 destination_target 一起进行链接

举例可以看我提供的链接:blog:CMake: Public VS Private VS Interface (opens new window)

# 链接库文件

当我们创建可执行文件 hello_binary 时,因为 main.cpp 中用到了 #include "static/Hello.h" ,而 Hello.cpp 已经被我们制作成了库文件 hello_library.a 对应的目标时 hello.library,因此我们还需要告诉链接器在链接阶段链接这个库文件:

add_executable(hello_binary
    src/main.cpp
)

target_link_libraries(hello_binary
    PRIVATE
        hello_library
)
1
2
3
4
5
6
7
8

比如本例中连接器就通过如下的指令链接 hello_library.o:

/usr/bin/c++ CMakeFiles/hello_binary.dir/src/main.cpp.o -o hello_binary -rdynamic libhello_library.a
1

当然了,正如我们在解析 target_link_libraries 讨论过的, target_link_libraries 还可以帮助我们把库目标 hello_binary 的 INTERFACE_INCLUDE_DIRECTORIES 属性传递给目标 hello_binary 的 INCLUDE_DIRECTORIES 的属性,使得生成目标 hello_binary 时能够提供给编译器正确的包含路径。

# 构建本例

$ mkdir build

$ cd build

$ cmake ..
-- The C compiler identification is GNU 9.4.0
-- The CXX compiler identification is GNU 9.4.0
-- Check for working C compiler: /usr/lib/ccache/cc
-- Check for working C compiler: /usr/lib/ccache/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/lib/ccache/c++
-- Check for working CXX compiler: /usr/lib/ccache/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/tartarus/cmake/cmake-examples/01-basic/C-static-library/build

$ make 
Scanning dependencies of target hello_lib
[ 25%] Building CXX object CMakeFiles/hello_lib.dir/src/Hello.cpp.o
[ 50%] Linking CXX static library libhello_lib.a
[ 50%] Built target hello_lib
Scanning dependencies of target hello_binary
[ 75%] Building CXX object CMakeFiles/hello_binary.dir/src/main.cpp.o
[100%] Linking CXX executable hello_binary
[100%] Built target hello_binary
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

从 verbose 输出中有几个地方比较有特点需要解释一下,这样可以对 cmake 中一些命令背后做了什么有个大体的了解:

// 编译汇编Hello.cpp为Hello.cpp.o
/usr/lib/ccache/c++ -I/home/tartarus/cmake/cmake-examples/01-basic/C-static-library/include -o CMakeFiles/hello_library.dir/src/Hello.cpp.o -c /home/tartarus/cmake/cmake-examples/01-basic/C-static-library/src/Hello.cpp

// 将Hello.cpp.o制作为静态库libhello_library.a
/usr/bin/ar qc libhello_library.a  CMakeFiles/hello_library.dir/src/Hello.cpp.o
/usr/bin/ranlib libhello_library.a

// 编译汇编的到mian.cpp.o
/usr/lib/ccache/c++   -I/home/tartarus/cmake/cmake-examples/01-basic/C-static-library/include   -o CMakeFiles/hello_binary.dir/src/main.cpp.o -c /home/    tartarus/cmake/cmake-examples/01-basic/C-static-library/src/main.cpp

// 链接生成可执行文件
/usr/lib/ccache/c++     CMakeFiles/hello_binary.dir/src/main.cpp.o  -o hello_binary  libhello_lib.a 
1
2
3
4
5
6
7
8
9
10
11
12
上次更新: 12/27/2023, 8:55:47 AM
Basic 02-hello-headers
Basic 04-shared-library

← Basic 02-hello-headers Basic 04-shared-library→

Theme by Vdoing | Copyright © 2023-2023 tartarus | CC BY-NC-SA 4.0
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式