cmake簡明使用指南
Last update 2018/8/8
先執行cmake生成makefile,然后看看里面的內容,(至少在ubuntu16.04上的cmake3.5.1上),有如下內容提供:
# 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
可以交互式的修改一些變量,挺好的。
why this manual?
必須用cmake
很多開源項目如KDE、VTK、OpenCV、Caffe等,都使用cmake來構建。要玩轉這些項目,就需要掌握cmake。而且趨勢是cmake會更加流行。
實際情況往往是,系統預裝的opencv不夠用,嘗試編譯opencv的時候遇到cmake各種問題,被折騰的死去活來,於是決定好好學一下cmake。
cmake資料不夠好
cmake經歷了多個版本發展,現在(2017.05.05)它的官方文檔比原來有了很多進步。
現有的
小白視角
從一個cmake小白的角度來總結cmake的用法。
環境說明
cmake提供命令行方式,以及圖形界面方式(包括cmake-GUI和ccmake)。這里僅使用cmake命令行方式,因為更簡單直接。
使用ubuntu16.04, cmake3.5版。其他系統和cmake版本問題也都不大,因為本文盡量做到具備較好的通用性。
先確保安裝了gcc,g++和cmake
hello world
最簡單的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
windows下的做法
可以用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使用原理
自頂向下看,cmake執行的是給定路徑下的CMakeLists.txt,比如"."表示當前路徑,".."表示當前目錄的父目錄。
CMakeLists.txt中指定項目的輸入和輸出:
輸出:產生的可執行文件(或者庫文件?)
輸入:產生輸出所依賴的源文件(以及庫文件?)
CMakeLists.txt中通過cmake的語法編寫相應代碼語句,這些語句被cmake解釋執行。進而產生makefile,用make去執行就完成編譯。
cmake語法並不很復雜,往往翻看下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()
一節即可。
鑒於現有一些博客對於find_package()
這條命令介紹不夠好,比如這篇內容陳舊、不全面;比如這篇純粹是官方文檔翻譯,而官方文檔不夠突出重點,所以再羅嗦一小節的內容。
平時用cmake,遇到問題最多就是這個find_package()
命令的使用導致的。最常見於尋找opencv
包的情況。
這里噴一下opencv:要想用opencv各種特性,就要自行把opencv_contrib
編譯進去。編譯時候還會出現各種3rdparty的包從github上無法下載要手動解決的問題,不方便。
find_package()是如何工作的
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
則跳過該查找項。 -
在cmake特有的環境變量中查找。包括:
<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變量:
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**
```c++
#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了。
cmake變量
這篇寫的還可以: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])的方式