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