CMake 入门与进阶
目錄
- cmake 簡介
- cmake 和Makefile
- cmake 的下載
- cmake 的使用方法
- 示例一:單個源文件
- 示例二:多個源文件
- 示例三:生成庫文件
- 示例四:將源文件組織到不同的目錄
- 示例五:將生成的可執行文件和庫文件放置到單獨的目錄下
- CMakeLists.txt 語法規則
- 簡單地語法介紹
- 部分常用命令
在實際的項目中,并非如此簡單,一個工程中可能包含幾十、成百甚至上千個源文件,這些源文件按照其類型、功能、模塊分別放置在不同的目錄中;面對這樣的一個工程,通常會使用make 工具進行管理、編譯,make 工具依賴于Makefile 文件,通過Makefile 文件來定義整個工程的編譯規則。
Makefile 帶來的好處就是——“自動化編譯”,一旦寫好,只需要一個make 命令,整個工程完全按照Makefile 文件定義的編譯規則進行自動編譯,極大的提高了軟件開發的效率。大都數的IDE 都有這個工具,譬如Visual C++的nmake、linux 下的GNU make、Qt 的qmake 等等,這些make 工具遵循著不同的規范和標準,對應的Makefile 文件其語法、格式也不相同,這樣就帶來了一個嚴峻的問題:如果軟件想跨平臺,必須要保證能夠在不同平臺下編譯,而如果使用上面的make 工具,就得為每一種標準寫一次Makefile,這將是一件讓人抓狂的工作。
而cmake 就是針對這個問題所誕生,允許開發者編寫一種與平臺無關的CMakeLists.txt 文件來制定整個工程的編譯流程,再根據具體的編譯平臺,生成本地化的Makefile 和工程文件,最后執行make 編譯。
cmake 簡介
? 開放源代碼。我們可以直接從cmake 官網https://cmake.org/下載到它的源代碼;
? 跨平臺。它允許開發者編寫一種與平臺無關的CMakeLists.txt 文件來制定整個工程的編譯流程,cmake 工具會解析CMakeLists.txt 文件語法規則,再根據當前的編譯平臺,生成本地化的Makefile 和工程文件。
? 語法規則簡單。Makefile 語法規則比較復雜。
cmake 和Makefile
直觀上理解,cmake 就是用來產生Makefile 的工具,解析CMakeLists.txt 自動生成Makefile:
除了cmake 之外,還有一些其它的自動構建工具,常用的譬如automake、autoconf 等,有興趣的朋友可以自己了解下。
cmake 的下載
cmake 就是一個工具命令,在Ubuntu 系統下通過apt-get 命令可以在線安裝,如下所示:
sudo apt-get install cmake
筆者的Ubuntu 系統上已經安裝了cmake 工具,安裝完成之后可以通過cmake --version 命令查看cmake的版本號,如下所示:
使用哪個版本都是可以的,差別并不會太大。
安裝完cmake 工具之后,接著我們就來學習如何去使用cmake。cmake 官方也給大家提供相應教程,鏈接地址如下所示:
https://cmake.org/documentation/ //文檔總鏈接地址
https://cmake.org/cmake/help/latest/guide/tutorial/index.html //培訓教程
cmake 的使用方法
示例一:單個源文件
//main.c #include <stdio.h> int main() {printf("Hello World!\n");return 0; }新建一個CMakeLists.txt 文件,在文件中寫入如下內容:
project(HELLO) add_executable(hello ./main.c)寫入完成之后,保存退出,當前工程目錄結構如下所示:
├──CMakeLists.txt └──main.c在工程目錄下直接執行cmake 命令,如下所示:
cmake ./cmake 后面攜帶的路徑指定了CMakeLists.txt 文件的所在路徑,執行結果如下所示:
執行完cmake 之后,除了源文件main.c 和CMakeLists.txt 之外,生成了很多其它的文件或文件夾,包括:CMakeCache.txt、CmakeFiles、cmake_install.cmake、Makefile。
有了Makefile 之后,接著我們使用make 工具編譯我們的工程,如下所示:
在Ubuntu 下運行即可,如下所示:
CMakeLists.txt 文件解析
? 第一行project(HELLO)
project 命令用于設置工程的名稱,括號中的參數HELLO 便是我們要設置的工程名稱;通常需要我們提供參數,多個參數使用空格分隔而不是逗號“,”。
設置工程名稱并不是強制性的,但是最好加上。
? 第二行add_executable(hello ./main.c)
add_executable(hello ./main.c)表示需要生成一個名為hello 的可執行文件,所需源文件為當前目錄下的main.c。
使用out-of-source 方式構建
在上面的例子中,cmake 生成的文件以及最終的可執行文件hello 與工程的源碼文件main.c 混在了一起,這使得工程看起來非常亂,不讓它們混雜在一起,使用out-of-source 方式構建。
將cmake 編譯生成的文件清理下,然后在工程目錄下創建一個build 目錄,如下所示:
├──build ├──CMakeLists.txt └──main.c然后進入到build 目錄下執行cmake:
cd build/ cmake ../ make
這樣cmake 生成的中間文件以及make 編譯生成的可執行文件就全部在build 目錄下了,如果要清理工程,直接刪除build 目錄即可,這樣就方便多了。
示例二:多個源文件
? hello.h 文件內容
#ifndef __TEST_HELLO_ #define __TEST_HELLO_void hello(const char *name);#endif //__TEST_HELLO_? hello.c 文件內容
#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 文件
project(HELLO) set(SRC_LIST main.c hello.c) add_executable(hello ${SRC_LIST})工程目錄結構如下所示:
├──build //文件夾 ├──CMakeLists.txt ├──hello.c ├──hello.h └──main.c同樣,進入到build 目錄下,執行cmake、再執行make 編譯工程,最終就會得到可執行文件hello。
在本例子中,set 命令用于設置變量,如果變量不存在則創建該變量并設置它;我們定義了一個SRC_LIST 變量,記錄main.c 和hello.c,在add_executable 命令引用了該變量。
當然我們也可以不去定義SRC_LIST 變量,直接將源文件列表寫在add_executable 命令中,如下:
add_executable(hello main.c hello.c)示例三:生成庫文件
在本例中,除了生成可執行文件hello 之外,我們還需要將hello.c 編譯為靜態庫文件或者動態庫文件,在示例二的基礎上對CMakeLists.txt 文件進行修改,如下所示:
project(HELLO) add_library(libhello hello.c) add_executable(hello main.c) target_link_libraries(hello libhello)進入到build 目錄下,執行cmake、再執行make 編譯工程,編譯完成之后,在build 目錄下就會生成可執行文件hello 和庫文件,如下所示:
目錄結構如下所示:
CMakeLists.txt 文件解釋
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 程序。
修改生成的庫文件名字
本例生成的庫為liblibhello.a,名字非常不好看;想生成libhello.a直接修改add_library 命令的參數可以嗎?
答案是不行的,因為hello 這個目標已經存在了(add_executable(hello main.c)),目標名對于整個工程來說是唯一的,不可出現相同名字的目標。
實際上我們只需要在CMakeLists.txt文件中添加下面這條命令即可:
set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")set_target_properties 用于設置目標的屬性,這里通過set_target_properties 命令對libhello 目標的OUTPUT_NAME 屬性進行了設置,將其設置為hello。
我們進行實驗,此時CMakeLists.txt 文件中的內容如下所示:
除了添加set_target_properties 命令之外,我們還加入了
cmake_minimum_required 命令,該命令用于設置當前工程的cmake 最低版本號要求,這個并不是強制性的,但是最好還是加上。進入到build 目錄下,使用cmake+make 編譯整個工程,編譯完成之后會發現,生成的庫文件為libhello.a,而不是liblibhello.a。
├──build │ ├──hello │ └──libhello.so ├──CMakeLists.txt ├──hello.c ├──hello.h └──main.c示例四:將源文件組織到不同的目錄
上面的示例源文件都是放在同一個目錄下,將這些源文件按照類型、功能、模塊給它們放置到不同的目錄下,于是筆者將工程源碼進行了整理,當前目錄結構如下所示:
├──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示例五:將生成的可執行文件和庫文件放置到單獨的目錄下
如果我想讓可執行文件單獨放置在bin 目錄下,而庫文件單獨放置在lib 目錄下,就像下面這樣:
├──build├──lib│ └──libhello.a└──bin└──hello可以通過兩個變量來實現,將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")EXECUTABLE_OUTPUT_PATH 變量控制可執行文件的輸出路徑,而
LIBRARY_OUTPUT_PATH 變量控制庫文件的輸出路徑。
修改完成之后,再次按照步驟對工程進行構建、編譯,此時便會按照我們的要求將生成的可執行文件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.cCMakeLists.txt 語法規則
在上一小節中,筆者通過幾個簡單地示例向大家演示了cmake 的使用方法,由此可知,cmake 的使用方法其實還是非常簡單的,重點在于編寫CMakeLists.txt,CMakeLists.txt 的語法規則也簡單,并沒有Makefile
的語法規則那么復雜難以理解!本小節我們來學習CMakeLists.txt 的語法規則。
簡單地語法介紹
? 注釋
在CMakeLists.txt 文件中,使用“#”號進行單行注釋,譬如:
? 命令(command)
通常在CMakeLists.txt 文件中,使用最多的是命令,譬如上例中的cmake_minimum_required、project 都是命令;命令的使用方式有點類似于C 語言中的函數,因為命令后面需要提供一對括號,并且通常需要我們提供參數,多個參數使用空格分隔而不是逗號“,”,這是與函數不同的地方。命令的語法格式如下所示:
不同的命令所需的參數不同,需要注意的是,參數可以分為必要參數和可選參數(通常稱為選項),很多命令都提供了這兩類參數,必要參數使用<參數>表示,而可選參數使用[參數]表示,譬如set 命令:
set(<variable> <value>... [PARENT_SCOPE])set 命令用于設置變量,第一個參數和第二個參數是必要參數,在參數列表(…表示參數個數沒有限制)的最后可以添加一個可選參數PARENT_SCOPE(PARENT_SCOPE 選項),既然是可選的,那就不是必須的,根據實際使用情況確定是否需要添加。
在CMakeLists.txt 中,命令名不區分大小寫,可以使用大寫字母或小寫字母書寫命令名,譬如:
這倆的效果是相同的,指定的是同一個命令,并沒區別;這個主要看個人喜好,個人喜歡用小寫字母,主要是為了和變量區分開來,因為cmake 的內置變量其名稱都是使用大寫字母組成的。
? 變量(variable)
在CMakeLists.txt 文件中可以使用變量,使用set 命令可以對變量進行設置,譬如:
上例中,通過set 命令對變量MY_VAL 進行設置,將其內容設置為"Hello World!";那如何引用這個變量呢?這與Makefile 是相同的,通過${MY_VAL}方式來引用變量,如下所示:
#設置變量MY_VAL set(MY_VAL "Hello World!")#引用變量MY_VAL message(${MY_VAL})變量可以分為cmake 內置變量以及自定義變量,譬如上例中所定義的MY_VAL 就是一個自定義變量;譬如在32.3.5 小節中所使用的LIBRARY_OUTPUT_PATH 和EXECUTABLE_OUTPUT_PATH 變量則是
cmake 的內置變量,每一個內置變量都有自己的含義,像這樣的內置變量還有很多,稍后向大家介紹。
部分常用命令
cmake 提供了很多命令,每一個命令都有它自己的功能、作用,通過這個鏈接地址https://cmake.org/cmake/help/v3.5/manual/cmake-commands.7.html 可以查詢到所有的命令及其相應的介紹、使用方法等等,如下所示:
大家可以把這個鏈接地址保存起來,可以把它當成字典的形式在有需要的時候進行查詢,由于命令非常多,筆者不可能將所有命令都給大家介紹一遍,這里給大家介紹一些基本的命令,如下表所示:
總結
以上是生活随笔為你收集整理的CMake 入门与进阶的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: matlab两条曲线方程求交点_matl
- 下一篇: C语言写俄罗斯方块,可上机运行