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

7.3 · 7.2 · 7.1 · 7.0 · 6.5

簡介

第一次使用 Bazel 嗎?開啟 Google Play 就對了!請按照這份「第一次建構」教學課程,簡單瞭解如何使用 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 檔案都需要至少一個 rule 做為一組指示,告訴 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-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-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 會先建構 hello-greet 程式庫 (使用 Bazel 內建的 cc_library rule),接著建構 hello-world 二進位檔。hello-world 目標中的 deps 屬性會告訴 Bazel,建構 hello-world 二進位檔時需要 hello-greet 程式庫。

您必須先變更目錄,然後執行以下指令碼,才能建構這個新版本的專案:

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` 套件中的目標。

為了讓建構作業成功,您可以使用可見度屬性,讓 lib/BUILD 中的 //lib:hello-time 目標明確顯示在 main/BUILD 中的目標。這是因為根據預設,目標只能對同一個 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:

祝您建構愉快!