深入理解CMake:find_package()的使用

find_package()原理解讀

根據cmake官方文檔可以知道,find_package()有Module模式(基本用法,basic signature)和Config模式(full signature,完全用法),其中Module模式是基礎,Config模式則更復雜高級些。

區分Module模式和Config模式

Module模式也就是基礎用法(Basic Signature,這裏Signature表示“用法”,而不是“簽名”),Config模式也就是高級用法(Full Signature)。

The CONFIG option, the synonymous NO_MODULE option, or the use of options not specified in the basic signature all enforce pure Config mode. In pure Config mode, the command skips Module mode search and proceeds at once with Config mode search.

也就是說,只有這3種情況下才是Config模式:

  • find_package()中指定CONFIG關鍵字
  • find_package()中指定NO_MODULE關鍵字
  • find_package()中使用了不在"basic signature"(也就是Module模式下所有支持的配置)關鍵字

換句話說,只要我不指定"CONFIG",不指定“NO_MODULE",也不使用"full signature"中的關鍵字,那我就是在Module模式。排查find_package()的第一步,應當判斷它是Module模式還是Config模式

find-package.jpg

image.png

Module模式下find_package()的用法

find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE]

 

         [REQUIRED] [[COMPONENTS] [components...]]
         [OPTIONAL_COMPONENTS components...]
         [NO_POLICY_SCOPE])

Module模式下,相比於Config模式,可選配置參數少一些,並且如果按用戶指定的配置卻找不到包,就會自動進入Config模式(如上圖所示)。

關鍵字解釋
versionEXACT: 都是可選的,version指定的是版本,如果指定就必須檢查找到的包的版本是否和version兼容。如果指定EXACT則表示必須完全匹配的版本而不是兼容版本就可以。

QUIET 可選字段,表示如果查找失敗,不會在屏幕進行輸出(但是如果指定了REQUIRED字段,則QUIET無效,仍然會輸出查找失敗提示語)。

MODULE 可選字段。前面提到說“如果Module模式查找失敗則回退到Config模式進行查找”,但是假如設定了MODULE選項,那麼就只在Module模式查找,如果Module模式下查找失敗並不回落到Config模式查找。

REQUIRED可選字段。表示一定要找到包,找不到的話就立即停掉整個cmake。而如果不指定REQUIRED則cmake會繼續執行。

COMPONENTScomponents:可選字段,表示查找的包中必須要找到的組件(components),如果有任何一個找不到就算失敗,類似於REQUIRED,導致cmake停止執行。

OPTIONAL_COMPONENTScomponents:可選的模塊,找不到也不會讓cmake停止執行。

Module模式查找順序
Module模式下是要查找到名爲Find<PackageName>.cmake的文件。

先在CMAKE_MODULE_PATH變量對應的路徑中查找。如果路徑爲空,或者路徑中查找失敗,則在cmake module directory(cmake安裝時的Modules目錄,比如/usr/local/share/cmake/Modules)查找。

Config模式下find_package()的用法

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模式要找<PackageName>Config.cmake<lower-case-package-name>-config.cmake。查找順序爲:

  1. 名爲<PackageName>_ROOT的cmake變量或環境變量。CMake3.12新增。設定CMP0074 Policy來關閉。
    注意:如果定義了<PackageName>_DIR cmake變量,那麼<PackageName>_ROOT 不起作用。舉例:

 

cmake_minimum_required(VERSION 3.13)

project(fk_cmk)

set(OpenCV_ROOT "F:/zhangzhuo/lib/opencv_249/build")

set(OpenCV_DIR "F:/zhangzhuo/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這一cmake變量的值最先起作用。

  1. cmake特定的緩存變量:

CMAKE_PREFIX_PATH
CMAKE_FRAMEWORK_PATH
CMAKE_APPBUNDLE_PATH
可以通過設定NO_CMAKE_PATH來關閉這一查找順序

  1. cmake特定的環境變量

<PackageName>_DIR
CMAKE_PREFIX_PATH
CMAKE_FRAMEWORK_PATH
CMAKE_APPBUNDLE_PATH
可以通過NO_CMAKE_ENVIRONMENT_PATH來跳過。

  1. HINT字段指定的路徑

  2. 搜索標準的系統環境變量PATH。
    其中如果是以/bin或者/sbin結尾的,會自動轉化爲其父目錄。
    通過指定NO_SYSTEM_ENVIRONMENT_PATH來跳過。

  3. 存儲在cmake的"User Package Registry"(用戶包註冊表)中的路徑。
    通過設定NO_CMAKE_PACKAGE_REGISTRY,或者:
    設定CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY爲true,
    來避開。

  4. 設定爲當前系統定義的cmake變量:

CMAKE_SYSTEM_PREFIX_PATH
CMAKE_SYSTEM_FRAMEWORK_PATH
CMAKE_SYSTEM_APPBUNDLE_PATH
通過設定NO_CMAKE_SYSTEM_PATH來跳過。

  1. 在cmake的"System Package Registry"(系統包註冊表)中查找。
    通過設定NO_CMAKE_SYSTEM_PACKAGE_REGISTRY跳過。
    或者通過設定CMAKE_FIND_PACKAGE_NO_SYSTEM_PACKAGE_REGISTRY爲true。

  2. PATHS字段指定的路徑中查找。

再次總結思路:

    1. 判斷find_package()實際執行的是module模式還是config模式
    • 1.1 find_package(<PackageName>)這樣的用法並不能看出是module模式還是config模式。要看CMAKE_MODULE_PATH或cmake安裝路徑下是否有Find<PackageName>.cmake腳本存在,並且這個腳本是否能正確的找到包。如果上述兩個位置不存在Find<PackageName>.cmake,或者這個Find<PackageName>.cmake執行失敗,則進入config模式。
    • 1.2 通過CONFIG、NO_MODULE、CONFIG模式特有字段,來設定爲config模式
    1. 明確<PackageName>_DIR是config模式特有的緩存變量
    • 2.1可以在find_package()前設定<PackageName>_DIR,指向包含<PackageName>Config.cmake或<lower-case-package-name>-config.cmake的目錄。
      • <PackageName>_ROOT先設定,再設定<PackageName>_DIR,最後find_package(<PackageName>);並且兩個都能找到包,則<PackageName>_DIR起作用。
    • 2.2 也可在find_package()後使用例如打印。
    • 2.3 module模式下在find_package()前使用<PackageName>_DIR,並不能用來幫助find_package()找到包;並且在find_package()後,也並沒有<PackageName>_DIR緩存變量自動存在。
    1. 明確<PackageName>_ROOT是cmake3.12起支持的變量
    • <PackageName>_ROOT變量被find_package, find_library, find_path, find_program, find_file支持。因此,儘管從find_package()文檔頁看會以爲<PackageName>_ROOT只被config模式支持而不被module模式支持,但是module模式下通過另外4個find命令會間接的使用到<PackageName>_ROOT,從而find_package命令的module模式間接的支持<PackageName>_ROOT變量。
    • <PackageName>_ROOT設定後,find_package()的config模式會在<PackageName>_ROOT目錄及其子目錄下尋找cmake的config文件;而<PackageName>_DIR則很傻,不會在子目錄中尋找。
    1. 檢查路徑是否拼寫正確
      以上的3點是正確的,但有時候總髮現幺蛾子,懷疑上面三點說的不對。這時候要檢查路徑是否拼寫正確。
    • 4.1 路徑是否拼寫錯誤,比如少字母、字母寫錯、大小寫拼錯
    • 4.2 如果使用了環境變量來構成cmake變量,注意使用varName。


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章