Bazel入門:編譯C++項(xiàng)目

官網(wǎng):https://www.bazel.build
Github: https://github.com/bazelbuild/bazel

最近用到tensorflow的時(shí)候遇到了個(gè)新的編譯工具Bazel,踩了無(wú)數(shù)坑之后終于決定還是系統(tǒng)地學(xué)習(xí)一下這貨。

Bazel是一個(gè)類似于Make的編譯工具,是Google為其內(nèi)部軟件開(kāi)發(fā)的特點(diǎn)量身定制的工具,如今Google使用它來(lái)構(gòu)建內(nèi)部大多數(shù)的軟件。Google認(rèn)為直接用Makefile構(gòu)建軟件速度太慢,結(jié)果不可靠,所以構(gòu)建了一個(gè)新的工具叫做Bazel,Bazel的規(guī)則層級(jí)更高。

下面就以C++和Bazel結(jié)合的例子理解一下Bazel的工作原理。

Install

安裝過(guò)程請(qǐng)參考:http://bazel.io/docs/install.html

建立工作區(qū)(workspace)

Bazel的編譯是基于工作區(qū)(workspace)的概念。工作區(qū)是一個(gè)存放了所有源代碼和Bazel編譯輸出文件的目錄,也就是整個(gè)項(xiàng)目的根目錄。同時(shí)它也包含一些Bazel認(rèn)識(shí)的文件:

  1. WORKSPACE文件,用于指定當(dāng)前文件夾就是一個(gè)Bazel的工作區(qū)。所以WORKSPACE文件總是存在于項(xiàng)目的根目錄下。
  2. 一個(gè)或多個(gè)BUILD文件,用于告訴Bazel怎么構(gòu)建項(xiàng)目的不同部分。(如果工作區(qū)中的一個(gè)目錄包含BUILD文件,那么它就是一個(gè)package。)

那么要指定一個(gè)目錄為Bazel的工作區(qū),就只要在該目錄下創(chuàng)建一個(gè)空的WORKSPACE文件即可。

當(dāng)Bazel編譯項(xiàng)目時(shí),所有的輸入和依賴項(xiàng)都必須在同一個(gè)工作區(qū)。屬于不同工作區(qū)的文件,除非linked否則彼此獨(dú)立。

理解BUILD文件

一個(gè)BUILD文件包含了幾種不同類型的指令。其中最重要的是編譯指令,它告訴Bazel如何編譯想要的輸出,比如可執(zhí)行二進(jìn)制文件或庫(kù)。BUILD文件中的每一條編譯指令被稱為一個(gè)target,它指向一系列的源文件和依賴,一個(gè)target也可以指向別的target。

舉個(gè)例子,下面這個(gè)hello-world的target利用了Bazel內(nèi)置的cc_binary編譯指令,來(lái)從hello-world.cc源文件(沒(méi)有其他依賴項(xiàng))構(gòu)建一個(gè)可執(zhí)行二進(jìn)制文件。指令里面有些屬性是強(qiáng)制的,比如name,有些屬性則是可選的,srcs表示的是源文件。

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

使用Bazel編譯項(xiàng)目

Bazel提供了一些編譯的例子,在https://github.com/bazelbuild/examples/,可以clone到本地試一下。其中examples/cpp-tutorial目錄下包含了這么些文件:

examples
└── cpp-tutorial
    ├──stage1
    │  └── main
    │      ├── BUILD
    │      ├── hello-world.cc
    │  └── WORKSPACE
    ├──stage2
    │  ├── main
    │  │   ├── BUILD
    │  │   ├── hello-world.cc
    │  │   ├── hello-greet.cc
    │  │   ├── hello-greet.h
    │  └── WORKSPACE
    └──stage3
       ├── main
       │   ├── BUILD
       │   ├── hello-world.cc
       │   ├── hello-greet.cc
       │   └── hello-greet.h
       ├── lib
       │   ├── BUILD
       │   ├── hello-time.cc
       │   └── hello-time.h
       └── WORKSPACE

可以看到分成了3組文件,分別對(duì)應(yīng)本文中的3個(gè)例子。在第一個(gè)例子中,我們首先學(xué)習(xí)如何構(gòu)建單個(gè)package中的單個(gè)target。在第二個(gè)例子中,我們將把整個(gè)項(xiàng)目拆分成單個(gè)package的多個(gè)target。第三個(gè)例子則將項(xiàng)目拆分成多個(gè)package,用多個(gè)target編譯。

1. 編譯你的第一個(gè)Bazel項(xiàng)目

首先進(jìn)入到cpp-tutorial/stage1目錄下,然后運(yùn)行以下指令:

bazel build //main:hello-world

注意target中的//main:是BUILD文件相對(duì)于WORKSPACE文件的位置,hello-world則是我們?cè)贐UILD文件中命名好的target的名字。

然后Bazel就會(huì)有一些類似這樣的輸出:

INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 2.267s, Critical Path: 0.25s

恭喜,這樣你的第一個(gè)Bazel target就編譯好了!Bazel將編譯的輸出放在項(xiàng)目根目錄下的bazel-bin目錄下,可以看一下這個(gè)目錄,理解一下Bazel的輸出結(jié)構(gòu)。

現(xiàn)在你可以測(cè)試你剛剛生成的二進(jìn)制文件了:

bazel-bin/main/hello-world

2. 查看依賴圖

一個(gè)成功的build將所有的依賴都顯式定義在了BUILD文件中。Bazel使用這些定義來(lái)創(chuàng)建項(xiàng)目的依賴圖,這能夠加速編譯的過(guò)程。

讓我們來(lái)可視化一下我們項(xiàng)目的依賴吧。首先,生成依賴圖的一段文字描述(即在工作區(qū)根目錄下運(yùn)行下述指令):

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

這個(gè)指令告訴Bazel查找target //main:hello-world的所有依賴項(xiàng)(不包括host和隱式依賴),然后輸出圖的文字描述。再把文字描述貼到GraphViz里,你就可以看到如下的依賴圖了??梢钥闯鲞@個(gè)項(xiàng)目是用單個(gè)源文件編譯出的單個(gè)target,并沒(méi)有別的依賴。

Dependency graph for 'hello-world'

好的,到目前為止,我們已經(jīng)建立了工作區(qū),編譯了一個(gè)項(xiàng)目,并且查看了它的依賴。接下來(lái)讓我們加點(diǎn)難度。

3. 多個(gè)target的編譯

單個(gè)target的方式對(duì)于小項(xiàng)目來(lái)說(shuō)是高效的,但是對(duì)于大項(xiàng)目來(lái)說(shuō),你可能會(huì)想把它拆分成多個(gè)target和多個(gè)package來(lái)實(shí)現(xiàn)快速增量的編譯(這樣就只需要重新編譯改變過(guò)的部分)。

首先我們來(lái)嘗試著把項(xiàng)目拆分成兩個(gè)target??匆幌?code>cpp-tutorial/stage2/main目錄下的BUILD文件,它是這樣的:

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",
    ],
)

我們看到在這個(gè)BUILD文件中,Bazel首先編譯了hello-greet這個(gè)庫(kù)(利用Bazel內(nèi)置的cc_library編譯指令),然后編譯hello-world這個(gè)二進(jìn)制文件。hello-world這個(gè)target的deps屬性告訴Bazel,要構(gòu)建hello-world這個(gè)二進(jìn)制文件需要hello-greet這個(gè)庫(kù)。

好,讓我們編譯一下新的版本。進(jìn)入到cpp-tutorial/stage2目錄下然后運(yùn)行以下指令:

bazel build //main:hello-world

然后Bazel又會(huì)有一些類似這樣的輸出:

INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 2.399s, Critical Path: 0.30s

現(xiàn)在又可以測(cè)試剛剛生成的二進(jìn)制文件了:

bazel-bin/main/hello-world

注意,如果你現(xiàn)在修改一下hello-greet.cc然后重新編譯整個(gè)項(xiàng)目的話,Bazel其實(shí)只會(huì)編譯修改過(guò)的那個(gè)文件。

然后我們?cè)賮?lái)看一下依賴圖,發(fā)現(xiàn)hello-world在編譯時(shí)候的結(jié)構(gòu)和之前有所不同,現(xiàn)在是有兩個(gè)targets。hello-world這個(gè)target從一個(gè)源文件編譯而來(lái),同時(shí)依賴于另一個(gè)target//main:hello-greet,這個(gè)target又是從兩個(gè)源文件編譯而來(lái)。

Dependency graph for 'hello-world'

4. 多個(gè)package的編譯

我們現(xiàn)在再將項(xiàng)目拆分成多個(gè)package。看一下cpp-tutorial/stage3目錄下的內(nèi)容:

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

注意到我們現(xiàn)在有兩個(gè)子目錄了,每個(gè)子目錄中都包含了BUILD文件。因此,對(duì)于Bazel來(lái)說(shuō),整個(gè)工作區(qū)現(xiàn)在就包含了兩個(gè)package:libmain。

lib/BUILD文件長(zhǎng)這樣:

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

main/BUILD文件長(zhǎng)這樣:

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這個(gè)mainpackage中的target依賴于lib package中的hello-time target(即target label為://lib:hello-time)- Bazel是通過(guò)deps這個(gè)屬性知道自己的依賴項(xiàng)的。那么現(xiàn)在依賴圖就變成了下圖的樣子:

Dependency graph for 'hello-world'

注意到lib/BUILD文件中我們將hello-time這個(gè)target顯式可見(jiàn)了(通過(guò)visibility屬性)。這是因?yàn)槟J(rèn)情況下,targets只對(duì)同一個(gè)BUILD文件里的其他targets可見(jiàn)(Bazel使用target visibility來(lái)防止像公有API中庫(kù)的實(shí)現(xiàn)細(xì)節(jié)的泄露等情況)。

好,讓我們編譯一下新的版本。進(jìn)入到cpp-tutorial/stage3目錄下然后運(yùn)行以下指令:

bazel build //main:hello-world

然后Bazel又會(huì)有一些類似這樣的輸出:

INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 0.167s, Critical Path: 0.00s

現(xiàn)在又可以測(cè)試剛剛生成的二進(jìn)制文件了:

bazel-bin/main/hello-world

好,現(xiàn)在我們學(xué)會(huì)了編譯一個(gè)包含2個(gè)package和3個(gè)target的項(xiàng)目,并且理解了它們之前的依賴關(guān)系。

Reference

  1. Google開(kāi)源構(gòu)建工具Bazel
  2. Introduction to Bazel: Build a C++ Project
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 時(shí)間:2018-12-18 作者:魏文應(yīng) 一、前 言 通過(guò)這個(gè)本文,你將知道如何構(gòu)建一個(gè)C++項(xiàng)目: 編譯一個(gè)目...
    秋的懵懂閱讀 9,714評(píng)論 0 9
  • Bazel簡(jiǎn)介:構(gòu)建C ++項(xiàng)目 在本教程中,您將學(xué)習(xí)使用Bazel構(gòu)建C ++應(yīng)用程序的基礎(chǔ)知識(shí)。您將設(shè)置工作區(qū)...
    YottaYuan閱讀 1,728評(píng)論 0 2
  • bazel的所有代碼都在當(dāng)前工程,每個(gè)工程都是一個(gè) WORKSPACE 。每個(gè)WORKSPACE下有多個(gè)BUILD...
    peteyuan閱讀 26,525評(píng)論 0 4
  • 在上一篇的基礎(chǔ)之上開(kāi)始學(xué)習(xí)如何用 bazel 構(gòu)建一個(gè)簡(jiǎn)單的c++ 項(xiàng)目,也會(huì)深入了解一些 bazel 中的概念 ...
    Wizard團(tuán)隊(duì)閱讀 711評(píng)論 0 0
  • 久違的晴天,家長(zhǎng)會(huì)。 家長(zhǎng)大會(huì)開(kāi)好到教室時(shí),離放學(xué)已經(jīng)沒(méi)多少時(shí)間了。班主任說(shuō)已經(jīng)安排了三個(gè)家長(zhǎng)分享經(jīng)驗(yàn)。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,870評(píng)論 16 22

友情鏈接更多精彩內(nèi)容