目錄
- 使用CMake創(chuàng)建一個(gè)可執(zhí)行程序
- 創(chuàng)建一個(gè)動(dòng)/靜態(tài)庫,并以庫方式使用
- 以源碼方式引入第三方庫,以多層目錄方式使用
- 跨平臺(tái)共用lib第三庫,改變代碼的層級(jí)結(jié)構(gòu)
- 其他的一些小細(xì)節(jié)(項(xiàng)目實(shí)踐中遇到的問題)
- 資料
- 收獲
CMake是我們CPP開發(fā)中很基礎(chǔ)也是很重要的環(huán)節(jié),就像Java的ant、Gradle作用類似構(gòu)建編譯CPP代碼。
關(guān)于系統(tǒng)性的CMake的學(xué)習(xí)資料也很多,我是通過 cmake實(shí)踐 和[cmake-examples] (https://github.com/ttroy50/cmake-examples)進(jìn)行學(xué)習(xí)實(shí)踐的。建議先看下這些資料內(nèi)容系統(tǒng)學(xué)習(xí)并進(jìn)行實(shí)踐、實(shí)踐、實(shí)踐,再看這篇文章。
這篇文章一方面是對(duì)學(xué)習(xí)實(shí)踐的收獲以“問題-解決方案”的形式進(jìn)行總結(jié),另外一方面也是對(duì)最近工作中使用Cmake遇到的問題和踩到的坑進(jìn)行記錄。
下面開啟我們的CMake學(xué)習(xí)實(shí)踐之旅。
一、使用CMake創(chuàng)建一個(gè)可執(zhí)行程序
源代碼文件
#include <stdio.h>
int main()
{
printf("Hello World from t1 main\n");
return 0;
}
CMakeLists.txt文件
//每個(gè)CMakeList都有自己的POJECT,通過該指令定義這個(gè)CMake編譯的最終產(chǎn)物的名稱。
PROJECT (HELLO)
//SET可以設(shè)置變量的值,這里是把源文件名稱賦值給變量SRC_LIST,如果有多個(gè)源文件使用空格分開
SET(SRC_LIST helloworld.cpp)
//下面四條語句是通過MESSAGE打印出一些變量的值。
//其中<projectname>_SOURCE_DIR、<projectname>_BINARY_DIR為隱式變量,projectname即上面第一行定義的值。
//PROJECT_SROURCE_DIR、PROJECT_BINARY_DIR則是顯式變量,作用和上面兩個(gè)一樣。
//由于Projectname可以通過第一條指令改變,如果使用隱式變量,也要做相應(yīng)的修改,而顯式變量則不用。
//PROJECT_SROURCE_DIR:源文件(也可以理解為CMAKE)所在的路徑
//PROJECT_BINARY_DIR:生成的二進(jìn)制文件產(chǎn)物的路徑,一般采用build目錄下分離源文件和產(chǎn)物文件,則此時(shí)對(duì)應(yīng)的路徑為build文件夾路徑
MESSAGE(STATUS "This is BINARY DIR ${HELLO_BINARY_DIR}")
MESSAGE(STATUS "This is PROJECT BINARY DIR ${PROJECT_BINARY_DIR}")
MESSAGE(STATUS "This is SOURCE DIR ${HELLO_SOURCE_DIR}")
MESSAGE(STATUS "This is PROJECT SOURCE DIR ${PROJECT_SOURCE_DIR}")
//這個(gè)指令用于生成可執(zhí)行文件,第一個(gè)參數(shù)是生產(chǎn)可執(zhí)行文件產(chǎn)物的名稱、第二個(gè)用于生成該可執(zhí)行文件的源文件
ADD_EXECUTABLE(hello ${SRC_LIST}) # hello可以寫成${PROJECT_NAME}
然后在命令行中執(zhí)行 mkdir build && cd build && cmake .. && make
可以看到輸出的MESSAGE信息以及生成的可執(zhí)行文件
mkdir build && cd build && cmake .. && make
...
-- This is BINARY DIR /xxx/cmake/cmake實(shí)踐/t1/build
-- This is PROJECT BINARY DIR /xxx/cmake/cmake實(shí)踐/t1/build
-- This is SOURCE DIR /xxx/cmake/cmake實(shí)踐/t1
-- This is PROJECT SOURCE DIR /xxx/cmake/cmake實(shí)踐/t1
[100%] Linking CXX executable hello
[100%] Built target hello
/xxx/.../build % ls
CMakeCache.txt Makefile hello
CMakeFiles cmake_install.cmake
./hello
Hello World from t1 main
小節(jié)小結(jié):
- 學(xué)習(xí)三個(gè)指令:PROJECT、MESSAGE、ADD_EXECUTABLE
- 了解五個(gè)變量:PROJECT_SROURCE_DIR、PROJECT_BINARY_DIR、<projectname>_SOURCE_DIR、<projectname>_BINARY_DIR、PROJECT_NAME
- 通過Cmake構(gòu)建了一個(gè)簡(jiǎn)單的可執(zhí)行程序
二、創(chuàng)建一個(gè)動(dòng)/靜態(tài)庫,并以庫方式使用
源文件:helloworld.cpp
#include <stdio.h>
void func()
{
printf("Hello World\n");
}
CMakeList.txt
SET(LIB_HELLOWORLD_SRC helloworld.cpp)
//ADD_LIBRARY(libname [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL] source1 source2 .. sourceN)
//類型有三種SHARED|STATIC|MODULE
//SHARED:動(dòng)態(tài)庫 mac下生成的是dylib,linux下生成的是so
//STATIC:靜態(tài)庫
//MODULE:一般用不到
//這個(gè)指令用于生成二進(jìn)制庫文件,第一個(gè)參數(shù)是產(chǎn)物的名稱;第二個(gè)參數(shù)是庫的類型:動(dòng)態(tài)庫、靜態(tài)庫等;第三個(gè)參數(shù)是用于生成該產(chǎn)物的源文件
ADD_LIBRARY(hello SHARED ${LIB_HELLOWORLD_SRC})
//上面的那條指令通過SHARED生成的是動(dòng)態(tài)庫、而這條指令通過STATIC生成的是靜態(tài)庫
ADD_LIBRARY(hello_static STATIC ${LIB_HELLOWORLD_SRC})
//這條指令用于設(shè)定輸出target產(chǎn)物的一些屬性,需要四個(gè)參數(shù)
//第一個(gè)參數(shù)是 target的名稱
//第二個(gè)參數(shù)是固定的關(guān)鍵詞 PROPERTIES
//第三個(gè)參數(shù)是屬性名稱 就像Map的key一樣
//第四個(gè)參數(shù)是該屬性的設(shè)置的值
// 為了靜態(tài)庫和動(dòng)態(tài)庫的名稱為同樣的名稱,使用PROPERTIES OUTPUT_NAME "XXX"
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
//為了同時(shí)生成靜態(tài)庫和動(dòng)態(tài)庫,并且后者不清除前者,使用PROPERTIES CLEAN_DIRECT_OUTPUT 1
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
同樣執(zhí)行mkdir build && cd build && cmake .. && make
可以看到同時(shí)有動(dòng)態(tài)庫libhello.dylib和靜態(tài)庫libhello.a生成
[ 25%] Building CXX object lib/CMakeFiles/hello_static.dir/helloworld.o
[ 50%] Linking CXX static library libhello.a
[ 50%] Built target hello_static
[ 75%] Building CXX object lib/CMakeFiles/hello.dir/helloworld.o
[100%] Linking CXX shared library libhello.dylib
[100%] Built target hello
yangbin@yangbindeMacBook-Pro build % ls lib/
CMakeFiles cmake_install.cmake libhello.dylib
Makefile libhello.a
使用生成的動(dòng)/靜態(tài)庫, 生產(chǎn)可執(zhí)行文件
源文件:main.cpp
#include "helloworld.h"
int main()
{
func();
return 0;
}
CMakeLists.txt
ADD_EXECUTABLE(main main.cpp)
MESSAGE(STATUS "PROJECT_BINARY_DIR"${PROJECT_BINARY_DIR})
MESSAGE(STATUS "PROJECT_SOURCE_DIR"${PROJECT_SOURCE_DIR})
#LINK_DIRECTORIES(lib) # 如果該行寫在ADD_EXECUTABLE 報(bào)錯(cuò) ld: warning: directory not found for option '-Llib'
#使用動(dòng)態(tài)庫
#ADD_LIBRARY(hello SHARED IMPORTED)
#SET_TARGET_PROPERTIES(hello PROPERTIES IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/lib/libhello.dylib) # 這里用../lib/libhello.dylib就是不行,提示link時(shí)找不到對(duì)應(yīng)的庫,使用${PROJECT_SOURCE_DIR}絕對(duì)路徑來設(shè)置才可以
#使用靜態(tài)庫
ADD_LIBRARY(hello STATIC IMPORTED)
SET_TARGET_PROPERTIES(hello PROPERTIES IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/lib/libhello.a) # 這里用../lib/libhello.a就是不行,提示link時(shí)找不到對(duì)應(yīng)的庫,使用${PROJECT_SOURCE_DIR}絕對(duì)路徑來設(shè)置才可以
INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/lib/include)
TARGET_LINK_LIBRARIES(main hello)
小節(jié)小結(jié):
- 學(xué)習(xí)兩個(gè)指令:ADD_LIBRARY、SET_TARGET_PROPERTIES
- 學(xué)習(xí)生產(chǎn)動(dòng)態(tài)庫和靜態(tài)庫的方式
- 使用動(dòng)/靜態(tài)庫
三、以源碼方式引入第三方庫,以多層目錄方式使用
上面一小節(jié),我們學(xué)習(xí)時(shí)間了,通過動(dòng)態(tài)庫或者靜態(tài)庫的方式使用。而有些場(chǎng)景需要我們以源碼的方式而不是動(dòng)/靜態(tài)的方式引入。同時(shí)為了保證代碼的相互獨(dú)立,以多層目錄而不是在同一個(gè)目錄中使用。比如:為了方便的調(diào)用、修改、調(diào)試第三方庫的場(chǎng)景。這小節(jié)我們對(duì)其進(jìn)行實(shí)踐。
├── CMakeLists.txt
├── lib
│ ├── CMakeLists.txt
│ ├── helloworld.cpp
│ └── include
│ └── helloworld.h
└── main.cpp
代碼不變,修改點(diǎn)在于CMakeList
外層CMakeList.txt
ADD_EXECUTABLE(main main.cpp)
//這里用到了一個(gè)新的指令 添加子文件夾,有子文件夾的CmakeList來進(jìn)行編譯成庫給外層使用
//ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
//這個(gè)指令用于向當(dāng)前工程添加存放源文件的子目錄,并且可以通過第二個(gè)參數(shù)指定中間二進(jìn)制和目標(biāo)二進(jìn)制存放的位置。EXCLUDE_FROM_ALL參數(shù)的含義是將這個(gè)目錄從編譯過程中排出,比如工程中的test或者sample目錄,可能需要工程構(gòu)建完成后,再進(jìn)入對(duì)應(yīng)的目錄單獨(dú)構(gòu)建
ADD_SUBDIRECTORY(lib)
//下面兩個(gè)指令,都是添加頭文件,但是使用方式和作用還是有些不同的。
//主要區(qū)別在于:
//1. include_directories 將作用于整個(gè)工程,target_include_directories 將作用于target 的項(xiàng)目
//2. target目標(biāo)文件必須已經(jīng)存在(由命令add_executable()或add_library()所創(chuàng)建),并且不能被IMPORTED修飾
//3. 關(guān)鍵字INTERFACE,PUBLIC和PRIVATE指定它后面參數(shù)的作用域。PRIVATE和PUBLIC項(xiàng)會(huì)填充targe目標(biāo)文件的INCLUDE_DIRECTORIES屬性。
//4 PUBLIC和INTERFACE項(xiàng)會(huì)填充target目標(biāo)文件的INTERFACE_INCLUDE_DIRECTORIES屬性。隨后的參數(shù)指定包含目錄。
//target_include_directories(<target> [SYSTEM] [AFTER|BEFORE] <INTERFACE|PUBLIC|PRIVATE> [items1…])
TARGET_INCLUDE_DIRECTORIES(main PUBLIC lib/include)
//include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 …])
#INCLUDE_DIRECTORIES(lib/include)
TARGET_LINK_LIBRARIES(main hello)
內(nèi)層子文件夾的CMakeList.txt
SET(LIB_HELLOWORLD_SRC helloworld.cpp)
ADD_LIBRARY(hello SHARED ${LIB_HELLOWORLD_SRC})
INCLUDE_DIRECTORIES(include)
小節(jié)小結(jié):
- 學(xué)習(xí)實(shí)踐三個(gè)指令:ADD_SUBDIRECTORY、INCLUDE_DIRECTORIES、TARGET_INCLUDE_DIRECTORIES
- 以子文件夾的結(jié)構(gòu)上組織代碼的形式進(jìn)行使用
四、跨平臺(tái)共用lib第三庫,改變代碼的層級(jí)結(jié)構(gòu)
如果代碼結(jié)構(gòu)發(fā)生變化,把lib不放到src的子文件夾下,有什么差異吶
這種場(chǎng)景的應(yīng)用也很多,比如 lib是一些通用的跨平臺(tái)庫,而src是android
或者ios等平臺(tái)的一些特有的代碼。為了方便的公用lib就會(huì)采用這種組織形式
├── lib
│ ├── CMakeLists.txt
│ ├── helloworld.cpp
│ └── include
│ └── helloworld.h
└── src
├── CMakeLists.txt
└── main.cpp
Camke .. 時(shí)會(huì)報(bào)如下錯(cuò)誤。
CMake Error at CMakeLists.txt:3 (ADD_SUBDIRECTORY):
ADD_SUBDIRECTORY not given a binary directory but the given source
directory "/xxx/cmake實(shí)踐/t6/lib"
is not a subdirectory of
"/xxx/cmake實(shí)踐/t6/src". When
specifying an out-of-tree source a binary directory must be explicitly
specified.
原因是也很明顯,如果文件結(jié)構(gòu)上ADD_SUBDIRECTORY的文件夾不是target的子文件,則需要第二個(gè)參數(shù)指明,該子target生成的二進(jìn)制產(chǎn)物的路徑。
修改點(diǎn):外層CmakeList
ADD_EXECUTABLE(main main.cpp)
//如果outputs文件夾不存在,則創(chuàng)建
file(MAKE_DIRECTORY output)
//添加第二個(gè)參數(shù)指明編譯該子target的產(chǎn)物的存放位置
ADD_SUBDIRECTORY(../lib output)
TARGET_INCLUDE_DIRECTORIES(main PUBLIC ../lib/include)
#INCLUDE_DIRECTORIES(../lib/include)
TARGET_LINK_LIBRARIES(main hello)
小節(jié)小結(jié):
- 改變代碼的層級(jí)結(jié)構(gòu),更好的跨屏臺(tái)支持。
五、其他的一些小細(xì)節(jié)(項(xiàng)目實(shí)踐中遇到的問題)
如何使用CMake調(diào)用外部的工具庫
如果子target通過SET_TARGET_PROPERTIES的OUTPUT_NAME屬性設(shè)置了輸出的library的名稱,如下所示:
ADD_LIBRARY(hello_static STATIC ${LIB_HELLOWORLD_SRC})
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
在目標(biāo)target中使用時(shí)最好采用ADD_LIBRARY時(shí)的命名(即hello_static),而不是OUTPUT_NAME后的名稱(即hello),否則link時(shí)找不到,在一些IDE(比如androidstuido或者clion)上編譯會(huì)報(bào)錯(cuò),特別是androidstudio不直接提示子target的產(chǎn)物找不到,而是子target和目標(biāo)target并行編譯了。。。
Message為什么有時(shí)打印不出來
這個(gè)也可能和平臺(tái)兼容性有關(guān)系,在電腦端或者ios端都沒問題,而android上卻不一定能正常輸出。需要設(shè)置為大于等于WARNING級(jí)別才可以。STATUS級(jí)別andorid上無法輸出對(duì)應(yīng)log
//android平臺(tái)上無法輸出該log,但是其他平臺(tái)都可以
MESSAGE(STATUS "PROJECT_SOURCE_DIR"${PROJECT_SOURCE_DIR})
//andorid平臺(tái)也可以輸出
MESSAGE(WARNING "PROJECT_SOURCE_DIR"${PROJECT_SOURCE_DIR})
本文實(shí)踐的代碼已經(jīng)上傳到github-mediajourney
六、資料
[練習(xí)項(xiàng)目-cmake-examples-Chinese ]
練習(xí)項(xiàng)目-cmake-examples
圖書-cmake實(shí)踐.pdf
圖書-CMake菜譜(CMake Cookbook中文版)
七、收獲
通過本篇的學(xué)習(xí)實(shí)踐,
- 了解Cmake的一些常用指令
- 通過Cmake進(jìn)行工程化實(shí)踐(以庫、源碼、跨屏的組織形式等多個(gè)角度實(shí)踐)
- 總結(jié)記錄實(shí)踐中遇到的問題
感謝你的閱讀
歡迎關(guān)注公眾號(hào)“音視頻開發(fā)之旅”,一起學(xué)習(xí)成長(zhǎng)。
歡迎交流