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í)際中最常遇到的問題:基于CMake構(gòu)建Caffe,如何確保每一個(gè)依賴被正確找到狡恬?盡管用了CMake用了find_package()
珠叔,也看到了Caffe官方的CI構(gòu)建腳本scripts/install-deps.sh
,但是自己機(jī)器不是docker環(huán)境弟劲、如何切換多個(gè)版本的依賴包祷安?
find_package()
命令是用來查找依賴包的,理想情況下兔乞,一句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版本的問題
- apt或brew等系統(tǒng)包管理工具安裝的opencv绘沉,和手動(dòng)編譯的OpenCV共存問題
- 手動(dòng)編譯安裝了多個(gè)版本的OpenCV問題,也許你同時(shí)需要opencv2和opencv3豺总,甚至opencv4
- 多個(gè)protobuf版本問題
- protobuf的python包需要和proto C編譯器protoc版本一致车伞,否則帶python layer的prototxt解析失敗
- 安裝了TensorFlow時(shí)被迫安裝的protobuf3,但是Caffe這邊用的python2喻喳,python protobuf包的版本問題
上面列出的opencv和protobuf是重災(zāi)區(qū)另玖,還有沒有列出來的比如boost版本問題等。解決起來也不難:
- 明確
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 synonymousNO_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
關(guān)鍵字 -
find_package()
中指定NO_MODULE
關(guān)鍵字 -
find_package()
中使用了不在"basic signature"(也就是Module模式下所有支持的配置)關(guān)鍵字
換句話說奶赠,只要我不指定"CONFIG",不指定“NO_MODULE"药有,也不使用"full signature"中的關(guān)鍵字毅戈,那我就是在Module模式。排查find_package()
的第一步愤惰,應(yīng)當(dāng)判斷它是Module模式還是Config模式苇经。
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)鍵字解釋
version
和EXACT
: 都是可選的令花,version
指定的是版本,如果指定就必須檢查找到的包的版本是否和version
兼容凉倚。如果指定EXACT
則表示必須完全匹配的版本而不是兼容版本就可以兼都。
QUIET
可選字段,表示如果查找失敗稽寒,不會(huì)在屏幕進(jìn)行輸出(但是如果指定了REQUIRED
字段扮碧,則QUIET
無效,仍然會(huì)輸出查找失敗提示語(yǔ))杏糙。
MODULE
可選字段慎王。前面提到說“如果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_COMPONENTS
和components
:可選的模塊狐粱,找不到也不會(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>
- 名為
<PackageName>_ROOT
的cmake變量或環(huán)境變量渗饮。CMake3.12新增但汞。設(shè)定CMP0074 Policy來關(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變量的值最先起作用胡桃。
- cmake特定的緩存變量:
CMAKE_PREFIX_PATH
CMAKE_FRAMEWORK_PATH
CMAKE_APPBUNDLE_PATH
可以通過設(shè)定NO_CMAKE_PATH
來關(guān)閉這一查找順序
- cmake特定的環(huán)境變量
<PackageName>_DIR
CMAKE_PREFIX_PATH
CMAKE_FRAMEWORK_PATH
CMAKE_APPBUNDLE_PATH
可以通過NO_CMAKE_ENVIRONMENT_PATH
來跳過踩叭。
HINT
字段指定的路徑搜索標(biāo)準(zhǔn)的系統(tǒng)環(huán)境變量PATH。
其中如果是以/bin
或者/sbin
結(jié)尾的翠胰,會(huì)自動(dòng)轉(zhuǎn)化為其父目錄容贝。
通過指定NO_SYSTEM_ENVIRONMENT_PATH
來跳過。存儲(chǔ)在cmake的"User Package Registry"(用戶包注冊(cè)表)中的路徑之景。
通過設(shè)定NO_CMAKE_PACKAGE_REGISTRY
斤富,或者:
設(shè)定CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY
為true,
來避開闺兢。設(shè)定為當(dāng)前系統(tǒng)定義的cmake變量:
CMAKE_SYSTEM_PREFIX_PATH
CMAKE_SYSTEM_FRAMEWORK_PATH
CMAKE_SYSTEM_APPBUNDLE_PATH
通過設(shè)定NO_CMAKE_SYSTEM_PATH
來跳過茂缚。
在cmake的"System Package Registry"(系統(tǒng)包注冊(cè)表)中查找。
通過設(shè)定NO_CMAKE_SYSTEM_PACKAGE_REGISTRY
跳過屋谭。
或者通過設(shè)定CMAKE_FIND_PACKAGE_NO_SYSTEM_PACKAGE_REGISTRY
為true脚囊。從
PATHS
字段指定的路徑中查找。
再次總結(jié)思路:
-
- 判斷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 通過CONFIG、NO_MODULE看峻、CONFIG模式特有字段阶淘,來設(shè)定為config模式
-
- 明確<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宛瞄,并不能用來幫助find_package()找到包;并且在find_package()后交胚,也并沒有<PackageName>_DIR緩存變量自動(dòng)存在份汗。
-
- 明確<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模式下通過另外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ì)在子目錄中尋找均践。
-
- 檢查路徑是否拼寫正確
以上的3點(diǎn)是正確的,但有時(shí)候總發(fā)現(xiàn)幺蛾子摩幔,懷疑上面三點(diǎn)說的不對(duì)彤委。這時(shí)候要檢查路徑是否拼寫正確。
- 4.1 路徑是否拼寫錯(cuò)誤或衡,比如少字母焦影、字母寫錯(cuò)、大小寫拼錯(cuò)
- 4.2 如果使用了環(huán)境變量來構(gòu)成cmake變量封断,注意使用
$ENV{varName}
而不是$varName
斯辰。
- 檢查路徑是否拼寫正確