使用 Bazel 建構程式

回報問題 查看來源

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

快速入門導覽課程

如要執行 Bazel,請前往基礎 workspace 目錄或其任一子目錄,並輸入 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:JVM 託管 Bazel 的選項。
  • bazel helptarget-syntax:說明指定目標的語法。
  • bazel help info-keys:顯示資訊指令使用的金鑰清單。

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

此為null build。由於沒有任何變更,因此沒有可重新載入的套件,也沒有可執行的建構步驟。如果「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 是套件則為 //foo/bar/wiz:wiz
  • 如果 foo/bar 是套件則為 //foo/bar:wiz
  • //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/bar 底下的目標」以外。(您必須使用 -- 引數,以免將以 - 開頭的後續引數解讀為其他選項)。

請特別注意,透過這種方式減去目標並無法保證系統並不會建構這些目標,因為它們可能是未減去的目標依附元件。例如,如果目標 //foo:all-apis 眾多且依附於 //foo/bar:api,系統就會在建構前者時建構後者。

含有 tags = ["manual"] 的目標不會包含在萬用字元目標模式中 (...:*:all 等),但會在類似 bazel buildbazel test 等指令中指定,但會列入萬用字元。如果您想要 Bazel 建構/測試這些測試目標,就必須在指令列中使用明確的目標模式來指定這類測試目標。相較之下,bazel query 不會自動執行任何這類篩選 (會打破 bazel query 的目的)。

擷取外部依附元件

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

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

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

  • 在首次建構前進行建構。
  • 加入新的外部依附元件後。

執行作業完成後,在 WORKSPACE 檔案變更之前,您不需要再次執行該程式碼。

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

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

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

bazel fetch //...

使用 Bazel 7 以上版本時,如果已啟用 Bzlmod,您也可以執行

bazel fetch

如果您在工作區根目錄下擁有使用的所有工具 (從程式庫 jar 到 JDK 本身),則無需執行 bazel 擷取。然而,如果您使用工作區目錄以外的項目,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 選項,指定用來尋找檔案的其他唯讀目錄,而不擷取檔案。如果檔案名稱等於網址的基本名稱,且檔案的雜湊等於下載要求中指定的檔案,系統就會從這類目錄取得檔案。只有在 WORKSPACE 宣告中指定檔案雜湊時才有效。

雖然檔案名稱上的條件不一定是正確性,但可將候選檔案數量減少為每個指定目錄一個。這樣一來,即使該目錄中的檔案數量不斷增加,指定發布檔案目錄仍能保持效率。

在封閉的環境中執行 Bazel

為保持 Bazel 二進位檔的大小,系統會在首次執行時透過網路擷取 Bazel 的隱含依附元件。這些隱含依附元件包含不一定每個人都需要的工具鍊和規則。舉例來說,Android 工具是無組合的,只會在建構 Android 專案時擷取。

然而,即使您已整合所有外部依附元件,這些隱含依附元件可能會導致系統在封閉的環境中執行 Bazel 時出現問題。如要解決這個問題,您可以在具備網路存取權的機器上,準備包含這些依附元件的存放區快取 (使用 Bazel 7 以上版本) 或發布目錄 (在 7 以下版本中),接著使用離線方法將這些依附元件轉移至無線存取環境。

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

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

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

mkdir empty_workspace && cd empty_workspace
touch MODULE.bazel
touch WORKSPACE

如要擷取內建的 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"
發布目錄 (Bazel 早於 7)

如要準備發布目錄,請使用 --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 匯出到可複製到 Airgapped 環境的目錄。請注意 --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,但建構系統也必須建構建構期間所使用的各種工具,例如透過原始碼建構的工具,然後後續使用,例如 gen 規則,而且這些工具必須建構在工作站上執行。因此,我們可以找出兩種設定:用於建構在建構期間執行的工具的「執行設定」,以及「目標設定」(或「要求設定」,但更常說「目標設定」即使該字詞已有許多含義,但這個詞已用於建構您最終要求的二進位檔)。

一般而言,有許多程式庫必須同時滿足要求的建構目標 (//foo:bin) 和一或多個執行工具 (例如某些基礎程式庫)。這類程式庫必須建構兩次、一次用於執行設定,另一次則是針對目標設定建構。Bazel 會確保這兩個變化版本都已建構,且衍生檔案會分開存放,以避免干擾。通常可以同時建構這類目標,因為它們彼此獨立。如果進度訊息指出特定目標正在建構兩次,則很可能是說明的。

執行設定衍生自目標設定,如下所示:

  • 除非指定了 --host_crosstool_top,否則請使用要求設定中指定的跨工具版本 (--crosstool_top)。
  • --cpu 中使用 --host_cpu 的值 (預設值:k8)。
  • 請使用與要求設定中指定的選項值相同的值:--compiler--use_ijars,如果您使用 --host_crosstool_top,系統會使用 --host_cpu 的值在跨工具中查詢 default_toolchain (忽略 --compiler) 以便執行設定。
  • 針對「--javabase」使用 --host_javabase 的值
  • 針對「--java_toolchain」使用 --host_java_toolchain 的值
  • 使用 C++ 程式碼 (-c opt) 的最佳化版本。
  • 不產生偵錯資訊 (--copt=-g0)。
  • 從執行檔和共用程式庫 (--strip=always) 中移除偵錯資訊。
  • 將所有衍生檔案放在特殊位置,與任何可能的要求設定所使用的不同。
  • 使用建構資料隱藏二進位檔戳記 (請參閱 --embed_* 選項)。
  • 所有其他值則維持不變。

從要求設定中選取不同的執行設定可考慮的原因有很多。最重要的是:

首先,只要使用經過最佳化的二進位檔,就能縮短連結和執行工具所花費的時間、工具佔用的磁碟空間,以及分散式建構中的網路 I/O 時間。

其次,將所有建構作業中的 exec 和要求設定分離,避免執行成本高昂的重新建構作業,因為要求設定只有小幅變更 (例如變更連接器選項),如前文所述。

修正漸進式重新建構

Bazel 專案的主要目標之一,是確保正確逐步重新建構。先前的建構工具 (尤其是以 Make 為基礎的工具) 會在實作漸進式建構作業時做出多項未音效假設,

首先,檔案的時間戳記只會單調遞增。雖然這是典型的情況,但很容易忽略這個假設;與檔案先前的修訂版本會導致檔案的修改時間減少;Make 型系統不會重新建構。

廣泛來說,儘管 Make 會偵測檔案的變更,也不會偵測指令的變更。如果您在特定建構步驟中修改傳送至編譯器的選項,Make 不會重新執行編譯器,而必須使用 make clean 手動捨棄前一個建構作業的無效輸出內容。

此外,Make 並不是針對其中一個子程序開始寫入輸出檔案後因子程序失敗而終止的問題。雖然目前執行 Make 會失敗,但後續叫用 Make 會盲目假設遭截斷的輸出檔案有效 (因為該檔案比輸入內容有效),而且不會重新建構。同樣地,如果終止 Make 程序,也可能會發生類似情況。

Bazel 也能避免這些假設或其他假設。Bazel 會保留先前完成的所有工作資料庫,並且只有在發現該建構步驟的輸入檔案 (及其時間戳記) 和該建構步驟的編譯指令完全相符,以及資料庫項目的輸出檔案集 (及其時間戳記) 完全相符時,才會省略建構步驟。變更輸入檔案、輸出檔案或指令本身,將導致重新執行建構步驟。

使用正確的漸進式建構作業的好處是:因為融合而浪費時間較少。(此外,不管是必要或預先測試,使用 make clean 導致重新建構所花費的時間也變少了)。

建構一致性和漸進式建構作業

我們正式將所有預期的輸出檔案都存在時,將建構的狀態定義為一致,而且其內容也正確無誤,如同建立建構作業所需的步驟或規則。當您編輯來源檔案時,系統判斷建構的狀態為「不一致」,且在您下次執行建構工具成功完成前會保持不一致。我們會將這種情況描述為不一致的一致性,因為這只是暫時性的,執行建構工具即可還原一致性。

另外還有一種不一致的不一致問題:穩定不穩定。如果版本達到穩定不穩定的狀態,則重複成功叫用建構工具無法還原一致性:建構作業已「停滯」,且輸出內容仍不正確。穩定版的不一致是 Make (和其他建構工具) 類型 make clean 的主要原因。發現建構工具以上述方式失敗後 (再從此工具復原) 可能相當耗時且令人困擾。

從概念上來說,如要達到一致的建構作業,最簡單的方法就是捨棄所有先前的建構輸出內容,然後重新開始:讓每個建構作業都建構乾淨。這個方法顯然非常耗時 (除了版本工程師時除外),因此在不影響一致性的情況下,建構工具必須能夠執行漸進式建構作業,才能派上用場。

修正漸進式依附元件分析並不容易,如上所述,許多其他建構工具在避免漸進式建構作業期間,就無法執行穩定不一致的狀態。相對地,Bazel 提供以下保證:當您成功叫用建構工具且未進行任何編輯時,建構作業會處於一致的狀態。(如果您在建構期間編輯來源檔案,Bazel 無法保證目前建構作業的一致性。但無法保證「下一個」版本的結果會還原一致性。)

和所有保證一樣,系統會提供幾項細膩效果:使用 Bazel 時,可以透過一些已知方式達到穩定不一致的狀態。我們無法保證使用者會自行嘗試在漸進式依附元件分析中發現錯誤,因此不保證會加以調查,但對於正常或「合理」的使用建構工具導致所有穩定的不穩定狀態,我們會進行調查並盡力修正。

如果您發現 Bazel 偵測到持續不一致的狀態,請回報錯誤。

採用沙箱機制的執行作業

Bazel 使用沙箱來確保動作會以演算法且正確的方式執行。Bazel 會在沙箱中執行「spawns」(寬鬆來說是「動作」) 的沙箱,而該作業只含有工具執行工作所需的最少檔案組合。目前沙箱作業適用於 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 的訊息。在這種情況下,請嘗試使用 --strategy=Genrule=standalone 停用 genrules 的沙箱,並使用 --spawn_strategy=standalone 停用其他規則。此外,請在我們的 Issue Tracker 上回報錯誤,並說明您使用的是哪個 Linux 發行版,以便我們進行調查,並在後續版本中提供修正檔。

建構階段

在 Bazel 中,建構作業分為三個不同的階段;身為使用者,瞭解不同階段之間的差異,可讓您深入瞭解控制建構作業的選項 (詳情請見下文)。

正在載入階段

第一個項目是「載入」初始目標的所有必要 BUILD 檔案,以及其依附元件的遞移關閉,然後載入、剖析、評估和快取。

對於 Bazel 伺服器啟動後的第一個建構作業,載入階段通常需要從檔案系統載入 BUILD 檔案所需的秒數。在後續建構作業中,尤其是在沒有任何 BUILD 檔案變更的情況下,載入作業的速度非常快。

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

分析階段

第二階段「分析」涉及每個建構規則的語意分析和驗證、建構依附元件圖的建構,以及確定建構中每個步驟中會完成的工作。

就像載入一樣,分析的完整計算也需要幾秒鐘。不過,Bazel 會將依附元件圖表從一個建構作業快取到下一個建構,然後只重新分析其需要的內容,如果套件在上次建構後尚未變更,則可大幅加快漸進式建構作業的速度。

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

由於 Bazel 可避免在這個階段執行不必要的檔案 I/O,因此僅讀取 BUILD 檔案,以判斷要完成的工作,因此載入速度很快,因此載入速度很快。這是從設計到的,且 Bazel 是分析工具 (例如 Bazel 的 query 指令) 做為良好基礎,而這些工具已在載入階段之上實作。

執行階段

建構的第三階段,最後一個階段是「執行」。這個階段可確保建構作業中每個步驟的輸出內容與輸入內容一致,並視需要重新執行編譯/連結等工具。這個步驟會將建構作業花費大部分的時間,從數秒到一小時以上的時間。這個階段回報的錯誤包括:缺少來源檔案、由某些建構動作執行的工具發生錯誤,或工具無法產生預期的輸出內容組合。