cmake簡明使用指南


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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM