CMake指令入門 ——以構建OpenCV項目為例
轉自:https://blog.csdn.net/sandalphon4869/article/details/100589747
一、安裝
sudo apt-get install cmake
安裝好后,輸入
cmake -version
如果出現了cmake的版本顯示,那么說明安裝成功
二、cmake編譯
cmake的作用就是將在IDE編譯器中的編譯功能拿出來,可以在終端上完成。類似于vim和文本編輯器。
cmake的編譯方式:
-
內部構建(in-source-build)
-
外部構建(out-of-source-build)
兩者的區別僅僅是前者將生成的編譯文件和源代碼、CMakeLists.txt 混雜在一起,后者就只是創建了一個文件夾 build 存儲生成的編譯文件,更好刪除編譯生成的文件而已(直接刪文件夾)。
三、語法
1. 基本語法
-
指令是大小寫無關的,參數和變量是大小寫相關的。但推薦你全部使用大寫指令。
-
變量使用
${}
方式取值,但是在 IF 控制語句中是直接使用變量名。如:${SRC_LIST}
-
指令(參數 1 參數 2…) 參數使用括弧括起,參數之間使用空格或分號分開。
如:
ADD_EXECUTABLE(hello main.c func.c)
或者ADD_EXECUTABLE(hello main.c;func.c)
。 -
注釋:
# comment
2. 指令
搞清當前目錄的意思:
-
CMakeLists.txt 中的
.
表示當前目錄是CMakeLists.txt文件所在的目錄,而非執行時終端的當前目錄。 -
cmake .
這條終端命令是執行時終端的當前目錄。
(1) PROJECT()
PROJECT(projectname [CXX] [C] [Java])
總結:定義工程名稱,并可指定工程支持的語言。
-
定義工程名稱:如
PROJECT(HELLO)
,那么工程的名稱就是HELLO
-
支持的語言列表:支持的語言列表是可以忽略的, 默認情況表示支持所有語言。
如指定C++:PROJECT(HELLO CXX)
-
這個指令隱式的定義了四個 cmake 變量:
<projectname>_BINARY_DIR
以及<projectname>_SOURCE_DIR
(格式,并非實際的變量)。對于這個工程就是HELLO_BINARY_DIR
和HELLO_SOURCE_DIR
(之后就可以直接使用了這兩個變量),使用<projectname>_BINARY_DIR
這個是無效的。
PROJECT_BINARY_DIR
和PROJECT_SOURCE_DIR
變量。- 他們的值分別跟
HELLO_BINARY_DIR
與HELLO_SOURCE_DIR
一致。區別是這兩個會自動根據工程名字的變化而變化。 - 建議以后直接使用
PROJECT_BINARY_DIR
,PROJECT_SOURCE_DIR
,即使修改了工程名稱,也不會影響這兩個變量。如:從HELLO該為WORLD,那么前者就得寫成WORLD_BINARY_DIR
和WORLD_SOURCE_DIR
,但后者還是PROJECT_BINARY_DIR
和PROJECT_SOURCE_DIR
。
(2) CMAKE_MINIMUM_REQUIRED()
CMAKE_MINIMUM_REQUIRED(VERSION 3.10)
cmake最低版本
(3) SET()
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
作用:定義變量的值
比如:
-
如果有多個源文件,也可以定義成:
SET(SRC_LIST main.c t1.c t2.c)
-
可以用
""
來處理包含空格的文件名:SET(SRC_LIST "hello world.cpp")
(建議使用方式) -
使用c++11特性:
set(CMAKE_CXX_FLAGS "${CAMKE_CXX_FLAGS} -std=c++11")
,防止出現因為c++11才有的編譯錯誤,比如 error: ‘to_string’ is not a member of ‘std’ -
設置編譯模式 Build Type,要加引號,不加可能報錯。
- Debug模式:
set(CMAKE_BUILD_TYPE "Debug")
- Release模式:
set(CMAKE_BUILD_TYPE "Release")
,更快。
- Debug模式:
(4) MESSAGE()
MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display")
作用:在cmake編譯過程中向終端輸出用戶定義的信息。
三種信息類型:
-
SEND_ERROR:產生錯誤,生成過程被跳過
-
STATUS:輸出前綴為
--
的信息。 -
FATAL_ERROR:立即終止所有 cmake 過程
輸出內容:"hello ${<variant name>}"
比如:
PROJECT(HELLO)
SET(SRC_LIST main.cpp)
MESSAGE(STATUS "The value of HELLO_SOURCE_DIR is${HELLO_SOURCE_DIR}" )
ADD_EXECUTABLE(hello ${SRC_LIST})
輸出:
...
The value of HELLO_SOURCE_DIR is/home/song/code
...
ps:引號的作用是保留空格
-
"hello world ${HELLO_SOURCE_DIR}"
:輸出為hello world /home
-
hello world ${HELLO_SOURCE_DIR}
:輸出為helloworld/home
(5) AUX_SOURCE_DIRECTORY()
①基本含義
AUX_SOURCE_DIRECTORY(dir VARIABLE)
作用是發現一個目錄 dir 下所有的源代碼文件并將列表存儲在一個變量中,這個指令臨時被用來自動構建源文件列表。因為目前 cmake 還不能自動發現新添加的源文件。 (本目錄為CMakeLists.txt所在的目錄)
比如:
AUX_SOURCE_DIRECTORY(. SRC_LIST)
ADD_EXECUTABLE(main ${SRC_LIST})
將本目錄下的源代碼文件添加到SRC_LIST變量中,再將這些文件編譯成生成文件。
②子目錄
注意:如果本目錄下有子目錄,是不會將子目錄下的源代碼文件添加進去的,得手動打出來。比如:
# 本目錄下的子目錄subDir
AUX_SOURCE_DIRECTORY(./subDir SRC_LIST)
③可添加多個
這兩個一起添加進入 SRL_LIST
,并不是后者覆蓋前者。
AUX_SOURCE_DIRECTORY(./src/text SRC_LIST)
AUX_SOURCE_DIRECTORY(./src/serial SRC_LIST)
(6) ADD_SUBDIRECTORY
ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
這個指令用于向當前工程添加存放源文件的子目錄,并可以指定中間二進制和目標二進制存放的位置。
EXCLUDE_FROM_ALL參數的含義:將這個目錄從編譯過程中排除,比如,工程 的 example,可能就需要工程構建完成后,再進入 example 目錄單獨進行構建(當然,你 也可以通過定義依賴來解決此類問題)。
比如:
將 src 子目錄加入工程,并指定編譯輸出(包含編譯中間結果)路徑為bin 目錄。
(7) FIND_PACKAGE()
FIND_PACKAGE(<name> [major.minor] [QUIET] [NO_MODULE]
[[REQUIRED|COMPONENTS] [componets...]])
參數:
REQUIRED 參數:如果使用了這個參數,說明這 個鏈接庫是必備庫,如果找不到這個鏈接庫,則工程不能編譯。
功能:只找一個名為 name
的包(后面都是修飾條件),所以如果要找多個包,要分多個FIND_PACKAGE()
寫。
比如:
-
OpenCV3:
find_package(OpenCV REQUIRED)
-
ROS:
find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs)
(8) INCLUDE_DIRECTORIES()
include_directories()
功能:鏈接頭文件
描述:可以將要鏈接的頭文件都寫在括號里,不用分成多個 INCLUDE_DIRECTORIES()
寫。
比如:
- OpenCV3:
${OpenCV_INCLUDE_DIRS}
- ROS:
${catkin_INCLUDE_DIRS}
include_directories(${OpenCV_INCLUDE_DIRS} ${catkin_INCLUDE_DIRS})
(9) ADD_EXECUTABLE()
ADD_EXECUTABLE(hello ${SRC_LIST})
生成可執行文件。
-
相關的源文件是SRC_LIST 中 定義的源文件列表, 你也可以直接寫成
ADD_EXECUTABLE(hello main.c ti.c)
。 -
可以寫成
ADD_EXECUTABLE(hello main)
cmake 會自動的在本目錄查找 main.c 或者 main.cpp等,當然,最好不要偷這個懶,以免這個目錄確實存在一個 main.c 和一個 main.cpp -
hello是最終要執行的可執行文件:
./hello
(10) TARGET_LINK_LIBRARIES()
TARGET_LINK_LIBRARIES(target library1 <debug | optimized> library2...)
功能:這個必須寫在 ADD_EXECUTABLE()
之后,為生成文件target添加庫。
比如:
- OpenCV3:
${OpenCV_LIBS}
- ROS:
${catkin_LIBRARIES}
3. 總結
添加文件的方式
-
手動添加:
set()
SET(SRC_LIST "main.cpp forest.hpp") ADD_EXECUTABLE(main ${SRC_LIST})
-
自動添加:
AUX_SOURCE_DIRECTORY()
AUX_SOURCE_DIRECTORY(. SRC_LIST) ADD_EXECUTABLE(main ${SRC_LIST})
四、構建OpenCV項目
項目結構
為了使整個項目更加條理,我們的文件夾采用如下組織方式:
bin
build
src
CMakeLists.txt
其中bin目錄用于放編譯生成的可執行文件,build目錄用于cmake構建項目,src用于放源代碼。
OpenCV源文件
下面是我們寫的一個OpenCV示例代碼:
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>using namespace std;
using namespace cv;int main(int argc, char** argv)
{if (argc != 2){cout << "Usage: opencv_test <image path>" << endl;return -1;}char *imgName = argv[1];Mat image;image = imread(imgName, 1);if (!image.data){cout << "No image data" << endl;return -1;}Mat gray_img;cvtColor(image, gray_img, CV_BGR2GRAY);imwrite("images/result.jpg", gray_img);return 0;
}
這是一個很簡單的例子:讀取圖片然后轉化成灰度圖。
編寫CMakeLists.txt
CMake文件的文件名CMakeLists.txt有嚴格的大小寫要求,注意不要寫錯。
# project name
PROJECT(HELLO)# using C++11
set(CMAKE_CXX_FLAGS "${CAMKE_CXX_FLAGS} -std=c++11 ")# cmake version
CMAKE_MINIMUM_REQUIRED(VERSION 3.10)# find OpenCV
FIND_PACKAGE(OpenCV REQUIRED)# show the message of OpenCV
message(STATUS "OpenCV library status:")
message(STATUS " version: ${OpenCV_VERSION}")
message(STATUS " headers: ${OpenCV_INCLUDE_DIRS}")
message(STATUS " libraries: ${OpenCV_LIBS}")# link headers
INCLUDE_DIRECTORIES({OpenCV_INCLUDE_DIRS})# 添加源代碼文件到SRC_LIST變量中
AUX_SOURCE_DIRECTORY(. SRC_LIST)# 生成可執行文件
ADD_EXECUTABLE(hello ${SRC_LIST})# after ADD_EXECUTABLE,為生成文件target添加庫
TARGET_LINK_LIBRARIES(hello ${OpenCV_LIBS})
PROJECT
指令的語法是:
PROJECT(projectname [CXX] [C] [Java])
你可以用這個指令定義工程名稱,并可指定工程支持的語言,支持的語言列表是可以忽略的,這個指令隱式的定義了兩個cmake變量: <projectname>_BINARY_DIR
以及<projectname>_SOURCE_DIR
。前者指構建路徑,后者指工程路徑,即 CMakeLists.txt 所在的路徑。
同時cmake系統也幫助我們預定義了 PROJECT_BINARY_DIR
和 PROJECT_SOURCE_DIR
變量,他們的值分別跟 opencv_test_BINARY_DIR
與 opencv_test_SOURCE_DIR
一致。
為了統一起見,建議以后直接使用 PROJECT_BINARY_DIR
,PROJECT_SOURCE_DIR
,即使修改了工程名稱,也不會影響這兩個變量。如果使用了<projectname>_SOURCE_DIR
,修改工程名稱后,需要同時修改這些變量。
接下來是設置cmake要求的最低版本號:
cmake_minimum_required(VERSION 3.5)
SET指令的語法是:
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
現階段,你只需要了解SET指令可以用來顯式的定義變量即可。這里我們將變量CMAKE_RUNTIME_OUTPUT_DIRECTORY定義為${opencv_test_SOURCE_DIR}/bin也就是工程路徑下的bin目錄。
下面介紹一個很重要的指令:find_package
這個指令以被用來在系統中自動查找配置構建工程所需的程序庫。在linux和unix類系統下這個命令尤其有用。CMake自帶的模塊文件里有大半是對各種常見開源庫的 find_package
支持,支持庫的種類非常多。
當它找到OpenCV程序庫之后,就會幫助我們預定義幾個變量,OpenCV_FOUND
、OpenCV_INCLUDE_DIRS
、OpenCV_LIBRARY_DIRS
、OpenCV_LIBRARIES
,它們分別指是否找到OpenCV,OpenCV的頭文件目錄,OpenCV的庫文件目錄,OpenCV的所有庫文件列表。接著我們就可以使用這些變量來配置了:
include_directories(${OpenCV_INCLUDE_DIRS})
上面這個指令用來設置包含的頭文件的路徑。
link_directories(${OpenCV_LIBRARY_DIRS})
上面這個指令用來設置庫文件的路徑。
target_link_libraries(opencv_test ${OpenCV_LIBS})
上面這個指令用來設置需要的庫文件,它的語法是:
TARGET_LINK_LIBRARIES(target library1<debug | optimized> library2...)
其中的target就是前面設置生成的目標文件(可執行文件):
add_executable(opencv_test src/opencv_test.cpp)
這個命令很好理解,首先是可執行文件的名字,然后是源碼的名字。因此,這個命令一定要在TARGET_LINK_LIBRARIES
之前使用。
現在我們的CMakeLists.txt就介紹完了。
構建項目并運行
mkdir build && cd build
cmake ..
make
# 至此構建完成,下面運行
./opencv_test