You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
105 lines
4.5 KiB
105 lines
4.5 KiB
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
#
|
|
# proto -> Go 生成脚本(yunyan-sas monorepo)
|
|
#
|
|
# 用法:在本目录(apps/proto)下执行 `python3 pb.py`(也可从任意目录执行,脚本会自定位)。
|
|
#
|
|
# 约定:
|
|
# - proto 源码根目录 = 本脚本所在目录(apps/proto);所有 .proto 的 go_package 均为 ".;pb",
|
|
# 故生成的 *.pb.go 一律平铺输出到 apps/services/pb(basename 命名,忽略子目录层级)。
|
|
# - -I 指向 proto 根目录,用于解析形如 `import "db.proto"` / `import "api/api_db.proto"` 的引用。
|
|
# - 递归自动发现全部 .proto(EXCLUDE_DIRS 除外),新增 proto/子目录无需改本脚本 —— 方便以后做
|
|
# 数据结构与协议更新:改完 .proto 直接重跑本脚本即可。
|
|
# - 生成后把 .proto 里 `//@go_tags(`gorm:"..." json:"..."`)` 注释回填成真正的 struct tag
|
|
# (protoc-gen-go 本身不写 gorm/json tag,靠本步骤注入;写死在 .pb.go 里手改无效,须改 .proto 重生成)。
|
|
#
|
|
# 前置:protoc、protoc-gen-go(v1.36.x,在 $PATH 或 $GOBIN 里)。
|
|
|
|
import io
|
|
import os
|
|
import re
|
|
import subprocess
|
|
|
|
# 本脚本所在目录 = proto 源码根;生成产物到 apps/services/pb。
|
|
PROTO_ROOT = os.path.dirname(os.path.abspath(__file__))
|
|
OUT_DIR = os.path.abspath(os.path.join(PROTO_ROOT, os.pardir, 'services', 'pb'))
|
|
|
|
# 不参与编译的目录:google/* 仅为 import 提供 well-known 类型定义,编译会与 Go 运行时内置类型冲突。
|
|
EXCLUDE_DIRS = {'google'}
|
|
|
|
|
|
def apply_go_tags(pbgo_file):
|
|
"""把 protoc 生成文件里的 //@go_tags(`k:"v" ...`) 注释回填成真正的 struct tag。
|
|
|
|
合并规则:保留 protoc 原本写入的 tag(如 protobuf:"..."),再叠加 @go_tags 里声明的
|
|
键值对(如 gorm/json),同名以 @go_tags 为准。
|
|
"""
|
|
file_data = ""
|
|
with io.open(pbgo_file, "r", encoding='utf-8') as f:
|
|
for line in f:
|
|
if '@go_tags' in line:
|
|
tags = {}
|
|
tagsstr = ""
|
|
# 1. 解析 protoc 原有反引号 tag(第一个反引号组)
|
|
existing = re.findall(r"`(.+?)`", line)
|
|
if existing:
|
|
for v in existing[0].split():
|
|
tag = v.split(':')
|
|
if len(tag) >= 2:
|
|
tags[tag[0]] = tag[1]
|
|
# 2. 解析 @go_tags(`...`) 里的 key:"value",覆盖同名
|
|
go_tags_content = re.findall(r"@go_tags\(\`(.+?)\`", line)
|
|
if go_tags_content:
|
|
for key, value in re.findall(r'(\w+):"([^"]*)"', go_tags_content[0]):
|
|
tags[key] = f"\"{value}\""
|
|
# 3. 重建 tag 串并替换回该行第一个反引号组
|
|
for key, value in tags.items():
|
|
tagsstr += "{0}:{1} ".format(key, value)
|
|
line = re.sub(r"`([^`]*)`", "`{0}`".format(tagsstr.strip()), line, count=1)
|
|
file_data += line
|
|
with io.open(pbgo_file, "w", encoding='utf-8') as f:
|
|
f.write(file_data)
|
|
|
|
|
|
def build_one(proto_file):
|
|
"""编译单个 .proto 并回填 go_tags。proto_file 为绝对路径。"""
|
|
rel = os.path.relpath(proto_file, PROTO_ROOT)
|
|
cmd = ['protoc', '--go_out=' + OUT_DIR, '-I', PROTO_ROOT, rel]
|
|
print(' '.join(cmd))
|
|
if subprocess.call(cmd, cwd=PROTO_ROOT) != 0:
|
|
raise SystemExit('protoc 编译失败: ' + rel)
|
|
# go_package=".;pb" -> 平铺输出为 <OUT_DIR>/<basename>.pb.go
|
|
base = os.path.splitext(os.path.basename(proto_file))[0]
|
|
out = os.path.join(OUT_DIR, base + '.pb.go')
|
|
if os.path.exists(out):
|
|
apply_go_tags(out)
|
|
else:
|
|
print(' 警告:未找到生成文件 {0}(检查 .proto 的 go_package 是否为 ".;pb")'.format(out))
|
|
|
|
|
|
def discover_protos():
|
|
"""递归发现 PROTO_ROOT 下全部 .proto(跳过 EXCLUDE_DIRS)。"""
|
|
protos = []
|
|
for dirpath, dirnames, filenames in os.walk(PROTO_ROOT):
|
|
dirnames[:] = [d for d in dirnames if d not in EXCLUDE_DIRS]
|
|
for fn in filenames:
|
|
if fn.endswith('.proto'):
|
|
protos.append(os.path.join(dirpath, fn))
|
|
protos.sort()
|
|
return protos
|
|
|
|
|
|
def main():
|
|
if not os.path.isdir(OUT_DIR):
|
|
raise SystemExit('输出目录不存在: ' + OUT_DIR)
|
|
protos = discover_protos()
|
|
if not protos:
|
|
raise SystemExit('未在 {0} 下发现任何 .proto'.format(PROTO_ROOT))
|
|
for p in protos:
|
|
build_one(p)
|
|
print('完成:编译 {0} 个 .proto -> {1}'.format(len(protos), OUT_DIR))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|
|
|