在 Windows 上編寫規則

回報問題 查看來源

本頁面著重說明如何編寫與 Windows 相容的規則、撰寫可攜規則的常見問題,以及一些解決方案。

路徑

問題:

  • 長度限制:路徑長度上限為 259 個字元。

    雖然 Windows 也支援較長的路徑 (最多 32767 個字元),但許多程式是以較低限制建構而成。

    請注意以下操作有關您在行動中執行的程式。

  • 工作目錄:長度限制為 259 個字元。

    處理程序中的目錄不得 cd 超過 259 個字元。

  • 區分大小寫:Windows 路徑不區分大小寫,Unix 路徑有大小寫之分。

    建立動作的指令列時,請特別留意這一點。

  • 路徑分隔符:為反斜線 (\`), not forward slash (/`)。

    Bazel 會使用 / 分隔符儲存 Unix 樣式的路徑。雖然部分 Windows 程式支援 Unix 樣式路徑,但有些則不支援。cmd.exe 中的某些內建指令支援這些指令,有些則無法。

    為動作建立指令列和環境變數時,建議您一律使用 \` separators on Windows: replace/with`。

  • 絕對路徑:請勿以斜線 (/) 開頭。

    Windows 的絕對路徑開頭為磁碟機字母代號,例如 C:\foo\bar.txt。並沒有單一檔案系統根目錄,

    如果規則檢查路徑是否為絕對路徑,請留意這一點。絕對路徑通常無法遷移,因此應避免使用。

解決方法:

  • 保持精簡的路徑。

    請避免使用冗長的目錄名稱、深層巢狀目錄結構、長檔案名稱、長工作區名稱、較長的目標名稱。

    這些項目可能會成為動作輸入檔案的路徑元件,並可能會用盡路徑長度限制。

  • 使用簡短的輸出根目錄。

    使用 --output_user_root=<path> 旗標指定 Bazel 輸出內容的短路徑。建議您只針對 Bazel 輸出 (例如 D:\`), and adding this line to your.bazelrc` 檔案) 建立一個硬碟 (或虛擬硬碟):

    build --output_user_root=D:/
    

    build --output_user_root=C:/_bzl
    
  • 使用連接號。

    交點式概略來說[1],是目錄符號連結。交流區很容易建立,可指向具有較長路徑的目錄 (在同一台電腦上)。如果建構動作會建立路徑很短但目標很長的連接線,則具有較短路徑限制的工具可以存取連接區目錄中的檔案。

    .bat 檔案或 cmd.exe 中,您可以建立等號,如下所示:

    mklink /J c:\path\to\junction c:\path\to\very\long\target\path
    

    [1]:嚴格來說,交集不是符號連結,但為了建構建構動作,您可能需要將「交流」視為目錄符號連結。

  • 將動作 / envvars 中的 / 替換成「`」。

    為動作建立指令列或環境變數時,請將路徑設為 Windows 樣式。示例:

    def as_path(p, is_windows):
        if is_windows:
            return p.replace("/", "\\")
        else:
            return p
    

環境變數

問題:

  • 區分大小寫:Windows 環境變數名稱不區分大小寫。

    例如,在 Java System.getenv("SystemRoot")System.getenv("SYSTEMROOT") 中,會產生相同的結果。(這也適用於其他語言)。

  • 密封度:請盡量減少使用自訂環境變數的動作。

    環境變數是動作快取金鑰的一部分。如果動作使用了經常變更或使用者自訂的環境變數,就會使規則難以快取。

解決方法:

  • 僅使用大寫的環境變數名稱。

    這項功能適用於 Windows、macOS 和 Linux。

  • 盡可能減少動作環境。

    使用 ctx.actions.run 時,請將環境設為 ctx.configuration.default_shell_env。如果動作需要更多環境變數,請將變數全部放入字典中,再傳送至動作。示例:

    load("@bazel_skylib//lib:dicts.bzl", "dicts")
    
    def _make_env(ctx, output_file, is_windows):
        out_path = output_file.path
        if is_windows:
            out_path = out_path.replace("/", "\\")
        return dicts.add(ctx.configuration.default_shell_env, {"MY_OUTPUT": out_path})
    

動作

問題:

  • 可執行檔的輸出:每個執行檔都必須有可執行的副檔名。

    最常見的副檔名為 .exe (二進位檔案) 和 .bat (批次指令碼)。

    請注意,殼層指令碼 (.sh) 在 Windows 上「無法」執行,您無法將其指定為 ctx.actions.runexecutable。此外,檔案沒有 +x 權限,因此您無法執行任意檔案,就像在 Linux 上一樣。

  • Bash 指令:為便於可攜性,請避免直接在動作中執行 Bash 指令。

    Bash 在 Unix 類系統上相當普及,但通常不適用於 Windows。Bazel 本身就 Bash (MSYS2) 的依賴程度較低,因此未來使用者較不會隨 Bazel 安裝 MSYS2。為了讓規則在 Windows 中更容易使用,請避免在動作中執行 Bash 指令。

  • 行結尾:Windows 使用 CRLF (\r\n),類似 Unix 系統會使用 LF (\n)。

    比較文字檔案時請注意這一點。請留意 Git 設定,尤其是結帳或結帳時要注意行結尾。(請參閱 Git 的 core.autocrlf 設定)。

解決方法:

  • 使用非 Bash 特定用途的規則。

    native.genrule() 是 Bash 指令的包裝函式,通常用於解決簡單的問題,例如複製檔案或編寫文字檔案。您可以避免依賴 Bash (並重新設計輪盤):看看 bazel-skylib 是否有專為您的需求而建立的規則。在 Windows 上建構/測試時,兩者都不會依賴 Bash。

    建構規則範例:

    • copy_file() (來源說明文件):將檔案複製到其他位置,選擇性地將檔案設為可執行

    • write_file() (來源說明文件):編寫文字檔,並使用所需的行結尾 (autounixwindows),選擇性使其可執行 (如果是指令碼)

    • run_binary() (來源說明文件):執行包含指定輸入內容和預期輸出內容做為建構動作的二進位檔 (或 *_binary 規則) (這是 ctx.actions.run 的建構規則包裝函式)

    • native_binary() (來源說明文件):在 *_binary 規則中納入原生二進位檔,可用於 bazel run,或是用於 run_binary()tool 屬性或 native.genrule()tools 屬性中

    測試規則範例:

    • diff_test() (來源說明文件):比較兩個檔案內容的測試

    • native_test() (來源說明文件):將原生二進位檔納入 *_test 規則中,您可以 bazel test

  • 在 Windows 上,建議你使用 .bat 指令碼執行一些簡單的操作。

    與其使用 .sh 指令碼,您可以利用 .bat 指令碼解決一般工作。

    舉例來說,如果您需要不執行任何動作或輸出訊息的指令碼,或是以已修正的錯誤代碼結束指令碼,那麼簡單的 .bat 檔案就已足夠。如果規則傳回 DefaultInfo() 提供者,executable 欄位可能會參照 Windows 上的該 .bat 檔案。

    此外,macOS 和 Linux 上沒有副檔名,因此您隨時都能使用 .bat 做為擴充功能,即使是殼層指令碼也沒問題。

    請注意,系統無法執行空白的 .bat 檔案。如果您需要空白指令碼,請在其中寫一個空格。

  • 有條理地使用 Bash。

    在 Starlark 建構與測試規則中,使用 ctx.actions.run_shell 執行 Bash 指令碼和 Bash 指令做為動作。

    在 Starlark 巨集中,將 Bash 指令碼和指令納入 native.sh_binary()native.genrule()。Bazel 會檢查 Bash 是否可用,並透過 Bash 執行指令碼或指令。

    在 Starlark 存放區規則中,請嘗試完全避免 Bash。Bazel 目前尚無法在存放區規則中以合乎原則的方式執行 Bash 指令。

刪除檔案

問題:

  • 檔案開啟時無法刪除。

    根據預設,開啟的檔案無法刪除,且嘗試會導致「存取遭拒」錯誤。如果您無法刪除檔案,可能是因為執行中的程序仍會將檔案保持開啟狀態。

  • 無法刪除執行中程序的工作目錄。

    程序對工作目錄具有開放的控制代碼,必須等到程序終止後才能刪除。

解決方法:

  • 在程式碼中嘗試快速關閉檔案。

    在 Java 中,使用 try-with-resources。在 Python 中使用 with open(...) as f:。原則上,請盡快關閉控點。