什么是cmake?
????????cmake 工具通過解析 CMakeLists.txt 自動幫我們生成 Makefile,可以實現跨平臺的編譯。cmake 就是用來產生 Makefile 的工具,解析 CMakeLists.txt 自動生成 Makefile:
cmake 的使用方法
?
cmake 就是一個工具命令,在 Ubuntu 系統下通過 apt-get 命令可以在線安裝,如下所示
?
sudo apt-get install cmake
利用cmake --version命令檢查:
使用實例1.單個源文件:
一個經典的 C 程序“Hello World”,如何用 cmake 來進行構建呢?
首先利用touch命令創建倆文件(mkdir是創建文件目錄),后利用vi工具將代碼寫入。
touch main.c
touch CMakeLists.txt
//main.c
#include <stdio.h>
int main()
{
printf("Hello World!\n");
return 0;
}
????????現在我們需要新建一個CMakeLists.txt文件, CMakeLists.txt文件會被cmake工具解析,就好比是Makefile文件會被 make 工具解析一樣; CMakeLists.txt 創建完成之后,在文件中寫入如下內容:
project(HELLO)
add_executable(hello ./main.c)
寫入完成之后,保存退出,當前工程目錄結構如下所示:
?
├── CMakeLists.txt
└── main.c
在我們的工程目錄下有兩個文件,源文件 main.c 和 CMakeLists.txt,接著我們在工程目錄下直接執行cmake 命令,如下所示:
?
cmake ./
可見
成功生成了文件。有了 Makefile 之后,接著我們使用 make 工具編譯我們的工程
make
利用file hello 可見:
其中有arm,需要在arm開發板上運行,利用scp指令將文件傳至開發板
開發板執行./hello,可見:
是不是有疑問,為什么是在開發板上運行而不是Ubuntu上?
這是因為電腦安裝了交叉編譯鏈,所以生成了arm環境下的編譯文件。需要在ubuntu上運行也很簡單;
我們先來看看CMakeLists.txt文件里的語句的意思:
project(HELLO)
????????這條指令定義了一個名為 HELLO
的項目。它會告訴 CMake 這是一個新項目,并且所有接下來的指令都將在這個項目的上下文中執行。多個參數使用空格分隔而不是逗號“,” 。
add_executable(hello ./main.c)
????????這條指令用于定義一個可執行目標文件。它告訴 CMake 要創建一個名為 hello
的可執行文件,并且這個可執行文件是由 main.c
源文件編譯而來的。第一個參數表示生成的可執行文件對應的文件名,第二個參數表示對應的源文件; 所以 add_executable(hello ./main.c)表示需要生成一個名為 hello 的可執行文件,所需源文件為當前目錄下的 main.c
因為我們之前設置的環境默認為交叉編譯鏈的 ,所以只要在里面聲明就行
將之前編譯生成的文件刪除。
后修改?CMakeLists.txt文件,改為
set(CMAKE_C_COMPILER "gcc")
set(CMAKE_CXX_COMPILER "g++")project(HELLO)
add_executable(hello ./main.c)
第一句指定c語言的利用gcc,c++的利用g++編譯器。
后make,執行file hello可見:
環境為x86-64.成功
在ubuntu執行
./hello
但是各位是否有沒有發現,當我們改編譯器重新編譯的時候,需要把生成文件刪除特別麻煩,所以我們可以用一個更加簡便的方法:
使用 out-of-source 方式構建
?
????????我們需要將構建過程生成的文件與源文件分離開來, 不讓它們混雜在一起,也就是使用 out-of-source 方式構建。
????????將 cmake 編譯生成的文件清理下,然后在工程目錄下創建一個 build 目錄
然后我們進入build文件夾進行編譯:
利用
cmake ../
可以編譯上級文件夾
日后如果要清除,直接返回上級目錄 執行:
rm -rf build/*
這樣子編譯文件就刪光啦!
使用示例二:多個源文件
?
????????我們再加入一個 hello.h 頭文件和 hello.c 源文件。在 hello.c 文件中定義了一個函數 hello,然后在 main.c 源文件中將會調用該函數
touch hello. c hello.h生成倆文件
hello.h 文件內容
#ifndef __TEST_HELLO_
#define __TEST_HELLO_
void hello(const char *name);
#endif //__TEST_HELLO_
hello.c 文件內容(注意,%s指的是將name換成字符串,!在后面)
?
#include <stdio.h>
#include "hello.h"
void hello(const char *name)
{
printf("Hello %s!\n", name);
}
main.c 文件內容
#include "hello.h"
int main(void)
{
hello("World");
return 0;
}
然后準備好 CMakeLists.txt 文件
(如果你默認環境是gcc,想要在開發板運行,你只要把gcc換成你的編譯工具鏈就行,比如set(CMAKE_C_COMPILER "?arm-linux-gnueabihf-gcc"))
set(CMAKE_C_COMPILER "gcc")
set(CMAKE_CXX_COMPILER "g++")
project(HELLO)
set(SRC_LIST main.c hello.c)
add_executable(hello ${SRC_LIST})
set(SRC_LIST main.c hello.c)
是 CMakeLists.txt 文件中的一條命令,用于定義一個變量并給它賦值。在這個例子中,SRC_LIST
變量被設置為包含兩個源文件 main.c
和 hello.c
。
SRC_LIST
SRC_LIST
是變量名。你可以使用任何合適的名稱來代表你的變量。- 這個變量通常用來存儲源文件列表,以便在后續命令中引用。
add_executable(hello ${SRC_LIST})
:這行代碼定義了一個名為 hello
的可執行文件,并使用 SRC_LIST
變量中的源文件來構建該可執行文件。add_executable
函數將 main.c
和 hello.c
編譯并鏈接為一個名為 hello
的可執行文件。
cmake ../
make,file hello查看文件
編譯成功。
使用示例三:生成庫文件
????????在本例中,除了生成可執行文件 hello 之外,我們還需要將 hello.c 編譯為靜態庫文件或者動態庫文件,在示例二的基礎上對 CMakeLists.txt 文件進行修改,如下所示:
set(CMAKE_C_COMPILER "gcc")
set(CMAKE_CXX_COMPILER "g++")project(HELLO)
add_library(libhello hello.c)
add_executable(hello main.c)
target_link_libraries(hello libhello)
????????進入到 build 目錄下,執行 cmake、再執行 make 編譯工程,編譯完成之后,在 build 目錄下就會生成可執行文件 hello 和庫文件,如下所示:
本例中我們使用到了 add_library 命令和 target_link_libraries 命令。
????????add_library 命令用于生成庫文件,在本例中我們傳入了兩個參數,第一個參數表示庫文件的名字,需要注意的是,這個名字是不包含前綴和后綴的名字; 在 Linux 系統中,庫文件的前綴是 lib,動態庫文件的后綴是.so,而靜態庫文件的后綴是.a; 所以,意味著最終生成的庫文件對應的名字會自動添加上前綴和后綴。
????????第二個參數表示庫文件對應的源文件。
????????本例中, add_library 命令生成了一個靜態庫文件 liblibhello.a,如果要生成動態庫文件,可以這樣做:
add_library(libhello SHARED hello.c) #生成動態庫文件
add_library(libhello STATIC hello.c) #生成靜態庫文件
????????target_link_libraries 命令為目標指定依賴庫,在本例中, hello.c 被編譯為庫文件, 并將其鏈接進 hello 程序
總結
-
add_library(libhello hello.c)
:定義一個名為libhello
的庫,使用hello.c
源文件。 add_executable(hello main.c)
:定義一個名為hello
的可執行文件,使用main.c
源文件。target_link_libraries(hello libhello)
:將libhello
庫鏈接到hello
可執行文件,使得可執行文件可以調用庫中的函數。
作用是什么呢?
代碼復用
- 靜態庫(Static Library):將常用的函數和邏輯封裝在一個靜態庫中,可以在多個項目中復用這些函數,而不需要每次都復制代碼。
- 動態庫(Dynamic Library):動態庫在不同的程序之間共享庫中的代碼,節省內存空間,并且可以在程序運行時加載和更新。
如果在CMakeLists.txt文件中添加下面這條命令:
set_target_properties(libhello PROPERTIES OUTPUT_
可以將生成的庫為 liblibhello.a改成libhello.a。
使用示例四:將源文件組織到不同的目錄
?
????????上面的示例中,我們已經加入了多個源文件,但是這些源文件都是放在同一個目錄下,這樣還是不太正規,我們應該將這些源文件按照類型、功能、模塊給它們放置到不同的目錄下,于是筆者將工程源碼進行了整理,當前目錄結構如下所示:
├── build #build 目錄
├── CMakeLists.txt
├── libhello
│ ├── CMakeLists.txt
│ ├── hello.c
│ └── hello.h
└── src
├── CMakeLists.txt
└── main.c
????????在工程目錄下,我們創建了 src 和 libhello 目錄,并將 hello.c 和 hello.h 文件移動到 libhello 目錄下,將main.c 文件移動到 src 目錄下,并且在頂層目錄、 libhello 目錄以及 src 目錄下都有一個 CMakeLists.txt 文件。CMakeLists.txt 文件的數量從 1 個一下變成了 3 個,我們來看看每一個 CMakeLists.txt 文件的內容。
頂層 CMakeLists.txt
?
cmake_minimum_required(VERSION 3.5)
project(HELLO)
add_subdirectory(libhello)
add_subdirectory(src)
src 目錄下的 CMakeLists.txt
?
include_directories(${PROJECT_SOURCE_DIR}/libhello)
add_executable(hello main.c)
target_link_libraries(hello libhello)
libhello 目錄下的 CMakeLists.txt
?
add_library(libhello hello.c)
set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")
????????頂層 CMakeLists.txt 中使用了 add_subdirectory 命令, 該命令告訴 cmake 去子目錄中尋找新的CMakeLists.txt 文件并解析它;而在 src 的 CMakeList.txt 文件中,新增加了 include_directories 命令用來指明頭文件所在的路徑,并且使用到了 PROJECT_SOURCE_DIR 變量,該變量指向了一個路徑,從命名上可知,該變量表示工程源碼的目錄。
????????和前面一樣,進入到 build 目錄下進行構建、編譯,最終會得到可執行文件 hello(build/src/hello)和庫文件 libhello.a(build/libhello/libhello.a):
├── build
│ ├── libhello
│ │ └── libhello.a
│ └── src
│ └── hello
├── CMakeLists.txt
├── libhello
│ ├── CMakeLists.txt
│ ├── hello.c
│ └── hello.h
└── src
├── CMakeLists.txt
└── main.c
使用實例5:將生成的可執行文件和庫文件放置到單獨的目錄下
?
????????在默認情況下, make 編譯生成的可執行文件和庫文件會與 cmake 命令產生的中間文件(CMakeCache.txt、 CmakeFiles、 cmake_install.cmake 以及 Makefile 等)混在一起,也就是它們在同一個目錄下; 如果我想讓可執行文件單獨放置在 bin 目錄下,而庫文件單獨放置在 lib 目錄下,就像下面這樣:
├── build
├── lib
│ └── libhello.a
└── bin
└── hello
????????將庫文件存放在 build 目錄下的 lib 目錄中,而將可執行文件存放在 build 目錄下的 bin 目錄中,這個時候又該怎么做呢?這個時候我們可以通過兩個變量來實現,將 src 目錄下的 CMakeList.txt 文件進行修改,如下所示:
include_directories(${PROJECT_SOURCE_DIR}/libhello)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
add_executable(hello main.c)
target_link_libraries(hello libhello)
然后再對 libhello 目錄下的 CMakeList.txt 文件進行修改,如下所示:
?
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
add_library(libhello hello.c)
set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")
????????修改完成之后,再次按照步驟對工程進行構建、編譯,此時便會按照我們的要求將生成的可執行文件hello 放置在 build/bin 目錄下、庫文件 libhello.a 放置在 build/lib 目錄下。 最終的目錄結構就如下所示:
├── build
│ ├── bin
│ │ └── hello
│ └── lib
│ └── libhello.a
├── CMakeLists.txt
├── libhello
│ ├── CMakeLists.txt
│ ├── hello.c
│ └── hello.h
└── src
├── CMakeLists.txt
└── main.c
????????其實實現這個需求非常簡單,通過對 LIBRARY_OUTPUT_PATH 和 EXECUTABLE_OUTPUT_PATH變 量 進 行 設 置 即 可 完 成 ; EXECUTABLE_OUTPUT_PATH 變 量 控 制 可 執 行 文 件 的 輸 出 路 徑 , 而LIBRARY_OUTPUT_PATH 變量控制庫文件的輸出路徑。
CMakeLists.txt 語法規則
?
篇幅較長,建議看手冊