CMake極速入門

深川發表於2024-05-01

引言

還在手寫晦澀難懂的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 .
image
可以看到,cmake正在檢查系統環境,檢查c,c++編譯器路徑,命令執行完畢後,你會發現當前目錄下多了很多東西:
image
其中最關鍵的是Makefile檔案,也就是說CMake自動將CMakeLists.txt中的語句轉化為Makefile檔案了,執行make命令:
image
可以看到,成功執行了,相比於手寫Makefile檔案,優雅許多。

目錄簡潔化

輸入ls,檢視專案當前檔案:
image
可以看到,cmake生成了很多中間產物,這就把目錄搞髒了,有一個約定俗成的處理方法是,新建一個build目錄:
image
在build目錄下執行cmake ..make
image
可以看到,這樣專案目錄就乾淨多了,編譯檔案和專案檔案互不干涉。但是編譯後的可執行檔案也混在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;
}

當前專案結構變為:
image
再修改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;
}

編譯,執行,成功!
image

將程式碼封裝成庫

實際開發中,為了縮短編譯時間,便於程式碼移植等原因,通常會將程式碼封裝成靜/動態庫。

靜態庫

靜態庫的封裝

在CMakeLists.txt檔案中加入:

add_library(add STATIC ${SRC_FILES})

其中add_library函式第一個引數是庫的名字(注意不能和可執行檔名重複了)
編譯:
image

設定靜態庫生成目錄

可以從上圖看到,libadd.a直接編譯到build目錄下了,不整潔。專案根目錄下新建lib資料夾,修改CMakeLists.txt檔案,加入:

set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)

重新編譯,可以看到:
image

連結靜態庫

CMakeLIsts.txt檔案中,加入:

#包含靜態庫路徑
link_directories(${PROJECT_SOURCE_DIR}/lib)
#連結靜態庫
link_libraries(add)

編譯,執行,可以看到成功連結了libadd.a:
image

動態庫

將上文靜態庫的CMakeLists.txt檔案中的add_library(add STATIC ${SRC_FILES})改為add_library(add SHARED ${SRC_FILES})即可。
執行效果:
image

一些有用的設定

  • 設定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)

相關文章