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

created in 2019-03-03 11:44:11
updated in 2019-3-28 14:31:21

依賴包的實(shí)際情況:找不到,不知道如何切換版本

前面兩篇從cmake源碼中命令行入口參數(shù)、Caffe源碼頂層CMakeLists.txt進(jìn)行了解讀,整體有了一個(gè)印象。在此基礎(chǔ)上,考慮實(shí)際中最常遇到的問(wèn)題:基于CMake構(gòu)建Caffe,如何確保每一個(gè)依賴被正確找到?盡管用了CMake用了find_package(),也看到了Caffe官方的CI構(gòu)建腳本scripts/install-deps.sh,但是自己機(jī)器不是docker環(huán)境、如何切換多個(gè)版本的依賴包?

find_package()命令是用來(lái)查找依賴包的,理想情況下,一句find_package()把一整個(gè)依賴包的頭文件包含路徑、庫(kù)路徑、庫(kù)名字、版本號(hào)等情況都獲取到,后續(xù)只管用就好了。但實(shí)際中往往CMake失敗就是出在find_package()的失敗上(這里不考慮后續(xù)make/nmake/msbuild以及編譯器、鏈接器直接執(zhí)行時(shí)的編譯、鏈接出錯(cuò),只討論cmake根據(jù)CMakeLists.txt執(zhí)行時(shí)候的情況),例如:

  • 多個(gè)OpenCV版本的問(wèn)題
    • apt或brew等系統(tǒng)包管理工具安裝的opencv,和手動(dòng)編譯的OpenCV共存問(wèn)題
    • 手動(dòng)編譯安裝了多個(gè)版本的OpenCV問(wèn)題,也許你同時(shí)需要opencv2和opencv3,甚至opencv4
  • 多個(gè)protobuf版本問(wèn)題
    • protobuf的python包需要和proto C編譯器protoc版本一致,否則帶python layer的prototxt解析失敗
    • 安裝了TensorFlow時(shí)被迫安裝的protobuf3,但是Caffe這邊用的python2,python protobuf包的版本問(wèn)題

上面列出的opencv和protobuf是重災(zāi)區(qū),還有沒(méi)有列出來(lái)的比如boost版本問(wèn)題等。解決起來(lái)也不難:

  • 明確find_package()的N大查找順序
  • 知道如何讓find_package()找到非CMake構(gòu)建安裝的依賴包

find_package()原理解讀

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

區(qū)分Module模式和Config模式

Module模式也就是基礎(chǔ)用法(Basic Signature,這里Signature表示“用法”,而不是“簽名”),Config模式也就是高級(jí)用法(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.

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

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

換句話說(shuō),只要我不指定"CONFIG",不指定“NO_MODULE",也不使用"full signature"中的關(guān)鍵字,那我就是在Module模式。排查find_package()的第一步,應(yīng)當(dāng)判斷它是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模式,可選配置參數(shù)少一些,并且如果按用戶指定的配置卻找不到包,就會(huì)自動(dòng)進(jìn)入Config模式(如上圖所示)。

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

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

MODULE 可選字段。前面提到說(shuō)“如果Module模式查找失敗則回退到Config模式進(jìn)行查找”,但是假如設(shè)定了MODULE選項(xiàng),那么就只在Module模式查找,如果Module模式下查找失敗并不回落到Config模式查找。

REQUIRED可選字段。表示一定要找到包,找不到的話就立即停掉整個(gè)cmake。而如果不指定REQUIRED則cmake會(huì)繼續(xù)執(zhí)行。

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

OPTIONAL_COMPONENTScomponents:可選的模塊,找不到也不會(huì)讓cmake停止執(zhí)行。

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

先在CMAKE_MODULE_PATH變量對(duì)應(yīng)的路徑中查找。如果路徑為空,或者路徑中查找失敗,則在cmake module directory(cmake安裝時(shí)的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比老版本的有更多的查找順序(新增的在最優(yōu)先的查找順序)。它要找的文件名字也不一樣,Config模式要找<PackageName>Config.cmake<lower-case-package-name>-config.cmake。查找順序?yàn)椋?/p>

  1. 名為<PackageName>_ROOT的cmake變量或環(huán)境變量。CMake3.12新增。設(shè)定CMP0074 Policy來(lái)關(guān)閉。
    注意:如果定義了<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}")

實(shí)際上會(huì)找到opencv300,也就是OpenCV_DIR這一cmake變量的值最先起作用。

  1. cmake特定的緩存變量:

CMAKE_PREFIX_PATH
CMAKE_FRAMEWORK_PATH
CMAKE_APPBUNDLE_PATH
可以通過(guò)設(shè)定NO_CMAKE_PATH來(lái)關(guān)閉這一查找順序

  1. cmake特定的環(huán)境變量

<PackageName>_DIR
CMAKE_PREFIX_PATH
CMAKE_FRAMEWORK_PATH
CMAKE_APPBUNDLE_PATH
可以通過(guò)NO_CMAKE_ENVIRONMENT_PATH來(lái)跳過(guò)。

  1. HINT字段指定的路徑

  2. 搜索標(biāo)準(zhǔn)的系統(tǒng)環(huán)境變量PATH。
    其中如果是以/bin或者/sbin結(jié)尾的,會(huì)自動(dòng)轉(zhuǎn)化為其父目錄。
    通過(guò)指定NO_SYSTEM_ENVIRONMENT_PATH來(lái)跳過(guò)。

  3. 存儲(chǔ)在cmake的"User Package Registry"(用戶包注冊(cè)表)中的路徑。
    通過(guò)設(shè)定NO_CMAKE_PACKAGE_REGISTRY,或者:
    設(shè)定CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY為true,
    來(lái)避開(kāi)。

  4. 設(shè)定為當(dāng)前系統(tǒng)定義的cmake變量:

CMAKE_SYSTEM_PREFIX_PATH
CMAKE_SYSTEM_FRAMEWORK_PATH
CMAKE_SYSTEM_APPBUNDLE_PATH
通過(guò)設(shè)定NO_CMAKE_SYSTEM_PATH來(lái)跳過(guò)。

  1. 在cmake的"System Package Registry"(系統(tǒng)包注冊(cè)表)中查找。
    通過(guò)設(shè)定NO_CMAKE_SYSTEM_PACKAGE_REGISTRY跳過(guò)。
    或者通過(guò)設(shè)定CMAKE_FIND_PACKAGE_NO_SYSTEM_PACKAGE_REGISTRY為true。

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

再次總結(jié)思路:

    1. 判斷find_package()實(shí)際執(zhí)行的是module模式還是config模式
    • 1.1 find_package(<PackageName>)這樣的用法并不能看出是module模式還是config模式。要看CMAKE_MODULE_PATH或cmake安裝路徑下是否有Find<PackageName>.cmake腳本存在,并且這個(gè)腳本是否能正確的找到包。如果上述兩個(gè)位置不存在Find<PackageName>.cmake,或者這個(gè)Find<PackageName>.cmake執(zhí)行失敗,則進(jìn)入config模式。
    • 1.2 通過(guò)CONFIG、NO_MODULE、CONFIG模式特有字段,來(lái)設(shè)定為config模式
    1. 明確<PackageName>_DIR是config模式特有的緩存變量
    • 2.1可以在find_package()前設(shè)定<PackageName>_DIR,指向包含<PackageName>Config.cmake或<lower-case-package-name>-config.cmake的目錄。
      • <PackageName>_ROOT先設(shè)定,再設(shè)定<PackageName>_DIR,最后find_package(<PackageName>);并且兩個(gè)都能找到包,則<PackageName>_DIR起作用。
    • 2.2 也可在find_package()后使用例如打印。
    • 2.3 module模式下在find_package()前使用<PackageName>_DIR,并不能用來(lái)幫助find_package()找到包;并且在find_package()后,也并沒(méi)有<PackageName>_DIR緩存變量自動(dòng)存在。
    1. 明確<PackageName>_ROOT是cmake3.12起支持的變量
    • <PackageName>_ROOT變量被find_package, find_library, find_path, find_program, find_file支持。因此,盡管從find_package()文檔頁(yè)看會(huì)以為<PackageName>_ROOT只被config模式支持而不被module模式支持,但是module模式下通過(guò)另外4個(gè)find命令會(huì)間接的使用到<PackageName>_ROOT,從而find_package命令的module模式間接的支持<PackageName>_ROOT變量。
    • <PackageName>_ROOT設(shè)定后,find_package()的config模式會(huì)在<PackageName>_ROOT目錄及其子目錄下尋找cmake的config文件;而<PackageName>_DIR則很傻,不會(huì)在子目錄中尋找。
    1. 檢查路徑是否拼寫正確
      以上的3點(diǎn)是正確的,但有時(shí)候總發(fā)現(xiàn)幺蛾子,懷疑上面三點(diǎn)說(shuō)的不對(duì)。這時(shí)候要檢查路徑是否拼寫正確。
    • 4.1 路徑是否拼寫錯(cuò)誤,比如少字母、字母寫錯(cuò)、大小寫拼錯(cuò)
    • 4.2 如果使用了環(huán)境變量來(lái)構(gòu)成cmake變量,注意使用$ENV{varName}而不是$varName
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 引用cmake學(xué)習(xí)筆記-cmakelist.txt創(chuàng)建項(xiàng)目示例cmake的介紹和使用 Cmake實(shí)踐推薦cmake...
    scott_yu779閱讀 6,152評(píng)論 0 3
  • 首先強(qiáng)烈推薦對(duì)CMake不熟的同學(xué)先看這本書《Cmake實(shí)踐》(提取碼:qgca)。 CMake說(shuō)起來(lái)是個(gè)好東西,...
    金戈大王閱讀 48,437評(píng)論 5 24
  • 注:首發(fā)地址 1. 前言 當(dāng)在做 Android NDK 開(kāi)發(fā)時(shí),如果不熟悉用 CMake 來(lái)構(gòu)建,讀不懂 CMa...
    cfanr閱讀 24,808評(píng)論 1 53
  • CMake學(xué)習(xí) 本篇分享一下有關(guān)CMake的一些學(xué)習(xí)心得以及相關(guān)使用。 本文目錄如下: [1、CMake介紹] [...
    AlphaGL閱讀 12,450評(píng)論 11 79
  • CMake 全稱“cross platform make”,是開(kāi)源、跨平臺(tái)的自動(dòng)化構(gòu)建系統(tǒng)。CMake 由 Kit...
    神齊閱讀 4,303評(píng)論 0 6

友情鏈接更多精彩內(nèi)容