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