Linux下CMake簡明教程


轉載地址:https://blog.csdn.net/whahu1989/article/details/82078563

CMake是開源、跨平台的構建工具,可以讓我們通過編寫簡單的配置文件去生成本地的Makefile,這個配置文件是獨立於運行平台和編譯器的,這樣就不用親自去編寫Makefile了,而且配置文件可以直接拿到其它平台上使用,無需修改,非常方便。

本文主要講述在Linux下如何使用CMake來編譯我們的程序。

一 安裝CMake

本文使用ubuntu18.04,安裝cmake使用如下命令,
sudo apt install cmake
安裝完成后,在終端下輸入cmake -version查看cmake版本

這里寫圖片描述

這樣cmake就安裝好了。


 

二 簡單樣例

首先讓我們從最簡單的代碼入手,先來體驗下cmake是如何操作的。編寫main.c,如下

 

1 #include <stdio.h>
2 
3 int main(void)
4 {
5     printf("Hello World\n");
6 
7     return 0;
8 }

然后在main.c相同目錄下編寫CMakeLists.txt,內容如下,

 

1 cmake_minimum_required (VERSION 2.8)
2 
3 project (demo)
4 
5 add_executable(main main.c)

第一行意思是表示cmake的最低版本要求是2.8,我們安裝的是3.10.2;

第二行是表示本工程信息,也就是工程名叫demo;

第三行比較關鍵,表示最終要生成的elf文件的名字叫main,使用的源文件是main.c
在終端下切到main.c所在的目錄下,然后輸入以下命令運行cmake,
cmake .
會輸出如下信息:
這里寫圖片描述

再來看看目錄下的文件如下:

這里寫圖片描述

可以看到成功生成了Makefile,還有一些cmake運行時自動生成的文件。
然后在終端下輸入make並回車,
這里寫圖片描述
可以看到執行cmake生成的Makefile可以顯示進度,並帶顏色。再看下目錄下的文件

這里寫圖片描述
可以看到我們需要的elf文件main也成功生成了,然后運行main,
這里寫圖片描述
運行成功!


三 同一目錄下多個源文件

接下來進入稍微復雜的例子:在同一個目錄下有多個源文件。
在之前的目錄下添加2個文件,testFunc.c和testFunc.h。添加完后整體文件結構如下,
這里寫圖片描述
testFunc.c內容如下,

 

 1 /*
 2 ** testFunc.c
 3 */
 4 
 5 #include <stdio.h>
 6 #include "testFunc.h"
 7 
 8 void func(int data)
 9 {
10     printf("data is %d\n", data);
11 }

testFunc.h內容如下,

 

 1 /*
 2 ** testFunc.h
 3 */
 4 
 5 #ifndef _TEST_FUNC_H_
 6 #define _TEST_FUNC_H_
 7 
 8 void func(int data);
 9 
10 #endif

修改main.c,調用testFunc.h里聲明的函數func(),

 

 1 #include <stdio.h>
 2 
 3 #include "testFunc.h"
 4 
 5 int main(void)
 6 {
 7     func(100);
 8 
 9     return 0;
10 }

修改CMakeLists.txt,在add_executable的參數里把testFunc.c加進來

 

1 cmake_minimum_required (VERSION 2.8)
2 
3 project (demo)
4 
5 add_executable(main main.c testFunc.c)

然后重新執行cmake生成Makefile並運行make

這里寫圖片描述

然后運行重新生成的elf文件main,
這里寫圖片描述
運行成功!

可以類推,如果在同一目錄下有多個源文件,那么只要在add_executable里把所有源文件都添加進去就可以了。但是如果有一百個源文件,再這樣做就有點坑了,無法體現cmake的優越性,cmake提供了一個命令可以把指定目錄下所有的源文件存儲在一個變量中,這個命令就是 aux_source_directory(dir  var)。
第一個參數dir是指定目錄,第二個參數var是用於存放源文件列表的變量。


我們在main.c所在目錄下再添加2個文件,testFunc1.c和testFunc1.h。添加完后整體文件結構如下,
這里寫圖片描述

testFunc1.c如下,

 1 /*
 2 ** testFunc1.c
 3 */
 4 
 5 #include <stdio.h>
 6 #include "testFunc1.h"
 7 
 8 void func1(int data)
 9 {
10     printf("data is %d\n", data);
11 }

testFunc1.h如下,

 1 /*
 2 ** testFunc1.h
 3 */
 4 
 5 #ifndef _TEST_FUNC1_H_
 6 #define _TEST_FUNC1_H_
 7 
 8 void func1(int data);
 9 
10 #endif

再修改main.c,調用testFunc1.h里聲明的函數func1(),

 1 #include <stdio.h>
 2 
 3 #include "testFunc.h"
 4 #include "testFunc1.h"
 5 
 6 int main(void)
 7 {
 8     func(100);
 9     func1(200);
10 
11     return 0;
12 }

修改CMakeLists.txt,

1 cmake_minimum_required (VERSION 2.8)
2 
3 project (demo)
4 
5 aux_source_directory(. SRC_LIST)
6 
7 add_executable(main ${SRC_LIST})

使用aux_source_directory把當前目錄下的源文件存列表存放到變量SRC_LIST里,然后在add_executable里調用SRC_LIST(注意調用變量時的寫法)。
再次執行cmake和make,並運行main,
這里寫圖片描述
可以看到運行成功了。


四 不同目錄下多個源文件

一般來說,當程序文件比較多時,我們會進行分類管理,把代碼根據功能放在不同的目錄下,這樣方便查找。那么這種情況下如何編寫CMakeLists.txt呢?
我們把之前的源文件整理一下(新建2個目錄test_func和test_func1),整理好后整體文件結構如下,
這里寫圖片描述

把之前的testFunc.c和testFunc.h放到test_func目錄下,testFunc1.c和testFunc1.h則放到test_func1目錄下。

其中,CMakeLists.txt和main.c在同一目錄下,內容修改成如下所示,

 1 cmake_minimum_required (VERSION 2.8)
 2 
 3 project (demo)
 4 
 5 include_directories (test_func test_func1)
 6 
 7 aux_source_directory (test_func SRC_LIST)
 8 aux_source_directory (test_func1 SRC_LIST1)
 9 
10 add_executable (main main.c ${SRC_LIST} ${SRC_LIST1})

這里出現了一個新的命令:include_directories。該命令是用來向工程添加多個指定頭文件的搜索路徑,路徑之間用空格分隔。
因為main.c里include了testFunc.h和testFunc1.h,如果沒有這個命令來指定頭文件所在位置,就會無法編譯。當然,也可以在main.c里使用include來指定路徑,如下

1 #include "test_func/testFunc.h"
2 #include "test_func1/testFunc1.h"

只是這種寫法不好看。
另外,我們使用了2次aux_source_directory,因為源文件分布在2個目錄下,所以添加2次。


五 正規一點的組織結構

 

正規一點來說,一般會把源文件放到src目錄下,把頭文件放入到include文件下,生成的對象文件放入到build目錄下,最終輸出的elf文件會放到bin目錄下,這樣整個結構更加清晰。讓我們把前面的文件再次重新組織下,

這里寫圖片描述

我們在最外層目錄下新建一個CMakeLists.txt,內容如下,

1 cmake_minimum_required (VERSION 2.8)
2 
3 project (demo)
4 
5 add_subdirectory (src)

這里出現一個新的命令add_subdirectory(),這個命令可以向當前工程添加存放源文件的子目錄,並可以指定中間二進制和目標二進制的存放位置,具體用法可以百度。
這里指定src目錄下存放了源文件,當執行cmake時,就會進入src目錄下去找src目錄下的CMakeLists.txt,所以在src目錄下也建立一個CMakeLists.txt,內容如下,

1 aux_source_directory (. SRC_LIST)
2 
3 include_directories (../include)
4 
5 add_executable (main ${SRC_LIST})
6 
7 set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

這里又出現一個新的命令set,是用於定義變量的,EXECUTABLE_OUT_PATH和PROJECT_SOURCE_DIR是CMake自帶的預定義變量,其意義如下,

EXECUTABLE_OUTPUT_PATH :目標二進制可執行文件的存放位置      PROJECT_SOURCE_DIR:工程的根目錄

所以,這里set的意思是把存放elf文件的位置設置為工程根目錄下的bin目錄。

添加好以上這2個CMakeLists.txt后,整體文件結構如下。(cmake有很多預定義變量,詳細的可以網上搜索一下)

這里寫圖片描述

下面來運行cmake,不過這次先讓我們切到build目錄下,然后輸入以下命令,
cmake ..
Makefile會在build目錄下生成,然后在build目錄下運行make,
這里寫圖片描述
運行ok,我們再切到bin目錄下,發現main已經生成,並運行測試,
這里寫圖片描述

測試OK!
這里解釋一下為什么在build目錄下運行cmake?從前面幾個case中可以看到,如果不這樣做,cmake運行時生成的附帶文件就會跟源碼文件混在一起,這樣會對程序的目錄結構造成污染,而在build目錄下運行cmake,生成的附帶文件就只會待在build目錄下,如果我們不想要這些文件了就可以直接清空build目錄,非常方便。
另外一種寫法:
前面的工程使用了2個CMakeLists.txt,這種寫法是為了處理需要生成多個elf文件的情況,最外層的CMakeLists.txt用於掌控全局,使用add_subdirectory來添加要生成elf文件的源碼目錄。
如果只生成一個elf文件,那么上面的例子可以只使用一個CMakeLists.txt,可以把最外層的CMakeLists.txt內容改成如下,

 1 cmake_minimum_required (VERSION 2.8)
 2 
 3 project (demo)
 4 
 5 aux_source_directory (src SRC_LIST)
 6 
 7 include_directories (include)
 8 
 9 add_executable (main ${SRC_LIST})
10 
11 set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

同時,還要把src目錄下的CMakeLists.txt刪除。


六 動態庫和靜態庫的編譯控制

有時我們只需要編譯出動態庫,靜態庫,然后等着讓其它程序去使用。讓我們看下這種情況該如何使用cmake。首先按照如下重新組織文件,只留下testFunc.h和TestFunc.c,

這里寫圖片描述

我們會在build目錄下運行cmake,並把生成的庫文件存放到lib目錄下。
最外層的CMakeLists.txt內容如下,

1 cmake_minimum_required (VERSION 2.8)
2 
3 project (demo)
4 
5 add_subdirectory (lib_testFunc)

lib_testFunc目錄下的CMakeLists.txt如下,

1 aux_source_directory (. SRC_LIST)
2 
3 add_library (testFunc_shared SHARED ${SRC_LIST})
4 add_library (testFunc_static STATIC ${SRC_LIST})
5 
6 set_target_properties (testFunc_shared PROPERTIES OUTPUT_NAME "testFunc")
7 set_target_properties (testFunc_static PROPERTIES OUTPUT_NAME "testFunc")
8 
9 set (LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)

這里又出現了新的命令和預定義變量,

add_library: 生成動態庫或靜態庫(第1個參數指定庫的名字;第2個參數決定是動態還是靜態,如果沒有就默認靜態;第3個參數指定生成庫的源文件) set_target_properties: 設置輸出的名稱,還有其它功能,如設置庫的版本號等等 LIBRARY_OUTPUT_PATH: 庫文件的默認輸出路徑,這里設置為工程目錄下的lib目錄

好了,讓我們進入build目錄下運行cmake ..,成功后再運行make,

這里寫圖片描述

cd到lib目錄下進行查看,發現已經成功生成了動態庫和靜態庫,
這里寫圖片描述

ps:可以看出前面使用set_target_properties重新定義了庫的輸出名字,如果不用set_target_properties也可以,那么庫的名字就是add_library里定義的名字,只是我們連續2次使用add_library指定庫名字時,這個名字不能相同,而set_target_properties可以把名字設置為相同,只是最終生成的庫文件后綴不同,這樣相對來說會好看點。


七 對庫進行鏈接

既然我們已經生成了庫,那么就進行鏈接測試下。把build里的文件都刪除,然后在在工程目錄下新建src目錄和bin目錄,在src目錄下添加一個main.c和一個CMakeLists.txt,整體結構如下,
這里寫圖片描述

main.c內容如下,

 1 #include <stdio.h>
 2 
 3 #include "testFunc.h"
 4 
 5 int main(void)
 6 {
 7     func(100);
 8     
 9     return 0;
10 }

修改工程目錄下的CMakeLists.txt,如下,

1 cmake_minimum_required (VERSION 2.8)
2 
3 project (demo)
4 
5 add_subdirectory (lib_testFunc)
6 
7 add_subdirectory (src)

只是使用add_subdirectory把src目錄添加進來。
src目錄下的CMakeLists.txt如下,

 1 aux_source_directory (. SRC_LIST)
 2 
 3 # find testFunc.h
 4 include_directories (../lib_testFunc)
 5 
 6 link_directories (${PROJECT_SOURCE_DIR}/lib)
 7 
 8 add_executable (main ${SRC_LIST})
 9 
10 target_link_libraries (main testFunc)
11 
12 set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

這里出現2個新的命令,

  • link_directories: 添加非標准的共享庫搜索路徑
  • target_link_libraries: 把目標文件與庫文件進行鏈接

cd到build目錄下,然后運行cmake ..,成功后再運行make, 這里寫圖片描述

make成功,進入到bin目錄下查看,發現main已經生成,並運行,
這里寫圖片描述
運行成功!

ps:在lib目錄下有testFunc的靜態庫和動態庫,target_link_libraries (main testFunc)默認是使用動態庫,如果lib目錄下只有靜態庫,那么這種寫法就會去鏈接靜態庫。也可以直接指定使用動態庫還是靜態庫,寫法是:target_link_libraries (main libtestFunc.so)或target_link_libraries (main libtestFunc.a)
ps: 查看elf文件使用了哪些庫,可以使用readelf -d ./xx來查看


八 總結
以上是自己學習CMake的一點學習記錄,通過簡單的例子讓大家入門CMake,學習的同時也閱讀了很多網友的博客。CMake的知識點還有很多,具體詳情可以在網上搜索。總之,CMake可以讓我們不用去編寫復雜的Makefile,並且跨平台,是個非常強大並值得一學的工具。
如果有寫的不對的地方,希望能留言指正,謝謝閱讀。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM