范形式到詞法校驗的權威指南)
Bazel Labels 全面解析從規(guī)范形式到詞法校驗的權威指南【免費下載鏈接】bazela fast, scalable, multi-language and extensible build system項目地址: https://gitcode.com/GitHub_Trending/ba/bazel導讀Label標簽是 Bazel 中標識構建目標target的核心概念無論是聲明deps依賴、執(zhí)行bazel build還是編寫B(tài)UILD文件都離不開它。本文以 Bazel 官方文檔《Labels》為骨架結合本倉庫bazel 構建系統(tǒng)源碼中的標簽解析與校驗實現(xiàn)src/main/java/com/google/devtools/build/lib/cmdline/LabelParser.java、LabelValidator.java完整講解 Label 的規(guī)范形式、縮寫規(guī)則、詞法限制、包名與目標名的邊界以及它在BUILD文件、查詢語言和命令行中的實際用法。讀完本文你將能準確寫出無歧義的 Label、避開跨包引用的常見陷阱并理解 Bazel 為何要對 Label 字符集做如此嚴格的約束。一、Label 是什么目標的全局標識符Label是 Bazel 中一個 target構建目標的標識符。一個典型的完整規(guī)范形式full canonical form的 Label 長這樣myrepo//my/app/main:app_binary它由三部分組成組成部分示例含義倉庫名repository namemyrepo標識目標所在的倉庫包名package namemy/app/main包相對于倉庫根目錄的路徑目標名target nameapp_binary包內具體的目標1.1 規(guī)范倉庫名Canonical Repo Name與雙語法Label 的第一部分是倉庫名。雙語法表示這是一個canonical規(guī)范倉庫名在整個 workspace 內全局唯一。帶有規(guī)范倉庫名的 Label 無論出現(xiàn)在什么上下文中都能無歧義地標識同一個目標相關概念見 外部依賴總覽。不過規(guī)范的倉庫名往往是一串晦澀的字符串例如rules_javatoolchainslocal_jdk這種名稱由 Bzlmod 的模塊解析機制生成可讀性差。因此實際代碼中更常見的是帶apparent表面?zhèn)}庫名的 Label其唯一區(qū)別是倉庫名前綴只有一個myrepo//my/app/main:app_binarymyrepo是 apparent 名稱它可能因 Label 出現(xiàn)的上下文不同而指向不同的倉庫。從源碼結構看Bazel 在解析階段會同時區(qū)分這兩類語法LabelParser.java 中通過rawLabel.startsWith()判斷repoIsCanonical從而決定后續(xù)按哪種語義解析倉庫部分。1.2 倉庫名可以省略的情形在典型場景下Label 引用的就是它所在的同一個倉庫此時倉庫名部分可以省略。例如在myrepo內部第一個 Label 通常寫作//my/app/main:app_binary1.3 包名的兩層含義未限定包名與全限定包名Label 的第二部分是未限定包名un-qualified package namemy/app/main即包相對于倉庫根目錄的路徑。倉庫名與未限定包名合在一起構成全限定包名fully-qualified package namemyrepo//my/app/main1.4 包名與冒號可以省略的情形當 Label 引用的是它所在包內的目標時包名以及可選的冒號都可以省略。因此在myrepo//my/app/main包內下面兩種寫法等價app_binary :app_binary按慣例文件目標省略冒號、規(guī)則目標保留冒號但這并非強制冒號本身在語法上沒有其他含義。1.5 目標名與包路徑末段重合時可以省略冒號后面的app_binary是未限定目標名。當它恰好與包路徑的最后一個組成部分同名時目標名和冒號都可以省略。因此下面兩個 Label 完全等價//my/app/lib //my/app/lib:lib1.6 包內子目錄中的文件目標位于包內子目錄中的文件目標其名稱是文件相對于包根目錄即包含BUILD文件的目錄的路徑。例如下面的文件位于倉庫的my/app/main/testdata子目錄中前提是my/app/main是一個包//my/app/main:testdata/input.txt二、//my/app的雙重含義包還是目標像//my/app和some_repo//my/app這樣的字符串在不同上下文中有兩種含義當 Bazel期望一個 Label時它們分別等價于//my/app:app和some_repo//my/app:app當 Bazel期望一個包名例如在package_group規(guī)范中時它們引用的是包含該 Label 的那個包。2.1 最常見的錯誤用//my/app引用整個包在BUILD文件中一個常見錯誤是用//my/app來指代一個包或者指代包內所有目標——它并不會這么做。請記住//my/app等價于//my/app:app它命名的是當前倉庫my/app包中的app目標。不過在package_group的規(guī)范中或在.bzl文件中用//my/app指代包是被鼓勵的寫法因為它能清楚地表達包名是絕對的、以 workspace 頂層目錄為根。2.2 跨包引用必須使用完整路徑相對 Label 不能用于引用其他包中的目標這種情況下必須始終給出倉庫標識符和包名。例如假設源碼樹中同時存在包my/app和包my/app/testdata這兩個目錄各有自己的BUILD文件后者包含一個名為testdepot.zip的文件。下面是//my/app:BUILD中引用該文件的兩種方式一錯一對錯誤——testdata是另一個包不能使用相對路徑testdata/testdepot.zip正確—— 使用完整路徑引用testdata//my/app/testdata:testdepot.zip2.3//引用主倉庫外部倉庫也能用以//開頭的 Label 是對**主倉庫main repository**的引用即使從外部倉庫中使用也依然有效。因此從外部倉庫引用時//a/b/c與//a/b/c是不同的//a/b/c指回主倉庫//a/b/c會在外部倉庫自身內部查找//a/b/c。這一點在編寫「主倉庫中的規(guī)則、但會被外部倉庫使用」的場景下尤為重要——如果規(guī)則內引用主倉庫目標時寫成//a/b/c一旦該規(guī)則被外部倉庫引用就會解析失敗。關于在命令行中指定構建目標的更多方式目標模式 target patterns見 build 命令的目標模式章節(jié)。三、Label 的詞法規(guī)范Lexical SpecificationLabel 語法刻意避免使用對 shell 有特殊含義的元字符。這有助于避免意外的引號問題也讓構造、操作 Label 的工具和腳本例如 Bazel Query 語言更加容易。3.1 目標名規(guī)則 —package-name:target-nametarget-name是目標在包內的名稱規(guī)則目標的名稱是其在BUILD文件聲明中name屬性的值文件目標的名稱是文件相對于包含BUILD文件目錄的路徑名。目標名允許的字符集a–z、A–Z、0–9以及標點符號!%-^_#$()*,;?[]{|}~/.。文件名的額外約束文件名必須是正規(guī)形式的相對路徑名即不能以斜杠開頭或結尾例如/foo和foo/都是禁止的不能包含連續(xù)多個斜杠作為路徑分隔符例如foo//bar被禁止不能包含上級引用..或當前目錄引用./。錯誤—— 不要用..引用其他包中的文件../other_pkg/foo.cc正確—— 使用//package-name:filename//other_pkg:foo.cc斜杠的使用建議文件目標名中經(jīng)常使用/但應盡量避免在規(guī)則名中使用/尤其在用 Label 的縮寫形式時容易讓讀者混淆。Label//foo/bar/wiz永遠是//foo/bar/wiz:wiz的縮寫即使不存在foo/bar/wiz這個包也是如此它永遠不會指//foo:bar/wiz即使該目標確實存在。當然也存在必須用斜杠的場景某些規(guī)則的名稱必須與其主源文件同名而該源文件可能位于包的子目錄中例如cc_library與同名頭文件子目錄的組合。源碼級驗證目標名校驗實現(xiàn)從源碼結構看Bazel 在 LabelValidator.java 的validateTargetName中逐字符執(zhí)行這些約束以/開頭或結尾均報錯以..、../、./開頭的路徑段分別被標記為 up-level references 或 . as a path segment循環(huán)中遇到/../、/./、//會立即返回對應錯誤控制字符\u001f及以下和\u007fDEL被明確拒絕并給出\xXX十六進制提示未在允許集合內的字符統(tǒng)一報錯target names may not contain c。值得注意的是ALWAYS_ALLOWED_TARGET_CHARACTERS還通過CharMatcher.inRange(128, 65535)放行了全部非 ASCII 字符源碼注釋說明這與 Bazel 內部字符串與 Unicode 字符串的雙編碼兼容有關因此非 ASCII 文件名在目標名中是允許的。3.2 包名規(guī)則 —//package-name:target-name包名是包含其BUILD文件的目錄名相對于所在倉庫的頂層目錄。例如my/app。技術層面的強制約束Bazel 對包名施加以下硬性規(guī)則允許的字符小寫字母a–z、大寫字母A–Z、數(shù)字0–9以及字符! # $ % ( ) * , - . ; ? [ ] ^ _ { | }注意其中包含一個空格字符當然還有正斜杠/作為目錄分隔符。不能以/開頭或結尾。不能包含子串//——否則對應的目錄路徑無法定義。不能包含/./、/../、/.../等子串——這是為了避免在邏輯包名與物理目錄名之間轉換時路徑字符串中.的語義造成混淆。源碼 LabelValidator.java 的validatePackageName完整實現(xiàn)了上述規(guī)則先用ALLOWED_CHARACTERS_IN_PACKAGE_NAME字符矩陣做整體校驗再從字符串尾部反向掃描檢測//連續(xù)分隔符與純.路徑段PACKAGE_NAME_DOT_ERRORpackage name component contains only . characters。實踐層面的建議對于目錄結構對模塊系統(tǒng)有意義的語言例如 Java務必選擇在語言中合法的標識符作為目錄名。例如不要以數(shù)字開頭避免特殊字符尤其是下劃線和連字符_、-因為它們在 Java 包名中會有問題。雖然 Bazel 支持 workspace 根包中的目標例如//:foo但最好讓根包保持為空這樣所有有意義的包都有描述性的名稱。四、Rules規(guī)則與 Label 的關系規(guī)則rule描述的是輸入與輸出之間的關系以及構建輸出的步驟。規(guī)則有很多種類有時稱為rule class它們可以產(chǎn)出可執(zhí)行文件與庫、測試可執(zhí)行文件等受支持的輸出詳見本倉庫的 構建百科全書式參考 所關聯(lián)的規(guī)則體系。BUILD文件通過調用規(guī)則rules來聲明目標targets。下面這個例子用cc_binary規(guī)則聲明了目標my_appcc_binary( name my_app, srcs [my_app.cc], deps [ //absl/base, //absl/strings, ], )4.1name屬性與屬性類型每次規(guī)則調用都必須有一個name屬性必須是合法的 目標名它在BUILD文件所在的包內聲明一個目標。每條規(guī)則都有一組屬性attributes。某條規(guī)則適用的屬性以及每個屬性的意義和語義取決于規(guī)則的種類rule kind。每個屬性都有名稱和類型常見類型包括屬性類型說明integer整數(shù)值label單個目標引用list of labels目標引用列表string字符串值list of strings字符串列表output label輸出目標引用list of output labels輸出目標引用列表并非所有屬性都需要在每條規(guī)則中指定。屬性由此構成一個從鍵名稱到可選、類型化值的字典。許多規(guī)則都有的srcs屬性類型是 list of labels若給出其值是一個 Label 列表每個 Label 都是該規(guī)則輸入目標的名稱。4.2 規(guī)則名何時重要有些情況下規(guī)則種類rule kind的名稱有些隨意更值得關注的是規(guī)則生成文件的名稱——這正是 genrule 的情況見 General Rules: genrule 相關說明。而在另一些情況下名稱至關重要例如對*_binary和*_test規(guī)則規(guī)則名直接決定了構建產(chǎn)出的可執(zhí)行文件名。4.3 目標圖與查詢工具目標之間構成的這個有向無環(huán)圖被稱為目標圖target graph或構建依賴圖build dependency graph它是 Bazel Query 工具 作用的領域。理解 Label 的解析規(guī)則是正確使用bazel query、bazel cquery與bazel build目標模式的前提。五、延伸Label 解析與命令行目標模式5.1 解析器眼中的 Label 形態(tài)從源碼結構看Bazel 的 LabelParser.java 將原始 Label 字符串拆解為repo倉庫名、repoIsCanonical是否雙、pkgIsAbsolute包部分是否以//開頭、pkg包部分、pkgEndsWithTripleDots是否以...結尾、target目標部分等字段。其內置的解析表覆蓋了全部常見形態(tài)原始字符串reporepoIsCanonicalpkgIsAbsolutepkg解析出的 targetfoo/barnullfalsefalsefoo/bar//foo/barnullfalsetruefoo/barbarreporepofalsetruereporeporepotruetruereporepo//foo/barrepofalsetruefoo/barbarrepo//foo/barrepotruetruefoo/barbar:quuxnullfalsefalsequux//foo/bar:quuxnullfalsetruefoo/barquuxrepo//foo/bar:quuxrepofalsetruefoo/barquux其中foo被特殊處理為foo//:foo的同義形式。這張表直觀地印證了前文所述的省略規(guī)則無倉庫名、無//前綴、無冒號時目標名就是整個字符串本身只有//前綴時目標名默認取包路徑的最后一段。5.2 從 Label 到目標模式Target PatternsLabel 用于標識單個目標例如在BUILD文件的依賴聲明中而 bazel build 等命令 接受的目標模式target patterns是 Label 語法的集合泛化支持通配符。最簡單的情形下任何合法 Label 本身就是一個目標模式它標識恰好一個目標的集合。常用模式包括目標模式含義//foo/bar:wiz單個目標//foo/bar:wiz//foo/bar等價于//foo/bar:bar//foo/bar:all包foo/bar中的所有規(guī)則目標//foo/...foo目錄下所有包中的所有規(guī)則目標//foo/...:*foo目錄下所有包中的全部目標含規(guī)則和文件//...主倉庫所有包中的規(guī)則目標不含外部倉庫//:allworkspace 根包中的全部規(guī)則目標不帶//開頭的目標模式相對于當前工作目錄解析:all是目標級通配符匹配包內所有規(guī)則...是包級通配符遞歸匹配目錄下所有包二者可組合為foo/...:all并縮寫為foo/...。另外:*或:all-targets匹配的是所有目標包括不被任何規(guī)則正常構建的文件如java_binary的_deploy.jar因此:*是:all的超集。六、常見誤區(qū)速查寫法實際含義是否推薦//my/app//my/app:app目標不是包、不是包內全部目標在deps等 Label 語境中要明確//my/app:all包內所有規(guī)則僅命令行目標模式可用testdata/input.txt跨包非法跨包必須寫全路徑錯誤寫法//my/app/testdata:input.txt跨包引用的正確寫法正確寫法//foo/bar/wiz恒等于//foo/bar/wiz:wiz注意與//foo:bar/wiz區(qū)分//a/b/c主倉庫中的目標外部倉庫中也能用寫規(guī)則時優(yōu)先考慮結語Label 是 Bazel 世界的地基。理解其「倉庫名 包名 目標名」的三段式結構、雙規(guī)范倉庫名與單表面?zhèn)}庫名的區(qū)別、各組成部分的省略時機以及目標名與包名各自的字符集約束能讓你在編寫B(tài)UILD文件、調試依賴解析、使用bazel query時少走大量彎路。本倉庫中的 LabelValidator.java 與 LabelParser.java 是這些規(guī)則的權威實現(xiàn)遇到模糊的邊界情況時直接查閱源碼即是最可靠的答案。【免費下載鏈接】bazela fast, scalable, multi-language and extensible build system項目地址: https://gitcode.com/GitHub_Trending/ba/bazel創(chuàng)作聲明:本文部分內容由AI輔助生成(AIGC),僅供參考