cmake筆記

安河桥北i發表於2024-04-10

demo1

cmake_minimum_required(VERSION 2.8) #cmake的最低版本
project(test) #定義工程名稱


add_executable(main add.cpp sub.cpp mul.cpp div.cpp main.cpp) 
#定義工程會生成一個可執行程式,格式:add_executable(可執行程式名 原始檔名稱)
#這裡的可執行程式名和project中的專案名沒有任何關係

set(CMAKE_CXX_STANDARD 11) #增加std=c++11/14/17

set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
#指定可執行檔案生成的位置,PROJECT_SOURCE_DIR為CMakeLists.txt所在目錄


demo2

cmake_minimum_required(VERSION 2.8) #cmake的最低版本
project(test) #定義工程名稱

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

#增加std=c++11/14/17,注意:必須在add_executable前

set(SRC add.cpp sub.cpp mul.cpp div.cpp main.cpp)
add_executable(main ${SRC})
#定義工程會生成一個可執行程式,格式:add_executable(可執行程式名 原始檔名稱)
#這裡的可執行程式名和project中的專案名沒有任何關係



set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
#指定可執行檔案生成的位置,PROJECT_SOURCE_DIR為CMakeLists.txt所在目錄


搜尋檔案

aux_source_directory(${PROJECT_SOURCE_DIR} SRC)
#aux_source_directory(<dir> <var>) 搜尋dir路徑中所有檔案,並命名為var

file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
file(GLOB HEAD ${CMAKE_CURRENT_SOURCE_DIR}/*.h)
#file(GLOB/GLOB_RECURSE 變數名 要搜尋的檔案路徑和檔案型別)

注意:PROJECT_SOURCE_DIR和CMAKE_CURRENT_SOURCE_DIR在巢狀cmake下不同,一般情況下相同

指定標頭檔案目錄

include_directories(${PROJECT_SOURCE_DIR}/include)
#指定包含標頭檔案的目錄 include_directories(headpath)

專案結構

image

透過Cmake製作庫檔案 demo2

set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
#指定動態庫/靜態庫生成的位置

add_library(庫名稱 STATIC 原始檔1 [原始檔2] ...)


eg.
add_library(calc_shared SHARED ${SRC})
#製作名稱為calc的動態庫(.so)

add_library(calc_static STATIC ${SRC})
# #製作名稱為calc的靜態庫(.a)

注意:cmake後需要make才會生成庫檔案

在程式中連結靜態庫 場景:原始檔較少 demo3

link_directories(<lib path>) #指定連結庫的目錄
link_libraries(<static lib> [<static lib>...]) #指定出要連結的靜態庫的名字
eg.
include_directories(${PROJECT_SOURCE_DIR}/include)
#指定包含標頭檔案的目錄 include_directories(headpath)

file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)

link_directories(${PROJECT_SOURCE_DIR}/lib)
#指定靜態庫的路徑,否則自定義的連結庫找不到
link_libraries(libcalc_static.a)
#指定靜態庫,可以全名,也可以掐頭去尾
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
#指定可執行檔案生成的位置,PROJECT_SOURCE_DIR為CMakeLists.txt所在目錄
add_executable(main ${SRC})

注意:可以是全名libxxx.a,也可以是掐頭(lib)去尾(.a)之後的名字xxx

專案結構

image

在程式中連結動態庫 場景:原始檔較多 demo4

target_link_libraries(
    <target> 
    <PRIVATE|PUBLIC|INTERFACE> <item>... 
    [<PRIVATE|PUBLIC|INTERFACE> <item>...]...)
  • target指定要載入動態庫的檔案的名字
    該檔案可能是一個原始檔
    該檔案可能是一個動態庫檔案
    該檔案可能是一個可執行檔案
  • PRIVATE|PUBLIC|INTERFACE:動態庫的訪問許可權,預設為PUBLIC
    如果各個動態庫之間沒有依賴關係,無需做任何設定,三者沒有沒有區別,一般無需指定,使用預設的 PUBLIC 即可。
  • 動態庫的連結具有傳遞性,如果動態庫 A 連結了動態庫B、C,動態庫D連結了動態庫A,此時動態庫D相當於也連結了動態庫B、C,並可以使用動態庫B、C中定義的方法。
    PUBLIC:在public後面的庫會被Link到前面的target中,並且裡面的符號也會被匯出,提供給第三方使用。
    PRIVATE:在private後面的庫僅被link到前面的target中,並且終結掉,第三方不能感知你調了啥庫。(只傳遞一次)
    INTERFACE:在interface後面引入的庫不會被連結到前面的target中,只會匯出符號。(不發生傳遞)
include_directories(${PROJECT_SOURCE_DIR}/include)
#指定包含標頭檔案的目錄 include_directories(headpath)

file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)

link_directories(${PROJECT_SOURCE_DIR}/lib)
#指定動態庫的路徑,否則自定義的連結庫找不到

set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
#指定可執行檔案生成的位置,PROJECT_SOURCE_DIR為CMakeLists.txt所在目錄
add_executable(main ${SRC})
target_link_libraries(main libcalc_shared.so)
#指定動態庫,可以全名,也可以掐頭去尾,注意:需要放在指令碼末尾

日誌

message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "message to display" ...)

(無) :重要訊息
STATUS :非重要訊息
WARNING:CMake 警告, 會繼續執行
AUTHOR_WARNING:CMake 警告 (dev), 會繼續執行
SEND_ERROR:CMake 錯誤, 繼續執行,但是會跳過生成的步驟
FATAL_ERROR:CMake 錯誤, 終止所有處理過程

變數操作 demo5

使用set進行拼接

set(變數名1 ${變數名1} ${變數名2} ...)

關於上面的命令其實就是將從第二個引數開始往後所有的字串進行拼接,最後將結果儲存到第一個引數中,如果第一個引數中原來有資料會對原資料就行覆蓋。

使用list進行拼接

list(APPEND <list> [<element> ...])

list命令的功能比set要強大,字串拼接只是它的其中一個功能,所以需要在它第一個引數的位置指定出我們要做的操作,APPEND表示進行資料追加,後邊的引數和set就一樣了。

set(TEMP "abc" "123")
file(GLOB SRC ${PROJECT_SOURCE_DIR}/src/*.cpp)
set(RES1 ${SRC} ${TEMP})
list(APPEND RES2 ${SRC} ${TEMP})
message(STATUS "message:${RES1}")
message(STATUS "message:${RES2}")

注意:file得到的是絕對路徑

字串移除

我們在透過file搜尋某個目錄就得到了該目錄下所有的原始檔,但是其中有些原始檔並不是我們所需要的,在當前這麼目錄有五個原始檔,其中main.cpp是一個測試檔案。如果我們想要把計算器相關的原始檔生成一個動態庫給別人使用,那麼只需要add.cpp、div.cp、mult.cpp、sub.cpp這四個原始檔就可以了。此時,就需要將main.cpp從搜尋到的資料中剔除出去,想要實現這個功能,也可以使用list

list(REMOVE_ITEM <list> <value> [<value> ...])

eg.

aux_source_directory(${PROJECT_SOURCE_DIR}/src SRC)

message(${SRC})

list(REMOVE_ITEM SRC ${PROJECT_SOURCE_DIR}/src/main.cpp)

message(${SRC})

set(str1 "123" "abc")
set(str2 "456" "def")
list(APPEND SRC ${str1} ${str2})
message(${SRC})

list的其它操作 demo5

獲取list長度

list(LENGTH <list> <output variable>)
LENGTH:子命令LENGTH用於讀取列表長度
<list>:當前操作的列表
<output variable>:新建立的變數,用於儲存列表的長度。

讀取列表中指定索引的的元素,可以指定多個索引

list(GET <list> <element index> [<element index> ...] <output variable>)
<list>:當前操作的列表
<element index>:列表元素的索引
從0開始編號,索引0的元素為列表中的第一個元素;
索引也可以是負數,-1表示列表的最後一個元素,-2表示列表倒數第二個元素,以此類推
當索引(不管是正還是負)超過列表的長度,執行會報錯
<output variable>:新建立的變數,儲存指定索引元素的返回結果,也是一個列表。

將列表中的元素用連線符(字串)連線起來組成一個字串

list (JOIN <list> <glue> <output variable>)
<list>:當前操作的列表
<glue>:指定的連線符(字串)
<output variable>:新建立的變數,儲存返回的字串

查詢列表是否存在指定的元素,若未找到,返回-1

list(FIND <list> <value> <output variable>)
<list>:當前操作的列表
<value>:需要再列表中搜尋的元素
<output variable>:新建立的變數
如果列表<list>中存在<value>,那麼返回<value>在列表中的索引
如果未找到則返回-1。

在list中指定的位置之前插入若干元素

list(INSERT <list> <element_index> <element> [<element> ...])

將元素插入到列表的0索引位置

list (PREPEND <list> [<element> ...])

將列表中最後元素移除

list (POP_BACK <list> [<out-var>...])

將列表中第一個元素移除

list (POP_FRONT <list> [<out-var>...])

將指定的元素從列表中移除

list (REMOVE_ITEM <list> <value> [<value> ...])

將指定索引的元素從列表中移除

list (REMOVE_AT <list> <index> [<index> ...])

移除列表中的重複元素

list (REMOVE_DUPLICATES <list>)

列表翻轉

list(REVERSE <list>)

列表排序

list (SORT <list> [COMPARE <compare>] [CASE <case>] [ORDER <order>])

COMPARE:指定排序方法。有如下幾種值可選:

  • STRING:按照字母順序進行排序,為預設的排序方法
  • FILE_BASENAME:如果是一系列路徑名,會使用basename進行排序
  • NATURAL:使用自然數順序排序

CASE:指明是否大小寫敏感。有如下幾種值可選:

  • SENSITIVE: 按照大小寫敏感的方式進行排序,為預設值
  • INSENSITIVE:按照大小寫不敏感方式進行排序

ORDER:指明排序的順序。有如下幾種值可選:

  • ASCENDING:按照升序排列,為預設值
  • DESCENDING:按照降序排列

宏定義 demo6

在進行程式測試的時候,我們可以在程式碼中新增一些宏定義,透過這些宏來控制這些程式碼是否生效,如下所示:

#include <stdio.h>
#define NUMBER  3

int main()
{
    int a = 10;
#ifdef DEBUG
    printf("我是一個程式猿, 我不會爬樹...\n");
#endif
    for(int i=0; i<NUMBER; ++i)
    {
        printf("hello, GCC!!!\n");
    }
    return 0;
}

在程式的第七行對DEBUG宏進行了判斷,如果該宏被定義了,那麼第八行就會進行日誌輸出,如果沒有定義這個宏,第八行就相當於被註釋掉了,因此最終無法看到日誌輸入出(上述程式碼中並沒有定義這個宏)。
在gcc/g++命令中透過引數 -D指定出要定義的宏的名字,這樣就相當於在程式碼中定義了一個宏,其名字為DEBUG。

在CMake中我們也可以做類似的事情,對應的命令叫做add_definitions:

add_definitions(-D宏名稱)

eg.

int main()
{
    int a = 20,b = 12;
    printf("a = %d,b = %d\n",a,b);
    printf("a + b = %d\n",add(a,b));
    printf("a - b = %d\n",sub(a,b));
    
    printf("a * b = %d\n",mul(a,b));
    
    printf("a / b = %.3lf\n",div(a,b));
    
    #ifdef DEBUG
    printf("除錯資訊:xxxxx\n");
    #endif
    return 0;
}
add_definitions(-DDEBUG)

aux_source_directory(${PROJECT_SOURCE_DIR}/src SRC)

include_directories(${PROJECT_SOURCE_DIR}/include)

set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

add_executable(main ${SRC})

巢狀的CMake 原始碼見demo7

image

靜態庫中連結靜態庫 demo8

靜態庫中連結動態庫 demo9

相關文章