Bazel 教學課程:建構 C++ 專案

簡介

第一次使用 Bazel 嗎?瀏覽 Google Ads 後,請按照這個 First Build 教學課程,簡化 Bazel 的使用方式。本教學課程定義了會在 Bazel 環境中使用的重要字詞,並逐步引導您完成 Bazel 工作流程的基本概念。從您需要的工具開始,您將建構並執行三個專案,但複雜程度會越來越高,並瞭解這些專案如何變得越來越複雜。

雖然 Bazel 是支援多語言建構作業的建構系統,但本教學課程使用 C++ 專案做為範例,並提供適用於大多數語言的一般準則和流程。

預計完成時間:30 分鐘。

必要條件

如果您尚未安裝 Bazel,請先安裝。本教學課程使用 Git 控管原始碼,因此如要獲得最佳效果,請一併安裝 Git

接著,請在您選擇的指令列工具中執行下列指令,從 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 (和 Git) 並複製本教學課程的存放區,您已使用 Bazel 為第一次建構做好準備。請繼續下一節,定義一些字詞並設定工作區

開始使用

設定工作區

您必須先設定專案的工作區,才能建立專案。工作區是保存專案的來源檔案和 Bazel 建構輸出內容的目錄。其中包含下列重要檔案:

  • WORKSPACE file 會將目錄和內容識別為 Bazel 工作區,並位於專案目錄結構的根目錄中。
  • 一或多個 BUILD files ,用於指示 Bazel 如何建構專案的不同部分。工作區中包含 BUILD 檔案的目錄是套件。(本教學課程稍後會進一步介紹套件)。

在日後的專案中,如要將目錄指定為 Bazel 工作區,請在該目錄中建立名為 WORKSPACE 的空白檔案。為進行本教學課程,每個階段中已有一個 WORKSPACE 檔案。

注意:Bazel 建構專案時,所有輸入都必須位於同一個工作區。除非建立連結,否則位於不同工作區中的檔案彼此獨立。如要進一步瞭解工作區規則,請參閱這份指南

瞭解 BUILD 檔案

BUILD 檔案包含多種適用於 Bazel 的指示。每個 BUILD 檔案至少需要一項規則作為一組操作說明,讓 Bazel 知道如何建構所需的輸出內容,例如可執行的二進位檔或程式庫。BUILD 檔案內每個建構規則的執行個體都稱為「目標」,並指向一組特定來源檔案和依附元件。目標也可以指向其他目標。

看看 cpp-tutorial/stage1/main 目錄中的 BUILD 檔案:

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

在我們的範例中,hello-world 目標會將 Bazel 內建的 cc_binary rule 執行個體化。這項規則會指示 Bazel 從沒有依附元件的 hello-world.cc 來源檔案中建構獨立的可執行二進位檔。

摘要:開始使用

現在,您已熟悉一些關鍵字詞,以及這些字詞在這項專案和 Bazel 整體環境中代表的意義。在下一節中,您將建構並測試專案的第 1 階段。

第 1 階段:單一目標、單一套件

現在正是建構專案的第一個部分了。為了方便參考,專案的第 1 階段結構如下:

examples
└── cpp-tutorial
    └──stage1
       ├── main
       │   ├── BUILD
       │   └── hello-world.cc
       └── WORKSPACE

執行下列指令,移至 cpp-tutorial/stage1 目錄:

cd cpp-tutorial/stage1

接著,請執行:

bazel build //main:hello-world

在目標標籤中,//main: 部分是 BUILD 檔案相對於工作區根目錄的位置,而 hello-worldBUILD 檔案中的目標名稱。

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-bin/main/hello-world

這樣會產生列印的「Hello world」訊息。

以下是第 1 階段的依附元件圖表:

hello-world 的依附元件圖表顯示具有單一來源檔案的單一目標。

摘要:第 1 階段

現在,您已經完成第一個建構,接下來對於建構的結構有基本瞭解。在下一階段中,您將新增其他目標來增加複雜度。

第 2 階段:多個建構目標

雖然一個目標就足以應付小型專案,但建議您將大型專案拆分為多個目標和套件。如此一來,就能加快漸進式建構作業,也就是 Bazel 只會重新建構已變更的內容,同時透過同時建構專案的多個部分來加快建構速度。本教學課程的這個階段會新增目標,接下來就會新增套件。

以下是第 2 階段的使用目錄:

    ├──stage2
    │  ├── main
    │  │   ├── BUILD
    │  │   ├── hello-world.cc
    │  │   ├── hello-greet.cc
    │  │   └── hello-greet.h
    │  └── WORKSPACE

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

透過這個 BUILD 檔案,Bazel 會先使用 Bazel 的內建 cc_library rule 建構 hello-greet 程式庫,然後再建構 hello-world 二進位檔。hello-world 目標中的 deps 屬性會告知 Bazel 需要 hello-greet 程式庫建構 hello-world 二進位檔。

您必須先變更目錄,並執行下列指令切換至 cpp-tutorial/stage2 目錄,才能建構這個新版專案:

cd ../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

您現在可以測試剛建構的二進位檔,並傳回另一個「Hello world」:

bazel-bin/main/hello-world

如果您現在修改 hello-greet.cc 並重新建構專案,Bazel 只會重新編譯該檔案。

從依附元件圖可以發現 hello-world 參考了與之前相同的輸入,但是建構作業的結構不同:

「hello-world」的依附元件圖會顯示修改檔案後的結構變更。

摘要:第 2 階段

現在您已建立含有兩個目標的專案。hello-world 目標會建構一個來源檔案,並依附於另一個目標 (//main:hello-greet),後者會建構兩個額外的來源檔案。在下一節中,請進一步 新增其他套件

第 3 階段:多個套件

這個下一個階段會新增一層小工具,並建構具有多個套件的專案。請查看以下 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 而言,工作區現在包含兩個套件:libmain

請看 lib/BUILD 檔案:

cc_library(
    name = "hello-time",
    srcs = ["hello-time.cc"],
    hdrs = ["hello-time.h"],
    visibility = ["//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",
        "//lib:hello-time",
    ],
)

主要套件中的 hello-world 目標取決於 lib 套件中的 hello-time 目標 (因此目標標籤 //lib:hello-time):Bazel 會透過 deps 屬性知道目標。這在依附元件圖中反映了這一點:

「hello-world」的依附元件圖表顯示主要套件中的目標如何依附於 `lib` 套件中的目標。

為了讓建構作業成功,您可以使用瀏覽權限屬性,對 main/BUILD 中的目標明確顯示 lib/BUILD 中的 //lib:hello-time 目標。這是因為根據預設,只有同一個 BUILD 檔案中的其他目標看得到。Bazel 會使用目標瀏覽權限,避免一些問題發生,例如包含實作詳細資料的程式庫外洩至公用 API。

現在請建構這個專案的最終版本。執行下列指令,切換至 cpp-tutorial/stage3 目錄:

cd  ../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

現在,針對最後的 Hello world 訊息測試本教學課程的最後一個二進位檔:

bazel-bin/main/hello-world

摘要:第 3 階段

您現在已將專案建構為包含三個目標的兩個套件,並瞭解兩者之間的依附關係,讓您可專心處理工作,並使用 Bazel 建構未來的專案。在下一節中,瞭解如何繼續進行 Bazel 旅程。

後續步驟

您已使用 Bazel 完成第一個基本建構作業,但這只是開始而已。以下是繼續透過 Bazel 學習的其他資源:

祝你建造愉快!