CMake实践

深入理解CMake(4):find_package寻找系统Pro

2020-05-29  本文已影响0人  BetterCV
package-153360_960_720.png

先前分析过find_package()原理,包括MODULE和CONFIG两种模式,每种模式各自的查找顺序也具体进行了解释。本篇以Protobuf为例,一步步确定cmake的find_package(Protobuf)是如何做到的。

实验基于Ubuntu 16.04系统,使用apt安装的libprotobuf-dev,并且系统里不存在其他版本的protobuf。

1. find_package在MODULE模式下找到Protobuf

find_package(Protobuf REQUIRED)  # 能找到

find_package(Protobuf REQUIRED CONFIG)  # 找不到

也即是:MODULE模式下找到了protobuf。而MODULE模式下无非是先后从CMAKE_MODULE_PATH所指示的路径、cmake安装的Modules目录(如~/soft/cmake/share/cmake-3.17/Modules),根据FindProtobuf.cmake来查找。CMAKE_MODULE_PATH变量默认为空,而cmake安装目录下的FindProtobuf.cmake则提供了完整的查找支持。

找到Protobuf后,提供头文件目录、库文件、可执行文件的具体位置/路径等变量:

``Protobuf_FOUND``
  Found the Google Protocol Buffers library
  (libprotobuf & header files)
``Protobuf_VERSION``
  Version of package found.
``Protobuf_INCLUDE_DIRS``
  Include directories for Google Protocol Buffers
``Protobuf_LIBRARIES``
  The protobuf libraries
``Protobuf_PROTOC_LIBRARIES``
  The protoc libraries
``Protobuf_LITE_LIBRARIES``
  The protobuf-lite libraries

下面根据FindProtobuf.cmake的内容,分析它们是如何被确定的。

FindProtobuf.cmake解读

protobuf的包含目录
使用find_path()定位了Protobuf_INCLUDE_DIR:

# Find the include directory
find_path(Protobuf_INCLUDE_DIR
    google/protobuf/service.h
    PATHS ${Protobuf_SRC_ROOT_FOLDER}/src
)
mark_as_advanced(Protobuf_INCLUDE_DIR)

这里其实埋下了一个坑:如果是自行编译安装的protobuf并且没有提供用于find_package(Protobuf)的脚本的话,你会用find_path来查找protobuf的头文件搜素目录吗?

实际上,apt装好的libprptobuf-dev把它头文件放在了/usr/include/google/protobuf目录下,而/usr/include是系统编译器默认的头文件查找目录。当然能找到了。

protobuf的可执行程序

也就是protoc,是按如下方式确定的:

# Find the protoc Executable
find_program(Protobuf_PROTOC_EXECUTABLE
    NAMES protoc
    DOC "The Google Protocol Buffers Compiler"
    PATHS
    ${Protobuf_SRC_ROOT_FOLDER}/vsprojects/${_PROTOBUF_ARCH_DIR}Release
    ${Protobuf_SRC_ROOT_FOLDER}/vsprojects/${_PROTOBUF_ARCH_DIR}Debug
)
mark_as_advanced(Protobuf_PROTOC_EXECUTABLE)

实际上,apt安装的libprotobuf-dev,包含了放在/usr/bin/protoc这一可执行文件,而/usr/bin默认在PATH环境变量中,因此可以找到。

protobuf的库文件

是这样被查找到的:

# The Protobuf library
_protobuf_find_libraries(Protobuf protobuf)
#DOC "The Google Protocol Buffers RELEASE Library"

_protobuf_find_libraries(Protobuf_LITE protobuf-lite)

过程略繁琐,_protobuf_find_libraries()函数定义如下:

# Internal function: search for normal library as well as a debug one
#    if the debug one is specified also include debug/optimized keywords
#    in *_LIBRARIES variable
function(_protobuf_find_libraries name filename)                                                                                                                                                                                       
  if(${name}_LIBRARIES)
    # Use result recorded by a previous call.
    return()
  elseif(${name}_LIBRARY)
    # Honor cache entry used by CMake 3.5 and lower.
    set(${name}_LIBRARIES "${${name}_LIBRARY}" PARENT_SCOPE)
  else()
    find_library(${name}_LIBRARY_RELEASE
      NAMES ${filename}
      PATHS ${Protobuf_SRC_ROOT_FOLDER}/vsprojects/${_PROTOBUF_ARCH_DIR}Release)
    mark_as_advanced(${name}_LIBRARY_RELEASE)

    find_library(${name}_LIBRARY_DEBUG
      NAMES ${filename}d ${filename}
      PATHS ${Protobuf_SRC_ROOT_FOLDER}/vsprojects/${_PROTOBUF_ARCH_DIR}Debug)
    mark_as_advanced(${name}_LIBRARY_DEBUG)

    select_library_configurations(${name})

    if(UNIX AND Threads_FOUND)
      list(APPEND ${name}_LIBRARIES ${CMAKE_THREAD_LIBS_INIT})
    endif()

    set(${name}_LIBRARY "${${name}_LIBRARY}" PARENT_SCOPE)
    set(${name}_LIBRARIES "${${name}_LIBRARIES}" PARENT_SCOPE)
  endif()
endfunction()

函数有点长,不过可以看出来,核心查找功能的实现是通过调用find_library()来查找库文件;分别找debug和release的库,然后用select_library_configurations来自动修正/设定如下4个变量:

Protobuf_LIBRARY
Protobuf_LIBRARIES
Protobuf_LIBRARY_DEBUG
Protobuf_LIBRARY_RELEASE

显然,这里的find_library()又是一个核心功能。

find_path()原理解读

find_path()的作用,是根据提供的一个文件(可以带有前缀子目录),查找到包含该文件的目录。在前面FindProtobuf.cmake中看到,提供google/protobuf/service.h文件,找到了包含它的目录是/usr/include,作为find_path()的输出变量的Protobuf_INCLUDE_DIR,被设定为/usr/include

如果在/usr/include不存在google/protobuf/service.h呢?find_path()有一堆查找规则,每个规则会查找一个或一些目录,只要查找到就不会进入下一个查找规则。可以关闭或定制其中某些规则。

第一个规则是,从<prefix>/include/<arch>或者<prefix>/include目录下查找。先不管<arch>,因为目标结果确实不在那里面。是<prefix>应该取值为什么呢?cmake文档说是从<PackageName>_ROOT这个cmake变量以及<PackageName>_ROOT环境变量里面遍历出来的;但实际上这个变量值为空。实测<prefix>是根据CMAKE_SYSTEM_PREFIX_PATH来查找的。

作为验证,自行单独写一个CMakeLists.txt进行验证。

先确认确实是第一条规则起作用,找到的头文件目录:

find_path(Protobuf_INCLUDE_DIR
    google/protobuf/service.h
    PATHS ${Protobuf_SRC_ROOT_FOLDER}/src

    NO_DEFAULT_PATH
)

message(STATUS "===== Protobuf_INCLUDE_DIR is: ${Protobuf_INCLUDE_DIR}")

这样就找不到结果了,提示

-- ===== Protobuf_INCLUDE_DIR is: Protobuf_INCLUDE_DIR-NOTFOUND

实际发现,<prefix>是根据CMAKE_SYSTEM_PREFIX_PATH来查找的。我这里打印出来是:

/usr/local;/usr;/;/home/zz/soft/cmake;/usr/local;/usr/X11R6;/usr/pkg;/opt

而尝试在find_path()之前,自行改掉CMAKE_SYSTEM_PREFIX_PATH,去掉其中的/usr,也就是:

set(CMAKE_SYSTEM_PREFIX_PATH "/usr/local;/usr;/;/home/zz/soft/cmake;/usr/local;/usr/X11R6;/usr/pkg;/opt")

然后再调用:

find_path(Protobuf_INCLUDE_DIR
    google/protobuf/service.h
    PATHS ${Protobuf_SRC_ROOT_FOLDER}/src
)

发现这次的结果是找不到。也就是说,我的猜测得到了验证,而这是官方文档(我用3.17版,看得3.17的文档)没说清楚、说错的地方。

还有其他一些个查找头文件的顺序,暂时没看。

find_library()原理解读

仍然在CMakeLists.txt中自行试验:

set(name Protobuf)
set(filename protobuf)

find_library(${name}_LIBRARY_RELEASE
    NAMES ${filename}
    PATHS ${Protobuf_SRC_ROOT_FOLDER}/vsprojects/${_PROTOBUF_ARCH_DIR}Release
    #NO_DEFAULT_PATH
)

mark_as_advanced(${name}_LIBRARY_RELEASE)

message(STATUS "===== Protobuf_LIBRARY_RELEASE is: ${Protobuf_LIBRARY_RELEASE}")

输出:

-- ===== Protobuf_LIBRARY_RELEASE is: /usr/lib/x86_64-linux-gnu/libprotobuf.so

而如果打开注释,也就是设定NO_DEFAULT_PATH则得到:

-- ===== Protobuf_LIBRARY_RELEASE is: Protobuf_LIBRARY_RELEASE-NOTFOUND

因此,对照find_library的文档,这libprotobuf.so也是第一个查找规则被找到的。

和find_path()一样,find_library()的第一条查找规则也是说的有误导性:说是从<prefix>/lib/<arch><prefix>/lib里查找,说<prefix>是从<PackageName>_ROOT着一个cmake变量和环境变量中遍历得到的。实测仍然是根据CMAKE_SYSTEM_PREFIX_PATH变量来遍历<prefix>的。

上一篇 下一篇

猜你喜欢

热点阅读