cmake和make

PamShao發表於2021-05-23

學計算機的,在寫程式碼的時候,IDE安裝好,環境按著教程配置好,就直接程式碼了,編輯器的具體原理只是一知半解,現在來系統學習一下,為了方便以後學習HElib!

make和cmake

寫程式大體步驟為:

1.用編輯器編寫原始碼,如.c檔案。

2.用編譯器編譯程式碼生成目標檔案,如.o。

3.用連結器連線目的碼生成可執行檔案,如.exe。

gcc

gcc是GNU Compiler Collection(就是GNU編譯器套件),也可以簡單認為是編譯器,它可以編譯很多種程式語言(括C、C++、Objective-C、Fortran、Java等等)。

gcc如何編譯參考:cmake 學習

當你的程式只有一個原始檔時,直接就可以用gcc命令編譯它。

但是當你的程式包含很多個原始檔時,用gcc命令逐個去編譯時,你就很容易混亂而且工作量大,一個一個編譯時就會特別麻煩,於是人們想到,為什麼不設計一種類似批處理的程式,來批處理編譯原始檔呢,於是就有了make工具。它是一個自動化編譯工具,你可以使用一條命令實現完全編譯。但是你需要編寫一個規則檔案,make依據它來批處理編譯,這個檔案就是makefile,所以編寫makefile檔案也是一個程式設計師所必備的技能。

對於一個大工程,編寫makefile實在是件複雜的事,於是人們又想,為什麼不設計一個工具,讀入所有原始檔之後,自動生成makefile呢,於是就出現了cmake工具,它能夠輸出各種各樣的makefile或者project檔案,從而幫助程式設計師減輕負擔。但是隨之而來也就是編寫cmakelist檔案,它是cmake所依據的規則。所以在程式設計的世界裡沒有捷徑可走,還是要腳踏實地的。

所以流程如下:

cmake使用

由上面可知,我們想要編譯,執行程式,只需要寫一個cmakelist檔案,用cmake去執行該檔案,生成project檔案和makefile檔案,然後再使用make,執行makefile檔案,進行編譯。

我這裡實驗環境實在Centos8.0下,首先先安裝,非常簡單:

yum install -y cmake

然後,接下來主要的就是cmakelist檔案的編寫,下面學習具體語法:

單個原始檔

1、準備一下原始檔 main.c .該程式的用途是計算一個數的指數冪

#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;
}

2、編寫 CMakeLists.txt 檔案,並和main.c放在同一個目錄 demo

# CMake 最低版本號要求
cmake_minimum_required (VERSION 2.8)

# 專案資訊
project (demo)

# 指定生成目標
add_executable(Demo main.c)

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

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

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

3、編譯

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

4、執行

./Demo

多個原始檔

當在一個目錄下,有多個原始檔時,實驗目錄為demo1

1、編寫原始檔

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

main.c

#include "MathFunctions.h"

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;
}

MathFunctions.c

#include "MathFunctions.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;
}

MathFunctions.h

#include <stdio.h>
#include <stdlib.h>

double power(double base, int exponent);

2、編寫CMakeLists.txt 檔案,並和原始檔放在同一個目錄 demo1

# CMake 最低版本號要求
cmake_minimum_required (VERSION 2.8)

# 專案資訊
project (demo1)

# 指定生成目標
add_executable(Demo main.c MathFunctions.c)

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

aux_source_directory(<dir> <variable>)

因此可以修改為:

# CMake 最低版本號要求
cmake_minimum_required (VERSION 2.8)

# 專案資訊
project (demo1)

# 查詢當前目錄下的所有原始檔
# 並將名稱儲存到 DIR_SRCS 變數
aux_source_directory(. DIR_SRCS)

# 指定生成目標
add_executable(Demo ${DIR_SRCS})

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

3、編譯和執行

多個目錄,多個原始檔

本實驗在demo2目錄下:

現在進一步將 MathFunctions.h 和 MathFunctions.c 檔案移動到 math 目錄下。

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

根目錄(demo2)中的 CMakeLists.txt

# CMake 最低版本號要求
cmake_minimum_required (VERSION 2.8)

# 專案資訊
project (demo2)

# 查詢當前目錄下的所有原始檔
# 並將名稱儲存到 DIR_SRCS 變數
aux_source_directory(. DIR_SRCS)

# 新增 math 子目錄
add_subdirectory(math)

# 指定生成目標 
add_executable(Demo main.c)

# 新增連結庫
target_link_libraries(Demo MathFunctions)

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

子目錄(math)中的 CMakeLists.txt

# 查詢當前目錄下的所有原始檔
# 並將名稱儲存到 DIR_LIB_SRCS 變數
aux_source_directory(. DIR_LIB_SRCS)

# 生成連結庫
add_library (MathFunctions ${DIR_LIB_SRCS})

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

編譯執行:

自定義編譯

本次實驗在demo3下:

CMake 允許為專案增加編譯選項,從而可以根據使用者的環境和需求選擇最合適的編譯方案。

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

只需修改CMakeLists.txt 檔案:

1、主目錄下CMakeLists.txt 檔案:

# CMake 最低版本號要求
cmake_minimum_required (VERSION 2.8)

# 專案資訊
project (demo3)

# 加入一個配置標頭檔案,用於處理 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 庫。

2、子目錄下CMakeLists.txt 檔案:

# 查詢當前目錄下的所有原始檔
# 並將名稱儲存到 DIR_LIB_SRCS 變數
aux_source_directory(. DIR_LIB_SRCS)

# 生成連結庫
add_library (MathFunctions ${DIR_LIB_SRCS})

3、修改main.c

讓其根據 USE_MYMATH 的預定義值來決定是否呼叫標準庫還是 MathFunctions 庫

#include <stdio.h>
#include <stdlib.h>
#include "config.h"

#ifdef USE_MYMATH
  #include "math/MathFunctions.h"
#else
  #include <math.h>
#endif


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]);
    
#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", base, exponent, result);
    return 0;
}

4、編寫config.h.in

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

#cmakedefine USE_MYMATH

這樣 CMake 會自動根據 CMakeLists 配置檔案中的設定自動生成 config.h 檔案。 

打包

配置生成各種平臺上的安裝包,包括二進位制安裝包和原始碼安裝包。為了完成這個任務,我們需要用到 CPack ,它同樣也是由 CMake 提供的一個工具,專門用於打包。

1、首先在頂層的 CMakeLists.txt 檔案尾部新增下面幾行:

# 構建一個 CPack 安裝包
include (InstallRequiredSystemLibraries)
set (CPACK_RESOURCE_FILE_LICENSE
  "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set (CPACK_PACKAGE_VERSION_MAJOR "${Demo_VERSION_MAJOR}")
set (CPACK_PACKAGE_VERSION_MINOR "${Demo_VERSION_MINOR}")
include (CPack)

上面的程式碼做了以下幾個工作:

  • 匯入 InstallRequiredSystemLibraries 模組,以便之後匯入 CPack 模組;
  • 設定一些 CPack 相關變數,包括版權資訊和版本資訊,其中版本資訊用了上一節定義的版本號;
  • 匯入 CPack 模組。

2、接下來的工作是像往常一樣構建工程,並執行 cpack 命令。

  • 生成二進位制安裝包:
cpack -C CPackConfig.cmake

這 3 個二進位制包檔案所包含的內容是完全相同的。我們可以執行其中一個。此時會出現一個由 CPack 自動生成的互動式安裝介面:

  • 生成原始碼安裝包
cpack -C CPackSourceConfig.cmake

 

以上學習內容源自:CMake 入門實戰

 感謝大佬分享!

參考

1、CMake與Make最簡單直接的區別

2、關於gcc、make和CMake的區別

3、CMake 入門實戰

相關文章