引言
還在手寫晦澀難懂的Makefile檔案嗎?現如今,主流的c++專案都採取CMake作為專案構建工具,CMake可以跨平臺執行,而且語法相對Makefile而言直觀很多,是時候將Makefile掃進垃圾堆了。
Hello, World!
首先先以單個原始檔專案為講解,新建一個main.cpp檔案:
#include<iostream>
using namespace std;
int main(void){
cout<<"Hello, World!"<<endl;
}
那麼如何使用CMake編譯這個檔案呢?CMake所有的語句都寫在一個名為CMakeLists.txt的檔案下,新建這個檔案:
#設定cmake執行時最低版本
cmake_minimum_required(VERSION 3.0.0)
#設定專案名
project(hello)
#新增可執行檔案
add_executable(${PROJECT_NAME} main.cpp)
cmake_minimum_required函式的作用是限定執行的cmake版本,因為cmake指令碼語言的sdk可能會有變化,老版本執行不起來。project函式就不解釋了,見聞知意。add_executable函式是起到編譯作用的,第一個引數是編譯後的可執行程式名稱(這裡PROJECT_NAME是一個變數,當我們用project函式設定專案名時,會自動賦值給它,而${}是CMake中呼叫變數的語法),之後的引數是編譯可執行檔案需要的原始檔依賴。
終端輸入cmake .
:
可以看到,cmake正在檢查系統環境,檢查c,c++編譯器路徑,命令執行完畢後,你會發現當前目錄下多了很多東西:
其中最關鍵的是Makefile檔案,也就是說CMake自動將CMakeLists.txt中的語句轉化為Makefile檔案了,執行make命令:
可以看到,成功執行了,相比於手寫Makefile檔案,優雅許多。
目錄簡潔化
輸入ls,檢視專案當前檔案:
可以看到,cmake生成了很多中間產物,這就把目錄搞髒了,有一個約定俗成的處理方法是,新建一個build目錄:
在build目錄下執行cmake ..
,make
:
可以看到,這樣專案目錄就乾淨多了,編譯檔案和專案檔案互不干涉。但是編譯後的可執行檔案也混在build目錄中了,如果我想把可執行檔案放在一個單獨的目錄裡,該怎麼做呢?
設定可執行檔案輸出目錄
我通常喜歡把可執行檔案放到專案目錄下的bin資料夾,新建一個bin目錄,並在CMakeLists.txt檔案中新增:
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
透過設定EXECUTABLE_OUTPUT_PATH變數即可實現。
引入庫檔案,標頭檔案
實際專案中不可能就一個原始檔,我們還會將程式碼分類,組織成不同的標頭檔案和庫檔案,那麼CMake怎麼引入呢?
假設我們現在想給Hello World程式加一個加法功能。在專案目錄下,新建一個src資料夾,用於存放庫檔案;新建一個inc資料夾,用於存放標頭檔案。
編寫inc/add.h:
#pragma once
int add(int a,int b);
編寫src/add.cpp:
#include"add.h"
int add(int a,int b){
return a+b;
}
當前專案結構變為:
再修改CMakeLists.txt檔案:
#設定cmake執行時最低版本
cmake_minimum_required(VERSION 3.0.0)
#設定專案名
project(hello)
#新增標頭檔案目錄
include_directories(${PROJECT_SOURCE_DIR}/inc)
#查詢所有庫檔案
file(GLOB SRC_FILES ${PROJECT_SOURCE_DIR}/src/*.cpp)
#新增可執行檔案
add_executable(${PROJECT_NAME} main.cpp ${SRC_FILES})
#設定可執行檔案生成目錄
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
可以看到,我們新增了include_directories
函式和file
函式,其中include_directories
函式顧名思義,就是用來指定標頭檔案目錄的。引數PROJECT_SOURCE_DIR則表示專案根目錄。而file
函式是用來讀取檔案系統的,GLOB引數代表匹配指定模式的檔案(即*.cpp檔案)。
然後我們再修改一下main.cpp檔案,呼叫一下add函式:
#include<iostream>
#include"add.h"
using namespace std;
int main(void){
cout<<"Hello, World!"<<endl;
cout<<"5+10="<<add(5,10)<<endl;
}
編譯,執行,成功!
將程式碼封裝成庫
實際開發中,為了縮短編譯時間,便於程式碼移植等原因,通常會將程式碼封裝成靜/動態庫。
靜態庫
靜態庫的封裝
在CMakeLists.txt檔案中加入:
add_library(add STATIC ${SRC_FILES})
其中add_library
函式第一個引數是庫的名字(注意不能和可執行檔名重複了)
編譯:
設定靜態庫生成目錄
可以從上圖看到,libadd.a直接編譯到build目錄下了,不整潔。專案根目錄下新建lib資料夾,修改CMakeLists.txt檔案,加入:
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
重新編譯,可以看到:
連結靜態庫
CMakeLIsts.txt檔案中,加入:
#包含靜態庫路徑
link_directories(${PROJECT_SOURCE_DIR}/lib)
#連結靜態庫
link_libraries(add)
編譯,執行,可以看到成功連結了libadd.a:
動態庫
將上文靜態庫的CMakeLists.txt檔案中的add_library(add STATIC ${SRC_FILES})
改為add_library(add SHARED ${SRC_FILES})
即可。
執行效果:
一些有用的設定
- 設定C++標準(假設設為C++11):
set(CMAKE_CXX_STANDARD 11)
- 設定編譯模式:
SET(CMAKE_BUILD_TYPE "Debug")
或SET(CMAKE_BUILD_TYPE "Release")
- 新增編譯引數,比如我們想新增更多警告:
add_compile_options(-Wall -Wextra -Wpedantic)
模板
直接編譯二進位制檔案
#設定cmake執行時最低版本
cmake_minimum_required(VERSION 3.0.0)
#設定專案名
project(hello)
#設定c++標準
set(CMAKE_CXX_STANDARD 11)
SET(CMAKE_BUILD_TYPE "Release")
add_compile_options(-Wall -Wextra -Wpedantic)
#新增標頭檔案目錄
include_directories(${PROJECT_SOURCE_DIR}/inc)
#查詢所有庫檔案
file(GLOB SRC_FILES ${PROJECT_SOURCE_DIR}/src/*.cpp)
#新增可執行檔案
add_executable(${PROJECT_NAME} main.cpp ${SRC_FILES})
#設定可執行檔案生成目錄
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
靜態庫
#設定cmake執行時最低版本
cmake_minimum_required(VERSION 3.0.0)
#設定專案名
project(hello)
#設定c++標準
set(CMAKE_CXX_STANDARD 11)
SET(CMAKE_BUILD_TYPE "Release")
add_compile_options(-Wall -Wextra -Wpedantic)
#新增標頭檔案目錄
include_directories(${PROJECT_SOURCE_DIR}/inc)
#查詢所有庫檔案
file(GLOB SRC_FILES ${PROJECT_SOURCE_DIR}/src/*.cpp)
#新增可執行檔案
add_executable(${PROJECT_NAME} main.cpp ${SRC_FILES})
#設定可執行檔案生成目錄
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
#設定靜態庫
add_library(add STATIC ${SRC_FILES})
#設定庫的生成目錄
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
#包含靜態庫路徑
link_directories(${PROJECT_SOURCE_DIR}/lib)
#連結庫
link_libraries(add)
動態庫
#設定cmake執行時最低版本
cmake_minimum_required(VERSION 3.0.0)
#設定專案名
project(hello)
#設定c++標準
set(CMAKE_CXX_STANDARD 11)
SET(CMAKE_BUILD_TYPE "Release")
add_compile_options(-Wall -Wextra -Wpedantic)
#新增標頭檔案目錄
include_directories(${PROJECT_SOURCE_DIR}/inc)
#查詢所有庫檔案
file(GLOB SRC_FILES ${PROJECT_SOURCE_DIR}/src/*.cpp)
#新增可執行檔案
add_executable(${PROJECT_NAME} main.cpp ${SRC_FILES})
#設定可執行檔案生成目錄
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
#設定動態庫
add_library(add SHARED ${SRC_FILES})
#設定庫的生成目錄
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
#包含庫路徑
link_directories(${PROJECT_SOURCE_DIR}/lib)
#連結庫
link_libraries(add)