建擴展 protobuf_distutils 實戰(zhàn):用 setuptools 在構(gòu)建期自動調(diào)用 protoc 生成 Python 源碼)
protobuf Python 構(gòu)建擴展 protobuf_distutils 實戰(zhàn)用 setuptools 在構(gòu)建期自動調(diào)用 protoc 生成 Python 源碼【免費下載鏈接】protobufProtocol Buffers - Googles data interchange format項目地址: https://gitcode.com/GitHub_Trending/pr/protobuf本文圍繞 protobuf 倉庫中的 Python setuptools 擴展 protobuf_distutils 展開它允許你的 Python 項目在setup.py構(gòu)建流程中直接聲明 .proto 文件位置由擴展在編譯期自動調(diào)用已安裝的protoc編譯器生成*_pb2.py源碼。讀完本文你能掌握該擴展的安裝方式、setup.py配置寫法、全部構(gòu)建選項的語義與默認值以及它在底層如何拼裝并執(zhí)行protoc命令行。一、這是什么一個注冊進 setuptools 的構(gòu)建命令protobuf_distutils是一個 setuptools 擴展包它的核心功能是使用一臺機器上已安裝的 protobuf 編譯器protoc在構(gòu)建 Python 包的過程中生成 Python 源碼而不是讓開發(fā)者手動運行protoc再把產(chǎn)物提交進倉庫。它的工作原理是 setuptools 的命令插件機制。在擴展包自身的 setup.py 中通過entry_points把一個自定義命令注冊到distutils.commands入口組entry_points{ distutils.commands: [ ( generate_py_protobufs protobuf_distutils.generate_py_protobufs:generate_py_protobufs ), ], },這一行意味著任何setup_requires[protobuf_distutils]的項目都會在 setuptools 中獲得一條新的子命令generate_py_protobufs。命令的具體實現(xiàn)位于 generate_py_protobufs.py它是一個繼承自setuptools.Command的類class generate_py_protobufs(Command): Generates Python sources for .proto files. description Generate Python sources for .proto files user_options [ (extra-proto-paths, None, Additional paths to resolve imports in .proto files.), (protoc, None, Path to a specific protoc command to use.), ] boolean_options [recurse]從源碼看該命令除了文檔中記錄的--extra-proto-paths和--protoc兩個命令行參數(shù)外還定義了一個布爾開關(guān)--recurse默認True見initialize_options控制是否遞歸掃描 .proto 文件——這一點 README 未單獨展開但直接決定了默認“遞歸生成source_dir下所有 .proto”的行為。包的元信息也值得留意setup.py 中聲明版本為1.0、許可證為BSD-3-ClausePython 版本分類器覆蓋 3.10 至 3.14注釋明確說明這些版本應(yīng)與 protobuf 主包保持一致。二、安裝擴展擴展本身需要被安裝到環(huán)境中才能被其他項目的setup.py導入。按照 README 的說明$ python setup.py build $ python -m pip install .如果你要修改擴展本身并反復驗證行為可以用開發(fā)模式安裝使改動即時生效$ python setup.py develop三、在你的項目中使用3.1 示例 setup.py 配置在業(yè)務(wù)項目中通過setup_requires聲明“僅在構(gòu)建階段依賴該擴展而非安裝到最終環(huán)境”并通過options字典為generate_py_protobufs命令提供配置。以下是 README 給出的完整示例可直接照搬結(jié)構(gòu)from setuptools import setup setup( # ... nameexample_project, # Require this package, but only for setup (not installation): setup_requires[protobuf_distutils], options{ # See below for details. generate_py_protobufs: { source_dir: path/to/protos, extra_proto_paths: [path/to/other/project/protos], output_dir: path/to/project/sources, # default . proto_files: [relative/path/to/just_this_file.proto], protoc: path/to/protoc.exe, }, }, )3.2 構(gòu)建調(diào)用步驟執(zhí)行下面三步后生成的 protobuf Python 源碼會被包含進example_project的構(gòu)建與安裝產(chǎn)物中$ python setup.py generate_py_protobufs $ python setup.py build $ python -m pip install .關(guān)鍵點在于generate_py_protobufs只是生成源碼這一步后續(xù)的build/pip install才會把生成的*_pb2.py當作普通 Python 模塊一并打包。四、選項詳解含源碼級語義以下逐項覆蓋 README “Options” 一節(jié)的全部內(nèi)容并結(jié)合 generate_py_protobufs.py 的實現(xiàn)補充默認值與判定邏輯。4.1 source_dir.proto 文件所在目錄這是待處理 .proto 文件所在的目錄默認行為是遞歸生成source_dir下所有 .proto 文件的源碼該行為可用下文選項控制。源碼中對應(yīng)的默認值與掃描邏輯在finalize_options里若未顯式給出proto_files則先 glob 頂層source_dir/*.proto再在recurseTrue時追加source_dir/**/*.proto遞歸 glob并把每個文件路徑轉(zhuǎn)換為相對proto_root_path的相對路徑若一個 .proto 都找不到則拋出OptionError(no .proto files were found under self.source_dir)。4.2 proto_root_pathimport 解析根路徑這是解析源 .proto 文件中import語句所用的根路徑默認值取[source_dir] self.extra_proto_paths中source_dir的最短前綴。這個默認計算背后有一個正確性陷阱源碼用一大段 “SUBTLE” 注釋解釋得很清楚。若source_dir是某個extra_proto_paths條目的子目錄就必須使用最短的--proto_path前綴即最長的相對 .proto 文件名。源碼給出的例子source_dir a/b/c extra_proto_paths [a/b, x/y]此時a/b/c/d/foo.proto必須規(guī)范地解析為c/d/foo.proto而不能只是d/foo.proto。否則當某個文件里寫import c/d/foo.proto;時同一個文件會因兩條不同的FileDescriptor.name鍵c/d/foo.proto與d/foo.proto被 protoc 判定為重復定義產(chǎn)生類似如下的錯誤c/d/foo.proto: packagename.MessageName is already defined in file d/foo.proto補充兩條源碼中的邊界規(guī)則如果顯式指定了proto_root_path而source_dir不在其之下會直接拋OptionErrorsource_dir ... is not under proto_root_path ...從源碼注釋看--proto_path的順序是有意義的若同一文件名在兩個不同的--proto_path下解析到不同文件影子文件名protoc 會以錯誤拒絕該路徑——注釋指出這一約束由 protoc 的DiskSourceTree類強制執(zhí)行。4.3 extra_proto_paths額外的 import 查找路徑指定除source_dir之外還應(yīng)用哪些路徑來解析 import常用于指向被source_dir下文件所引用的其他 protobuf 源碼位置注意位于extra_proto_paths下的 .proto 文件不會生成 Python 代碼它們只用于 import 解析。在構(gòu)建時這些路徑會被逐一追加為--proto_path...參數(shù)見下文第五節(jié)的命令行拼裝。4.4 output_dir生成代碼的落盤位置指定生成代碼應(yīng)放置的位置默認值為.initialize_options與finalize_options中雙重保底通常應(yīng)設(shè)為“生成的 Python 模塊應(yīng)位于其下的根包目錄”生成文件按相對proto_root_path的源路徑放置在output_dir之下。README 給出的映射示例源文件${proto_root_path}/subdir/message.proto會生成 Python 模塊${output_dir}/subdir/message_pb2.py。也就是說.proto 目錄結(jié)構(gòu)會被原樣鏡像到output_dir中并附加_pb2.py后綴。4.5 proto_files只生成指定文件一個字符串列表用于指定要生成代碼的具體 .proto 文件路徑而不是搜索source_dir下的全部 .proto 文件路徑是相對source_dir的。例如只想為${source_dir}/subdir/message.proto生成代碼就寫[subdir/message.proto]。源碼層面的細節(jié)proto_files最終會被轉(zhuǎn)換為相對proto_root_path的相對路徑finalize_options中有partition(self.proto_root_path os.path.sep)的處理保證傳給protoc的文件名與--proto_path前綴一致避免 4.2 節(jié)描述的重復定義問題。4.6 protoc編譯器二進制的解析順序默認情況下擴展通過搜索系統(tǒng)PATH找到protoc。若需指定特定編譯器可顯式給出路徑。README 明確了protoc值的四級解析順序如果給generate_py_protobufs傳了--protocVALUE命令行標志則使用VALUE$ python setup.py generate_py_protobufs --protoc/path/to/protoc否則如果setup.py的options中設(shè)置了protoc見 3.1 示例則使用該值否則如果設(shè)置了環(huán)境變量PROTOC則使用它$ PROTOC/path/to/protoc python setup.py generate_py_protobufs否則在$PATH中搜索protoc。源碼中第 24 級直接對應(yīng)finalize_options的三行兜底邏輯順序與文檔完全一致if self.protoc is None: self.protoc os.getenv(PROTOC) if self.protoc is None: self.protoc shutil.which(protoc)第 1、2 級由 setuptools 的user_options/options機制在調(diào)用本段代碼之前完成賦值。五、底層調(diào)用鏈擴展到底執(zhí)行了什么把上面所有選項消化完之后run()方法做的事非常直白拼裝一條protoc命令行并執(zhí)行def run(self): # All proto file paths were adjusted in finalize_options to be relative # to self.proto_root_path. proto_paths [--proto_path self.proto_root_path] proto_paths.extend([--proto_path x for x in self.extra_proto_paths]) # Run protoc. subprocess.run( [ self.protoc, --python_out self.output_dir, ] proto_paths self.proto_files )可以把它翻譯成一條等效的手工命令來理解整個擴展protoc \ --python_outoutput_dir \ --proto_pathproto_root_path \ --proto_pathextra_proto_paths 逐項追加 \ 相對 proto_root_path 的 proto 文件列表即generate_py_protobufs等價于幫你確定“用哪個protoc、以哪些目錄為 import 根、要編譯哪些文件、輸出到哪個包目錄”然后代為執(zhí)行一次標準protoc --python_out調(diào)用。從源碼結(jié)構(gòu)看subprocess.run的結(jié)果沒有做額外封裝擴展的職責到“執(zhí)行完成”為止——生成成敗與build/ 打包環(huán)節(jié)的銜接仍由你的setup.py流程保障。六、適用前提與使用注意前提是已安裝protoc可執(zhí)行文件該擴展只做“調(diào)用編譯器”的編排不提供編譯器本身找不到protoc既無--protoc/options/PROTOC指定也不在$PATH中時self.protoc將為None后續(xù)執(zhí)行會失敗。面向 setuptools 工作流它通過distutils.commands入口點注冊命令見 setup.py適用于python setup.py .../pip傳統(tǒng)構(gòu)建鏈路而非 Bazel、CMake 等其他構(gòu)建系統(tǒng)——protobuf 倉庫中這些系統(tǒng)有各自獨立的 proto 代碼生成方案。Python 版本擴展包分類器聲明支持 Python 3.103.14與 protobuf 主包對齊。import 根路徑的坑當source_dir嵌套在extra_proto_paths之內(nèi)時務(wù)必讓proto_root_path取最短公共前綴擴展的默認邏輯已自動處理否則會出現(xiàn) 4.2 節(jié)所述的 “already defined in file” 重復定義報錯。extra_proto_paths 不產(chǎn)出代碼只依賴、不生成跨項目 import 依賴請放這里而不是并入source_dir。七、小結(jié)protobuf_distutils用不到百行代碼解決了 Python 項目中最常見的一類構(gòu)建痛點把.proto到_pb2.py的生成步驟固化進setup.py流程。它的配置面很小source_dir、proto_root_path、extra_proto_paths、output_dir、proto_files、protoc六項 命令行--protoc/--extra-proto-paths但proto_root_path的最短前綴推導和protoc四級解析順序兩處邏輯直接對應(yīng)protoc源碼樹解析的真實約束是整個擴展中最值得理解的兩個設(shè)計點。相關(guān)文件均可在當前倉庫中直接查閱使用文檔python/protobuf_distutils/README.md包定義與命令注冊python/protobuf_distutils/setup.py命令實現(xiàn)python/protobuf_distutils/protobuf_distutils/generate_py_protobufs.py【免費下載鏈接】protobufProtocol Buffers - Googles data interchange format項目地址: https://gitcode.com/GitHub_Trending/pr/protobuf創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考