GNU开发工具——CMake模块

一、find_packge高级功能

1、find_package的模式

find_package()有Module模式(基本用法)和Config模式(完全用法),其中Module模式是基础,Config模式则提供复杂高级功能。
find_package是否使用Config模式可以通过下列条件判断:
(1)find_package()中指定CONFIG关键字
(2)find_package()中指定NO_MODULE关键字
(3)find_package()中使用了不再Module模式下所有支持配置的关键字

2、find_package工作流程

GNU开发工具——CMake模块

3、find_package的Module模式

find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE] [REQUIRED] [[COMPONENTS] [components...]] [OPTIONAL_COMPONENTS components...]
[NO_POLICY_SCOPE])

version和EXACT,可选,version指定版本,如果指定就必须检查找到的包的版本是否和version兼容。如果指定EXACT则表示必须完全匹配的版本而不是兼容版本就可以。
QUIET,可选字段,表示如果查找失败,不会在屏幕进行输出(但如果指定了REQUIRED字段,则QUIET无效,仍然会输出查找失败提示语)。
MODULE,可选字段。前面提到说“如果Module模式查找失败则回退到Config模式进行查找”,但是假如设定了MODULE选项,那么就只在Module模式查找,如果Module模式下查找失败并不回落到Config模式查找。
REQUIRED可选字段。表示一定要找到包,找不到的话就立即停掉整个cmake。而如果不指定REQUIRED则cmake会继续执行。
COMPONENTS,components:可选字段,表示查找的包中必须要找到的组件(components),如果有任何一个找不到就算失败,类似于REQUIRED,导致cmake停止执行。
Module模式下需要查找到名为FindPackageName.cmake的文件。
先在CMAKE_MODULE_PATH变量对应的路径中查找。如果路径为空,或者路径中查找失败,则在cmake module directory(cmake安装时的Modules目录,比如/usr/local/share/cmake/Modules)查找。

4、find_package的Config模式

find_package(<PackageName> [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_PACKAGE_ROOT_PATH]
[NO_CMAKE_PATH]
[NO_CMAKE_ENVIRONMENT_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])

Config模式下的查找顺序,比Module模式下要多得多,新版本的CMake比老版本的有更多的查找顺序(新增的在最优先的查找顺序)。Config模式下需要查找到名为lower-case-package-name-config.cmake或PackageNameConfig.cmake文件。查找顺序如下:
(1)名为PackageName_ROOT的cmake变量或环境变量。CMake3.12新增。设定CMP0074 Policy来关闭。如果定义了PackageName_DIR cmake变量,那么PackageName_ROOT 不起作用。

cmake_minimum_required(VERSION 3.13)
project(OpenCVDemo)
set(OpenCV_ROOT "/usr/local/lib/opencv_249/build")
set(OpenCV_DIR "/usr/local/lib/opencv_300/build")

find_package(OpenCV QUIET
    NO_MODULE
    NO_DEFAULT_PATH
    NO_CMAKE_PATH
    NO_CMAKE_ENVIRONMENT_PATH
    NO_SYSTEM_ENVIRONMENT_PATH
    NO_CMAKE_PACKAGE_REGISTRY
    NO_CMAKE_BUILDS_PATH
    NO_CMAKE_SYSTEM_PATH
    NO_CMAKE_SYSTEM_PACKAGE_REGISTRY
)
message(STATUS "OpenCV library status:")
message(STATUS "    version: ${OpenCV_VERSION}")
message(STATUS "    libraries: ${OpenCV_LIBS}")
message(STATUS "    include path: ${OpenCV_INCLUDE_DIRS}")

上述代码会找到opencv300,OpenCV_DIR变量的值有效OpenCV_ROOT变量无效。
(2)cmake特定的缓存变量

CMAKE_PREFIX_PATH
CMAKE_FRAMEWORK_PATH
CMAKE_APPBUNDLE_PATH

可以通过设定NO_CMAKE_PATH来关闭特定缓存变量的查找顺序
(3)CMake特定的环境变量

PackageName_DIR
CMAKE_PREFIX_PATH
CMAKE_FRAMEWORK_PATH
CMAKE_APPBUNDLE_PATH

可以通过NO_CMAKE_ENVIRONMENT_PATH来跳过。
(4)HINT字段指定的路径
(5)搜索标准的系统环境变量PATH。
其中如果是以/bin或者/sbin结尾的,会自动转化为其父目录。
通过指定NO_SYSTEM_ENVIRONMENT_PATH来跳过。
(6)存储在CMake的"User Package Registry"(用户包注册表)中的路径。
通过设定NO_CMAKE_PACKAGE_REGISTRY,或设定CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY为true,来避开。
(7)设定为当前系统定义的cmake变量:
CMAKE_SYSTEM_PREFIX_PATH
CMAKE_SYSTEM_FRAMEWORK_PATH
CMAKE_SYSTEM_APPBUNDLE_PATH
通过设定NO_CMAKE_SYSTEM_PATH来跳过。
(8)在cmake的"System Package Registry"(系统包注册表)中查找。
通过设定NO_CMAKE_SYSTEM_PACKAGE_REGISTRY跳过,或者通过设定CMAKE_FIND_PACKAGE_NO_SYSTEM_PACKAGE_REGISTRY为true避开。
(9)从PATHS字段指定的路径中查找。

二、CMake自定义模块

1、find_xxx指令

FIND_FILE(VAR name path1 path2 …)
VAR变量代表找到的文件全路径,包含文件名。
FIND_LIBRARY(VAR name path1 path2 …)
VAR变量代表找到的库全路径,包含库文件名。
FIND_PATH(VAR name path1 path2 …)
VAR变量代表包含文件的路径
FIND_PROGRAM(VAR name path1 path2 …)
VAR变量代表包含程序的全路径
FIND_PACKAGE(&lt;name&gt; [major.minor] [QUIET] [NO_MODULE] [[REQUIRED | COMPONENTS] [componets …]])
用来调用预定义在CMAKE_MODULE_PATH下的Findname.cmake模块,可以自己定义Findname模块,通过SET(CMAKE_MODULE_PATH dir)将其放入工程的某个目录供工程使用。

FIND_LIBRARY(libX X11 /usr/lib)
IF (NOT libx)
    MESSAGE(FATAL_ERROR "libX not found")
ENDIF(NOT libX)

2、系统预定义模块使用

系统预定义了各种模块,通常需要使用INCLUDE指令显式的调用, 但FIND_PACKAGE指令是一个特例,可以直接调用预定义的模块。
每一个模块都会定义以下3个变量:

<name>_FOUND
<name>_INCLUDE_DIR or <name>_INCLUDES
<name>_LIBRARY or <name>_LIBRARIES

因此,可以通过name_FOUND来判断模块是否被找到,如果没有找到,按照工程的需要关闭某些特性、给出提醒或者中止构建。
对于系统预定义的Findname.cmake模块,使用方法如下:

FIND_PACKAGE(NAME)
IF(NAME_FOUND)
    INCLUDE_DIRECTORIES(${NAME_INCLUDE_DIR})
    TARGET_LINK_LIBRARIES(targetname ${NAME_LIBRARY})
ELSE(NAME_FOUND)
    MESSAGE(FATAL_ERROR ”NAME library not found”)
ENDIF(NAME_FOUND)

工程实践中使用示例如下:

#通过<name>_FOUND来控制工程特性:
SET(SOURCES Viewer.cpp)
SET(OptionalSources)
SET(OptionalLibs)
FIND_PACKAGE(JPEG)
IF(JPEG_FOUND)
    SET(OptionalSources ${OptionalSources} JPEGView.cpp)
    INCLUDE_DIRECTORIES( ${JPEG_INCLUDE_DIR} )
    SET(optionalLibs ${OptionalLibs} ${JPEG_LIBRARIES} )
    ADD_DEFINITIONS(-DENABLE_JPEG_SUPPORT)
ENDIF(JPEG_FOUND)

IF(PNG_FOUND)
    SET(OptionalSources ${OptionalSources} PNGView.cpp)
    INCLUDE_DIRECTORIES( ${PNG_INCLUDE_DIR} )
    SET(OptionalLibs ${OptionalLibs} ${PNG_LIBRARIES} )
    ADD_DEFINITIONS(-DENABLE_PNG_SUPPORT)
ENDIF(PNG_FOUND)

ADD_EXECUTABLE(viewer ${SOURCES} ${OptionalSources} )
TARGET_LINK_LIBRARIES(Viewer ${OptionalLibs})
#通过判断系统是否提供了JPEG、PNG模块来决定程序是否支持JPEG、PNG功能。

3、自定义模块实现

本文基于libHello动态库,编写一个FindHelo.cmake模块。
创建一个FindHello项目目录,进入FindHello。
创建一个Hello目录,进入Hello目录,创建FindHelo.cmake文件,FindHelo.cmake文件如下:

#查找hello库头文件的安装路径
FIND_PATH(HELLO_INCLUDE_DIR Hello.h /usr/local/include/hello)
#查找hello库的安装路径
FIND_LIBRARY(HELLO_LIBRARY NAMES hello /usr/local/lib/libhello.so)

IF (HELLO_INCLUDE_DIR AND HELLO_LIBRARY)
    SET(HELLO_FOUND TRUE)
ENDIF (HELLO_INCLUDE_DIR AND HELLO_LIBRARY)

IF (HELLO_FOUND)
    IF (NOT HELLO_FIND_QUIETLY)
    MESSAGE(STATUS "Found Hello: ${HELLO_LIBRARY}")
    ENDIF (NOT HELLO_FIND_QUIETLY)
ELSE (HELLO_FOUND)
    IF (HELLO_FIND_REQUIRED)
    MESSAGE(FATAL_ERROR "Could not find hello library")
    ENDIF (HELLO_FIND_REQUIRED)
ENDIF (HELLO_FOUND)

返回FindHello目录,创建main.cpp文件:

#include <Hello.h>

int main(int argc, char* argv[])
{
    Hello hello;
    hello.Print();
    return 0;
}

创建工程CMakeLists.txt文件如下:

cmake_minimum_required(VERSION 2.8)
PROJECT(FindHello)
# 设置自定义模块路径
SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/Hello/")
MESSAGE(STATUS "CMAKE_MODULE_PATH " ${PROJECT_SOURCE_DIR}/Hello)
#查找自定义Hello模块
FIND_PACKAGE(Hello)
IF(HELLO_FOUND)
    ADD_EXECUTABLE(hello main.cpp)
    INCLUDE_DIRECTORIES(${HELLO_INCLUDE_DIR})
    MESSAGE(STATUS "LINK " ${HELLO_LIBRARY})
    TARGET_LINK_LIBRARIES(hello ${HELLO_LIBRARY})
ELSE(HELLO_FOUND)
    MESSAGE(STATUS "Could not find hello library")
ENDIF(HELLO_FOUND)

创建build目录,进行build目录进行构建。
cmake ../
make
./hello
结果打印出Hello world.