【cmake】自定义Findxxx.cmake模块
0. 前言
C在CMake中,模块是一种可重用的、可共享的代码单元,通常包含一组定义函数、宏和变量的CMake脚本。CMake自带一些标准模块,例如FindXXX.cmake系列模块,用于查找系统中已安装的库、头文件和程序等。
用户也可以编写自己的模块,并将其共享给其他项目使用。编写xxx.cmake文件是编写模块的一种方式。
1. 自定义模块的好处
在一个项目当中,我们自己的可执行文件和库文件很有可能去引入和链接其他库文件,例如一个demo可执行文件需要去链接一个名为libadd.so的动态库,比较经典的cmake语法如下:
cmake_minimum_required(VERSION 3.15)
project(demo)
# 引入头文件路径
include_directories(${CMAKE_SOURCE_DIR}/include)
# 引入库文件路径
link_directories(${CMAKE_SOURCE_DIR}/lib)
aux_source_directory(. SRC_LIST)
add_executable(${PROJECT_NAME} ${SRC_LIST})
# 链接目标库
target_link_libraries(${PROJECT_NAME} -ladd)
这种方式的弊端是我们作为应用开发者,需要清楚的知道依赖的三方库的路径和名称,以及三方库自己所依赖的外部库,所以我们需要cmake自己去找到需要的依赖库,find_package就可以实现这类的功能,find_package命令需要与xxx.cmake文件配合使用,以便将库的编译选项和链接选项导入到CMake工程中。
2. find_package的搜索模式
2.1 Module mode(模块模式)
此模式是基础工作模式(Basic Signature),也是默认工作模式。在这种模式下,cmake会查找名为FindXxx.cmake的文件,Module模式只有两个查找路径:CMAKE_MODULE_PATH和CMake安装路径下的Modules目录,搜包路径依次为:
CMAKE_MODULE_PATH
CMAKE_ROOT
先在CMAKE_MODULE_PATH变量对应的路径中查找。如果路径为空,或者路径中查找失败,则在CMake安装目录(即CMAKE_ROOT变量)下的Modules目录下(通常为/usr/share/cmake-3.10/Modules,3.10是CMake版本)查找。其中CMAKE_MODULE_PATH默认为空,可以利用set命令赋值。
在安装CMake时,CMake为我们提供了很多开发库的FindXXX.cmake模块文件,可以通过命令查询:
cmake --help-module-list | grep -E ^Find
基本语法:
find_package(<package> [version] [EXACT] [QUIET] [MODULE]
[REQUIRED] [[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[NO_POLICY_SCOPE])
参数解释:
package:必填参数<>下的参数为必填参数。需要查找的包名,注意大小写。
version和EXACT:可选参数,version指定的是版本,如果指定就必须检查找到的包的版本是否和version兼容。如果指定EXACT则表示必须完全匹配的版本而不是兼容版本就可以。
QUIET:可选参数,表示如果查找失败,不会在屏幕进行输出(但是如果指定了REQUIRED字段,则QUIET无效,仍然会输出查找失败提示语)。
MODULE:可选字段。前面提到说“如果Module模式查找失败则回退到Config模式进行查找”,但是假如加入了MODULE选项,那么就只在Module模式查找,如果Module模式下查找失败并不切换到Config模式查找。
REQUIRED:可选字段。表示一定要找到包,找不到的话就立即停掉整个CMake。而如果不指定REQUIRED则CMake会继续执行。
COMPONENTS,components:可选字段,表示查找的包中必须要找到的组件(components),如果有任何一个找不到就算失败,类似于REQUIRED,导致CMake停止执行。
2.1 Config mode(配置模式)
此模式高级工作模式(Full Signature),只有在find_package()中指定CONFIG、NO_MODULE等关键字,或者Module模式查找失败后才会进入到Config模式。在这种模式下查找的是 xxxConfig.cmake的文件,xxx-config.cmake 文件一般是小写开头,XXXConfig.cmake 文件一般是大写开头。配置模式路径较麻烦,可参考Config模式。总结就是,如果我们需要指定特定的库,我们也只需要设置优先级最高的几个变量名即可。包括下面两种情况:
1、如果你明确知道想要查找的库PackageNameConfig.cmake或lower-case-package-name-config.cmake文件所在路径,为了能够准确定位到这个包,可以直接设置变量PackageName_DIR为具体路径,如:
set(Add_DIR "/home/wang/code/FindPackagetest/cmake")
2、如果你有多个包的配置文件需要查找,可以将这些配置文件都统一放在一个命名为cmake的文件夹下,然后设置变量CMAKE_PREFIX_PATH变量指向这个cmake文件夹路径,需要注意根据上述的匹配规则,此时每个包的配置文件需要单独放置在命名为包名的文件夹下(文件夹名不区分大小写),否则会提示找不到。
config mode完整语法
find_package(<package> [version] [EXACT] [QUIET]
[REQUIRED] [[COMPONENTS] [components...]]
[CONFIG|NO_MODULE]
[NO_POLICY_SCOPE]
[NAMES name1 [name2 ...]]
[CONFIGS config1 [config2 ...]]
[HINTS path1 [path2 ... ]]
[PATHS path1 [path2 ... ]]
[PATH_SUFFIXES suffix1 [suffix2 ...]]
[NO_DEFAULT_PATH]
[NO_CMAKE_ENVIRONMENT_PATH]
[NO_CMAKE_PATH]
[NO_SYSTEM_ENVIRONMENT_PATH]
[NO_CMAKE_PACKAGE_REGISTRY]
[NO_CMAKE_BUILDS_PATH] # Deprecated; does nothing.
[NO_CMAKE_SYSTEM_PATH]
[NO_CMAKE_SYSTEM_PACKAGE_REGISTRY]
[CMAKE_FIND_ROOT_PATH_BOTH |
ONLY_CMAKE_FIND_ROOT_PATH |
NO_CMAKE_FIND_ROOT_PATH])
3. 自定义FindAdd.cmake
通过find_package和FindAddd.cmake文件轻松链接libadd.so。
项目结构如下:
├── bin
│ └── demo
├── cmake
│ └── FindAdd.cmake
├── CMakeLists.txt
├── include
│ └── add.h
├── lib
│ └── libadd.so
└── src
└── demo
├── CMakeLists.txt
└── main.cpp
/cmake/FindAdd.cmake如下:
# 查找库文件
FIND_LIBRARY(Add_LIBRARY
NAMES add
PATHS /home/wang/code/FindPackagetest/lib
)
# 查找头文件
FIND_PATH(Add_INCLUDE_DIR
NAMES add.h
PATHS /home/wang/code/FindPackagetest/include
)
# 输出查找结果
IF(Add_LIBRARY AND Add_INCLUDE_DIR)
MESSAGE(STATUS "Found Add: ${Add_LIBRARY}")
MESSAGE(STATUS "Found Add header: ${Add_INCLUDE_DIR}")
ELSE()
MESSAGE(FATAL_ERROR "Could not find Add library and header files")
ENDIF()
# 设置变量
# SET(Add_FOUND TRUE)
SET(Add_LIBRARIES ${Add_LIBRARY})
SET(Add_INCLUDE_DIRS ${Add_INCLUDE_DIR})
/CMakeLists.txt如下:
cmake_minimum_required(VERSION 3.15)
project(All)
# 指定查找的路径
list(APPEND CMAKE_MODULE_PATH /home/wang/code/FindPackagetest/cmake)
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin)
set(LIBRARY_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/lib)
add_subdirectory(src/demo)
/src/demo/CMakeLists.txt如下:
cmake_minimum_required(VERSION 3.15)
project(demo)
find_package(Add REQUIRED)
# 引入头文件路径
include_directories(${Add_INCLUDE_DIRS})
aux_source_directory(. SRC_LIST)
add_executable(${PROJECT_NAME} ${SRC_LIST})
# 链接目标库
target_link_libraries(${PROJECT_NAME} ${Add_LIBRARIES})