293 lines
11 KiB
Bash
Executable File
293 lines
11 KiB
Bash
Executable File
#!/bin/bash
|
||
|
||
# Proto 文件编译脚本(自动扫描模式)
|
||
# ----------------------------------------------------------------------------
|
||
# 扫描 proto/*.proto 自动编译为 Go 代码:
|
||
# - 含 service 的 proto:生成 .pb.go + .triple.go
|
||
# - 不含 service 的 proto(如 common.proto / event.proto):只生成 .pb.go
|
||
#
|
||
# protoc-gen-go(pb.go)按 --go_out + 源文件名输出;
|
||
# protoc-gen-go-triple v3.0.0(triple.go)忽略 --go-triple_out,
|
||
# 严格按 go_package 第一段生成目录、按源文件名生成文件。
|
||
# 因此同一个 proto 的两个产物可能落在不同目录(典型:castlove_config.proto
|
||
# → pb.go 在 pkg/proto/castlove_config/,triple.go 在 pkg/proto/castlove/)。
|
||
#
|
||
# 兼容 bash 3.2(macOS 自带 bash),不使用 declare -A 关联数组。
|
||
#
|
||
# 新增 proto 文件时无需修改本脚本,放到 proto/ 目录下即可。
|
||
# ----------------------------------------------------------------------------
|
||
|
||
set -e
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
cd "$SCRIPT_DIR/.."
|
||
|
||
export PATH="$PATH:$(go env GOPATH)/bin"
|
||
|
||
echo "======================================"
|
||
echo "开始编译 Proto 文件(自动扫描模式)"
|
||
echo "======================================"
|
||
|
||
# --- 检查 protoc ---
|
||
if ! command -v protoc &> /dev/null; then
|
||
echo "❌ protoc 未安装!"
|
||
echo " macOS: brew install protobuf"
|
||
echo " Ubuntu: apt-get install protobuf-compiler"
|
||
exit 1
|
||
fi
|
||
echo "✅ protoc 版本: $(protoc --version)"
|
||
|
||
# --- 检查 Go 插件 ---
|
||
MISSING_PLUGINS=false
|
||
if ! command -v protoc-gen-go &> /dev/null; then
|
||
echo "❌ protoc-gen-go 未安装"
|
||
MISSING_PLUGINS=true
|
||
fi
|
||
if ! command -v protoc-gen-go-triple &> /dev/null; then
|
||
echo "❌ protoc-gen-go-triple 未安装"
|
||
MISSING_PLUGINS=true
|
||
fi
|
||
if [ "$MISSING_PLUGINS" = true ]; then
|
||
echo ""
|
||
echo "请安装缺失的插件:"
|
||
echo " go install google.golang.org/protobuf/cmd/protoc-gen-go@latest"
|
||
echo " go install github.com/dubbogo/protoc-gen-go-triple/v3@latest"
|
||
exit 1
|
||
fi
|
||
echo "✅ 所有必需插件已安装"
|
||
echo ""
|
||
|
||
# --- 工具函数:从 proto 文件解析 go_package 第一段(生成路径段),用于定位 triple.go 输出目录 ---
|
||
# protoc-gen-go-triple v3.0.0 忽略 --go-triple_out,严格按 go_package 第一段生成路径,
|
||
# 但文件名仍用源文件名(如 castlove_config.proto → .../castlove/castlove_config.triple.go)。
|
||
# 退化:解析失败时按源文件名推断。
|
||
resolve_triple_out_dir() {
|
||
local name="$1"
|
||
local proto_file="proto/${name}.proto"
|
||
local go_pkg_path
|
||
# 用 sed -E 替代 grep -oP(macOS BSD grep 不支持 -P;sed -E 是 POSIX ERE)
|
||
# 匹配 option go_package = "PATH;NAME";,提取 PATH 段
|
||
go_pkg_path=$(sed -nE 's/.*option go_package = "([^";]+);.*/\1/p' "$proto_file" | head -1)
|
||
if [ -n "$go_pkg_path" ]; then
|
||
echo "pkg/proto/$(basename "$go_pkg_path")"
|
||
else
|
||
echo "pkg/proto/$name"
|
||
fi
|
||
}
|
||
|
||
# --- 工具函数:从 protoc-gen-go-triple v3 默认输出位置把 triple.go 搬到目标目录 ---
|
||
move_triple_files() {
|
||
local name="$1" # proto 文件名(不含扩展名)
|
||
local target_dir="$2" # triple.go 的目标目录(pkg/proto/<go_pkg_basename>)
|
||
local triple_file="${name}.triple.go"
|
||
# v3 生成位置:github.com/<go_pkg>/<name>.triple.go(go_pkg 第一段 + 源文件名)
|
||
local src_file="github.com/topfans/backend/pkg/proto/$(basename "$target_dir")/${triple_file}"
|
||
if [ -f "$src_file" ]; then
|
||
mv "$src_file" "$target_dir/"
|
||
# 清掉残留的空目录(v3 在 github.com/ 下生成的路径骨架)
|
||
local pkg_dir_name=$(basename "$target_dir")
|
||
rm -rf "github.com/topfans/backend/pkg/proto/${pkg_dir_name}"
|
||
echo " ✅ ${triple_file} 已移动到 ${target_dir}/"
|
||
else
|
||
# 兜底:尝试源文件名目录(兼容不同版本行为)
|
||
local fallback="github.com/topfans/backend/pkg/proto/${name}/${triple_file}"
|
||
if [ -f "$fallback" ]; then
|
||
mv "$fallback" "$target_dir/"
|
||
rm -rf "github.com/topfans/backend/pkg/proto/${name}"
|
||
echo " ✅ ${triple_file} 已在兜底路径找到并移动"
|
||
else
|
||
echo " ⚠️ 未找到 ${src_file}(v3 插件输出位置可能已变,需检查)"
|
||
fi
|
||
fi
|
||
}
|
||
|
||
# --- 跨包类型引用后处理:protoc-gen-go-triple v3.0.0 在跨包类型作 RPC 参数时丢包前缀 ---
|
||
# 已知触发:statistic.proto 引用 event.proto(event.Event / event.BatchEventRequest 作 RPC 参数)。
|
||
# 如果以后其他 proto 也出现同样症状,把 name 加到下方 case 分支即可。
|
||
patch_cross_package_refs() {
|
||
local name="$1"
|
||
local triple_dir
|
||
triple_dir=$(resolve_triple_out_dir "$name")
|
||
case "$name" in
|
||
statistic)
|
||
local TRIPLE_FILE="${triple_dir}/${name}.triple.go"
|
||
if [ ! -f "$TRIPLE_FILE" ]; then
|
||
return
|
||
fi
|
||
if grep -q 'github.com/topfans/backend/pkg/proto/event"' "$TRIPLE_FILE"; then
|
||
return # 已修复过
|
||
fi
|
||
python3 -c "
|
||
import re
|
||
with open('$TRIPLE_FILE', 'r') as f:
|
||
src = f.read()
|
||
pattern = re.compile(r'(import\s*\([^)]*\))', re.MULTILINE)
|
||
blocks = list(pattern.finditer(src))
|
||
if len(blocks) >= 2:
|
||
second = blocks[1]
|
||
block_text = second.group(1)
|
||
if 'pkg/proto/event' not in block_text:
|
||
new_block = block_text[:-1].rstrip() + '\n\tevent \"github.com/topfans/backend/pkg/proto/event\"\n)'
|
||
src = src[:second.start()] + new_block + src[second.end():]
|
||
with open('$TRIPLE_FILE', 'w') as f:
|
||
f.write(src)
|
||
"
|
||
sed -i '' \
|
||
-e 's/\*Event\([,)]\)/\*event.Event\1/g' \
|
||
-e 's/\*Event$/&XXX/g' \
|
||
-e 's/\*Event[^a-zA-Z._)/]/\*event.Event\&/g' \
|
||
-e 's/new(Event)/new(event.Event)/g' \
|
||
-e 's/\*BatchEventRequest\([,)]\)/\*event.BatchEventRequest\1/g' \
|
||
-e 's/new(BatchEventRequest)/new(event.BatchEventRequest)/g' \
|
||
"$TRIPLE_FILE"
|
||
sed -i '' \
|
||
-e 's/(\*Event)/(\*event.Event)/g' \
|
||
-e 's/(\*BatchEventRequest)/(\*event.BatchEventRequest)/g' \
|
||
"$TRIPLE_FILE"
|
||
echo " ✅ ${TRIPLE_FILE} 已修复跨包类型引用(protoc-gen-go-triple v3.0.0 bug)"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
# --- 扫描 proto/*.proto ---
|
||
shopt -s nullglob
|
||
PROTO_FILES=(proto/*.proto)
|
||
shopt -u nullglob
|
||
if [ ${#PROTO_FILES[@]} -eq 0 ]; then
|
||
echo "❌ proto/ 目录下未发现 .proto 文件"
|
||
exit 1
|
||
fi
|
||
|
||
# 按文件名排序,保证输出稳定、可复现
|
||
IFS=$'\n' PROTO_FILES=($(printf '%s\n' "${PROTO_FILES[@]}" | sort))
|
||
unset IFS
|
||
|
||
echo "🔍 发现 ${#PROTO_FILES[@]} 个 proto 文件:"
|
||
for f in "${PROTO_FILES[@]}"; do
|
||
echo " - $(basename "$f")"
|
||
done
|
||
echo ""
|
||
|
||
# --- 创建 pkg/proto 子目录 ---
|
||
echo "📁 创建目标目录..."
|
||
for f in "${PROTO_FILES[@]}"; do
|
||
name=$(basename "$f" .proto)
|
||
pb_dir="pkg/proto/$name"
|
||
triple_dir=$(resolve_triple_out_dir "$name")
|
||
mkdir -p "$pb_dir"
|
||
[ "$triple_dir" != "$pb_dir" ] && mkdir -p "$triple_dir"
|
||
done
|
||
echo ""
|
||
|
||
# --- 逐个编译 ---
|
||
echo "⚙️ 开始编译..."
|
||
for f in "${PROTO_FILES[@]}"; do
|
||
name=$(basename "$f" .proto)
|
||
pb_dir="pkg/proto/$name"
|
||
triple_dir=$(resolve_triple_out_dir "$name")
|
||
has_service=false
|
||
if grep -q '^service ' "$f"; then
|
||
has_service=true
|
||
fi
|
||
|
||
if [ "$has_service" = true ]; then
|
||
if [ "$pb_dir" = "$triple_dir" ]; then
|
||
echo "📦 编译 ${name}.proto (含 service → pb + triple 同目录)"
|
||
else
|
||
echo "📦 编译 ${name}.proto (含 service → pb@${pb_dir}/, triple@${triple_dir}/)"
|
||
fi
|
||
protoc \
|
||
--proto_path=proto \
|
||
--proto_path=. \
|
||
--go_out="$pb_dir" \
|
||
--go_opt=paths=source_relative \
|
||
--go-triple_out="$triple_dir" \
|
||
--go-triple_opt=paths=source_relative \
|
||
"$name.proto"
|
||
move_triple_files "$name" "$triple_dir"
|
||
else
|
||
# 不含 service:不生成 triple.go(避免未使用 import 编译失败)
|
||
echo "📦 编译 ${name}.proto (无 service → 仅生成 pb@${pb_dir}/)"
|
||
protoc \
|
||
--proto_path=proto \
|
||
--proto_path=. \
|
||
--go_out="$pb_dir" \
|
||
--go_opt=paths=source_relative \
|
||
"$name.proto"
|
||
fi
|
||
|
||
patch_cross_package_refs "$name"
|
||
done
|
||
|
||
echo ""
|
||
echo "✅ 编译阶段完成"
|
||
echo ""
|
||
|
||
# --- 清理 protoc 临时产物 ---
|
||
echo "🔄 清理冗余文件..."
|
||
if [ -d "github.com" ]; then
|
||
rm -rf github.com
|
||
echo " ✅ github.com/ 目录已清理"
|
||
fi
|
||
|
||
for f in "${PROTO_FILES[@]}"; do
|
||
name=$(basename "$f" .proto)
|
||
pb_dir="pkg/proto/$name"
|
||
triple_dir=$(resolve_triple_out_dir "$name")
|
||
if [ -f "proto/${name}.pb.go" ]; then
|
||
rm "proto/${name}.pb.go"
|
||
echo " ✅ proto/${name}.pb.go 已清理"
|
||
fi
|
||
if [ -f "proto/${name}.triple.go" ]; then
|
||
rm "proto/${name}.triple.go"
|
||
echo " ✅ proto/${name}.triple.go 已清理"
|
||
fi
|
||
for d in "$pb_dir" "$triple_dir"; do
|
||
[ "$d" = "$pb_dir" ] && [ "$d" = "$triple_dir" ] && continue
|
||
if [ -d "$d/proto" ]; then
|
||
rm -rf "$d/proto"
|
||
echo " ✅ ${d}/proto/ 子目录已清理"
|
||
fi
|
||
done
|
||
done
|
||
|
||
echo ""
|
||
|
||
# --- 汇总输出 ---
|
||
echo "📂 生成的文件结构:"
|
||
echo " pkg/proto/"
|
||
|
||
# 收集所有出现过的目录(pb_dir + triple_dir 去重),按目录名排序
|
||
TMP_DIRS=$(mktemp)
|
||
trap 'rm -f "$TMP_DIRS"' EXIT
|
||
for f in "${PROTO_FILES[@]}"; do
|
||
name=$(basename "$f" .proto)
|
||
pb_dir="pkg/proto/$name"
|
||
triple_dir=$(resolve_triple_out_dir "$name")
|
||
echo "$pb_dir" >> "$TMP_DIRS"
|
||
echo "$triple_dir" >> "$TMP_DIRS"
|
||
done
|
||
|
||
# 按目录排序后去重,逐个打印
|
||
sort -u "$TMP_DIRS" | while read -r d; do
|
||
dir_name=$(basename "$d")
|
||
pb_files=$(find "$d" -maxdepth 1 -name "*.pb.go" 2>/dev/null | sort)
|
||
triple_files=$(find "$d" -maxdepth 1 -name "*.triple.go" 2>/dev/null | sort)
|
||
echo " ├── ${dir_name}/"
|
||
for pf in $pb_files; do
|
||
echo " │ ├── $(basename "$pf")"
|
||
done
|
||
for tf in $triple_files; do
|
||
echo " │ └── $(basename "$tf")"
|
||
done
|
||
done
|
||
|
||
GENERATED_FILES=$(find pkg/proto -name "*.pb.go" -o -name "*.triple.go" 2>/dev/null | wc -l | tr -d ' ')
|
||
|
||
echo ""
|
||
echo "======================================"
|
||
echo "✅ 所有 Proto 文件编译完成!"
|
||
echo "======================================"
|
||
echo "生成文件总数: ${GENERATED_FILES}"
|
||
echo ""
|
||
echo "提示:以后新增 proto 只需放到 proto/ 目录,无需修改本脚本。" |