背景
当我们的程序引入三方依赖库的时候,我们需要知道以下几个信息:
- 三方库头文件位置:.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 可以很好地解决引用三方库时的路径问题,为不同的机器、不同的系统带来统一的构建方式。