Last update 2018/8/8html
先執行cmake生成makefile,而後看看裏面的內容,(至少在ubuntu16.04上的cmake3.5.1上),有以下內容提供:python
# Help Target help: @echo "The following are some of the valid targets for this Makefile:" @echo "... all (the default if no target is provided)" @echo "... clean" @echo "... depend" @echo "... edit_cache" @echo "... rebuild_cache" @echo "... net_demo" @echo "... src/net_demo.o" @echo "... src/net_demo.i" @echo "... src/net_demo.s" .PHONY : help
其中net_demo
是我本身的build target,能夠忽略。能夠看到,提供了edit_cache
選項,則經過make edit_cache
能夠交互式的修改一些變量,挺好的。ios
必須用cmake
不少開源項目如KDE、VTK、OpenCV、Caffe等,都使用cmake來構建。要玩轉這些項目,就須要掌握cmake。並且趨勢是cmake會更加流行。c++
實際狀況每每是,系統預裝的opencv不夠用,嘗試編譯opencv的時候遇到cmake各類問題,被折騰的死去活來,因而決定好好學一下cmake。git
cmake資料不夠好
cmake經歷了多個版本發展,如今(2017.05.05)它的官方文檔比原來有了不少進步。github
現有的shell
小白視角
從一個cmake小白的角度來總結cmake的用法。ubuntu
cmake提供命令行方式,以及圖形界面方式(包括cmake-GUI和ccmake)。這裏僅使用cmake命令行方式,由於更簡單直接。windows
使用ubuntu16.04, cmake3.5版。其餘系統和cmake版本問題也都不大,由於本文儘可能作到具有較好的通用性。瀏覽器
先確保安裝了gcc,g++和cmake
最簡單的cmake項目,不使用IDE(如CLion),也不考慮什麼編碼風格的,就是幹,簡單粗暴!
編輯兩個文件:main.cc和CMakeLists.txt。它們放在你的項目目錄,好比~/work/hello-cmake
main.cc
#include <iostream> using namespace std; int main(){ cout << "Hello World!" << endl; return 0; }
CMakeLists.txt
cmake_minimum_required (VERSION 2.8) project(hello-world) add_executable(hello main.cc)
開終端,運行這些命令:
cd ~/work/hello-cmake #進入你的cmake項目目錄 cmake . #執行cmake make #執行make
能夠用Visual Studio來編譯
假設C++代碼和CMakeLists.txt文件內容相同。那麼打開cmd執行以下命令,或者把以下命令保存到一個文件(例如build.bat)中再執行這個文件:
# 在源碼相同目錄下構建 cmake -G "Visual Studio 14" #生成.sln文件 cmake --build . #調用native build tool。在windows上,至關因而打開.sln而後手動點擊構建。
或者在專門的文件夾裏面編譯:
#固然,把編譯出來的東西單獨放到一個目錄也是OK的 mkdir build cd build cmake -G "Visual Studio 14" .. cmake --build . cd ..
以及,能夠指定用Release模式,x64架構:
BUILD_DIR=build rm -rf $BUILD_DIR mkdir -p $BUILD_DIR cd $BUILD_DIR cmake \ -G "Visual Studio 14 Win64" \ .. cmake --build . --config Release # 在windows上,必須在build的時候指定編譯類型,而不是cmake的階段
或者用Unix的構建工具套件,這裏使用的是tdmgcc64。安裝以後把mingw32-make.exe複製一份爲make.exe再執行
mkdir buildUnix cd buildUnix cmake -G "Unix Makefiles" .. make cd ..
cmake命令會在提供的路徑(上例爲".")下,找到CMakeLists.txt並執行它。執行成功後生成makefile(或其餘相似的,目前階段就認爲是makefile好了);執行make,會根據makefile內容去執行。
至於cmake和make執行了哪些操做,須要瞭解cmake的語法,以及makefile的編寫規則。有了cmake,其實能夠忽略makefile,不妨認爲makefile就是寫給編譯器gcc看的。
一般cmake運行後產生若干文件,和源碼目錄混雜在一塊兒並非一個好的選擇。一般新建一個build目錄,在build目錄執行cmake。
我的傾向於建一個腳本,每次用cmake構建時,參數比較多,用這個腳本比較方便:
compile.sh
#!/bin/bash set -x #把本行後的腳本執行內容,打印到屏幕。用於調試 set -e #本行後,若是某行執行結果返回值不是true,那麼終止 LOG="log.build" touch $LOG rm $LOG exec &> >(tee -a "$LOG") #將屏幕輸出內容,同時寫入log文件:便於後續查找 echo "Logging to $LOG" BUILD_ROOT=build if [ -d $BUILD_ROOT ]; then rm -rf $BUILD_ROOT fi mkdir -p $BUILD_ROOT cd $BUILD_ROOT echo "building root folder is $BUILD_ROOT" echo "Now do cmake" cmake .. echo "Now do make" make -j8 echo "Done"
執行構建腳本:
chmod +x compile.sh ./compile.sh cd build ./hello
自頂向下看,cmake執行的是給定路徑下的CMakeLists.txt,好比"."表示當前路徑,".."表示當前目錄的父目錄。
CMakeLists.txt中指定項目的輸入和輸出:
輸出:產生的可執行文件(或者庫文件?)
輸入:產生輸出所依賴的源文件(以及庫文件?)
CMakeLists.txt中經過cmake的語法編寫相應代碼語句,這些語句被cmake解釋執行。進而產生makefile,用make去執行就完成編譯。
cmake語法並不很複雜,每每翻看下cmake的官方手冊就能知道某個變量、命令、標準的各類細節了。
遇到cmake指令看不懂,直接看文檔,基本上解決問題。
https://cmake.org/cmake/help/latest/
實在是忍受不了查看文檔時刷一個個網頁一直出不來結果的狀況。本身動手豐衣足食。若是你網絡比較好,能夠跳過這一節。
查看哪些apt包是cmake的文檔:
aptitude search cmake
結果顯示cmake-doc
提供了文檔,cmake-data
則是另外一種形式的文檔。
安裝cmake及其文檔:
sudo apt install cmake cmake-doc cmake-data #安裝cmake文檔 dpkg -L cmake-doc #查看cmake-doc包安裝位置
發現是在/usr/share/doc/cmake-data目錄,它的馬甲目錄(連接)是/usr/share/doc/cmake-doc,那就開啓來好了:
cd /usr/share/doc/cmake-data/html python -m SimpleHTTPServer 4002 #用python開一個本地http服務器
瀏覽器訪問http://localhost:4002
查看文檔。
「另外一種形式的文檔」呢?在/usr/share/cmake-3.5/Help
目錄,是.rst
格式文檔,要用sphinx編譯:
sudo pip install sphinx #先確保裝了pip cd /usr/share/cmake-3.5/Help sudo sphinx-quickstart #按照提示來,惟一須要注意的是autogen選擇y。這一步生成makefile cd _build sudo make html #編譯生成html cd html python -m SimpleHTTPServer 4003
能夠把上述開啓文檔的http服務器命令用tmux開啓。
查詢某個命令:http://10.10.10.53:4003/command/
查詢某個變量:http://10.10.10.53:4003/variable/
查詢某個手冊:http://10.10.10.53:4003/manual/
查詢某個模塊:http://10.10.10.53:4003/module/
查詢發行日志:http://10.10.10.53:4003/release/
查詢 policy :http://10.10.10.53:4003/policy/ 包含了各類cmake規範
原本,直接查看手冊中find_package()
一節便可。
鑑於現有一些博客對於find_package()
這條命令介紹不夠好,好比這篇內容陳舊、不全面;好比這篇純粹是官方文檔翻譯,而官方文檔不夠突出重點,因此再羅嗦一小節的內容。
平時用cmake,遇到問題最多就是這個find_package()
命令的使用致使的。最多見於尋找opencv
包的狀況。
這裏噴一下opencv:要想用opencv各類特性,就要自行把opencv_contrib
編譯進去。編譯時候還會出現各類3rdparty的包從github上沒法下載要手動解決的問題,不方便。
find_package()
:
先從CMAKE_MODULE_PATH
變量表示的路徑下去找Find<name>.cmake
文件;
若是失敗,則在CMAKE安裝目錄/share/cmake-x.y/Modules
目錄下查找Find<name>.cmake
文件
若是上一步失敗,則查找<Name>Config.cmake
或者<lower-case-name>-config.cmake
文件。按8大順序查找你想要的包,若是前一個裏面找到了就不去後面的找,還能夠經過變量關閉某個查找順序項
好比如今要找opencv的包。那麼它的查找順序是(都是在尋找<Name>Config.cmake
或者<lower-case-name>-config.cmake
):
在CMAKE_PREFIX_PATH
變量所表示的路徑下尋找。
換言之,CMAKE_PREFIX_PATH
有最高的查找優先級。
在find_package()
參數列表中填寫NO_CMAKE_PATH
則跳過該查找項。
<package>_DIR CMAKE_PREFIX_PATH CMAKE_FRAMEWORK_PATH CMAKE_APPBUNDLE_PATH
對於OpenCV,就是OpenCV_DIR
了。
在find_package()
參數列表中填寫NO_CMAKE_ENVIRONMENT_PATH
跳過該查找項。
find_package()
的HINTS參數指定
系統環境變量PATH裏尋找。
在find_package()
參數列表中填寫NO_SYSTEM_ENVIRONMENT_PATH
跳過該查找項。
搜索在CMake GUI中最新配置過的工程的構建樹。在find_package()
參數列表中填寫NO_CMAKE_BUILDS_PATH
跳過該查找項。
搜索存儲在CMake用戶包註冊表(User Package Registry)中的路徑。
在find_package()
參數列表中填寫NO_CMAKE_PACKAGE_REGISTRY
或者設定CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY
變量值爲TRUE
跳過該查找項。(彷佛Linux下沒啥用!)
(update@2018-07-31 00:47:33 今天被這個User Package Registry坑慘了!具體實例和解決方法看SO上的一個問答:how-to-avoid-cmake-to-read-in-its-system-cache-home-cmake。簡單說,Caffe的cmake腳本中有一句export Caffe,這會向User Package Registry寫東西,在ubuntu下的表現就是在~/.cmake/package/Caffe路徑下寫入一個文件,文件名字是一個hash值,文件內容是cmake編譯的caffe的路徑。一旦有多個版本的caffe存在而且都用了cmake編譯,而若是前面1~5的設置路徑有錯誤,那麼就加載這個~/.cmake/package/Caffe/xxx文件中的路徑。很坑!)
CMAKE_SYSTEM_PREFIX_PATH CMAKE_SYSTEM_FRAMEWORK_PATH CMAKE_SYSTEM_APPBUNDLE_PATH
在find_package()
參數列表中填寫NO_CMAKE_SYSTEM_PATH
選項跳過這些路徑。
注意 這裏測試發現,CMAKE_SYSTEM_PREFIX_PATH
是/usr/local;/usr;/;/usr;/usr/local
,若是前面的查找順序項都失敗或者被關閉了,那麼在這一查找項上,會在/usr/local
這樣的路徑下,查找opencv
開頭的目錄,好比/usr/local/opencv-git-master
會被找到;而假如我把opencv的路徑換成不以opencv開頭,好比/usr/local/what-opencv
則不能找到opencv。
find_package()
參數列表中用PATHS
指定搜索路徑。這些路徑通常是硬編碼的參考路徑。。。。(太長並且我的以爲沒啥用,想看的去找手冊好了)寫了這麼多查找順序,其實就記住一個好了,先設定定CMAKE_PREFIX_PATH
變量,再用find_package()
去找包,保證萬事大吉:
list(APPEND CMAKE_PREFIX_PATH "/opt/opencv-git-master") #引號裏是個人opencv安裝路徑 find_package(OpenCV)
成功查找到包後,產生這些變量嗎?
按照cmake手冊的說法,find_package()
執行後會產生幾個變量:
但嘗試找opencv的包時,不管是apt裝的opencv仍是自行編譯的opencv,都是同樣:並不是這幾個變量都有值:
CMAKE_MODULE_PATH is:
-- Found OpenCV: /usr/local/opencv-git-master (found version "3.2.0")
OpenCV_FOUND is : 1
OpenCV_INCLUDE_DIRS is : /usr/local/opencv-git-master/include;/usr/local/opencv-git-master/include/opencv
OpenCV_INCLUDES is :
OpenCV_LIBRARIES is opencv_calib3d;opencv_core;opencv_features2d;opencv_flann;opencv_highgui;opencv_imgcodecs;opencv_imgproc;opencv_ml;opencv_objdetect;opencv_photo;opencv_shape;opencv_stitching;opencv_superres;opencv_video;opencv_videoio;opencv_videostab;opencv_aruco;opencv_bgsegm;opencv_bioinspired;opencv_ccalib;opencv_datasets;opencv_dnn;opencv_dnn_modern;opencv_dpm;opencv_face;opencv_freetype;opencv_fuzzy;opencv_hdf;opencv_line_descriptor;opencv_optflow;opencv_phase_unwrapping;opencv_plot;opencv_reg;opencv_rgbd;opencv_saliency;opencv_stereo;opencv_structured_light;opencv_surface_matching;opencv_text;opencv_tracking;opencv_xfeatures2d;opencv_ximgproc;opencv_xobjdetect;opencv_xphoto
OpenCV_LIBS is : opencv_calib3d;opencv_core;opencv_features2d;opencv_flann;opencv_highgui;opencv_imgcodecs;opencv_imgproc;opencv_ml;opencv_objdetect;opencv_photo;opencv_shape;opencv_stitching;opencv_superres;opencv_video;opencv_videoio;opencv_videostab;opencv_aruco;opencv_bgsegm;opencv_bioinspired;opencv_ccalib;opencv_datasets;opencv_dnn;opencv_dnn_modern;opencv_dpm;opencv_face;opencv_freetype;opencv_fuzzy;opencv_hdf;opencv_line_descriptor;opencv_optflow;opencv_phase_unwrapping;opencv_plot;opencv_reg;opencv_rgbd;opencv_saliency;opencv_stereo;opencv_structured_light;opencv_surface_matching;opencv_text;opencv_tracking;opencv_xfeatures2d;opencv_ximgproc;opencv_xobjdetect;opencv_xphoto
OpenCV_DEFINITIONS is :
```
好比讀取jpeg圖像,並轉化到Lab空間以及灰度圖像。使用到opencv庫,利用find_package()
進行查找。代碼以下:
main.cc
#include <iostream> #include <opencv2/opencv.hpp> using cv::Mat; using cv::imread; using cv::imshow; using cv::waitKey; using cv::cvtColor; using cv::COLOR_BGR2Lab; using cv::COLOR_BGR2GRAY; int main(){ // Load image Mat srcImage = imread("cat.jpg", -1); Mat dstImage; imshow("RGB Space", srcImage); // Convert to other color space cvtColor(srcImage, dstImage, COLOR_BGR2Lab); imshow("Lab Space", dstImage); cvtColor(srcImage, dstImage, COLOR_BGR2GRAY); imshow("Gray Scale", dstImage); waitKey(); return 0; }
CMakeLists.txt
cmake_minimum_required(VERSION 3.2) project(hello-cmake) list(APPEND CMAKE_PREFIX_PATH "/usr/share/OpenCV") # apt裝的opencv #list(APPEND CMAKE_PREFIX_PATH "/usr/local/opencv-git-master") #自行編譯的opencv message("CMAKE_MODULE_PATH is: ${CMAKE_MODULE_PATH}") find_package(OpenCV #NO_CMAKE_PATH #NO_CMAKE_ENVIRONMENT_PATH #NO_SYSTEM_ENVIRONMENT_PATH #NO_CMAKE_PACKAGE_REGISTRY #NO_CMAKE_SYSTEM_PATH ) message("OpenCV_FOUND is : ${OpenCV_FOUND}") message("OpenCV_INCLUDE_DIRS is : ${OpenCV_INCLUDE_DIRS}") message("OpenCV_INCLUDES is : ${OpenCV_INCLUDES}") message("OpenCV_LIBRARIES is ${OpenCV_LIBRARIES}") message("OpenCV_LIBS is : ${OpenCV_LIBS}") message("OpenCV_DEFINITIONS is : ${OpenCV_DEFINITIONS}") add_executable(hello main.cc) target_link_libraries(hello ${OpenCV_LIBS})
build.sh
#!/bin/bash set -x set -e LOG="log.build" touch $LOG rm $LOG exec &> >(tee -a "$LOG") echo "Logging to $LOG" BUILD_ROOT=build if [ -d $BUILD_ROOT ]; then rm -rf $BUILD_ROOT fi mkdir -p $BUILD_ROOT cd $BUILD_ROOT echo "building root folder is $BUILD_ROOT" echo "Now do cmake" cmake .. make -j8 echo "Done"
利用add_library()
生成庫文件。
main.cc
#include <iostream> using namespace std; void smile(){ cout << "Nice smile" << endl; }
CMakeLists.txt
cmake_minimum_required(VERSION 3.2) message("BUILD_SHARED_LIBS is ${BUILD_SHARED_LIBS}") #set(BUILD_SHARED_LIBS ON) # 設定BUILD_SHARED_LIBS來表示add_library構建共享庫(shared)仍是靜態庫(static) message("BUILD_SHARED_LIBS is ${BUILD_SHARED_LIBS}") add_library(smile STATIC main.cc) #不過,add_library()顯示地設定SHARED和STATIC,更直觀
build.sh
其實每次個人build.sh
都是同樣的:)
#!/bin/bash set -x set -e LOG="log.build" touch $LOG rm $LOG exec &> >(tee -a "$LOG") echo "Logging to $LOG" BUILD_ROOT=build if [ -d $BUILD_ROOT ]; then rm -rf $BUILD_ROOT fi mkdir -p $BUILD_ROOT cd $BUILD_ROOT echo "building root folder is $BUILD_ROOT" echo "Now do cmake" cmake .. make -j8 echo "Done"
如今,本身的庫寫好了,怎麼用呢?一種方法是make install,另外一種方法是提供配置文件,好比mylibrary-config.cmake這種,後者須要配合.cmake.in
文件進行生成,能夠參考官方wiki:https://cmake.org/Wiki/CMake/Tutorials/How_to_create_a_ProjectConfig.cmake_file
===================
2018年8月16日 20點46分
這裏插播一個實際遇到的狀況,挺有意思,領教了CMAKE_CXX_COMPILER
變量的厲害(採坑)。
小夥伴的ubuntu上要編譯Caffe,官方源碼下載後用CMake構建,到95%左右,convert_mnist_siamse_data.o這裏會報錯,說DSO Missing,具體指向的是libcstdc++.
開始時百思不得其解,由於查看g++和cmake,以及protobuf什麼的,都是apt裝的或者系統自帶的,爲啥缺庫呢。
後來有人指出是連接階段沒有連接libstdc++.so.6。可是爲啥要明確連接它?
個人Debug方法:利用message( )
函數(至關於C/C++裏的printf( ))暴力輸出各類連接庫的名字,仍是不能發現問題,逐步溯源到CMake的輸出,發現CMAKE_CXX_COMPILER
這個變量的取值出了問題。
CMAKE_CXX_COMPILER
CXX這個環境變量被設定了,設定的值爲/usr/bin/gcc-5
CMake會讀取這個$CXX 而後設定CMAKE_CXX_COMPILER爲這個值,也就是用gcc-5替代了g++
因此後續編譯的話都缺乏stdc++這個庫
在終端裏,unset CXX,或者把你的bashrc/zshrc裏面的export CXX=/usr/bin/gcc-5
註釋掉並退出從新進入shell,就能夠正常使用CMAKE_CXX_COMPILER
這一變量來編譯Caffe了。
這篇寫的還能夠:https://chaopei.github.io/blog/2018/10/cmake-variable.html
這篇更詳細一些:https://riptutorial.com/zh-CN/cmake/example/11821/變量和全局變量緩存
若是但願cmake -Dvar=value的方式,臨時設定變量的值,則必須用set(VAR "some value" CACHE STRING "ignore this" [FORCE])的方式