CMake入門筆記

CMake入門學習筆記

CMake is great. don't waste time on other C++ build tools, seriously.html

1. CMake是什麼?

CMake是一款工程構建工具,相似的工具還有autotoolqmakeScons等等。ios

具體見:
爲何選用cmakec++

隨着功能的不斷增長和複雜,咱們寫的C++程序也不可能再像Helloworld.cpp那樣只有一個源文件了,整個程序工程中,會有不少的源文件與庫文件,如何將他們組合,編譯成可執行的程序,就是CMake做爲工程構建工具的做用:它須要告訴計算機,整個複雜工程的文件之間有怎麼樣的關係。git

這個過程經過一個叫CMakeList.txt的文件來進行。github

  • CMakeLists.txt是使用CMake時惟一須要編寫的文件:
cmake_minimum_required(VERSION 2.6)
    project(itest)
    
    # C++標準
    set(CMAKE_CXX_STANDARD 11)
    
    # 指定參與編譯的源文件
    add_executable(itest src/main.cpp src/cal/Calculator.cpp src/cal/Calculator.h)
    
    # 指定安裝路徑,make install 時運用
    install (TARGETS itest DESTINATION bin)
    install(DIRECTORY src/ DESTINATION include/itest FILES_MATCHING PATTERN "*.h")
    
    # 設置不一樣build類別時的編譯參數
    #set(CMAKE_BUILD_TYPE "Debug")
    set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")
    set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
一個示例

2. CMake怎麼用?

根據CMake 入門實戰 - Hahack的實例來學習記錄。編程

原文C語言Demo源碼出自《CMake入門實戰》源碼 - github, wzpan,本文改寫爲C++,代碼地址 - githubbash

  • Linux平臺使用CMake生成Makefile並編譯的流程:
  1. 編寫CMake配置文件CMakeList.txt
  2. 執行命令cmake ${CMAKELIST_PATH}生成Makefile${CMAKELIST_PATH}CMakeList.txt所在的目錄。
  3. 執行命令make進行編譯。

2.1. 單個源文件的例子

2.1.1. 編寫CMakeList.txt

如下面的Demo_1 - github的實例來學習程序爲例子,項目中的一個源文件main.cpp以下:ide

/*
 * power - Calculate the power of number.
 * @param base: Base value.
 * @param exponent: Exponent value.
 *
 * @return base raised to the power exponent.
 */
 
#include <iostream>

using namespace std;

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]);
        cout << "Usage: " << argv[0] << "base exponent" <<endl;
        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);
    cout << base << "^" << exponent << " is " << result <<endl;
    return 0;
}
~/Demo_1/main.cpp

首先編寫CMakeList.txt,並保存在與main.cpp文件同一個目錄下:函數

~/Demo_1
    |
    +--main.cpp
    +--CMakeList.txt

一個最基本的CMakeList.txt是這樣的,在這個例子中,咱們只須要告訴計算機,咱們要使用main.cpp文件,來編譯一個名爲Demo的可執行文件:工具

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

# 項目信息
project (Demo_1)

# 指定生成目標
add_executable(Demo main.cpp)
~/Demo_1/CMakeList.txt

CMake中,#後面的內容爲註釋,命令不區分大小寫,參數之間用空格分隔,基本格式爲:

commandName(arg1 arg2 ...)

上面的CMakeList.txt中的三個命令:

  • cmake_minmum_required()用來指定所需的CMake的最低版本。
  • project()中的參數表示項目名稱。
  • add_excutable()中有兩個參數Demomain.cc,意思是將main.cc源文件編譯成一個名爲Demo的可執行文件。
2.1.2. 編譯和運行 Demo_1 - github

在編寫完CMakeList.txt以後,執行cmake ${CMAKELIST_PATH}

shi@shi-Z370M-S01:~/Demo_1$ cmake .
-- Configuring done
-- Generating done
-- Build files have been written to: /home/shi/Demo_1

會在按照文件中的配置和參數,在當前目錄生成編譯所須要的一系列文件,其中包含Makefile文件。

  • cmake命令是能夠附帶各類參數的,在命令後用空格隔開使用,--build--target等。
~/Demo_1
    |
    +--CMakeFiles
    +--CMakeCache.txt
    +--cmake_install.cmake
    +--CMakeList.txt
    +--main.cpp
    +--Makefile

生成Makefile後再執行make ${MAKEFILE_PATH}命令便可編譯獲得Demo可執行文件。

shi@shi-Z370M-S01:~/Demo_1$ make
Scanning dependencies of target Demo
[ 50%] Building CXX object CMakeFiles/Demo.dir/main.cpp.o
[100%] Linking CXX executable Demo
[100%] Built target Demo

運行編譯後的可執行文件:

shi@shi-Z370M-S01:~/Demo_1$ ./Demo 2 10
2^10 is 1024

2.2. 多個源文件的例子

2.2.1. 同目錄下多個源文件 Demo_2 - github

在實際編程過程當中,若是有多個源文件,好比將Demo_1.cpp中的power函數單獨寫進一個mathfunctions.cpp源文件中:

/*
 * 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;
}
~/Demo_2/mathfunctions.cpp

而後在main函數中引用MathFunctions.cpppower函數來進行計算:

#ifndef POWER_H
#define POWER_H

extern double power(double base, int exponent);

#endif
~/Demo_2/mathfunctions.h
#include <iostream>

//  import power function
#include "MathFunctions.h" 

int main(int argc, char *argv[])
{
    if (argc < 3){
        // printf("Usage: %s base exponent \n", argv[0]);
        cout << "Usage: " << argv[0] << "base exponent" <<endl;
        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);
    cout << base << "^" << exponent << " is " << result <<endl;
    return 0;
}
~/Demo_2/main.cpp

令工程變成以下結構:

~/Demo_2
    |
    +--CMakeList.txt
    +--main.cpp
    +--MathFunctions.cpp
    +--MathFunctions.h

這時咱們只須要在CMakeList.txt中的add_executable()命令上添加新參數,變成這樣就能夠了:

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

# 項目信息
project (Demo_1)

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

這裏咱們經過CMake告訴了計算機,咱們要使用主文件main.cpp和函數文件MathFunctions.cpp來生成可執行程序Demo

可是在源文件數量較多的時候,在add_executable()命令上添加新參數就會變得很麻煩,這時候咱們可使用aux_source_directory()命令:

aux_source_directory(<dir> <variable>)

這個命令會查找<dir>參數的目錄下全部的源文件,而後將結果存進<variable>變量中,所以能夠修改CMakeList.txt以下:

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

# 項目信息
project (Demo_1)

# 查找當前目錄下的全部源文件
# 並將名稱保存到 DIR_SRCS 變量
aux_source_directory(. DIR_SRCS)

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

編譯運行一下:

shi@shi-Z370M-S01:~/Demo_2$ cmake .
-- The C compiler identification is GNU 7.4.0
-- The CXX compiler identification is GNU 7.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/shi/Demo_2
shi@shi-Z370M-S01:~/Demo_2$ make
Scanning dependencies of target Demo
[ 33%] Building CXX object CMakeFiles/Demo.dir/MathFunctions.cpp.o
[ 66%] Building CXX object CMakeFiles/Demo.dir/main.cpp.o
[100%] Linking CXX executable Demo
[100%] Built target Demo
shi@shi-Z370M-S01:~/Demo_2$ ./Demo 2 10
2^10 is 1024
2.2.2 多個目錄和多個源文件 Demo_3 - github

接下來咱們將MathFunctions.cppMathFunctions.h文件移動到math目錄下:

~/Demo_3
    |
    +--CMakeList.txt
    +--main.cpp
    +--math/
        |
        +--CMakeList.txt
        +--MathFunctions.cpp
        +--MathFunctions.h

這時候項目工程分紅了主程序和庫文件兩個部分,這時候須要咱們分別在根目錄~/Demo_3庫目錄~/Demo_3/math/各編寫一個CMakeList.txt文件,將庫目錄~/Demo_3/math/裏的文件編譯成靜態庫再由根目錄主程序main.cpp中的main()函數調用。

庫目錄~/Demo_3/math/下的CMakeList.txt

# 查找當前目錄下的全部源文件
# 並將名稱保存到 DIR_LIB_SRCS變量
aux_source_directory(. DIR_LIB_SRCS)

add_library(MathFunctions ${DIR_LIB_SRCS})
~/Demo_3/math/CMakeList.txt

以前咱們使用add_executable()命令,編譯生成可執行文件,在這個庫目錄的CMakeList.txt文件中,咱們使用了add_library()函數,來將庫目錄中的文件編譯爲靜態鏈接庫

這樣咱們先告訴了計算機,咱們須要用庫文件math目錄下的源文件,組成一個名爲MathFunctions的庫(library)。

以後咱們回到根目錄~/Demo_3/中,修改根目錄的CMakeList.txt文件以下:

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

# 項目信息
project(Demo_3)

# 查找當前目錄下的全部源文件
# 並將名稱保存到 DIR_SRCS 變量
# aux_source_directory(. DIR_SRCS)

# 添加 math 子目錄
add_subdirectory(math)

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

# 添加連接庫
target_link_libraries(Demo MathFunctions)
~/Demo_3/CMakeList.txt

在根目錄新的CMakeList.txt中,咱們使用add_subdirectory()命令,將庫目錄/math添加爲一個輔助目錄(subdirectory),告訴計算機咱們將要用到這個目錄下的文件或者庫。

以後咱們使用target_link_libraries()命令,將庫目錄/math下,已經在上一個CMakeList.txt中定義好的庫MathFunctions與咱們想要生成的可執行文件Demo相鏈接。

  • 不要忘記修改主目錄下main.cpp的頭文件引用:
// #include "MathFunctions.h"  old

#include "math/MathFunctions.h"  // new

編譯並運行一下:

shi@shi-Z370M-S01:~/Demo_3$ cmake .
-- The C compiler identification is GNU 7.4.0
-- The CXX compiler identification is GNU 7.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/shi/Demo_3
shi@shi-Z370M-S01:~/Demo_3$ make 
[ 50%] Built target MathFunctions
Scanning dependencies of target Demo
[ 75%] Building CXX object CMakeFiles/Demo.dir/main.cpp.o
[100%] Linking CXX executable Demo
[100%] Built target Demo
shi@shi-Z370M-S01:~/Demo_3$ ./Demo 2 10
2^10 is 1024
2.2.3. 自定義編譯選項 Demo_4 - github

CMake能夠自定義編譯選項,從而能夠容許咱們根據用戶的環境和需求,選擇最合適的編譯方案,好比能夠將上一節的咱們本身寫的MathFunctions庫設置爲一個可選的庫,從而能夠自由選擇編譯時是使用咱們本身建立的庫,仍是調用標準庫中的數學函數。

爲此咱們須要在頂層CMakeLists.txt中添加選項。

首先須要在其中添加:

# 加入一個配置頭文件,用於處理 CMake 對源碼的設置
configure_file (
  "${PROJECT_SOURCE_DIR}/config.h.in"
  "${PROJECT_BINARY_DIR}/config.h"
  )

configure_file()命令用來加入一個配置頭文件config.h,這個文件將由CMakeconfig.h.in生成,經過這個機制,咱們能夠預約義一些參數和變量來控制代碼生成。
而後再添加:

# 是否使用本身的 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)

option()命令中,咱們定義了一個名爲USE_MYMATH變量,並設置其默認值爲ON
隨後的if()根據USE_MYMATH變量的值來決定咱們是否使用本身編寫的MathFunctions庫。若是是,則添加頭文件目錄${PROJECT_SOURCE_DIR}/math,子目錄math,並將可選庫地址存於EXTRA_LIBS

完整的CMakeList.txt以下:

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

# 項目信息
project(Demo_4)

# 加入一個配置頭文件,用於處理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")
  # 添加math子目錄
  add_subdirectory(math)
  # 收集可選庫地址存於EXTRA_LIBS
  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})

這時咱們須要生成的可執行文件Demo是由DIR_SRCS目錄中的源文件生成,並與EXTRA_LIBS目錄下的庫進行鏈接。

配置文件config.h並不須要咱們直接編寫,咱們須要編寫一個config.h.in文件,來讓CMake自動生成config,h,內容以下:

#cmakedefine USE_MYMATH

工程目錄結構以下:

~/Demo_4
    |
    +--CMakeList.txt
    +--main.cpp
    +--config.h.in
    +--math/
        |
        +--CMakeList.txt
        +--MathFunctions.cpp
        +--MathFunctions.h

以後咱們須要修改咱們的源文件main.cpp,咱們須要引用新的配置頭文件config.h,讓其根據USE_MYMATH的預約義值來選擇調用MathFunctions庫仍是標準庫。

修改後的main.cpp文件:

#include <iostream>
// #include "math/MathFunctions.h"
#include "config.h"

//判斷函數的調用
#ifdef USE_MYMATH
    #include "math/MathFunctions.h"
#else
    #include<math.h>
#endif

using namespace std;

int main(int argc, char *argv[])
{
    if (argc < 3){
        cout << "Usage: " << argv[0] << "base exponent" <<endl;
        return 1;
    }
    double base = atof(argv[1]);
    int exponent = atoi(argv[2]);

#ifdef USE_MYMATH // 調用自制庫
    cout << "Using our own math lib. "<<endl;
    double result = power(base, exponent);
#else // 調用標準庫
    cout << "Using standard math lib. "<<endl;
    double result = pow(base, exponent);
#endif
    cout << base << "^" << exponent << " is " << result <<endl;
    return 0;
}

編譯一下:

shi@shi-Z370M-S01:~/Demo_4$ cmake .
-- The C compiler identification is GNU 7.4.0
-- The CXX compiler identification is GNU 7.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/shi/Demo_4
shi@shi-Z370M-S01:~/Demo_4$ make
Scanning dependencies of target MathFunctions
[ 25%] Building CXX object math/CMakeFiles/MathFunctions.dir/MathFunctions.cpp.o
[ 50%] Linking CXX static library libMathFunctions.a
[ 50%] Built target MathFunctions
Scanning dependencies of target Demo
[ 75%] Building CXX object CMakeFiles/Demo.dir/main.cpp.o
[100%] Linking CXX executable Demo
[100%] Built target Demo
shi@shi-Z370M-S01:~/Demo_4$ ./Demo 2 10
Using standard math lib. 
2^10 is 1024

這裏出現了一個問題,用默認的cmake命令編譯以後,執行可執行文件Demo時發現,並無引用咱們本身編寫的庫,而是引用了標準庫。

以後檢查全部文件沒有發現問題,安裝帶gui的CMake後,執行ccmake命令,在gui中顯示USE_MYMATH正常的被配置爲ON,執行編譯後運行:

shi@shi-Z370M-S01:~/Demo_4$ ./Demo 2 10
Using our own math lib. 
2^10 is 1024

此次則正常調用了本身編寫的庫,目前緣由未知。(2019.10.21)

參考:

  1. CMake 入門實戰 - Hahack.com
  2. CMake簡易入門 - cnblogs
相關文章
相關標籤/搜索