跨平台工程构建:CMake 的使用(三) 引入库

前一章介绍了 cmake 的基本语法 以及如何构建一个最简单的工程。这里接着聊一聊使用 cmake 构建工程的常用操作:添加库

一、由源代码添加库

这一节中我们将向工程中添加一个库项目。

假设我们需要开发一个 mathlib 库, 并在其他项目中调用,可以像下面这样操作:

  1. 在工程路径下建立子文件夹 mathlib

  2. ./mathlib 中添加项目源文件 mymath.h , mymath.cxx,并添加 CMakeLists.txt:

mymath.h :

1
2
3
4
5
6
7
8
9
#ifdef WIN32
#define MATH_API __declspec(dllexport)
#else
#define MATH_API
#endif

namespace MyMath{
MATH_API int add(int, int);
}

mymath.cxx:

1
2
3
4
5
#include "mymath.h"

int MyMath::add(int a, int b){
return a+b;
}

CMakeLists.txt:

1
add_library(mathlib mymath.cxx mymath.h)
  1. 在主工程中添加代码:
    src.cxx:
1
2
3
4
5
6
7
#include <iostream>
#include "mymath.h"

int main(){
std::cout<<MyMath::add(4,2)<<std::endl;
return 0;
}

CMakeLists.txt:

1
2
3
4
5
6
7
cmake_minimum_required(VERSION 3.0)
add_subdirectory(mathlib)
include_directories(mathlib)

add_executable(proj src.cxx)

target_link_libraries(proj mathlib)

二、由二进制文件添加库

很多时候我们没有库的源码,或者不想源码引入工程中重复编译。此时我们将添加二进制的库。

添加二进制库的一般操作为:

  1. 使用 link_directories 设置库的路径

  2. add_executable 生成文件

  3. target_link_libraries 链接库

如下:
CMakeLists.txt:

1
2
3
4
5
6
7
cmake_minimum_required(VERSION 3.0)

include_directories(./include)

link_directories(${CMAKE_BINARY_DIR}/lib)
add_executable(proj src.cpp)
target_link_libraries(proj mathlib)

src.cpp:

1
2
3
4
5
6
7
#include <iostream>
#include "mymath/mymath.h"

int main(){
std::cout<<MyMath::add(2,4)<<std::endl;
return 0;
}

其中库文件 mathlib.lib 放在 build/lib 路径下。另外要注意这几个命令的顺序。

三、使用 find_package 添加库(包)

在使用以上两种方法时,我们需要知道库的源文件(或头文件) / 库文件(a/lib/so/dylib) 的位置,然后通过 include_directories link_directories 等命令引入工程。如果事先不知道这些信息,也可以使用 find_package 命令来添加预定义在 CMAKE_MODULE_PATH 的或内置的 Find.cmake 模块。

1
2
3
4
find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE]
[REQUIRED] [[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[NO_POLICY_SCOPE])
  • version 兼容版本号。格式应该是 major[.minor[.patch[.tweak]]]

  • EXACT 可选,要求精确版本号

  • QUIET 可选 , 当没有发现库时不发生警告信息

  • REQUIRED 可选, 指明该库是必须的,若没发现库则会终止 cmake

  • COMPONENTS 可选,指明该包需要的组件

cmake 内置了很多常用的包的 cmake 模块,可以直接调用,它们在 cmake 安装目录下的 share/cmake-3.13/Modules 下。例如当我们需要引入 boost 时的工程:
CMakeLists.txt:

1
2
3
4
5
6
7
8
9
10
11
12
cmake_minimum_required(VERSION 3.0)

set(BOOST_ROOT D:/code/opensource/boost/boost_1_68_0) #where you boost source dir
set(BOOST_LIBRARYDIR D:/code/opensource/boost/boost_1_68_0/stage/lib) # where you boost lib dir
find_package(Boost 1.60 REQUIRED)

IF(Boost_FOUND)
include_directories(${Boost_INCLUDE_DIR})
link_directories(${Boost_LIBRARY_DIR})
ENDIF()

add_executable(test src.cxx)

src.cxx:

1
2
3
4
5
6
#include <iostream>
#include <boost/asio/io_service.hpp>
int main(){
boost::asio::io_service ios;
return 0;
}

find_package 工作原理

find_package( ) 先在 ${CMAKE_MODULE_PATH} 路径下,然后在 <CMake_Install_dir>/share/cmake-3.13/Modules 下查找 Find<Nmae>.cmake 。如果没有找到,则会寻找 <Name>Config.cmake<lower-case-name>-config.cmake。前一种称为 模块模式 ,后一种称为 配置模式 。cmake 默认优先使用模块模式,如果找不到模块,则使用配置模式,通常安装库时会拷贝一份 Config.cmake 到系统目录中,因此没有指定搜索路径时也有可能顺利找到包。 不管哪种模式,只要找到包就会定义下面这些变量:

1
2
3
4
<NAME>_FOUND
<NAME>_INCLUDE_DIRS or <NAME>_INCLUDES
<NAME>_LIBRARIES or <NAME>_LIBRARIES or <NAME>_LIBS
<NAME>_DEFINITIONS

如果 <NAME>_FOUND 被定义,说明包已经找到了。那么可以使用 include_directories() 引入 <NAME>_INCLUDE_DIRS 头文件 ,使用 link_directories()target_link_libraries() 来引入库文件。

自定义包

这里以前面的 mathlib 为例,看一下如何自定义一个 cmake 包。
mathlib的 CMakeLists.txt 定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
cmake_minimum_required(VERSION 3.0)
add_library(mathlib mymath.cxx mymath.h)
SET(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR}/install) #设置默认安装路径

#如何安装头文件
target_include_directories(mathlib PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> #头文件源目录
$<INSTALL_INTERFACE:include>) #头文件安装目录

#头文件名称
set_target_properties(mathlib PROPERTIES PUBLIC_HEADER "mymath.h")

#安装库文件
install(TARGETS mathlib
EXPORT mathlib-targets
PUBLIC_HEADER DESTINATION include/mathlib
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin)

#安装 cmake 文件
install(EXPORT mathlib-targets
FILE mathlib-config.cmake
DESTINATION lib/cmake/mathlib)

完成编译后,以上工程会在源文件目录下生成:

  • install/include/mymath 包含了头文件 mymath.h

  • install/lib 包含生成的库文件 mathlib.lib

  • install/lib/cmake/mathlib 包含了两个 cmake 文件 : mathlib-config.cmake , mathlib-config-debug.cmake

使用上面的包:
src.cpp:

1
2
3
4
5
6
#include <iostream>
#include <mathlib/mymath.h>
int main(){
std::cout<<MyMath::add(2,4)<<std::endl;
return 0;
}

CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
cmake_minimum_required(VERSION 3.0)

set(mathlib_DIR "...xxx.../mathlib/install/lib/cmake/mathlib")
find_package(mathlib REQUIRED)

IF(mathlib_FOUND)
message("mathlib founded")
include_directories(${mathlib_INCLUDES})
link_directories(${mathlib_LIBRARIES})
ENDIF()

add_executable(proj src.cpp)
target_link_libraries(proj mathlib)