前言
最近看了很多項(xiàng)目的代碼部默,代碼是用cmake編譯的,由于各種庫(kù)之間鏈接關(guān)系錯(cuò)綜復(fù)雜造虎,加上PRIVATE傅蹂,PUBLIC,INTERFACE屬性值算凿,我在添加代碼的時(shí)候總會(huì)遇到稀奇古怪的編譯的問題份蝴,網(wǎng)上看了很多文章,寫的都不是很靠譜澎媒,正好看到一個(gè)b站視頻講的不錯(cuò)搞乏,解決了我很多疑惑,我又有了新的疑惑戒努,折騰了一晚上終于把這個(gè)搞明白了请敦,分享給大家。
一储玫、原理
從 modern cmake(>=3.0) 開始侍筛,使用的范式從 director-oriented 轉(zhuǎn)換到了 target-oriented。 這其中最重要的有三個(gè)概念:
target
target相應(yīng)的properties
可見性
所謂target就是編譯的目標(biāo)撒穷,一般就三種:
靜態(tài)庫(kù): 使用add_library()
動(dòng)態(tài)庫(kù): 使用add_library() 指定SHARED關(guān)鍵字
可執(zhí)行文件: 使用add_executable
所謂properties就是target的屬性匣椰,最常見的有以下五種:
編譯標(biāo)志:使用target_complie_option
預(yù)處理宏標(biāo)志:使用 target_compile_definitions
頭文件目錄:使用 target_include_directories
鏈接庫(kù):使用 target_link_libraries
鏈接標(biāo)志:使用 target_link_options
所謂可見性就是上述這些屬性在不同target之間的傳遞性。有三種:
PRIVATE
PUBLIC
INTERFACE
缺省值為PUBLIC
二端礼、可見性的傳遞(非常重要)
每一個(gè)Target對(duì)于自身設(shè)置的不同屬性處理
對(duì)于private的property禽笑,不會(huì)傳遞入录,只會(huì)自己用。
對(duì)于public的property佳镜,會(huì)傳遞僚稿,也自己用。
對(duì)于interface的property蟀伸,會(huì)傳遞蚀同,但不會(huì)自己用
public和interface的屬性是可傳遞屬性
可見性的傳遞是依靠target_link_libraries,傳遞的規(guī)則如下:
假設(shè)如下鏈接關(guān)系
target_link_libraries(B XXX A)// XXX為private啊掏,public蠢络,interface
如果XXX為private,A的可傳遞屬性變成B的private property
如果XXX為public迟蜜,A的可傳遞屬性變成B的public property
如果XXX為interface刹孔,A的可傳遞屬性變成B的interface property
三、實(shí)戰(zhàn)1
3.1 最簡(jiǎn)單的demo
interface_a.h
#ifndef CPP_INTERFACE_A_H
#define CPP_INTERFACE_A_H
int addA(int a, int b);
#endif //CPP_INTERFACE_A_H
interface_b.h
#ifndef CPP_INTERFACE_B_H
#define CPP_INTERFACE_B_H
int addB(int a, int b);
#endif //CPP_INTERFACE_B_H
interface_a.cpp
#include <stdio.h>
#include "interface_a.h"
int addA(int a, int b) {
printf("addA\n");
return a + b;
}
interface_b.cpp
#include <stdio.h>
#include "interface_b.h"
#include "interface_a.h"
int addB(int a, int b)
{
printf("addB\n");
return addA(a, b);
}
main.cpp
#include "interface_b.h"
#include <stdio.h>
int main()
{
printf("main\n");
addB(1, 2);
return 0;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
project(CPP)
set(CMAKE_CXX_STANDARD 17)
add_library(A libA/interface_a.c)
target_include_directories(A PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includeA)
add_library(B SHARED libB/interface_b.c)
target_link_libraries(B A)
target_include_directories(B PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includeB)
add_executable(CPP main.c)
target_link_libraries(CPP B)
用圖來表示代碼就如下小泉,CPP調(diào)用B中addB芦疏,B中的addB調(diào)用addA
最后運(yùn)行的結(jié)果
main
addB
addA
這例子簡(jiǎn)單吧冕杠,我們進(jìn)一步來解讀一下CMakeLists.txt微姊,紅色為傳遞過來的屬性
查看對(duì)應(yīng)的cmake的編譯中間文件,可以進(jìn)一步驗(yàn)證我們的判斷分预,正好和對(duì)應(yīng)的屬性對(duì)應(yīng)兢交。
3.2 main中能否調(diào)用addA
可以看到CPP擁有target_include_directories(CPP PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includeA和target_link_libraries(CPP A)的屬性
理論上來說肯定main.cpp可以調(diào)用addA
修改main.cpp
#include "interface_b.h"
#include "interface_a.h"
#include <stdio.h>
int main()
{
printf("main\n");
addA(1, 2);
addB(1, 2);
return 0;
}
成功運(yùn)行
main
addA
addB
addA
3.3 將PUBLIC改成PRIVATE
如果我們對(duì)CMakeLists.txt做如下修改,請(qǐng)問上面main.c還能不能正常運(yùn)行
cmake_minimum_required(VERSION 3.22)
project(CPP)
set(CMAKE_CXX_STANDARD 17)
add_library(A libA/interface_a.c)
target_include_directories(A PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includeA)
add_library(B SHARED libB/interface_b.c)
target_link_libraries(B PRIVATE A)//改動(dòng)的地方
target_include_directories(B PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includeB)
add_executable(CPP main.c)
target_link_libraries(CPP B)
解讀一下CmakeLists.txt笼痹,紅色為傳遞過來的屬性
和3.2中最大的差異就是CPP中includeA沒了配喳,那main.c肯定找不到#include "interface_a.h",所以會(huì)編譯報(bào)錯(cuò)找不到頭文件interface_a.h
運(yùn)行結(jié)果果然和預(yù)料的一樣凳干。
/home/kobe/submits/CPP/main.c:2:10: fatal error: interface_a.h: No such file or directory
2 | #include "interface_a.h"
| ^~~~~~~~~~~~~~~
3.4 手動(dòng)添加includeA
繼續(xù)修改 CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
project(CPP)
set(CMAKE_CXX_STANDARD 17)
add_library(A libA/interface_a.c)
target_include_directories(A PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includeA)
add_library(B SHARED libB/interface_b.c)
target_link_libraries(B PRIVATE A)
target_include_directories(B PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includeB)
add_executable(CPP main.c)
target_link_libraries(CPP B)
target_include_directories(CPP PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includeA)//修改的代碼
解讀一下CmakeLists.txt晴裹,紅色為傳遞過來的屬性,紫色是CPP額外加的屬性
看到C自身屬性添加了includeA救赐,那頭文件也有了涧团,鏈接的時(shí)候,CPP鏈接B经磅,B鏈接A泌绣,最后可以鏈接到一起,CPP應(yīng)該可以使用addA了
運(yùn)行結(jié)果果然可以
main
addA
addB
addA
四.實(shí)戰(zhàn)2
4.1 Interface的作用
修改文件interface_b.cpp预厌,移除B對(duì)A的addA的使用
#include <stdio.h>
#include "interface_b.h"
int addB(int a, int b)
{
printf("addB\n");
return a + b;
}
修改文件cmakelists.txt
cmake_minimum_required(VERSION 3.22)
project(CPP)
set(CMAKE_CXX_STANDARD 17)
add_library(A libA/interface_a.c)
target_include_directories(A PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includeA)
add_library(B SHARED libB/interface_b.c)
target_link_libraries(B INTERFACE A)
target_include_directories(B PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includeB)
add_executable(CPP main.c)
target_link_libraries(CPP B)
解讀一下CmakeLists.txt阿迈,紅色為傳遞過來的屬性
因?yàn)镃PP使用到A的接口和B的接口,B沒有使用A的接口轧叽,所以按照上面的屬性苗沧,A刊棕,B,CPP三個(gè)都可以正常編譯運(yùn)行
main
addA
addB
4.2 add_library(C INTERFACE) -- 比較特殊的用法
修改文件cmakelists.txt
cmake_minimum_required(VERSION 3.22)
project(CPP)
set(CMAKE_CXX_STANDARD 17)
add_library(A libA/interface_a.c)
target_include_directories(A PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includeA)
add_library(C INTERFACE)
target_include_directories(C INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/includeB)
add_library(B SHARED libB/interface_b.c)
target_link_libraries(B PUBLIC C INTERFACE A)
add_executable(CPP main.c)
target_link_libraries(CPP B)
這是種特殊的用法待逞,就是創(chuàng)建一個(gè)虛擬的target C鞠绰,add_library(C INTERFACE)不會(huì)編譯出任何庫(kù)和可執(zhí)行文件,而且C的所有屬性必須設(shè)置為INTERFACE
解讀一下CmakeLists.txt飒焦,紅色為傳遞過來的屬性
最后也可以完美的運(yùn)行蜈膨!
這里C就是一個(gè)header-only的庫(kù),他的所有屬性都是Interface的牺荠,不會(huì)編譯出任何庫(kù)翁巍,唯一作用就是將屬性傳遞給link它的目標(biāo)。
五休雌、總結(jié)
按照1.原理和2.可見性的傳遞灶壶,對(duì)應(yīng)每一個(gè)項(xiàng)目,用這樣子的表格列出來每一個(gè)target對(duì)應(yīng)的屬性杈曲,也就可以了解到每一個(gè)target編譯依賴的頭文件以及庫(kù)文件驰凛。記住以Target的視角來看待每一個(gè)屬性,關(guān)注兩個(gè)Target之間的link的屬性担扑,以及兩個(gè)Target之間的屬性傳遞恰响。