Bazel簡介:構建C ++項目
在本教程中,您將學習使用Bazel構建C ++應用程序的基礎知識。您將設置工作區并構建一個簡單的C ++項目,該項目說明了Bazel的關鍵概念,例如目標和BUILD
文件。完成本教程后,請查看 Common C ++ Build Use Cases,以獲取有關更高級概念(如編寫和運行C ++測試)的信息。
預計完成時間:30分鐘。
您將學到什么
在本教程中,您將學習如何:
- 建立目標
- 可視化項目的依賴關系
- 將項目分為多個目標和程序包
- 控制整個程序包的目標可見性
- 通過標簽參考目標
內容
在你開始之前
要開始本教程,請先安裝Bazel(如果尚未安裝)。然后,從Bazel的GitHub存儲庫中檢索示例項目:
git clone https://github.com/bazelbuild/examples
本教程的示例項目在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
如您所見,共有三組文件,每組文件代表本教程中的一個階段。在第一階段,您將構建一個駐留在單個程序包中的單個目標。在第二階段,您將把您的項目分成多個目標,但將其保存在一個程序包中。在第三階段(也是最后一個階段)中,您將把您的項目分成多個包,并使用多個目標進行構建。
用Bazel構建
設置工作區
在構建項目之前,您需要設置其工作區。工作區是一個目錄,其中包含項目的源文件和Bazel的構建輸出。它還包含Bazel認為特殊的文件:
該
WORKSPACE
文件將目錄及其內容標識為Bazel工作區,并位于項目目錄結構的根目錄中,一個或多個
BUILD
文件,這些文件告訴Bazel如何構建項目的不同部分。(工作空間中包含BUILD
文件的目錄是一個包。您將在本教程的后面部分中學習有關包的信息。)
要將目錄指定為Bazel工作區,請WORKSPACE
在該目錄中創建一個空文件 。
Bazel構建項目時,所有輸入和依賴項必須位于同一工作空間中。除非鏈接,否則位于不同工作空間中的文件彼此獨立,這超出了本教程的范圍。
了解BUILD文件
一個BUILD
文件包含幾種不同類型的用于Bazel指令。最重要的類型是構建規則(build rule),該規則(rule)告訴Bazel如何構建所需的輸出,例如可執行二進制文件或庫。BUILD
文件中構建規則的每個實例都稱為目標(target)并指向一組特定的源文件和依賴項。一個目標也可以指向其他目標。
看一下目錄BUILD
中的cpp-tutorial/stage1/main
文件:
cc_binary(
name = "hello-world",
srcs = ["hello-world.cc"],
)
在我們的示例中,hello-world
目標實例化了Bazel內置的 cc_binary
rule。該規則告訴Bazel從hello-world.cc
源文件構建一個自包含的可執行二進制文件,而沒有任何依賴關系。
目標中的屬性明確聲明其依賴項和選項。雖然該name
屬性是強制性的,但許多是可選的。例如,在 hello-world
目標中name
是不言自明的, 其srcs
屬性指定Bazel從中構建目標的源文件。
建立項目
讓我們構建您的示例項目。切換到cpp-tutorial/stage1
目錄并運行以下命令:
bazel build //main:hello-world
注意目標標簽-該//main:
部分是BUILD
文件相對于工作空間根目錄的位置,hello-world
也是我們在BUILD
文件中命名該目標的名稱。(您將在本教程的最后詳細了解目標標簽。)
Bazel產生類似于以下內容的輸出:
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
恭喜,您剛剛建立了第一個Bazel目標!Bazel將構建輸出放置bazel-bin
在工作空間根目錄中的目錄中。瀏覽其內容以了解Bazel的輸出結構。
現在測試您新構建的二進制文件:
bazel-bin/main/hello-world
查看依賴關系圖
成功的構建具有在BUILD
文件中明確聲明的所有依賴項。Bazel使用這些語句來創建項目的依賴關系圖,從而實現準確的增量構建。
讓我們可視化示例項目的依賴關系。首先,生成依賴關系圖的文本表示(在工作區根目錄運行命令):
bazel query --notool_deps --noimplicit_deps 'deps(//main:hello-world)' \
--output graph
上面的命令告訴Bazel查找目標的所有依賴關系 //main:hello-world
(不包括主機和隱式依賴關系),并將輸出格式化為圖形。
然后,將文本粘貼到GraphViz中。
在Ubuntu上,可以通過安裝GraphViz和xdot Dot Viewer在本地查看圖形:
sudo apt update && sudo apt install graphviz xdot
然后,您可以通過將上面的文本輸出直接傳遞到xdot來生成和查看圖形:
xdot <(bazel query --notool_deps --noimplicit_deps 'deps(//main:hello-world)' \
--output graph)
如您所見,示例項目的第一階段有一個目標,該目標構建一個沒有附加依賴項的源文件:
既然您已經設置了工作區,構建了項目并檢查了其依賴性,那么讓我們增加一些復雜性。
優化您的Bazel構建
盡管單個目標足以滿足小型項目的需要,但您可能希望將較大的項目拆分為多個目標和程序包,以實現快速增量構建(即,僅重建更改的內容)并通過同時構建項目的多個部分來加快構建速度。
指定多個構建目標
讓我們將示例項目構建分為兩個目標??匆幌?目錄BUILD
中的cpp-tutorial/stage2/main
文件:
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
庫(使用Bazel的內置cc_library
規則),然后構建hello-world
二進制文件。目標中的deps
屬性hello-world
告訴Bazel,該hello-greet
庫是構建hello-world
二進制文件所必需的。
讓我們構建這個項目的新版本。切換到 cpp-tutorial/stage2
目錄并運行以下命令:
bazel build //main:hello-world
Bazel產生類似于以下內容的輸出:
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
現在測試您新構建的二進制文件:
bazel-bin/main/hello-world
如果現在修改hello-greet.cc
并重建項目,Bazel將僅重新編譯該文件。
查看依賴關系圖,您可以看到它hello-world
依賴與以前相同的輸入,但是構建的結構不同:
您現在已經用兩個目標構建了該項目。的hello-world
目標建立一個源文件,并依賴于目標(//main:hello-greet
),它建立兩個附加的源文件。
使用多個包
現在讓我們將項目分成多個包。看一下cpp-tutorial/stage3
目錄的內容:
└──stage3
├── main
│ ├── BUILD
│ ├── hello-world.cc
│ ├── hello-greet.cc
│ └── hello-greet.h
├── lib
│ ├── BUILD
│ ├── hello-time.cc
│ └── hello-time.h
└── WORKSPACE
注意,我們現在有兩個子目錄,每個子目錄都包含一個BUILD
文件。因此,對于Bazel,工作空間現在包含兩個包,lib
和main
。
看一下lib/BUILD
文件:
cc_library(
name = "hello-time",
srcs = ["hello-time.cc"],
hdrs = ["hello-time.h"],
visibility = ["http://main:__pkg__"],
)
并在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",
"http://lib:hello-time",
],
)
如上所示,hello-world
在目標main
包依賴于 hello-time
目標lib
包(因此目標標簽 //lib:hello-time
) -Bazel通過知道這個deps
屬性??匆幌乱蕾噲D:
注意,為使構建成功,我們使用 屬性使//lib:hello-time
目標對于目標 lib/BUILD
明確可見。這是因為默認情況下,目標僅對同一文件中的其他目標可見。(Bazel使用目標可見性來防止諸如包含實現詳細信息的庫之類的問題泄漏到公共API中。)main/BUILD``visibility``BUILD
讓我們構建項目的最終版本。切換到 cpp-tutorial/stage3
目錄并運行以下命令:
bazel build //main:hello-world
Bazel產生類似于以下內容的輸出:
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
現在測試新構建的二進制文件:
bazel-bin/main/hello-world
現在,您已經將項目構建為帶有三個目標的兩個程序包,并了解了它們之間的依賴性。
使用標簽參考目標
在BUILD
文件中和命令行中,Bazel使用標簽來引用目標(例如//main:hello-world
或)//lib:hello-time
。它們的語法是:
//path/to/package:target-name
如果目標是規則目標,則path/to/package
是包含BUILD
文件的目錄的路徑,并且target-name
是您在BUILD
文件中命名目標(name
屬性)的名稱。如果目標是文件目標,則path/to/package
是包根目錄的路徑,并且 target-name
是目標文件的名稱,包括其完整路徑。
在存儲庫根目錄引用目標時,包路徑為空,只需使用即可//:target-name
。在同一BUILD
文件中引用目標時,您甚至可以跳過//
工作空間的根標識符,而只需使用 :target-name
。
進一步閱讀
恭喜你!您現在知道了使用Bazel構建C ++項目的基礎知識。接下來,閱讀最常見的C ++構建用例。然后,檢查以下內容:
外部依賴關系,以了解有關使用本地和遠程存儲庫的更多信息。
在其他規則以了解更多有關巴澤爾。
在Java構建教程上手與Bazel構建Java應用程序。
該Android應用教程上手構建移動應用程序為Android與Bazel。
在iOS版應用教程上手構建移動應用程序適用于iOS與Bazel。
構建愉快!