cmake簡明使用指南

cmake簡明使用指南

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

why this manual?

必須用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

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):

  1. CMAKE_PREFIX_PATH變量所表示的路徑下尋找。
    換言之,CMAKE_PREFIX_PATH有最高的查找優先級。
    find_package()參數列表中填寫NO_CMAKE_PATH則跳過該查找項。

  2. 在cmake特有的環境變量中查找。包括:
<package>_DIR
CMAKE_PREFIX_PATH
CMAKE_FRAMEWORK_PATH
CMAKE_APPBUNDLE_PATH

對於OpenCV,就是OpenCV_DIR了。
find_package()參數列表中填寫NO_CMAKE_ENVIRONMENT_PATH跳過該查找項。

  1. find_package()的HINTS參數指定

  2. 系統環境變量PATH裏尋找。
    find_package()參數列表中填寫NO_SYSTEM_ENVIRONMENT_PATH跳過該查找項。

  3. 搜索在CMake GUI中最新配置過的工程的構建樹。在find_package()參數列表中填寫NO_CMAKE_BUILDS_PATH跳過該查找項。

  4. 搜索存儲在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文件中的路徑。很坑!)

  5. 搜索在當前系統的平臺文件中定義的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。

  1. find_package()參數列表中用PATHS指定搜索路徑。這些路徑通常是硬編碼的參考路徑。。。。(太長並且我的以爲沒啥用,想看的去找手冊好了)

寫了這麼多查找順序,其實就記住一個好了,先設定定CMAKE_PREFIX_PATH變量,再用find_package()去找包,保證萬事大吉:

list(APPEND CMAKE_PREFIX_PATH "/opt/opencv-git-master") #引號裏是個人opencv安裝路徑
find_package(OpenCV)

成功查找到包後,產生這些變量嗎?
按照cmake手冊的說法,find_package()執行後會產生幾個變量:

 

_FOUND
_INCLUDE_DIRS 或者 _INCLUDES
_LIBRARIES 或者 _LIBS
_DEFINITIONS

但嘗試找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了。

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])的方式

相關文章
相關標籤/搜索