1. 程式人生 > 程式設計 >超詳細的cmake入門教程

超詳細的cmake入門教程

什麼是cmake

你或許聽過好幾種 Make 工具,例如 GNU Make ,QT 的 qmake ,微軟的 MSnmake,BSD Make(pmake),Makepp,等等。這些 Make 工具遵循著不同的規範和標準,所執行的 Makefile 格式也千差萬別。這樣就帶來了一個嚴峻的問題:如果軟體想跨平臺,必須要保證能夠在不同平臺編譯。而如果使用上面的 Make 工具,就得為每一種標準寫一次 Makefile ,這將是一件讓人抓狂的工作。
CMake CMake附圖 1 CMake就是針對上面問題所設計的工具:它首先允許開發者編寫一種平臺無關的 CMakeList.txt 檔案來定製整個編譯流程,然後再根據目標使用者的平臺進一步生成所需的本地化 Makefile 和工程檔案,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。從而做到“Write once,run everywhere”。顯然,CMake 是一個比上述幾種 make 更高階的編譯配置工具。一些使用 CMake 作為專案架構系統的知名開源專案有 VTK、ITK、KDE、OpenCV、OSG 等。

在 linux 平臺下使用 CMake 生成 Makefile 並編譯的流程如下:

  1. 編寫 CMake 配置檔案 CMakeLists.txt 。
  2. 執行命令 cmake PATH 或者 ccmake PATH 生成 Makefile。其中, PATH 是 CMakeLists.txt 所在的目錄。(ccmake 和 cmake 的區別在於前者提供了一個互動式的介面)
  3. 使用 make 命令進行編譯。

入門案例:單個原始檔

本節對應的原始碼所在目錄:Demo1。
對於簡單的專案,只需要寫幾行程式碼就可以了。例如,假設現在我們的專案中只有一個原始檔 main.cc ,該程式的用途是計算一個數的指數冪。

#include <stdio.h>
#include <stdlib.h>
/**
 * power - Calculate the power of number.
 * @param base: Base value.
 * @param exponent: Exponent value.
 *
 * @return base raised to the power exponent.
 */
double power(double base,int exponent)
{
  int result = base;
  int i;
  
  if (exponent == 0) {
    return 1;
  }
  
  for(i = 1; i < exponent; ++i){
    result = result * base;
  }
  return result;
}
int main(int argc,char *argv[])
{
  if (argc < 3){
    printf("Usage: %s base exponent \n",argv[0]);
    return 1;
  }
  double base = atof(argv[1]);
  int exponent = atoi(argv[2]);
  double result = power(base,exponent);
  printf("%g ^ %d is %g\n",base,exponent,result);
  return 0;
}

編寫 CMakeLists.txt

首先編寫 CMakeLists.txt 檔案,並儲存在與 main.cc 原始檔同個目錄下:

# CMake 最低版本號要求
cmake_minimum_required (VERSION 2.8)
# 專案資訊
project (Demo1)
# 指定生成目標
add_executable(Demo main.cc)

CMakeLists.txt 的語法比較簡單,由命令、註釋和空格組成,其中命令是不區分大小寫的。符號 # 後面的內容被認為是註釋。命令由命令名稱、小括號和引數組成,引數之間使用空格進行間隔。

對於上面的 CMakeLists.txt 檔案,依次出現了幾個命令:

  1. cmake_minimum_required:指定執行此配置檔案所需的 CMake 的最低版本;
  2. project:引數值是 Demo1,該命令表示專案的名稱是 Demo1 。
  3. add_executable: 將名為main.cc的原始檔編譯成一個名稱為 Demo 的可執行檔案。

編譯專案

之後,在當前目錄執行 cmake . ,得到 Makefile 後再使用 make 命令編譯得到 Demo1 可執行檔案。

[ehome@xman Demo1]$ cmake .
-- The C compiler identification is GNU 4.8.2
-- The CXX compiler identification is GNU 4.8.2
-- Check for working C compiler: /usr/sbin/cc
-- Check for working C compiler: /usr/sbin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler: /usr/sbin/c++
-- Check for working CXX compiler: /usr/sbin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/ehome/Documents/programming/C/power/Demo1
[ehome@xman Demo1]$ make
Scanning dependencies of target Demo
[100%] Building C object CMakeFiles/Demo.dir/main.cc.o
Linking C executable Demo
[100%] Built target Demo
[ehome@xman Demo1]$ ./Demo 5 4
5 ^ 4 is 625
[ehome@xman Demo1]$ ./Demo 7 3
7 ^ 3 is 343
[ehome@xman Demo1]$ ./Demo 2 10
2 ^ 10 is 1024

多個原始檔

同一目錄,多個原始檔

本小節對應的原始碼所在目錄:Demo2。
上面的例子只有單個原始檔。現在假如把 power 函式單獨寫進一個名為MathFunctions.c 的原始檔裡,使得這個工程變成如下的形式:

./Demo2
|
+--- main.cc
|
+--- MathFunctions.cc
|
+--- MathFunctions.h

這個時候,CMakeLists.txt 可以改成如下的形式:

# CMake 最低版本號要求
cmake_minimum_required (VERSION 2.8)
# 專案資訊
project (Demo2)
# 指定生成目標
add_executable(Demo main.cc MathFunctions.cc)

唯一的改動只是在 add_executable 命令中增加了一個 MathFunctions.cc 原始檔。這樣寫當然沒什麼問題,但是如果原始檔很多,把所有原始檔的名字都加進去將是一件煩人的工作。更省事的方法是使用 aux_source_directory 命令,該命令會查詢指定目錄下的所有原始檔,然後將結果存進指定變數名。其語法如下:

aux_source_directory(<dir> <variable>)

因此,可以修改 CMakeLists.txt 如下:

# CMake 最低版本號要求
cmake_minimum_required (VERSION 2.8)
# 專案資訊
project (Demo2)
# 查詢當前目錄下的所有原始檔
# 並將名稱儲存到 DIR_SRCS 變數
aux_source_directory(. DIR_SRCS)
# 指定生成目標
add_executable(Demo ${DIR_SRCS})

這樣,CMake 會將當前目錄所有原始檔的檔名賦值給變數 DIR_SRCS ,再指示變數 DIR_SRCS 中的原始檔需要編譯成一個名稱為 Demo 的可執行檔案。

多個目錄,多個原始檔

本小節對應的原始碼所在目錄:Demo3。
現在進一步將 MathFunctions.h 和 MathFunctions.cc 檔案移動到 math 目錄下。

./Demo3
|
+--- main.cc
|
+--- math/
|
+--- MathFunctions.cc
|
+--- MathFunctions.h

對於這種情況,需要分別在專案根目錄 Demo3 和 math 目錄裡各編寫一個 CMakeLists.txt 檔案。為了方便,我們可以先將 math 目錄裡的檔案編譯成靜態庫再由 main 函式呼叫。

根目錄中的 CMakeLists.txt :

# CMake 最低版本號要求
cmake_minimum_required (VERSION 2.8)
# 專案資訊
project (Demo3)
# 查詢當前目錄下的所有原始檔
# 並將名稱儲存到 DIR_SRCS 變數
aux_source_directory(. DIR_SRCS)
# 新增 math 子目錄
add_subdirectory(math)
# 指定生成目標
add_executable(Demo main.cc)
# 新增連結庫
target_link_libraries(Demo MathFunctions)

該檔案添加了下面的內容: 第3行,使用命令 add_subdirectory 指明本專案包含一個子目錄 math,這樣 math 目錄下的 CMakeLists.txt 檔案和原始碼也會被處理 。第6行,使用命令 target_link_libraries 指明可執行檔案 main 需要連線一個名為 MathFunctions 的連結庫 。

子目錄中的 CMakeLists.txt:

# 查詢當前目錄下的所有原始檔
# 並將名稱儲存到 DIR_LIB_SRCS 變數
aux_source_directory(. DIR_LIB_SRCS)
# 生成連結庫
add_library (MathFunctions ${DIR_LIB_SRCS})

在該檔案中使用命令 add_library 將 src 目錄中的原始檔編譯為靜態連結庫。

自定義編譯選項

本節對應的原始碼所在目錄:Demo4。
CMake 允許為專案增加編譯選項,從而可以根據使用者的環境和需求選擇最合適的編譯方案。

例如,可以將 MathFunctions 庫設為一個可選的庫,如果該選項為 ON ,就使用該庫定義的數學函式來進行運算。否則就呼叫標準庫中的數學函式庫。
修改 CMakeLists 檔案

我們要做的第一步是在頂層的 CMakeLists.txt 檔案中新增該選項:

# CMake 最低版本號要求
cmake_minimum_required (VERSION 2.8)
# 專案資訊
project (Demo4)
# 加入一個配置標頭檔案,用於處理 CMake 對原始碼的設定
configure_file (
"${PROJECT_SOURCE_DIR}/config.h.in"
"${PROJECT_BINARY_DIR}/config.h"
)
# 是否使用自己的 MathFunctions 庫
option (USE_MYMATH
"Use provided math implementation" ON)
# 是否加入 MathFunctions 庫
if (USE_MYMATH)
include_directories ("${PROJECT_SOURCE_DIR}/math")
add_subdirectory (math)
set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (USE_MYMATH)
# 查詢當前目錄下的所有原始檔
# 並將名稱儲存到 DIR_SRCS 變數
aux_source_directory(. DIR_SRCS)
# 指定生成目標
add_executable(Demo ${DIR_SRCS})
target_link_libraries (Demo ${EXTRA_LIBS})

其中:

第7行的 configure_file 命令用於加入一個配置標頭檔案 config.h ,這個檔案由 CMake 從 config.h.in 生成,通過這樣的機制,將可以通過預定義一些引數和變數來控制程式碼的生成。
第13行的 option 命令添加了一個 USE_MYMATH 選項,並且預設值為 ON 。
第17行根據 USE_MYMATH 變數的值來決定是否使用我們自己編寫的 MathFunctions 庫。

修改 main.cc 檔案

之後修改 main.cc 檔案,讓其根據 USE_MYMATH 的預定義值來決定是否呼叫標準庫還是 MathFunctions 庫:

#include 
#include 
#include "config.h"
#ifdef USE_MYMATH
 #include "math/MathFunctions.h"
#else
 #include 
#endif
int main(int argc,argv[0]);
    return 1;
  }
  double base = atof(argv[1]);
  int exponent = atoi(argv[2]);
  
#ifdef USE_MYMATH
  printf("Now we use our own Math library. \n");
  double result = power(base,exponent);
#else
  printf("Now we use the standard library. \n");
  double result = pow(base,exponent);
#endif
  printf("%g ^ %d is %g\n",result);
  return 0;
}

編寫 config.h.in 檔案

上面的程式值得注意的是第2行,這裡引用了一個 config.h 檔案,這個檔案預定義了 USE_MYMATH 的值。但我們並不直接編寫這個檔案,為了方便從 CMakeLists.txt 中匯入配置,我們編寫一個 config.h.in 檔案,內容如下:

#cmakedefine USE_MYMATH

這樣 CMake 會自動根據 CMakeLists 配置檔案中的設定自動生成 config.h 檔案。
編譯專案
現在編譯一下這個專案,為了便於互動式的選擇該變數的值,可以使用 ccmake 命令(也可以使用 cmake -i 命令,該命令會提供一個會話式的互動式配置介面。 )

超詳細的cmake入門教程

從中可以找到剛剛定義的 USE_MYMATH 選項,按鍵盤的方向鍵可以在不同的選項視窗間跳轉,按下 enter 鍵可以修改該選項。修改完成後可以按下 c 選項完成配置,之後再按 g 鍵確認生成 Makefile 。ccmake 的其他操作可以參考視窗下方給出的指令提示。

我們可以試試分別將 USE_MYMATH 設為 ON 和 OFF 得到的結果:

USE_MYMATH 為 ON

執行結果:

[ehome@xman Demo4]$ ./Demo
Now we use our own MathFunctions library.
7 ^ 3 = 343.000000
10 ^ 5 = 100000.000000
2 ^ 10 = 1024.000000

此時 config.h 的內容為:

#define USE_MYMATH

USE_MYMATH 為 OFF

執行結果:

[ehome@xman Demo4]$ ./Demo
Now we use the standard library.
7 ^ 3 = 343.000000
10 ^ 5 = 100000.000000
2 ^ 10 = 1024.000000

此時 config.h 的內容為:

/* #undef USE_MYMATH */

下面是其他網友的補充

使用cmake編譯,組織C++專案

前言
這篇部落格是我對cmake用法的一些經驗總結,還很淺顯,如果有錯誤或者更好的方案,歡迎指正~

使用方法統一為在build目錄中執行:

$: cmake ..
$: make

我覺得養成外部編譯是一個好習慣

例一

目錄結構為:

lzj@lzj:~/C-Plus-Plus/makefile_cmake/cmake_1$ tree
.
├── build
├── CMakeLists.txt
└── src
├── hello
│ ├── hello.cc
│ └── hello.h
├── main.cpp
└── world
├── world.cc
└── world.h

src 目錄中不同屬性類維護在不同目錄中

main.cpp中使用hello.h和world.h

CMakeLists.txt為 :

cmake_minimum_required (VERSION 3.0)
project (test_1)

aux_source_directory(${CMAKE_CURRENT_LIST_DIR}/src/hello SOURCE_HELLO)
aux_source_directory(${CMAKE_CURRENT_LIST_DIR}/src/world SOURCE_WORLD)

add_definitions("-g -Wall -std=c++11")

add_executable(main
${CMAKE_CURRENT_LIST_DIR}/src/main.cpp
${SOURCE_HELLO}
${SOURCE_WORLD})

例二

目錄結構為:

lzj@lzj:~/C-Plus-Plus/makefile_cmake/cmake_2$ tree
.
├── build
├── CMakeLists.txt
├── include
│ └── person.h
└── src
├── main.cpp
└── person.cc

include目錄下統一包含標頭檔案和巨集定義之類,原始檔放在 src 目錄下維護

person 類是一個簡單的空類,擁有一個私有成員變數val,一個公有成員函式來列印該變數,在main.cpp中呼叫

CMakeLists.txt為 :

cmake_minimum_required(VERSION 3.0)
project(test_2)

include_directories(${PROJECT_SOURCE_DIR}/include)

add_definitions("-g -Wall -std=c++11")

add_executable(main
${PROJECT_SOURCE_DIR}/src/main.cpp #這個路徑看這個main.cpp位於哪裡了
${PROJECT_SOURCE_DIR}/src/person.cc)

例三

目錄結構為:

lzj@lzj:~/C-Plus-Plus/makefile_cmake/cmake_3$ tree
.
├── build
├── CMakeLists.txt
├── main.cpp
└── src
├── CMakeLists.txt
├── hello.cc
├── hello.h
├── world.cc
└── world.h

將編寫的程式碼編譯為庫,在main.cpp中使用,編譯main.cpp時連結該庫

頂層目錄中CMakeLists.txt為:

cmake_minimum_required (VERSION 3.0)
project (test_3)

add_subdirectory(src)

add_definitions("-g -Wall -std=c++11")

add_executable(main main.cpp)
target_link_libraries(main TEST3) #自己的庫名為TEST3

子目錄 src 中的CMakeLists.txt為:

aux_source_directory(. DIR_LIB_SRCS)

add_library (TEST3 ${DIR_LIB_SRCS})

當然如果src目錄下為多檔案時,每個目錄下都要新增該語句的CMakeLists.txt

原始碼

這篇文章就介紹到這了,希望大家以後多多支援我們。