02_使用bazel構(gòu)建一個C++項目


時間:2018-12-18 作者:魏文應(yīng)


一、前 言

通過這個本文凄硼,你將知道如何構(gòu)建一個C++項目:

  • 編譯一個目標(target)。
  • 可視化項目中目標的相互依賴關(guān)系。
  • 控制包的可見性良哲,也就是限定包的作用域。

其實助隧,就是使你對 bazel 如何組織筑凫、編譯一個 C++ 項目有一個大致了解。這里,你可以參考 bazel 官方教程《Introduction to Bazel: Building a C++ Project》

https://docs.bazel.build/versions/master/tutorial/cpp.html

二巍实、bazel 構(gòu)建一個C++項目

下載示例代碼

首先滓技,找個位置,創(chuàng)建一個文件夾用于存放下載的代碼棚潦,依次執(zhí)行下面命令:

# 創(chuàng)建一個目錄
mkdir ~/wwy-dir
# 進入這個目錄
cd ~/wwy-dir

然后令漂,從 github 上下載官方提供的示例代碼,執(zhí)行下面命令:

git clone https://github.com/bazelbuild/examples/

下載完成以后丸边,就會在當前目錄下叠必,得到一個名為 examples 的文件夾:

下載得到的示例代碼

這個項目有 C++java妹窖、android纬朝、java-maven等示例,我們進入 C++ 項目的示例:

cd examples/cpp-tutorial/

你可以通過 tree 命令(你還可以指定子目錄層級骄呼,tree -L 2指定子目錄為2)玄组,查看一下有哪些文件。下面是目錄 examples/cpp-tutorial/ 中的內(nèi)容:

├── README.md
├── stage1
│   ├── main
│   │   ├── BUILD
│   │   └── hello-world.cc
│   ├── README.md
│   └── WORKSPACE
├── stage2
│   ├── main
│   │   ├── BUILD
│   │   ├── hello-greet.cc
│   │   ├── hello-greet.h
│   │   └── hello-world.cc
│   ├── README.md
│   └── WORKSPACE
└── stage3
    ├── lib
    │   ├── BUILD
    │   ├── hello-time.cc
    │   └── hello-time.h
    ├── main
    │   ├── BUILD
    │   ├── hello-greet.cc
    │   ├── hello-greet.h
    │   └── hello-world.cc
    ├── README.md
    └── WORKSPACE

上面 stage1谒麦、stage2俄讹、stage3 是三個示例,是三個獨立的工程項目(三個工程之間沒有直接關(guān)系)绕德。示例 stage1 演示了一個簡單的 C++ 程序患膛,示例 stage2 演示了一個 .cc 文件需要引用另一個 .cc 文件的情況,示例 stage3 演示了一個 .cc 文件和 main 程序不在同一個目錄下的情況(一個在 main 目錄耻蛇,一個在 lib 目錄)踪蹬。其中,以下幾點比較重要:

  • WORKSPACE : 工作空間臣咖,在項目工程的根(root)目錄下跃捣,有個 WORKSPACE 文件,表示這是一個工程項目夺蛇。比如疚漆,stage1 目錄下有一個 WORKSPACE 文件,說明這個目錄下有一個完整的工程項目刁赦。
  • BUILD : BUILD 文件娶聘,這個文件會告訴 bazel 如何編譯 .cc 源文件,限定多個源文件之間的關(guān)系甚脉。工作空間中的可能有多個文件夾丸升,每個目錄有代碼的源文件和 BUILD 文件,這些有 BUILD 文件的文件夾牺氨,被稱為 包(pakege)狡耻。

Bazel 去編譯構(gòu)建一個項目(project)時墩剖,所有的輸入文件和依賴文件,都應(yīng)該要求在同一個工作空間(WORKSPACE)中夷狰。 雖然可以使用某種鏈接方式涛碑,達到使用其它工作空間的文件的目的,但一般不這么使用孵淘。

簡單認識 BUILD 文件

BUILD 文件包含了一系列不同類型的指令蒲障,Bazel 工具執(zhí)行這些指令,來構(gòu)建和編譯我們的目標(target):

你可能會問:什么是目標target瘫证?target 就是你想生成的東西揉阎,比如使用源代碼生成二進制可執(zhí)行文件,這個可執(zhí)行文件就是target背捌。當然毙籽,target 還可以是一些 .o中間文件、lib 庫文件等等毡庆。

BUILD 文件中坑赡,最重要命令的就是 構(gòu)建規(guī)則(build rule),它告訴 Bazel 如何去創(chuàng)建和生成我們的目標么抗。比如 cpp-tutorial/stage1/main 目錄下的 BUILD 文件毅否,內(nèi)容如下:

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
)

上面規(guī)則中, hello-world 就是 目標target蝇刀, hello-world.cc 就是 依賴文件螟加,意思就是說,使用 hello-world.cc 文件吞琐,編譯生成一個我們想要的名為 hello-world 的可執(zhí)行文件捆探。雖然生成的目標文件 hello-world ,這個命名你可以隨意起站粟,但是黍图,我們一般會命名成和源文件名稱一致的名字。比如源文件是 srcs = [xxx.cc]奴烙,那么生成的文件就命名為 xxx 即可助被。

編譯工程

接下來,我們先來嘗試編譯一個簡單的工程缸沃。進入 cpp-tutorial/stage1 這個目錄恰起,并執(zhí)行下面命令:

bazel build //main:hello-world

//main: 是相對路徑修械,相對于工作區(qū)間 workspace 的根目錄的路徑趾牧。hello-world 是目標target,Bazel工具使用 main 目錄下 BUILD 這個文件指定的編譯規(guī)則肯污,來編譯生成目標翘单。編譯過程吨枉,會打印下面信息:

INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 3.756s, Critical Path: 0.45s
INFO: 2 processes: 2 linux-sandbox.
INFO: Build completed successfully, 6 total actions

恭喜你!第一次使用 Bazel 成功編譯了你的項目哄芜。這是貌亭,項目空間的根目錄下,生成目錄 bazel-bin 认臊,這個目錄中圃庭,包含有剛才編譯生成的目標:

編譯生成的文件

上圖中,bazel- 開頭的文件失晴,是編譯過程中剧腻,bazel 生成的中間文件目標文件。我們來執(zhí)行一些目標文件:

bazel-bin/main/hello-world

這時涂屁,就會運行可執(zhí)行程序 hello-world 书在,打印結(jié)果如下:

目標二進制可執(zhí)行程序執(zhí)行的結(jié)果

查看依賴關(guān)系圖

有時你想知道,目標是依賴哪些文件生成的呢拆又?你除了查看 BUILD 文件以外儒旬,還可以使用 bazel 工具查看。比如執(zhí)行下面命令:

bazel query --nohost_deps --noimplicit_deps 'deps(//main:hello-world)' \
  --output graph

deps(//main:hello-world) 說的是查看 //main:hello-world 這個目標的依賴關(guān)系帖族。--output graph 指定輸出格式為圖的格式栈源。打印結(jié)果如下:

INFO: Invocation ID: 661d04c1-f7f1-4419-a54c-afa1ba9619b4
digraph mygraph {
  node [shape=box];
"http://main:hello-world"
"http://main:hello-world" -> "http://main:hello-world.cc"
"http://main:hello-world.cc"
}

其中,出去 INFO 中的提示類信息竖般,下面的內(nèi)容描述的是一個圖:

digraph mygraph {
  node [shape=box];
"http://main:hello-world"
"http://main:hello-world" -> "http://main:hello-world.cc"
"http://main:hello-world.cc"
}

可以將上面這個代碼凉翻,粘貼到網(wǎng)頁版的 Graphviz 中:

Graphviz網(wǎng)站.png

然后點擊 Generate Graph 這個圖標按鈕,生成圖捻激,//main:hello-world 指向 //main:hello-world.cc制轰, 說明 //main:hello-world 的生成依賴于 //main:hello-world.cc 。對應(yīng) ubuntu 用戶來說胞谭,還可以安裝 Graphviz 客戶端垃杖,執(zhí)行下面命令安裝:

sudo apt update && sudo apt install graphviz xdot

然后使用 graphviz 客戶端軟件直接在本地顯示,執(zhí)行下面命令:

xdot <(bazel query --nohost_deps --noimplicit_deps 'deps(//main:hello-world)' \
  --output graph)

顯示效果是一樣的丈屹,如下:

graphviz客戶端顯示的效果

編譯多個目標

在上面的示例中调俘,我們只有一個 .cc 源文件,但我們項目開發(fā)中旺垒,一般會有多個 .cc 源文件彩库。下面,將演示如何編譯多個 .cc 源文件這種情況先蒋。先進入示例 stage2 工程目錄下:

cd cpp-tuorial/stage2

可以查看該項目下 main 文件夾下的 BUILD 文件骇钦,內(nèi)容如下:

cc_library(
    name = "hello-greet",
    srcs = ["hello-greet.cc"],
    hdrs = ["hello-greet.h"],
)

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
    deps = [
        ":hello-greet",
    ],
)

上面的 BUILD 腳本,Bazel 會先編譯 hello-greet 這個目標竞漾,這個目標是一個 C++ 的 lib 庫文件(使用 bazel 內(nèi)建規(guī)則 cc_library rule 來生成這個lib)眯搭。然后窥翩,Bazel 再編譯 hello-world 這個目標,這個目標是一個二進制可執(zhí)行文件鳞仙。deps 這個屬性寇蚊,告訴 Bazel 編譯 hello-world 時,需要用到 hello-greet 這個目標棍好,也就是 hello-world 的生成需要依賴(dependencies) hello-greet 仗岸。執(zhí)行下面命令來編譯 stage2 這個工程:

bazel build //main:hello-world

編譯完成以后,和剛才的示例 stage1 一樣借笙,可以嘗試執(zhí)行一下生成的二進制文件:

bazel-bin/main/hello-world

打印結(jié)果如下:

Hello world
Wed Dec 19 03:19:53 2018

如果這時候爹梁,你去修改 hello-greet.cc 這個源文件,那么提澎,bazel 只會重新編譯 ``hello-greet.cc` 這個文件姚垃,其它文件不會被重新編譯。其中:

# build 經(jīng)常被簡稱為編譯盼忌,其實是包含了很多步驟:編譯积糯、鏈接等。
# c++ 語言編譯過程大致如下:
#    - 編譯(compile): .cc文件編譯生成.o文件谦纱。
#    - 鏈接(link): 將所有.o文件鏈接生成二進制可執(zhí)行文件看成。

其實就是如果你 bazel build 編譯過一次,就會生成中間緩存文件(.o 文件和其它文件)跨嘉,然后將這些中間文件鏈接生成最終的二進制可執(zhí)行文件川慌,這個可執(zhí)行文件就是我們想要生成的最終的目標。等你下次編譯的時候祠乃,只會編譯你修改過的文件梦重,其它緩存文件不變,最終再次鏈接這些緩存文件亮瓷,生成新的最終目標琴拧。這么做,就得到了這樣一個目的:

編譯生成緩存文件的時間比較長嘱支,不需要生成緩存文件蚓胸,節(jié)約了編譯時間。這樣縮減了整個 build 過程的時間除师,提高構(gòu)建效率沛膳。

同樣,執(zhí)行下面命令汛聚,我們可以查看一下依賴關(guān)系圖:

xdot <(bazel query --nohost_deps --noimplicit_deps 'deps(//main:hello-world)' \
  --output graph)

結(jié)果如下:

stage2依賴關(guān)系圖

從中可以看出锹安,生成 hello-world 這個目標,需要依賴一個 hello-world.cc 源文件和一個 hello-greet 目標。這種增量式的構(gòu)建方式八毯,方便我們往項目中添加新的源文件搓侄,達到源文件分離瞄桨、目標分離的效果话速,減少耦合。

使用多個包

上面芯侥,我們講的是使用多個目標泊交。那么,包(package)又是什么玩意柱查?我們可以看示例 stage3 廓俭,來理解什么是包。示例 stage3 的文件結(jié)構(gòu)如下:

└──stage3
   ├── main
   │   ├── BUILD
   │   ├── hello-world.cc
   │   ├── hello-greet.cc
   │   └── hello-greet.h
   ├── lib
   │   ├── BUILD
   │   ├── hello-time.cc
   │   └── hello-time.h
   └── WORKSPACE

因為目錄 stage 下唉工,有兩個子目錄 main 和 lib研乒,而且子目錄內(nèi)都包含了 BUILD 文件,因此淋硝, mainlib 被稱為 包(package) 雹熬,它們是項目 stage3 的兩個包。前面 stage1 和 stage2 兩個項目中谣膳,源文件都在一個目錄下竿报,引入包的概念以后,源文件可以在項目空間中的不同目錄下继谚×揖可以看一下 lib/BUILD 這個文件的內(nèi)容:

cc_library(
    name = "hello-time",
    srcs = ["hello-time.cc"],
    hdrs = ["hello-time.h"],
    visibility = ["http://main:__pkg__"],
)

以及 main/BUILD 文件中的內(nèi)容:

cc_library(
    name = "hello-greet",
    srcs = ["hello-greet.cc"],
    hdrs = ["hello-greet.h"],
)

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
    deps = [
        ":hello-greet",
        "http://lib:hello-time",
    ],
)

你可以看到,hello-world 這個 target 在 package main 中花履,需要依賴 target hello-time 芽世,但是 hello-time 這個 target 在 package lib 中。默認情況下诡壁,target 只在同一個 BUILD 中有效捂襟,其它 BUILD 不可見,也就是無法使用欢峰。為了讓其它 package 下的 target 也能使用葬荷,那么,只需加上 visibility 即可纽帖。比如上面的 visibility = ["http://main:__pkg__"] 就是告訴 Bazel 宠漩,hello-time 這個 target ,main 這個包可以看到懊直,這樣 hello-world 就可以使用 hello-time 這個target 了扒吁。這就好比:

'''
下面打個形象的比喻:
    張三、李四室囊、王五各自家中有一些好吃的雕崩。張三拿出他家的紅棗給李四看魁索,
并說,李四你可以吃我家的紅棗盼铁,這時李四才能吃張三家的紅棗粗蔚,至于李四吃
不吃,又是另一回事饶火。同時鹏控,張三沒有說讓王五吃他家紅棗,連看都不讓王五
看肤寝,王五當然吃不到張三家的紅棗啦当辐。
'''

這樣,每個 package 目錄下有一個自己的 BUILD鲤看,既可以做到相互獨立缘揪,也可以做到相互調(diào)用,減少耦合义桂,利用維護找筝。你同樣可以通過下面命令編譯項目 stage3 :

bazel build //main:hello-world

編譯完成以后,執(zhí)行生成的二進制可執(zhí)行文件:

bazel-bin/main/hello-world

還可以通過下面指令澡刹,查看依賴關(guān)系圖:

xdot <(bazel query --nohost_deps --noimplicit_deps 'deps(//main:hello-world)' \
  --output graph)
bazel 生成的程序依賴關(guān)系圖

在目標中使用標簽

所謂標簽呻征,其實就是 路徑 + 目標 。在使用命令 bazel build //main:hello-world 和 BUILD 文件中 //main:hello-world 罢浇,以及 //lib:hello-time 陆赋。其中 //main://lib: 都是包的相對路徑,相對于項目空間的根目錄的路徑嚷闭。在命令 bazel build target攒岛,如果其中 target 這個目標是一個需要編譯的目標,那么這個目標所在的目錄下胞锰,要有一個 BUILD 腳本灾锯,而且這個target 在 BUILD 腳本中要有,名稱相同嗅榕。比如顺饮,上面 bazel build //main:hello-world 中的目標 //main:hello-world,在 main/BUILD 中有相應(yīng)的 target name = hello-world 凌那,兩者 hello-world 這個名字一致兼雄。

如果 target 和當前 BUILD 腳本在同一個 package 中(其實就是在同一個目錄下),那么路徑可以不寫帽蝶,比如 : //:hello-world 赦肋。如果是在同一個 BUILD 腳本中,可以更簡潔一些,這么寫 :hello-world 佃乘。

三囱井、小 結(jié)

至此,我們完成了 C++ 編譯的基本操作趣避。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末庞呕,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子鹅巍,更是在濱河造成了極大的恐慌千扶,老刑警劉巖料祠,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骆捧,死亡現(xiàn)場離奇詭異,居然都是意外死亡髓绽,警方通過查閱死者的電腦和手機敛苇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來顺呕,“玉大人枫攀,你說我怎么就攤上這事≈瓴瑁” “怎么了来涨?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長启盛。 經(jīng)常有香客問我蹦掐,道長,這世上最難降的妖魔是什么僵闯? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任卧抗,我火速辦了婚禮,結(jié)果婚禮上鳖粟,老公的妹妹穿的比我還像新娘社裆。我一直安慰自己,他們只是感情好向图,可當我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布泳秀。 她就那樣靜靜地躺著,像睡著了一般榄攀。 火紅的嫁衣襯著肌膚如雪嗜傅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天航攒,我揣著相機與錄音磺陡,去河邊找鬼。 笑死,一個胖子當著我的面吹牛币他,可吹牛的內(nèi)容都是我干的坞靶。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼蝴悉,長吁一口氣:“原來是場噩夢啊……” “哼彰阴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起拍冠,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤尿这,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后庆杜,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體射众,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年晃财,在試婚紗的時候發(fā)現(xiàn)自己被綠了叨橱。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡断盛,死狀恐怖罗洗,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情钢猛,我是刑警寧澤伙菜,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站命迈,受9級特大地震影響贩绕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜躺翻,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一丧叽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧公你,春花似錦踊淳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至剪芥,卻和暖如春垄开,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背税肪。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工溉躲, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留榜田,地道東北人。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓锻梳,卻偏偏與公主長得像箭券,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子疑枯,可洞房花燭夜當晚...
    茶點故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內(nèi)容