庫(kù) Bazel 構(gòu)建開(kāi)發(fā)工作流完全指南:從 BUILD.bazel 編寫到 clang-tidy 集成)
MongoDB 倉(cāng)庫(kù) Bazel 構(gòu)建開(kāi)發(fā)工作流完全指南從 BUILD.bazel 編寫到 clang-tidy 集成【免費(fèi)下載鏈接】mongoThe MongoDB Database項(xiàng)目地址: https://gitcode.com/GitHub_Trending/mo/mongo本文是 MongoDB 服務(wù)器源碼倉(cāng)庫(kù)中 Bazel 構(gòu)建系統(tǒng)的開(kāi)發(fā)者實(shí)戰(zhàn)指南聚焦 Server 開(kāi)發(fā)者日常修改 Bazel 構(gòu)建定義BUILD.bazel的完整工作流如何新建構(gòu)建目標(biāo)、靜態(tài)聲明頭文件與源文件、聲明庫(kù)依賴、以及通過(guò) Bazel 運(yùn)行 clang-tidy 靜態(tài)分析。讀完本文你將掌握 MongoDB 倉(cāng)庫(kù)特有的mongo_cc_binary/mongo_cc_library/idl_generator宏的用法與依賴解析規(guī)則并能獨(dú)立解決頭文件該加到哪里這一高頻構(gòu)建問(wèn)題。1. 概覽Bazel 在 MongoDB 倉(cāng)庫(kù)中的角色MongoDB 服務(wù)器倉(cāng)庫(kù)當(dāng)前工作目錄根即倉(cāng)庫(kù)根使用 Bazel 作為構(gòu)建系統(tǒng)構(gòu)建定義以BUILD.bazel文件的形式散落在源碼目錄中。與src/mongo目錄約 5700 個(gè).cpp與 3600 個(gè).h文件一一對(duì)應(yīng)的BUILD.bazel文件共同構(gòu)成了整個(gè)服務(wù)器的構(gòu)建圖。Bazel 的構(gòu)建目標(biāo)定義在源碼所在目錄的BUILD.bazel文件中。DevProd Build 團(tuán)隊(duì)為 MongoDB 定制了專用宏封裝在 bazel/mongo_src_rules.bzl 中包括mongo_cc_binary可執(zhí)行程序目標(biāo)cc_binary的封裝見(jiàn) mongo_src_rules.bzl#L799mongo_cc_library庫(kù)目標(biāo)cc_library的封裝見(jiàn) mongo_src_rules.bzl#L303idl_generatorIDLInterface Description Language代碼生成器目標(biāo)見(jiàn) mongo_src_rules.bzl#L1224。這些宏是普通 Bazel 原生規(guī)則的上層封裝會(huì)自動(dòng)注入 MongoDB 特有的全局依賴如libunwind并處理 auto-header 等倉(cāng)庫(kù)特有的構(gòu)建邏輯。2. 創(chuàng)建新的 BUILD.bazel 文件構(gòu)建目標(biāo)定義在源碼所在目錄下。例如要編譯src/mongo/hello_world.cpp就需要在src/mongo/目錄下創(chuàng)建src/mongo/BUILD.bazel內(nèi)容如下mongo_cc_binary( name hello_world, srcs [ hello_world.cpp ], }2.1 獲取 Bazel倉(cāng)庫(kù)不要求手工安裝 Bazel而是提供了安裝腳本。運(yùn)行python buildscripts/install_bazel.py該腳本buildscripts/install_bazel.py會(huì)下載 bazelisk當(dāng)前固定為 v1.26.0并放置到工具鏈目錄同時(shí)創(chuàng)建bazel - bazelisk符號(hào)鏈接create_bazel_to_bazelisk_symlink使后續(xù)bazel命令可直接使用。在ppc64le、s390x等 bazelisk 二進(jìn)制不支持的平臺(tái)上腳本會(huì)回退到倉(cāng)庫(kù)內(nèi)的 bazel/bazelisk.py。2.2 構(gòu)建與運(yùn)行目標(biāo)構(gòu)建該目標(biāo)bazel build //src/mongo:hello_world運(yùn)行該目標(biāo)bazel run //src/mongo:hello_world2.3 目標(biāo)標(biāo)簽Label的構(gòu)成規(guī)則完整的 Bazel 目標(biāo)標(biāo)簽由BUILD.bazel所在目錄與該目錄內(nèi)的目標(biāo)名組合而成//{BUILD.bazel dir}:{targetname}即//src/mongo:hello_world 包路徑src/mongo 目標(biāo)名hello_world。這個(gè)標(biāo)簽語(yǔ)法在bazel build/bazel run命令、以及下文deps聲明中通用。3. 添加新的頭文件 / 源文件Bazel 盡可能使用靜態(tài)分析來(lái)提升執(zhí)行與查詢速度。因此MongoDB 倉(cāng)庫(kù)禁止在構(gòu)建目標(biāo)中動(dòng)態(tài)聲明文件例如使用glob、通配符等。每新增一個(gè)頭文件或源文件都必須手工將其引用添加進(jìn)對(duì)應(yīng)構(gòu)建目標(biāo)mongo_cc_binary( name hello_world, srcs [ hello_world.cpp, new_source.cpp # 新增源文件時(shí)加入 ], hdrs [ new_header.h # 新增頭文件時(shí)加入 ], }3.1 源碼層面的印證靜態(tài)聲明如何被執(zhí)行從實(shí)現(xiàn)看mongo_cc_library會(huì)處理srcs、hdrs、private_hdrs、textual_hdrs等靜態(tài)聲明的文件列表mongo_src_rules.bzl#L303-L336。其中textual_hdrs用于無(wú)需單獨(dú)編譯、直接文本包含的.cpp文件private_hdrs是該庫(kù)私有、不對(duì)外導(dǎo)出的頭文件。此外倉(cāng)庫(kù)在 bazel/auto_header/ 中實(shí)現(xiàn)了 auto-header 機(jī)制見(jiàn) auto_header.bzl對(duì)于.c/.cc/.cpp/.h/.hpp/.hh/.hxx等擴(kuò)展名mongo_cc_library默認(rèn)auto_header True會(huì)基于已聲明的源文件自動(dòng)推導(dǎo)出與每個(gè)源文件同名的頭文件并將它們并入srcsmongo_src_rules.bzl#L396-L406。也就是說(shuō)常規(guī)的foo.cpp foo.h配對(duì)通??梢蕾囎詣?dòng)推導(dǎo)但新增一個(gè)獨(dú)立的、不與任何源文件同名的頭文件時(shí)必須顯式寫入hdrs。4. 添加新的庫(kù)創(chuàng)建新庫(kù)與創(chuàng)建新二進(jìn)制的步驟類似也是在對(duì)應(yīng)目錄的BUILD.bazel中新增一條mongo_cc_library定義mongo_cc_library( name new_library, srcs [ new_library_source_file.cpp ] }mongo_cc_library是對(duì)原生cc_library的封裝支持的常用參數(shù)包括默認(rèn)值見(jiàn) mongo_src_rules.bzl#L303-L336參數(shù)默認(rèn)值作用srcs[]參與編譯的源文件hdrs[]該庫(kù)對(duì)外公開(kāi)的頭文件textual_hdrs[]文本式包含的頭文件/源文件不單獨(dú)編譯private_hdrs[]庫(kù)私有頭文件deps[]依賴的 Bazel 目標(biāo)cc_deps[]同deps但不會(huì)作為共享庫(kù)依賴被加入copts/cxxopts[]額外編譯選項(xiàng)linkopts[]鏈接選項(xiàng)會(huì)傳遞給依賴該庫(kù)的目標(biāo)defines[]宏定義編譯本目標(biāo)及其依賴方時(shí)均生效local_defines[]宏定義僅編譯本目標(biāo)源碼時(shí)生效includes[]導(dǎo)出給依賴方的 include 目錄自動(dòng)加包路徑前綴skip_global_deps[]跳過(guò)全局注入依賴可選值如libunwind、allocatortestonlyFalse僅用于測(cè)試auto_headerTrue是否啟用頭文件自動(dòng)推導(dǎo)值得注意的倉(cāng)庫(kù)特性mongo_cc_library默認(rèn)會(huì)把libunwind加入deps除非在skip_global_deps中顯式排除見(jiàn) mongo_src_rules.bzl#L379-L380。linkstatic參數(shù)當(dāng)前不受支持——MongoDB 的構(gòu)建必須整體靜態(tài)鏈接或整體動(dòng)態(tài)鏈接由//config/bazel:linkstatic統(tǒng)一配置。4.1 IDL 生成目標(biāo)對(duì)于包含.idl文件的模塊使用idl_generator生成解析器代碼idl_generator( name some_command_gen, srcs [some_command.idl], )從源碼看idl_generator會(huì)自動(dòng)為每個(gè)目標(biāo)補(bǔ)充//src/mongo:idl_headers這一文件組其中包含bsonobj.h、idls_parser.h、server_parameter.h等 IDL 生成代碼必需的公共頭文件見(jiàn) src/mongo/BUILD.bazel#L311 起的idl_headersfilegroup并且默認(rèn)注入//src/mongo/db/query:explain_verbosity_gen作為隱式依賴除非idl_self_dep True詳見(jiàn) mongo_src_rules.bzl#L1224-L1237。5. 聲明依賴如果某個(gè)庫(kù)或二進(jìn)制依賴另一個(gè)庫(kù)必須在目標(biāo)的deps段中顯式聲明。引用庫(kù)的語(yǔ)法與bazel build/bazel run命令中的標(biāo)簽語(yǔ)法完全一致mongo_cc_library( name new_library, # ... } mongo_cc_binary( name hello_world, srcs [ hello_world.cpp, ], deps [ :new_library, # 引用同目錄 BUILD.bazel 中聲明的庫(kù) # //src/mongo:new_library # 絕對(duì)路徑寫法 # sub_directory:new_library # 子目錄的相對(duì)路徑寫法 ], }三種依賴引用方式說(shuō)明:new_library同包內(nèi)引用冒號(hào)前的包路徑留空表示當(dāng)前BUILD.bazel所在目錄//src/mongo:new_library從倉(cāng)庫(kù)根目錄開(kāi)始的絕對(duì)標(biāo)簽sub_directory:new_library相對(duì)于當(dāng)前包的子目錄引用。Bazel 會(huì)依據(jù)完整的依賴圖進(jìn)行增量構(gòu)建與緩存。從實(shí)現(xiàn)看mongo_cc_binary底層統(tǒng)一走_(dá)mongo_cc_binary_and_test邏輯mongo_src_rules.bzl#L851并自動(dòng)打上mongo_binarytag便于后續(xù)按 tag 過(guò)濾查詢。6. 通過(guò) Bazel 運(yùn)行 clang-tidy注意該特性仍處于開(kāi)發(fā)中相關(guān)跟蹤單為 SERVER-80396但倉(cāng)庫(kù)已提供了可用的配置與命令。6.1 運(yùn)行方式分析全部代碼bazel build --configclang-tidy src/...分析單個(gè)目標(biāo)例如environment_buffer。注意目標(biāo)名帶_with_debug后綴bazel build --configclang-tidy src/mongo/db/commands:environment_buffer_with_debug6.2 配置在倉(cāng)庫(kù)中的落點(diǎn)clang-tidy的構(gòu)建配置定義在根目錄 .bazelrc 中.bazelrc#L602-L610核心行為包括--remote_download_outputsall遠(yuǎn)程執(zhí)行時(shí)下載全部輸出含 clang-tidy 報(bào)告--build_tag_filtersnot_third_party,-mongo-tidy-tests,-mongo-tidy-tests_debug跳過(guò)第三方代碼與 tidy 自測(cè)目標(biāo)只分析 MongoDB 自身代碼--//bazel/config:compiler_typeclang強(qiáng)制使用 clang 編譯器clang-tidy 依賴 clang 工具鏈--keep_going單個(gè)目標(biāo)失敗不中斷其余分析--aspects bazel_clang_tidy//clang_tidy:clang_tidy.bzl%clang_tidy_aspect以 aspect 方式掛接到構(gòu)建圖上運(yùn)行 clang-tidy--output_groupsreport產(chǎn)出 tidy 報(bào)告作為輸出--jobs300并行分析任務(wù)數(shù)。clang-tidy 的配置文件由根目錄 BUILD.bazel 中的clang_tidy_config/clang_tidy_config_strict目標(biāo)生成通過(guò)//buildscripts:clang_tidy_config_gen.py生成器并通過(guò)clang_tidy_merge_tool//bazel:merge_tidy_configs合并MongoDB 自研的 tidy 檢查插件mongo_tidy_checks也通過(guò)clang_tidy_plugin_deps掛載見(jiàn) .bazelrc#L161-L167。6.3 自測(cè) clang-tidy 是否真的能發(fā)現(xiàn)問(wèn)題如果想驗(yàn)證 clang-tidy 確實(shí)在工作可以向某個(gè).cpp文件注入以下代碼觸發(fā)bugprone-incorrect-roundings警告const double f 1.0; const int foo (int)(f 0.5);該模式是浮點(diǎn)數(shù) 0.5 再截?cái)嗟慕?jīng)典錯(cuò)誤舍入寫法clang-tidy 的bugprone-incorrect-roundings檢查會(huì)對(duì)此報(bào)警。7. FAQ頭文件應(yīng)該加到哪里7.1 頭文件在多處被引用如何定位應(yīng)在哪些 BUILD.bazel 中聲明遵循以下循環(huán)定位流程直接用 Bazel 構(gòu)建以加速定位bazel build //src/...構(gòu)建會(huì)在第一個(gè)缺失頭文件依賴處失敗。在 bazel 構(gòu)建文件中搜索該頭文件所屬的庫(kù)目前仍存在頭文件放錯(cuò)位置的情況需要結(jié)合經(jīng)驗(yàn)判斷。若該頭文件已存在于某個(gè)庫(kù)中就把該庫(kù)加入deps。例如scoped_timer.h屬于scope_timer庫(kù)就在deps中加入//src/mongo/db/exec:scoped_timer——這樣做還能自動(dòng)帶上scoped_timer.h的傳遞依賴對(duì)應(yīng)庫(kù)定義位于 src/mongo/db/exec/BUILD.bazel#L64 附近的scoped_timer目標(biāo)。如果頭文件不屬于任何現(xiàn)有庫(kù)則直接把它加入編譯失敗的那個(gè)庫(kù)的hdrs字段再次直接構(gòu)建bazel build //src/...若出現(xiàn)依賴環(huán)則撤銷第 2 步加入的依賴改為把頭文件作為直接依賴放入hdrs字段然后回到第 1 步重新循環(huán)。7.2 頭文件被幾十處甚至更多位置引用正確歸位需要大規(guī)模重構(gòu)、阻塞關(guān)鍵工作時(shí)怎么辦如果已經(jīng)投入大量精力確認(rèn)將該頭文件放到正確位置通常是與其關(guān)聯(lián)的.cpp文件放在一起并讓所有依賴方把該庫(kù)加為dep需要一次大規(guī)模重構(gòu)可以創(chuàng)建 SERVER ticket說(shuō)明問(wèn)題、解決方案與解決所需的工作量打開(kāi)src/mongo/BUILD.bazel把頭文件加入core_headers文件組并在 TODO 注釋中引用該 ticket。這屬于最后的兜底手段僅當(dāng)重構(gòu)成本極高且阻塞其他工作時(shí)才應(yīng)使用。作為對(duì)照src/mongo/BUILD.bazel中已有的idl_headers文件組src/mongo/BUILD.bazel#L311展示了公共頭文件組的標(biāo)準(zhǔn)寫法集中列出一批頭文件標(biāo)簽供生成類規(guī)則統(tǒng)一引用。core_headers遵循同樣的 filegroup 模式但只承載這種不得已的例外頭文件。8. 小結(jié)MongoDB 倉(cāng)庫(kù)的 Bazel 開(kāi)發(fā)工作流可以概括為三條鐵律文件必須靜態(tài)聲明不使用glob/通配符新增文件手動(dòng)加入srcs/hdrs依賴必須顯式聲明通過(guò)deps用:local、//abs/path:target或subdir:target標(biāo)簽表達(dá)依賴關(guān)系優(yōu)先使用專用宏mongo_cc_binary、mongo_cc_library、idl_generator已封裝好全局依賴與頭文件推導(dǎo)等 MongoDB 特有邏輯。遇到頭文件歸屬問(wèn)題時(shí)先用bazel build //src/...讓構(gòu)建器指出缺失位置再?zèng)Q定是補(bǔ)deps還是補(bǔ)hdrs必要時(shí)才動(dòng)用core_headers兜底并登記 SERVER ticket。這套工作流保證了百萬(wàn)行級(jí)代碼庫(kù)在靜態(tài)依賴圖下的構(gòu)建速度與可查詢性。【免費(fèi)下載鏈接】mongoThe MongoDB Database項(xiàng)目地址: https://gitcode.com/GitHub_Trending/mo/mongo創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考