使用 Bazel 建構程式

回報問題 查看來源 Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

本頁面說明如何使用 Bazel 建構程式、建構指令語法,以及目標模式語法。

快速入門導覽課程

如要執行 Bazel,請前往基本工作區目錄或任何子目錄,然後輸入 bazel。如需建立新工作區,請參閱建構一文。

bazel help
                             [Bazel release bazel version]
Usage: bazel command options ...

可使用的指令

  • analyze-profile:分析建構設定檔資料。
  • aquery:在分析後動作圖上執行查詢。
  • build:建構指定目標。
  • canonicalize-flags:正規化 Bazel 旗標。
  • clean:移除輸出檔案,並視需要停止伺服器。
  • cquery:執行後分析依附元件圖表查詢。
  • dump:傾印 Bazel 伺服器程序的內部狀態。
  • help:列印指令說明或索引。
  • info:顯示 Bazel 伺服器的執行階段資訊。
  • fetch:擷取目標的所有外部依附元件。
  • mobile-install:在行動裝置上安裝應用程式。
  • query:執行依附元件圖表查詢。
  • run:執行指定的目標。
  • shutdown:停止 Bazel 伺服器。
  • test:建構及執行指定的測試目標。
  • version:顯示 Bazel 的版本資訊。

取得說明

  • bazel help command:列印 command 的說明和選項。
  • bazel helpstartup_options:代管 Bazel 的 JVM 選項。
  • bazel helptarget-syntax:說明指定目標的語法。
  • bazel help info-keys:顯示 info 指令使用的鍵清單。

bazel 工具會執行許多稱為指令的功能。最常用的類型是 bazel buildbazel test。您可以使用 bazel help 瀏覽線上說明訊息。

建立單一目標

您需要工作區才能開始建構。工作區是目錄樹,內含建構應用程式所需的所有來源檔案。Bazel 可讓您從完全唯讀的磁碟區執行建構作業。

如要使用 Bazel 建構程式,請輸入 bazel build,然後輸入要建構的目標

bazel build //foo

發出建構 //foo 的指令後,您會看到類似以下的輸出內容:

INFO: Analyzed target //foo:foo (14 packages loaded, 48 targets configured).
INFO: Found 1 target...
Target //foo:foo up-to-date:
  bazel-bin/foo/foo
INFO: Elapsed time: 9.905s, Critical Path: 3.25s
INFO: Build completed successfully, 6 total actions

首先,Bazel 會載入目標依附元件圖表中的所有套件。這包括已宣告的依附元件、直接列在目標 BUILD 檔案中的檔案,以及轉換依附元件,也就是列在目標依附元件 BUILD 檔案中的檔案。識別所有依附元件後,Bazel 會分析這些依附元件是否正確,並建立建構動作。最後,Bazel 會執行建構的編譯器和其他工具。

在建構的執行階段,Bazel 會列印進度訊息。進度訊息會包含目前的建構步驟 (例如編譯器或連結器),以及已完成的建構動作數量與建構動作總數。建構開始後,隨著 Bazel 探索整個動作圖,動作總數通常會增加,但會在幾秒內趨於穩定。

建構完成後,Bazel 會列印所要求的目標、是否成功建構,以及輸出檔案的位置。執行建構作業的指令碼可以可靠地剖析這項輸出內容;詳情請參閱 --show_result

如果再次輸入相同指令,建構作業會快得多。

bazel build //foo
INFO: Analyzed target //foo:foo (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //foo:foo up-to-date:
  bazel-bin/foo/foo
INFO: Elapsed time: 0.144s, Critical Path: 0.00s
INFO: Build completed successfully, 1 total action

這是空值建構。由於沒有任何變更,因此不需要重新載入任何套件,也不需要執行任何建構步驟。如果「foo」或其依附元件有任何變更,Bazel 會重新執行部分建構動作,或完成增量建構

建立多個目標

Bazel 提供多種方式來指定要建構的目標。這些統稱為「目標模式」。這類語法用於 buildtestquery 等指令。

標籤用於指定個別目標,例如在 BUILD 檔案中宣告依附元件,而 Bazel 的目標模式則指定多個目標。目標模式是目標標籤語法的一般化版本,使用萬用字元。在最簡單的情況下,任何有效標籤也是有效目標模式,可識別一組目標 (確切來說就是一個目標)。

所有以 // 開頭的目標模式都會相對於目前的工作區解析。

//foo/bar:wiz 單一目標 //foo/bar:wiz
//foo/bar 等同於 //foo/bar:bar
//foo/bar:all 套件 foo/bar 中的所有規則目標。
//foo/... 目錄 foo 下所有套件中的所有規則目標。
//foo/...:all 目錄 foo 下所有套件中的所有規則目標。
//foo/...:* 目錄 foo 下方所有套件中的所有目標 (規則和檔案)。
//foo/...:all-targets 目錄 foo 下方所有套件中的所有目標 (規則和檔案)。
//... 主要存放區中套件的所有規則目標。不包括外部存放區的目標。
//:all 頂層套件中的所有規則目標 (如果工作區根目錄中有 `BUILD` 檔案)。

如果目標模式不是以 // 開頭,系統會根據目前的工作目錄解析目標模式。這些範例假設工作目錄為 foo

:foo 等同於 //foo:foo
bar:wiz 等同於 //foo/bar:wiz
bar/wiz 等同於:
  • //foo/bar/wiz:wiz (如果 foo/bar/wiz 是套件)
  • //foo/bar:wiz (如果 foo/bar 是套件)
  • 其他情況則為 //foo:bar/wiz
bar:all 等同於 //foo/bar:all
:all 等同於 //foo:all
...:all 等同於 //foo/...:all
... 等同於 //foo/...:all
bar/...:all 等同於 //foo/bar/...:all

根據預設,系統會追蹤目錄符號連結的遞迴目標模式,但指向輸出基本目錄的連結除外,例如在工作區根目錄中建立的便利符號連結。

此外,在評估任何目錄中的遞迴目標模式時,如果目錄含有下列名稱的檔案,Bazel 就不會追蹤符號連結: DONT_FOLLOW_SYMLINKS_WHEN_TRAVERSING_THIS_DIRECTORY_VIA_A_RECURSIVE_TARGET_PATTERN

foo/...套件的萬用字元,表示目錄 foo 下的所有套件 (適用於套件路徑的所有根目錄)。:all目標的萬用字元,可比對套件中的所有規則。這兩者可以合併使用,例如 foo/...:all,如果同時使用這兩個萬用字元,可以縮寫為 foo/...

此外,:* (或 :all-targets) 是萬用字元,可比對相符套件中的「每個目標」,包括通常不會由任何規則建構的檔案,例如與 java_binary 規則相關聯的 _deploy.jar 檔案。

這表示 :*:all超集。雖然可能令人困惑,但這種語法允許使用熟悉的 :all 萬用字元進行一般建構,而不需要建構 _deploy.jar 等建構目標。

此外,Bazel 允許使用斜線,取代標籤語法中必要的半形冒號;使用 Bash 檔案名稱擴充功能時,這通常很方便。舉例來說,foo/bar/wiz 等同於 //foo/bar:wiz (如果存在套件 foo/bar),或等同於 //foo:bar/wiz (如果存在套件 foo)。

許多 Bazel 指令都會接受目標模式清單做為引數,而且都會遵守前置否定運算子 -。這可用於從先前引數指定的目標集中減去一組目標。請注意,這表示順序很重要。例如:

bazel build foo/... bar/...

表示「建構 foo 底下的所有目標 bar 底下的所有目標」,而

bazel build -- foo/... -foo/bar/...

表示「建構 foo 底下foo的所有目標, foo/bar 底下的目標除外」。(為避免以 - 開頭的後續引數解讀為其他選項,因此需要 -- 引數)。

但請注意,以這種方式減去目標並無法保證不會建構目標,因為這些目標可能是未減去目標的依附元件。舉例來說,如果目標 //foo:all-apis 依附於 //foo/bar:api 等目標,則後者會建構為前者建構作業的一部分。

bazel buildbazel test 等指令中指定時,含有 tags = ["manual"] 的目標不會納入萬用字元目標模式 (...:*:all 等),但會納入負萬用字元目標模式 (也就是會扣除)。如要讓 Bazel 建構/測試這些目標,請在指令列上使用明確的目標模式指定這些測試目標。相反地,bazel query 不會自動執行任何這類篩選作業 (否則會違背 bazel query 的目的)。

擷取外部依附元件

根據預設,Bazel 會在建構期間下載外部依附元件並建立符號連結。不過,您可能不希望這樣做,因為您想知道何時新增了外部依附元件,或是想「預先擷取」依附元件 (例如在離線的航班上)。如要避免在建構期間新增依附元件,可以指定 --fetch=false 標記。請注意,這項標記僅適用於未指向本機檔案系統目錄的存放區規則。舉例來說,無論 --fetch 的值為何,對 local_repositorynew_local_repository 和 Android SDK 與 NDK 存放區規則所做的變更,一律都會生效。

如果您禁止在建構期間擷取資料,而 Bazel 找到新的外部依附元件,建構作業就會失敗。

您可以執行 bazel fetch 手動擷取依附元件。如果在建構期間禁止擷取,您必須執行 bazel fetch

  • 首次建構前。
  • 新增外部依附元件後。

執行一次後,除非 MODULE.bazel 檔案變更,否則不需要再次執行。

fetch 會取得要擷取依附元件的目標清單。舉例來說,這會擷取建構 //foo:bar//bar:baz 所需的依附元件:

bazel fetch //foo:bar //bar:baz

如要擷取工作區的所有外部依附元件,請執行下列指令:

bazel fetch //...

如果使用 Bazel 7 以上版本,且已啟用 Bzlmod,也可以執行下列指令,擷取所有外部依附元件:

bazel fetch

如果您使用的所有工具 (從程式庫 JAR 到 JDK 本身) 都位於工作區根目錄下,則完全不需要執行 bazel fetch。不過,如果您使用工作區目錄以外的任何項目,Bazel 會在執行 bazel build 前自動執行 bazel fetch

存放區快取

即使不同工作區需要相同檔案,或是外部存放區的定義已變更,但仍需下載相同檔案,Bazel 也會盡量避免多次擷取相同檔案。為此,Bazel 會將所有下載的檔案快取到存放區快取中,預設位置為 ~/.cache/bazel/_bazel_$USER/cache/repos/v1/。您可以透過 --repository_cache 選項變更位置。所有工作區和已安裝的 Bazel 版本都會共用快取。如果 Bazel 確定有正確的檔案副本 (也就是下載要求具有指定的檔案 SHA256 總和,且快取中有該雜湊的檔案),就會從快取中擷取項目。因此,從安全角度來看,為每個外部檔案指定雜湊值不僅是個好主意,也有助於避免不必要的下載。

每次快取命中時,系統都會更新快取中檔案的修改時間。這樣一來,您就能輕鬆判斷快取目錄中檔案的最後使用時間,例如手動清除快取。快取不會自動清除,因為可能含有上游不再提供的檔案副本。

[已淘汰] 發布檔案目錄

已淘汰建議使用存放區快取來完成離線建構。

發布目錄是另一種 Bazel 機制,可避免不必要的下載作業。Bazel 會先搜尋發布目錄,再搜尋存放區快取。 主要差異在於發布目錄需要手動準備。

使用 --distdir=/path/to-directory 選項,您可以指定其他唯讀目錄來尋找檔案,而不是擷取檔案。如果檔案名稱等於網址的基本名稱,且檔案的雜湊值等於下載要求中指定的雜湊值,系統就會從這類目錄中取得檔案。只有在存放區規則宣告中指定檔案雜湊時,這項功能才會生效。

雖然檔案名稱的條件並非必要,但可將每個指定目錄的候選檔案數量減少為一個。這樣一來,即使這類目錄中的檔案數量大幅增加,指定發布檔案目錄仍有效率。

在無網路連線的環境中執行 Bazel

為縮減 Bazel 的二進位檔大小,系統會在首次執行時透過網路擷取 Bazel 的隱含依附元件。這些隱含依附元件包含工具鍊和規則,可能並非所有人都需要。舉例來說,Android 工具會解除綁定,且只會在建構 Android 專案時擷取。

不過,即使您已提供所有外部依附元件,在無網路連線的環境中執行 Bazel 時,這些隱含依附元件仍可能造成問題。如要解決這個問題,您可以在具有網路存取的機器上,準備包含這些依附元件的存放區快取 (使用 Bazel 7 以上版本) 或發布目錄 (使用 Bazel 7 之前的版本),然後以離線方式將這些依附元件轉移至無網路連線環境。

存放區快取 (使用 Bazel 7 以上版本)

如要準備存放區快取,請使用 --repository_cache 旗標。您必須為每個新的 Bazel 二進位版本執行這項操作,因為每個版本的隱含依附元件可能不同。

如要在氣隙環境外擷取這些依附元件,請先建立空白工作區:

mkdir empty_workspace && cd empty_workspace
touch MODULE.bazel

如要擷取內建的 Bzlmod 依附元件,請執行

bazel fetch --repository_cache="path/to/repository/cache"

如果您仍使用舊版 WORKSPACE 檔案,請執行下列指令,擷取內建 WORKSPACE 依附元件:

bazel sync --repository_cache="path/to/repository/cache"

最後,在無網路連線環境中使用 Bazel 時,請傳遞相同的 --repository_cache 旗標。為方便起見,您可以將其新增為 .bazelrc 項目:

common --repository_cache="path/to/repository/cache"

此外,您可能也需要在本機複製 BCR,並使用 --registry 標記指向本機副本,防止 Bazel 透過網際網路存取 BCR。在 .bazelrc 中新增下列程式碼:

common --registry="path/to/local/bcr/registry"
發布目錄 (使用 7 之前的 Bazel)

如要準備發布目錄,請使用 --distdir 旗標。您必須為每個新的 Bazel 二進位版本執行這項操作,因為每個版本的隱含依附元件可能不同。

如要在氣隙環境外建構這些依附元件,請先在正確版本中簽出 Bazel 來源樹狀結構:

git clone https://github.com/bazelbuild/bazel "$BAZEL_DIR"
cd "$BAZEL_DIR"
git checkout "$BAZEL_VERSION"

接著,建構包含該特定 Bazel 版本隱含執行階段依附元件的 tarball:

bazel build @additional_distfiles//:archives.tar

將這個 tarball 匯出至可複製到無網路環境的目錄。請注意 --strip-components 標記,因為 --distdir 對於目錄巢狀層級可能相當挑剔:

tar xvf bazel-bin/external/additional_distfiles/archives.tar \
  -C "$NEW_DIRECTORY" --strip-components=3

最後,在無網路連線環境中使用 Bazel 時,請傳遞指向目錄的 --distdir 標記。為方便起見,您可以將其新增為 .bazelrc 項目:

build --distdir=path/to/directory

建構設定和交叉編譯

指定特定建構作業行為和結果的所有輸入內容,都可以分成兩類。第一種是儲存在專案 BUILD 檔案中的內建資訊:建構規則、屬性值,以及完整的遞移依附元件集。第二種是外部或環境資料,由使用者或建構工具提供:目標架構的選擇、編譯和連結選項,以及其他工具鍊設定選項。我們將一組完整的環境資料稱為「設定」

在任何建構作業中,可能有多個設定。以交叉編譯為例,您為 64 位元架構建構 //foo:bin 可執行檔,但工作站是 32 位元機器。顯然,建構作業需要使用能夠建立 64 位元可執行檔的工具鍊建構 //foo:bin,但建構系統也必須建構建構作業本身使用的各種工具 (例如從來源建構,然後在 genrule 中使用的工具),且這些工具必須建構為可在工作站上執行。因此,我們可以識別兩種設定:執行設定,用於建構在建構期間執行的工具;以及目標設定 (或要求設定,但即使「目標」一詞已有許多含義,我們還是更常使用這個詞),用於建構您最終要求的二進位檔。

通常,所要求建構目標 (//foo:bin) 和一或多個執行工具都有許多必要程式庫,例如某些基本程式庫。這類程式庫必須建構兩次,一次是針對執行設定,另一次是針對目標設定。Bazel 會確保建構這兩種變體,並將衍生檔案分開存放,避免相互干擾;通常這類目標可以同時建構,因為彼此獨立。如果看到進度訊息,指出特定目標正在建構兩次,這很可能就是原因。

執行設定是從目標設定衍生而來,如下所示:

  • 使用與要求設定中指定的 Crosstool (--crosstool_top) 相同版本,除非指定 --host_crosstool_top
  • 請使用 --host_cpu 的值做為 --cpu (預設值:k8)。
  • 使用與要求設定中指定的選項相同的值:--compiler--use_ijars,以及 (如果使用 --host_crosstool_top) --host_cpu 的值,在 Crosstool 中查詢 default_toolchain (忽略 --compiler),以取得執行設定。
  • 針對 --javabase 使用 --host_javabase 的值
  • 針對 --java_toolchain 使用 --host_java_toolchain 的值
  • 針對 C++ 程式碼使用最佳化建構 (-c opt)。
  • 不產生偵錯資訊 (--copt=-g0)。
  • 從可執行檔和共用程式庫中刪除偵錯資訊 (--strip=always)。
  • 將所有衍生檔案放在特殊位置,與任何可能的請求設定所用的位置不同。
  • 禁止使用建構資料蓋印二進位檔 (請參閱 --embed_* 選項)。
  • 其他值則保留預設狀態。

有很多原因可能導致您偏好從要求設定中選取不同的執行設定。最重要的是:

首先,使用經過最佳化的精簡二進位檔,可減少連結和執行工具所花的時間、工具占用的磁碟空間,以及分散式建構中的網路 I/O 時間。

其次,在所有建構作業中,將執行和要求設定解除耦合,可避免因要求設定的微小變更 (例如變更連結器選項) 而導致成本高昂的重建作業,如先前所述。

修正增量重建

Bazel 專案的主要目標之一,是確保正確的增量重建。先前的建構工具 (尤其是以 Make 為基礎的工具) 在實作累加建構時,會做出幾項不合理的假設。

首先,檔案的時間戳記會單調遞增。雖然這是典型情況,但很容易違反這項假設;將檔案同步至較早的修訂版本會導致檔案的修改時間減少;以 Make 為基礎的系統不會重建。

一般來說,Make 會偵測檔案變更,但不會偵測指令變更。如果您在特定建構步驟中變更傳遞至編譯器的選項,Make 不會重新執行編譯器,您必須使用 make clean 手動捨棄先前建構的無效輸出內容。

此外,如果子程序開始寫入輸出檔案,但隨後終止作業失敗,Make 就不會採取任何補救措施。雖然 Make 目前的執行作業會失敗,但後續的 Make 呼叫會盲目假設截斷的輸出檔案有效 (因為該檔案比輸入檔案新),因此不會重建該檔案。同樣地,如果 Make 程序遭到終止,也可能發生類似情況。

Bazel 會避免這些和其他假設。Bazel 會維護所有先前完成工作的資料庫,只有在發現該建構步驟的輸入檔案集 (及其時間戳記) 和編譯指令,與資料庫中的完全相符,且資料庫項目的輸出檔案集 (及其時間戳記) 與磁碟上的檔案時間戳記完全相符時,才會省略建構步驟。輸入檔案、輸出檔案或指令本身如有任何變更,建構步驟就會重新執行。

正確的漸進式建構對使用者有以下好處:減少因混淆而浪費的時間。(此外,由於使用 make clean,無論是必要或預先重建,等待重建的時間也會減少)。

建構一致性和漸進式建構

正式來說,當所有預期的輸出檔案都存在,且內容正確無誤 (如建立這些檔案所需的步驟或規則所指定),我們就會將建構狀態定義為「一致」。編輯來源檔案時,建構狀態會不一致,且在您下次成功執行建構工具前,狀態都會維持不一致。我們將這種情況稱為「不穩定不一致」,因為這只是暫時性的問題,執行建構工具即可恢復一致性。

還有一種不一致性會造成嚴重影響,那就是「穩定不一致性」。如果建構達到不一致的穩定狀態,重複成功叫用建構工具也無法還原一致性:建構「卡住」了,輸出內容仍不正確。不一致的穩定狀態是使用者在 Make (和其他建構工具) 中輸入 make clean 的主要原因。發現建構工具以這種方式失敗 (然後從中復原) 可能會耗費大量時間,而且非常令人沮喪。

從概念上來說,要達成一致的建構作業,最簡單的方法就是捨棄所有先前的建構輸出內容,然後重新開始:讓每次建構作業都是乾淨的建構作業。顯然,這種做法太耗時,不切實際 (或許發布工程師除外),因此為了實用起見,建構工具必須能夠執行增量建構,同時確保一致性。

正確的增量依附元件分析很困難,如上所述,許多其他建構工具在增量建構期間,都無法有效避免不一致的穩定狀態。相較之下,Bazel 提供以下保證:成功叫用建構工具後,只要您未進行任何編輯,建構作業就會處於一致的狀態。(如果在建構期間編輯來源檔案,Bazel 無法保證目前建構結果的一致性。但可保證下一個建構作業的結果會還原一致性)。

與所有保證一樣,這項保證也有一些細則:目前已知有幾種方法會導致 Bazel 進入不穩定的不一致狀態。我們無法保證會調查因刻意嘗試在增量依附元件分析中尋找錯誤而導致的問題,但我們會調查並盡力修正因正常或「合理」使用建構工具而導致的所有穩定不一致狀態。

如果發現 Bazel 處於穩定但不一致的狀態,請回報錯誤。

沙箱執行

Bazel 會使用沙箱,確保動作以密封方式正確執行。Bazel 會在沙箱中執行「衍生項目」(廣義來說就是動作),這些沙箱只包含工具執行工作所需的最少檔案。目前,沙箱功能適用於 Linux 3.12 以上版本,並啟用 CONFIG_USER_NS 選項,也適用於 macOS 10.11 以上版本。

如果系統不支援沙箱,Bazel 會列印警告,提醒您建構作業不保證是密封的,且可能會以不明方式影響主機系統。如要停用這項警告,可以將 --ignore_unsupported_sandboxing 標記傳遞至 Bazel。

在部分平台 (例如 Google Kubernetes Engine 叢集節點或 Debian) 上,基於安全考量,使用者命名空間預設會停用。查看檔案 /proc/sys/kernel/unprivileged_userns_clone 即可確認:如果檔案存在且包含 0,則可使用 sudo sysctl kernel.unprivileged_userns_clone=1 啟用使用者命名空間。

在某些情況下,Bazel 沙箱會因為系統設定而無法執行規則。一般而言,徵兆是輸出類似 namespace-sandbox.c:633: execvp(argv[0], argv): No such file or directory 的訊息時發生失敗。 在這種情況下,請嘗試停用 genrules 的沙箱 (使用 --strategy=Genrule=standalone),以及其他規則的沙箱 (使用 --spawn_strategy=standalone)。此外,請在我們的問題追蹤工具上回報錯誤,並說明您使用的 Linux 發行版本,以便我們調查並在後續版本中提供修正。

建構階段

在 Bazel 中,建構作業會分為三個不同的階段。使用者瞭解這些階段的差異後,就能深入瞭解控制建構作業的選項 (請參閱下文)。

載入階段

第一種是「載入」,系統會載入初始目標的所有必要 BUILD 檔案,以及依附元件的遞移閉包,並剖析、評估及快取這些檔案。

啟動 Bazel 伺服器後,第一次建構時通常需要載入許多 BUILD 檔案,因此載入階段會耗費許多秒。在後續建構中,如果沒有 BUILD 檔案變更,載入速度會非常快。

這個階段回報的錯誤包括:找不到套件、找不到目標、BUILD 檔案中的詞彙和文法錯誤,以及評估錯誤。

分析階段

第二個階段是「分析」,包括對每個建構規則進行語意分析和驗證、建構建構依附元件圖形,以及判斷建構作業各步驟中要執行的確切工作。

與載入一樣,完整計算分析也需要幾秒鐘。 不過,Bazel 會將相依關係圖從一個建構作業快取到下一個建構作業,並只重新分析必須分析的內容,因此如果套件自上次建構作業以來沒有變更,漸進式建構作業的速度就會非常快。

這個階段回報的錯誤包括:不當的依附元件、規則的無效輸入內容,以及所有規則專屬的錯誤訊息。

由於 Bazel 在這個階段會避免不必要的檔案 I/O,只讀取 BUILD 檔案來判斷要執行的工作,因此載入和分析階段的速度很快。這是設計使然,可讓 Bazel 成為分析工具的良好基礎,例如 Bazel 的 query 指令,就是實作於載入階段之上。

執行階段

建構的第三個也是最後一個階段是執行。這個階段會確保建構作業中每個步驟的輸出內容與輸入內容一致,並視需要重新執行編譯/連結/等工具。這個步驟是建構作業耗時最長的部分,大型建構作業可能需要超過一小時,小型建構作業則只需幾秒鐘。這個階段回報的錯誤包括:缺少來源檔案、某些建構動作執行的工具發生錯誤,或是工具無法產生預期的輸出內容集。