CMake 进阶 - find_package

2024-06-14 14:34 杨晓琪 332

背景

当我们的程序引入三方依赖库的时候,我们需要知道以下几个信息:

  • 三方库头文件位置:.h
  • 三方库链接文件位置:.so / .a / .dll / .lib / .dylib
  • 三方库链接文件名称

有了这些信息,我们就可以引入三方库,但三方库的安装路径,不同的机器上可能差异很大,并且不同系统的链接文件后缀都不一样。比如 Linux 下是 .so 和 .a,Windows 下是 .dll 和 .lib,Mac 下是 .dylib。因此如果我们在 CMakeLists.txt 中直接指定自己三方库的安装目录,那么别人在使用时大概率就会报错(找不到路径)。

那么有没有什么方法可以解决上面这个问题?答案是肯定的,这时候就需要用到 find_package 了。

find_package

简介

假设我们要引入一个三方库 curl,原来 CMakelists.txt 的写法如下:

# 指定头文件
target_include_directiories({{target_name} PUBLIC /usr/include/curl)

# 指定库文件
target_link_libraries({{target_name} /usr/lib/libcurl.so)

如果用 find_package,写法如下:

find_package(CURL REQUIRED)

# 指定头文件
target_include_directories({{target_name} PUBLIC {{CURL_INCLUDE_DIRS})

# 指定库文件
target_link_libraries({{target_name} {{CURL_LIBRARIES})

对比两种写法,我们发现,第二种不需要指定具体三方库的头文件和库文件的路径,路径搜索由 find_package 来解决。

原理

那么 find_package 是怎么知道去哪里找三方库的头文件和库文件的呢?

首先,find_package 会先在模块路径下搜索 Find<Name>.cmake,这里模块路径由 CMake 的变量 {{CMAKE_MODULE_PATH} 指定。如果找到三方库,则 CMake 会指定以下几个变量:

  • <Name>_FOUND:判断三方库是否找到
  • <Name>_INCLUDE_DIR:三方库的头文件路径
  • <Name>_LIBRARY:三方库的库文件路径

这样我们就可以对三方库进行判断,在编译之前发现引用三方库是否正确。下面我们完善一下 CMakeLists.txt:

find_package(CURL REQUIRED)
if (CURL_FOUND)
  # 指定头文件
  target_include_directories({{target_name} PUBLIC {{CURL_INCLUDE_DIRS})
        # 指定库文件
        target_link_libraries({{target_name} {{CURL_LIBRARIES})
else ()
        # 提示没有找到三方库
        message(FATAL_ERROR "CURL library not found")
endif ()

编写自己的模块

在了解了 find_package 之后,我们就可以编写一个自己的模块(Find<Name>.cmake),让别人来引用。

目录

还是之前计算 demo 的例子,calc 是一个动态库,提供计算功能,供 main.cpp 调用。这次我们使用 find_package 的方式来引用动态库。

func/
- calc.h
- calc.cpp
- CMakeLists.txt
cmake/
- FindCalc.cmake
main.cpp
CMakeLists.txt

FindCalc.cmake

# 从环境变量中获取 Calc 的头文件和库文件
SET(Calc_INCLUDE_DIR {ENV{Calc_INCLUDE_DIR})
SET(Calc_LIBRARY {ENV{Calc_LIBRARY})

# 头文件和库文件都找到则置 FOUND 为 true
if(Calc_INCLUDE_DIR AND Calc_LIBRARY)
    SET(Calc_FOUND true)
endif()

MESSAGE(STATUS "Calc environment/cmake variables set that the user can override")
MESSAGE(STATUS "  Calc_INCLUDE_DIR : {{Calc_INCLUDE_DIR}")
MESSAGE(STATUS "  Calc_LIBRARY    : {{Calc_LIBRARY}")

CMakeLists.txt

cmake_minimum_required(VERSION 3.0)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)

project(Demo)

set(target_name demo)

# 设置模块搜索路径
set(CMAKE_MODULE_PATH {{CMAKE_MODULE_PATH} {{CMAKE_CURRENT_SOURCE_DIR}/cmake)
message("CMAKE_MODULE_PATH: {{CMAKE_MODULE_PATH}")

file(GLOB header_files *.h *.hpp)
file(GLOB src_files *.cpp)

add_executable({{target_name} {{src_files})

# 引用 Calc 动态库
find_package(Calc REQUIRED)
if(Calc_FOUND)
    target_include_directories({{target_name} PUBLIC {{Calc_INCLUDE_DIR})
    target_link_libraries({{target_name} {{Calc_LIBRARY})
else()
    message(FATAL_ERROR "Calc library not found")
endif()

构建

首先构建 Calc 库:

> cd func
> mkdir build
> cd build
> cmake ..
> make

构建完成后会在 build 目录下生成动态库 libcalc.so,我们需要根据 FindCalc.cmake 的搜索路径配置一下头文件和库文件的环境变量:

export Calc_INCLUDE_DIR={{demo_dir}/func
export Calc_LIBRARY={{demo_dir}/func/build/libcalc.so

环境变量生效后,构建调用程序:

> mkdir build
> cd build
> cmake ..
> make

构建完成后就会生成一个可执行文件,运行一下:

> ./demo
result: 3

总结

通过 find_package 可以很好地解决引用三方库时的路径问题,为不同的机器、不同的系统带来统一的构建方式。