feat(goctl/rpc): support external proto imports with cross-package ty… (#5472)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
kesonan
2026-03-22 12:01:20 +08:00
committed by GitHub
parent c12c82b2f6
commit 004995f06a
93 changed files with 4871 additions and 270 deletions

4
tools/goctl/rpc/test/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
# Ignore generated output from test scripts
proto/*/output/
proto/*/output_old/
proto/*/output_new/

View File

@@ -0,0 +1,69 @@
#!/usr/bin/env bash
# Scenario 01: compare old vs new goctl output — no imports
# Usage: bash compare.sh
# Requires: go install github.com/zeromicro/go-zero/tools/goctl@latest (auto-installed)
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GOCTL_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
NEW_GOCTL="$GOCTL_ROOT/bin/goctl"
OLD_GOCTL="$(go env GOPATH)/bin/goctl"
OUT_OLD="$SCRIPT_DIR/output_old"
OUT_NEW="$SCRIPT_DIR/output_new"
verify_build() {
local dir="$1" label="$2"
echo "Verifying $label ..."
cd "$dir"
go mod tidy
if go build ./...; then
echo "$label: build passed"
else
echo "$label: build failed"
exit 1
fi
cd "$SCRIPT_DIR"
}
# Install released goctl and build local goctl
echo ">>> Installing goctl@latest ..."
go install github.com/zeromicro/go-zero/tools/goctl@latest
echo ">>> Building local goctl ..."
go build -o "$NEW_GOCTL" "$GOCTL_ROOT"
# Generate with old goctl
echo ">>> Generating with old goctl ..."
rm -rf "$OUT_OLD" && mkdir -p "$OUT_OLD/pb"
(cd "$OUT_OLD" && go mod init example.com/demo/s01_no_import > /dev/null 2>&1)
cd "$SCRIPT_DIR"
"$OLD_GOCTL" rpc protoc greeter.proto \
--go_out="$OUT_OLD/pb" \
--go-grpc_out="$OUT_OLD/pb" \
--zrpc_out="$OUT_OLD/rpc" \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
--proto_path=.
verify_build "$OUT_OLD" "old"
# Generate with new goctl
echo ">>> Generating with new goctl ..."
rm -rf "$OUT_NEW" && mkdir -p "$OUT_NEW/pb"
(cd "$OUT_NEW" && go mod init example.com/demo/s01_no_import > /dev/null 2>&1)
cd "$SCRIPT_DIR"
"$NEW_GOCTL" rpc protoc greeter.proto \
--go_out="$OUT_NEW/pb" \
--go-grpc_out="$OUT_NEW/pb" \
--zrpc_out="$OUT_NEW/rpc" \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
--proto_path=.
verify_build "$OUT_NEW" "new"
# Diff old vs new (exclude go.mod / go.sum)
echo ""
echo ">>> Diff (old vs new):"
if diff -rq --exclude="go.mod" --exclude="go.sum" "$OUT_OLD" "$OUT_NEW" > /dev/null 2>&1; then
echo " [identical] no differences between old and new output"
else
diff -r --exclude="go.mod" --exclude="go.sum" "$OUT_OLD" "$OUT_NEW" || true
fi

View File

@@ -0,0 +1,39 @@
#!/usr/bin/env bash
# Scenario 01: no imports
# Usage: bash gen.sh
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GOCTL_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
GOCTL="$GOCTL_ROOT/bin/goctl"
OUT="$SCRIPT_DIR/output"
# Build goctl from source
go build -o "$GOCTL" "$GOCTL_ROOT"
# Clean and initialize output directory
rm -rf "$OUT" && mkdir -p "$OUT/pb"
(cd "$OUT" && go mod init example.com/demo/s01_no_import > /dev/null 2>&1)
# Generate code
cd "$SCRIPT_DIR"
"$GOCTL" rpc protoc greeter.proto \
--go_out="$OUT/pb" \
--go-grpc_out="$OUT/pb" \
--zrpc_out="$OUT/rpc" \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
--proto_path=.
# Verify build
echo "Running go mod tidy..."
cd "$OUT" && go mod tidy
echo "Checking build..."
if go build ./...; then
echo "✅ Build passed"
else
echo "❌ Build failed"
exit 1
fi
echo "Done. Output: $OUT"

View File

@@ -0,0 +1,19 @@
syntax = "proto3";
// 场景01无任何 import验证基础向后兼容性
package greeter;
option go_package = "example.com/demo/greeter";
message HelloReq {
string name = 1;
}
message HelloReply {
string message = 1;
}
service Greeter {
rpc Hello(HelloReq) returns (HelloReply);
rpc SayHello(HelloReq) returns (HelloReply);
}

View File

@@ -0,0 +1,69 @@
#!/usr/bin/env bash
# Scenario 02: compare old vs new goctl output — sibling import
# Usage: bash compare.sh
# Requires: go install github.com/zeromicro/go-zero/tools/goctl@latest (auto-installed)
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GOCTL_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
NEW_GOCTL="$GOCTL_ROOT/bin/goctl"
OLD_GOCTL="$(go env GOPATH)/bin/goctl"
OUT_OLD="$SCRIPT_DIR/output_old"
OUT_NEW="$SCRIPT_DIR/output_new"
verify_build() {
local dir="$1" label="$2"
echo "Verifying $label ..."
cd "$dir"
go mod tidy
if go build ./...; then
echo "$label: build passed"
else
echo "$label: build failed"
exit 1
fi
cd "$SCRIPT_DIR"
}
# Install released goctl and build local goctl
echo ">>> Installing goctl@latest ..."
go install github.com/zeromicro/go-zero/tools/goctl@latest
echo ">>> Building local goctl ..."
go build -o "$NEW_GOCTL" "$GOCTL_ROOT"
# Generate with old goctl
echo ">>> Generating with old goctl ..."
rm -rf "$OUT_OLD" && mkdir -p "$OUT_OLD/pb"
(cd "$OUT_OLD" && go mod init example.com/demo/s02_import_sibling > /dev/null 2>&1)
cd "$SCRIPT_DIR"
"$OLD_GOCTL" rpc protoc user.proto \
--go_out="$OUT_OLD/pb" \
--go-grpc_out="$OUT_OLD/pb" \
--zrpc_out="$OUT_OLD/rpc" \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
--proto_path=.
verify_build "$OUT_OLD" "old"
# Generate with new goctl
echo ">>> Generating with new goctl ..."
rm -rf "$OUT_NEW" && mkdir -p "$OUT_NEW/pb"
(cd "$OUT_NEW" && go mod init example.com/demo/s02_import_sibling > /dev/null 2>&1)
cd "$SCRIPT_DIR"
"$NEW_GOCTL" rpc protoc user.proto \
--go_out="$OUT_NEW/pb" \
--go-grpc_out="$OUT_NEW/pb" \
--zrpc_out="$OUT_NEW/rpc" \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
--proto_path=.
verify_build "$OUT_NEW" "new"
# Diff old vs new (exclude go.mod / go.sum)
echo ""
echo ">>> Diff (old vs new):"
if diff -rq --exclude="go.mod" --exclude="go.sum" "$OUT_OLD" "$OUT_NEW" > /dev/null 2>&1; then
echo " [identical] no differences between old and new output"
else
diff -r --exclude="go.mod" --exclude="go.sum" "$OUT_OLD" "$OUT_NEW" || true
fi

View File

@@ -0,0 +1,39 @@
#!/usr/bin/env bash
# Scenario 02: sibling import
# Usage: bash gen.sh
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GOCTL_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
GOCTL="$GOCTL_ROOT/bin/goctl"
OUT="$SCRIPT_DIR/output"
# Build goctl from source
go build -o "$GOCTL" "$GOCTL_ROOT"
# Clean and initialize output directory
rm -rf "$OUT" && mkdir -p "$OUT/pb"
(cd "$OUT" && go mod init example.com/demo/s02_import_sibling > /dev/null 2>&1)
# Generate code
cd "$SCRIPT_DIR"
"$GOCTL" rpc protoc user.proto \
--go_out="$OUT/pb" \
--go-grpc_out="$OUT/pb" \
--zrpc_out="$OUT/rpc" \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
--proto_path=.
# Verify build
echo "Running go mod tidy..."
cd "$OUT" && go mod tidy
echo "Checking build..."
if go build ./...; then
echo "✅ Build passed"
else
echo "❌ Build failed"
exit 1
fi
echo "Done. Output: $OUT"

View File

@@ -0,0 +1,12 @@
syntax = "proto3";
// 场景02被 import 的共享类型定义文件
package types;
option go_package = "example.com/demo/s02_import_sibling/pb";
message User {
string id = 1;
string name = 2;
int32 age = 3;
}

View File

@@ -0,0 +1,31 @@
syntax = "proto3";
// 场景02主 protoimport 同目录下的 types.proto
// 预期goctl 应同时生成 types.pb.go 和 user.pb.go
package usersvc;
option go_package = "example.com/demo/s02_import_sibling/pb";
import "types.proto";
message GetUserReq {
string id = 1;
}
message GetUserReply {
types.User user = 1;
}
message CreateUserReq {
string name = 1;
int32 age = 2;
}
message CreateUserReply {
types.User user = 1;
}
service UserService {
rpc GetUser(GetUserReq) returns (GetUserReply);
rpc CreateUser(CreateUserReq) returns (CreateUserReply);
}

View File

@@ -0,0 +1,16 @@
syntax = "proto3";
// 场景03被 import 的子目录公共类型
package common;
option go_package = "example.com/demo/s03_import_subdir/pb/common";
message PageInfo {
int32 page = 1;
int32 size = 2;
}
message SortInfo {
string field = 1;
string order = 2;
}

View File

@@ -0,0 +1,69 @@
#!/usr/bin/env bash
# Scenario 03: compare old vs new goctl output — subdirectory import
# Usage: bash compare.sh
# Requires: go install github.com/zeromicro/go-zero/tools/goctl@latest (auto-installed)
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GOCTL_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
NEW_GOCTL="$GOCTL_ROOT/bin/goctl"
OLD_GOCTL="$(go env GOPATH)/bin/goctl"
OUT_OLD="$SCRIPT_DIR/output_old"
OUT_NEW="$SCRIPT_DIR/output_new"
verify_build() {
local dir="$1" label="$2"
echo "Verifying $label ..."
cd "$dir"
go mod tidy
if go build ./...; then
echo "$label: build passed"
else
echo "$label: build failed"
exit 1
fi
cd "$SCRIPT_DIR"
}
# Install released goctl and build local goctl
echo ">>> Installing goctl@latest ..."
go install github.com/zeromicro/go-zero/tools/goctl@latest
echo ">>> Building local goctl ..."
go build -o "$NEW_GOCTL" "$GOCTL_ROOT"
# Generate with old goctl
echo ">>> Generating with old goctl ..."
rm -rf "$OUT_OLD" && mkdir -p "$OUT_OLD/pb"
(cd "$OUT_OLD" && go mod init example.com/demo/s03_import_subdir > /dev/null 2>&1)
cd "$SCRIPT_DIR"
"$OLD_GOCTL" rpc protoc order.proto \
--go_out="$OUT_OLD/pb" \
--go-grpc_out="$OUT_OLD/pb" \
--zrpc_out="$OUT_OLD/rpc" \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
--proto_path=.
verify_build "$OUT_OLD" "old"
# Generate with new goctl
echo ">>> Generating with new goctl ..."
rm -rf "$OUT_NEW" && mkdir -p "$OUT_NEW/pb"
(cd "$OUT_NEW" && go mod init example.com/demo/s03_import_subdir > /dev/null 2>&1)
cd "$SCRIPT_DIR"
"$NEW_GOCTL" rpc protoc order.proto \
--go_out="$OUT_NEW/pb" \
--go-grpc_out="$OUT_NEW/pb" \
--zrpc_out="$OUT_NEW/rpc" \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
--proto_path=.
verify_build "$OUT_NEW" "new"
# Diff old vs new (exclude go.mod / go.sum)
echo ""
echo ">>> Diff (old vs new):"
if diff -rq --exclude="go.mod" --exclude="go.sum" "$OUT_OLD" "$OUT_NEW" > /dev/null 2>&1; then
echo " [identical] no differences between old and new output"
else
diff -r --exclude="go.mod" --exclude="go.sum" "$OUT_OLD" "$OUT_NEW" || true
fi

View File

@@ -0,0 +1,39 @@
#!/usr/bin/env bash
# Scenario 03: subdirectory import
# Usage: bash gen.sh
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GOCTL_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
GOCTL="$GOCTL_ROOT/bin/goctl"
OUT="$SCRIPT_DIR/output"
# Build goctl from source
go build -o "$GOCTL" "$GOCTL_ROOT"
# Clean and initialize output directory
rm -rf "$OUT" && mkdir -p "$OUT/pb"
(cd "$OUT" && go mod init example.com/demo/s03_import_subdir > /dev/null 2>&1)
# Generate code
cd "$SCRIPT_DIR"
"$GOCTL" rpc protoc order.proto \
--go_out="$OUT/pb" \
--go-grpc_out="$OUT/pb" \
--zrpc_out="$OUT/rpc" \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
--proto_path=.
# Verify build
echo "Running go mod tidy..."
cd "$OUT" && go mod tidy
echo "Checking build..."
if go build ./...; then
echo "✅ Build passed"
else
echo "❌ Build failed"
exit 1
fi
echo "Done. Output: $OUT"

View File

@@ -0,0 +1,37 @@
syntax = "proto3";
// 场景03import 子目录下的 proto
// 预期goctl 应同时生成 common/types.pb.go 和 order.pb.go
package ordersvc;
option go_package = "example.com/demo/s03_import_subdir/pb";
import "common/types.proto";
message OrderItem {
string id = 1;
string name = 2;
double price = 3;
}
message ListOrdersReq {
common.PageInfo page = 1;
common.SortInfo sort = 2;
}
message ListOrdersReply {
repeated OrderItem orders = 1;
}
message GetOrderReq {
string id = 1;
}
message GetOrderReply {
OrderItem order = 1;
}
service OrderService {
rpc ListOrders(ListOrdersReq) returns (ListOrdersReply);
rpc GetOrder(GetOrderReq) returns (GetOrderReply);
}

View File

@@ -0,0 +1,11 @@
syntax = "proto3";
// 场景04最底层基础类型C 层)
package base;
option go_package = "example.com/demo/s04_transitive/pb";
message BaseResp {
int32 code = 1;
string msg = 2;
}

View File

@@ -0,0 +1,69 @@
#!/usr/bin/env bash
# Scenario 04: compare old vs new goctl output — transitive imports
# Usage: bash compare.sh
# Requires: go install github.com/zeromicro/go-zero/tools/goctl@latest (auto-installed)
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GOCTL_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
NEW_GOCTL="$GOCTL_ROOT/bin/goctl"
OLD_GOCTL="$(go env GOPATH)/bin/goctl"
OUT_OLD="$SCRIPT_DIR/output_old"
OUT_NEW="$SCRIPT_DIR/output_new"
verify_build() {
local dir="$1" label="$2"
echo "Verifying $label ..."
cd "$dir"
go mod tidy
if go build ./...; then
echo "$label: build passed"
else
echo "$label: build failed"
exit 1
fi
cd "$SCRIPT_DIR"
}
# Install released goctl and build local goctl
echo ">>> Installing goctl@latest ..."
go install github.com/zeromicro/go-zero/tools/goctl@latest
echo ">>> Building local goctl ..."
go build -o "$NEW_GOCTL" "$GOCTL_ROOT"
# Generate with old goctl
echo ">>> Generating with old goctl ..."
rm -rf "$OUT_OLD" && mkdir -p "$OUT_OLD/pb"
(cd "$OUT_OLD" && go mod init example.com/demo/s04_transitive > /dev/null 2>&1)
cd "$SCRIPT_DIR"
"$OLD_GOCTL" rpc protoc main.proto \
--go_out="$OUT_OLD/pb" \
--go-grpc_out="$OUT_OLD/pb" \
--zrpc_out="$OUT_OLD/rpc" \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
--proto_path=.
verify_build "$OUT_OLD" "old"
# Generate with new goctl
echo ">>> Generating with new goctl ..."
rm -rf "$OUT_NEW" && mkdir -p "$OUT_NEW/pb"
(cd "$OUT_NEW" && go mod init example.com/demo/s04_transitive > /dev/null 2>&1)
cd "$SCRIPT_DIR"
"$NEW_GOCTL" rpc protoc main.proto \
--go_out="$OUT_NEW/pb" \
--go-grpc_out="$OUT_NEW/pb" \
--zrpc_out="$OUT_NEW/rpc" \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
--proto_path=.
verify_build "$OUT_NEW" "new"
# Diff old vs new (exclude go.mod / go.sum)
echo ""
echo ">>> Diff (old vs new):"
if diff -rq --exclude="go.mod" --exclude="go.sum" "$OUT_OLD" "$OUT_NEW" > /dev/null 2>&1; then
echo " [identical] no differences between old and new output"
else
diff -r --exclude="go.mod" --exclude="go.sum" "$OUT_OLD" "$OUT_NEW" || true
fi

View File

@@ -0,0 +1,39 @@
#!/usr/bin/env bash
# Scenario 04: transitive imports
# Usage: bash gen.sh
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GOCTL_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
GOCTL="$GOCTL_ROOT/bin/goctl"
OUT="$SCRIPT_DIR/output"
# Build goctl from source
go build -o "$GOCTL" "$GOCTL_ROOT"
# Clean and initialize output directory
rm -rf "$OUT" && mkdir -p "$OUT/pb"
(cd "$OUT" && go mod init example.com/demo/s04_transitive > /dev/null 2>&1)
# Generate code
cd "$SCRIPT_DIR"
"$GOCTL" rpc protoc main.proto \
--go_out="$OUT/pb" \
--go-grpc_out="$OUT/pb" \
--zrpc_out="$OUT/rpc" \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
--proto_path=.
# Verify build
echo "Running go mod tidy..."
cd "$OUT" && go mod tidy
echo "Checking build..."
if go build ./...; then
echo "✅ Build passed"
else
echo "❌ Build failed"
exit 1
fi
echo "Done. Output: $OUT"

View File

@@ -0,0 +1,22 @@
syntax = "proto3";
// 场景04主 protoA 层),只 import middleware.proto
// 传递依赖A → B(middleware) → C(base)
// 预期goctl 应同时生成 base.pb.go、middleware.pb.go、main.pb.go
package pingsvc;
option go_package = "example.com/demo/s04_transitive/pb";
import "middleware.proto";
message PingReq {
middleware.RequestMeta meta = 1;
}
message PingReply {
string pong = 1;
}
service PingService {
rpc Ping(PingReq) returns (PingReply);
}

View File

@@ -0,0 +1,13 @@
syntax = "proto3";
// 场景04中间层B 层import base.proto
package middleware;
option go_package = "example.com/demo/s04_transitive/pb";
import "base.proto";
message RequestMeta {
string trace_id = 1;
base.BaseResp base = 2;
}

View File

@@ -0,0 +1,71 @@
#!/usr/bin/env bash
# Scenario 05: compare old vs new goctl output — multiple services (--multiple mode)
# Usage: bash compare.sh
# Requires: go install github.com/zeromicro/go-zero/tools/goctl@latest (auto-installed)
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GOCTL_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
NEW_GOCTL="$GOCTL_ROOT/bin/goctl"
OLD_GOCTL="$(go env GOPATH)/bin/goctl"
OUT_OLD="$SCRIPT_DIR/output_old"
OUT_NEW="$SCRIPT_DIR/output_new"
verify_build() {
local dir="$1" label="$2"
echo "Verifying $label ..."
cd "$dir"
go mod tidy
if go build ./...; then
echo "$label: build passed"
else
echo "$label: build failed"
exit 1
fi
cd "$SCRIPT_DIR"
}
# Install released goctl and build local goctl
echo ">>> Installing goctl@latest ..."
go install github.com/zeromicro/go-zero/tools/goctl@latest
echo ">>> Building local goctl ..."
go build -o "$NEW_GOCTL" "$GOCTL_ROOT"
# Generate with old goctl
echo ">>> Generating with old goctl ..."
rm -rf "$OUT_OLD" && mkdir -p "$OUT_OLD/pb"
(cd "$OUT_OLD" && go mod init example.com/demo/s05_multiple > /dev/null 2>&1)
cd "$SCRIPT_DIR"
"$OLD_GOCTL" rpc protoc multi.proto \
--go_out="$OUT_OLD/pb" \
--go-grpc_out="$OUT_OLD/pb" \
--zrpc_out="$OUT_OLD/rpc" \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
--proto_path=. \
--multiple
verify_build "$OUT_OLD" "old"
# Generate with new goctl
echo ">>> Generating with new goctl ..."
rm -rf "$OUT_NEW" && mkdir -p "$OUT_NEW/pb"
(cd "$OUT_NEW" && go mod init example.com/demo/s05_multiple > /dev/null 2>&1)
cd "$SCRIPT_DIR"
"$NEW_GOCTL" rpc protoc multi.proto \
--go_out="$OUT_NEW/pb" \
--go-grpc_out="$OUT_NEW/pb" \
--zrpc_out="$OUT_NEW/rpc" \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
--proto_path=. \
--multiple
verify_build "$OUT_NEW" "new"
# Diff old vs new (exclude go.mod / go.sum)
echo ""
echo ">>> Diff (old vs new):"
if diff -rq --exclude="go.mod" --exclude="go.sum" "$OUT_OLD" "$OUT_NEW" > /dev/null 2>&1; then
echo " [identical] no differences between old and new output"
else
diff -r --exclude="go.mod" --exclude="go.sum" "$OUT_OLD" "$OUT_NEW" || true
fi

View File

@@ -0,0 +1,40 @@
#!/usr/bin/env bash
# Scenario 05: multiple services (--multiple mode)
# Usage: bash gen.sh
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GOCTL_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
GOCTL="$GOCTL_ROOT/bin/goctl"
OUT="$SCRIPT_DIR/output"
# Build goctl from source
go build -o "$GOCTL" "$GOCTL_ROOT"
# Clean and initialize output directory
rm -rf "$OUT" && mkdir -p "$OUT/pb"
(cd "$OUT" && go mod init example.com/demo/s05_multiple > /dev/null 2>&1)
# Generate code
cd "$SCRIPT_DIR"
"$GOCTL" rpc protoc multi.proto \
--go_out="$OUT/pb" \
--go-grpc_out="$OUT/pb" \
--zrpc_out="$OUT/rpc" \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
--proto_path=. \
--multiple
# Verify build
echo "Running go mod tidy..."
cd "$OUT" && go mod tidy
echo "Checking build..."
if go build ./...; then
echo "✅ Build passed"
else
echo "❌ Build failed"
exit 1
fi
echo "Done. Output: $OUT"

View File

@@ -0,0 +1,35 @@
syntax = "proto3";
// 场景05多 service + import使用 --multiple 模式
// 预期goctl --multiple 应同时生成 shared.pb.go 和 multi.pb.go
package multisvc;
option go_package = "example.com/demo/s05_multiple/pb";
import "shared.proto";
message SearchReq {
shared.Meta meta = 1;
string keyword = 2;
}
message SearchReply {
repeated string items = 1;
}
message NotifyReq {
shared.Meta meta = 1;
string message = 2;
}
message NotifyReply {
bool ok = 1;
}
service SearchService {
rpc Search(SearchReq) returns (SearchReply);
}
service NotifyService {
rpc Notify(NotifyReq) returns (NotifyReply);
}

View File

@@ -0,0 +1,11 @@
syntax = "proto3";
// 场景05多 service 场景共享的基础类型
package shared;
option go_package = "example.com/demo/s05_multiple/pb";
message Meta {
string trace_id = 1;
string version = 2;
}

View File

@@ -0,0 +1,89 @@
#!/usr/bin/env bash
# Scenario 06: compare old vs new goctl output — well-known type imports
# Usage: bash compare.sh
# Requires: go install github.com/zeromicro/go-zero/tools/goctl@latest (auto-installed)
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GOCTL_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
NEW_GOCTL="$GOCTL_ROOT/bin/goctl"
OLD_GOCTL="$(go env GOPATH)/bin/goctl"
OUT_OLD="$SCRIPT_DIR/output_old"
OUT_NEW="$SCRIPT_DIR/output_new"
verify_build() {
local dir="$1" label="$2"
echo "Verifying $label ..."
cd "$dir"
go mod tidy
if go build ./...; then
echo "$label: build passed"
else
echo "$label: build failed"
exit 1
fi
cd "$SCRIPT_DIR"
}
# Find well-known types include path
PROTOC_INCLUDE=""
PROTOC_BIN="$(which protoc 2>/dev/null || true)"
if [ -n "$PROTOC_BIN" ]; then
CANDIDATE="$(cd "$(dirname "$PROTOC_BIN")/.." && pwd)/include"
[ -f "$CANDIDATE/google/protobuf/timestamp.proto" ] && PROTOC_INCLUDE="$CANDIDATE"
fi
if [ -z "$PROTOC_INCLUDE" ]; then
for d in /opt/homebrew/include /usr/local/include; do
[ -f "$d/google/protobuf/timestamp.proto" ] && PROTOC_INCLUDE="$d" && break
done
fi
if [ -z "$PROTOC_INCLUDE" ]; then
echo "Error: cannot find google/protobuf/timestamp.proto. Install protobuf (brew install protobuf)."
exit 1
fi
echo "Well-known types: $PROTOC_INCLUDE"
# Install released goctl and build local goctl
echo ">>> Installing goctl@latest ..."
go install github.com/zeromicro/go-zero/tools/goctl@latest
echo ">>> Building local goctl ..."
go build -o "$NEW_GOCTL" "$GOCTL_ROOT"
# Generate with old goctl
echo ">>> Generating with old goctl ..."
rm -rf "$OUT_OLD" && mkdir -p "$OUT_OLD/pb"
(cd "$OUT_OLD" && go mod init example.com/demo/s06_wellknown > /dev/null 2>&1)
cd "$SCRIPT_DIR"
"$OLD_GOCTL" rpc protoc events.proto \
--go_out="$OUT_OLD/pb" \
--go-grpc_out="$OUT_OLD/pb" \
--zrpc_out="$OUT_OLD/rpc" \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
--proto_path=. \
--proto_path="$PROTOC_INCLUDE"
verify_build "$OUT_OLD" "old"
# Generate with new goctl
echo ">>> Generating with new goctl ..."
rm -rf "$OUT_NEW" && mkdir -p "$OUT_NEW/pb"
(cd "$OUT_NEW" && go mod init example.com/demo/s06_wellknown > /dev/null 2>&1)
cd "$SCRIPT_DIR"
"$NEW_GOCTL" rpc protoc events.proto \
--go_out="$OUT_NEW/pb" \
--go-grpc_out="$OUT_NEW/pb" \
--zrpc_out="$OUT_NEW/rpc" \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
--proto_path=. \
--proto_path="$PROTOC_INCLUDE"
verify_build "$OUT_NEW" "new"
# Diff old vs new (exclude go.mod / go.sum)
echo ""
echo ">>> Diff (old vs new):"
if diff -rq --exclude="go.mod" --exclude="go.sum" "$OUT_OLD" "$OUT_NEW" > /dev/null 2>&1; then
echo " [identical] no differences between old and new output"
else
diff -r --exclude="go.mod" --exclude="go.sum" "$OUT_OLD" "$OUT_NEW" || true
fi

View File

@@ -0,0 +1,37 @@
syntax = "proto3";
// 场景06import google/protobuf/timestamp.proto (well-known type)
// 预期goctl 正常运行well-known type 的 pb.go 不会被重复生成(跳过)
package eventsvc;
option go_package = "example.com/demo/eventsvc";
import "google/protobuf/timestamp.proto";
message Event {
string id = 1;
string name = 2;
google.protobuf.Timestamp created_at = 3;
}
message CreateEventReq {
string name = 1;
}
message CreateEventReply {
Event event = 1;
}
message ListEventsReq {
int32 page = 1;
int32 size = 2;
}
message ListEventsReply {
repeated Event events = 1;
}
service EventService {
rpc CreateEvent(CreateEventReq) returns (CreateEventReply);
rpc ListEvents(ListEventsReq) returns (ListEventsReply);
}

View File

@@ -0,0 +1,58 @@
#!/usr/bin/env bash
# Scenario 06: well-known type imports
# Usage: bash gen.sh
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GOCTL_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
GOCTL="$GOCTL_ROOT/bin/goctl"
OUT="$SCRIPT_DIR/output"
# Find well-known types include path
PROTOC_INCLUDE=""
PROTOC_BIN="$(which protoc 2>/dev/null || true)"
if [ -n "$PROTOC_BIN" ]; then
CANDIDATE="$(cd "$(dirname "$PROTOC_BIN")/.." && pwd)/include"
[ -f "$CANDIDATE/google/protobuf/timestamp.proto" ] && PROTOC_INCLUDE="$CANDIDATE"
fi
if [ -z "$PROTOC_INCLUDE" ]; then
for d in /opt/homebrew/include /usr/local/include; do
[ -f "$d/google/protobuf/timestamp.proto" ] && PROTOC_INCLUDE="$d" && break
done
fi
if [ -z "$PROTOC_INCLUDE" ]; then
echo "Error: cannot find google/protobuf/timestamp.proto. Install protobuf (brew install protobuf)."
exit 1
fi
echo "Well-known types: $PROTOC_INCLUDE"
# Build goctl from source
go build -o "$GOCTL" "$GOCTL_ROOT"
# Clean and initialize output directory
rm -rf "$OUT" && mkdir -p "$OUT/pb"
(cd "$OUT" && go mod init example.com/demo/s06_wellknown > /dev/null 2>&1)
# Generate code
cd "$SCRIPT_DIR"
"$GOCTL" rpc protoc events.proto \
--go_out="$OUT/pb" \
--go-grpc_out="$OUT/pb" \
--zrpc_out="$OUT/rpc" \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
--proto_path=. \
--proto_path="$PROTOC_INCLUDE"
# Verify build
echo "Running go mod tidy..."
cd "$OUT" && go mod tidy
echo "Checking build..."
if go build ./...; then
echo "✅ Build passed"
else
echo "❌ Build failed"
exit 1
fi
echo "Done. Output: $OUT"

View File

@@ -0,0 +1,78 @@
#!/usr/bin/env bash
# Scenario 07: compare old vs new goctl output — external proto, same Go package
# Usage: bash compare.sh
# Requires: go install github.com/zeromicro/go-zero/tools/goctl@latest (auto-installed)
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GOCTL_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
NEW_GOCTL="$GOCTL_ROOT/bin/goctl"
OLD_GOCTL="$(go env GOPATH)/bin/goctl"
OUT_OLD="$SCRIPT_DIR/output_old"
OUT_NEW="$SCRIPT_DIR/output_new"
verify_build() {
local dir="$1" label="$2"
echo "Verifying $label ..."
cd "$dir"
go mod tidy
if go build ./...; then
echo "$label: build passed"
else
echo "$label: build failed"
exit 1
fi
cd "$SCRIPT_DIR"
}
# Install released goctl and build local goctl
echo ">>> Installing goctl@latest ..."
go install github.com/zeromicro/go-zero/tools/goctl@latest
echo ">>> Building local goctl ..."
go build -o "$NEW_GOCTL" "$GOCTL_ROOT"
# Generate with old goctl
echo ">>> Generating with old goctl ..."
rm -rf "$OUT_OLD" && mkdir -p "$OUT_OLD/pb"
(cd "$OUT_OLD" && go mod init example.com/demo/s07_ext_same_pkg > /dev/null 2>&1)
cd "$SCRIPT_DIR"
set +e
"$OLD_GOCTL" rpc protoc service.proto \
--go_out="$OUT_OLD/pb" \
--go-grpc_out="$OUT_OLD/pb" \
--zrpc_out="$OUT_OLD/rpc" \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
--proto_path=. \
--proto_path=./ext_protos
GEN_STATUS=$?
set -e
if [ "$GEN_STATUS" -ne 0 ]; then
echo " ⚠️ old goctl does not support this feature (expected)"
else
verify_build "$OUT_OLD" "old"
fi
# Generate with new goctl
echo ">>> Generating with new goctl ..."
rm -rf "$OUT_NEW" && mkdir -p "$OUT_NEW/pb"
(cd "$OUT_NEW" && go mod init example.com/demo/s07_ext_same_pkg > /dev/null 2>&1)
cd "$SCRIPT_DIR"
"$NEW_GOCTL" rpc protoc service.proto \
--go_out="$OUT_NEW/pb" \
--go-grpc_out="$OUT_NEW/pb" \
--zrpc_out="$OUT_NEW/rpc" \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
--proto_path=. \
--proto_path=./ext_protos
verify_build "$OUT_NEW" "new"
# Diff old vs new (exclude go.mod / go.sum)
echo ""
echo ">>> Diff (old vs new):"
if diff -rq --exclude="go.mod" --exclude="go.sum" "$OUT_OLD" "$OUT_NEW" > /dev/null 2>&1; then
echo " [identical] no differences between old and new output"
else
diff -r --exclude="go.mod" --exclude="go.sum" "$OUT_OLD" "$OUT_NEW" || true
fi

View File

@@ -0,0 +1,17 @@
syntax = "proto3";
// 场景07外部目录的 proto同 pkg
// 被 service.proto 通过额外 --proto_path 引入
// go_package 与 service.proto 相同,最终合并到同一 pb 包
package ext;
option go_package = "example.com/demo/s07_ext_same_pkg/pb";
message ExtReq {
string key = 1;
}
message ExtReply {
string value = 1;
int32 code = 2;
}

View File

@@ -0,0 +1,40 @@
#!/usr/bin/env bash
# Scenario 07: external proto, same Go package
# Usage: bash gen.sh
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GOCTL_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
GOCTL="$GOCTL_ROOT/bin/goctl"
OUT="$SCRIPT_DIR/output"
# Build goctl from source
go build -o "$GOCTL" "$GOCTL_ROOT"
# Clean and initialize output directory
rm -rf "$OUT" && mkdir -p "$OUT/pb"
(cd "$OUT" && go mod init example.com/demo/s07_ext_same_pkg > /dev/null 2>&1)
# Generate code
cd "$SCRIPT_DIR"
"$GOCTL" rpc protoc service.proto \
--go_out="$OUT/pb" \
--go-grpc_out="$OUT/pb" \
--zrpc_out="$OUT/rpc" \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
--proto_path=. \
--proto_path=./ext_protos
# Verify build
echo "Running go mod tidy..."
cd "$OUT" && go mod tidy
echo "Checking build..."
if go build ./...; then
echo "✅ Build passed"
else
echo "❌ Build failed"
exit 1
fi
echo "Done. Output: $OUT"

View File

@@ -0,0 +1,14 @@
syntax = "proto3";
// 场景07外部 proto同 Go pkg—— 请求/响应直接使用 ext.ExtReq / ext.ExtReply
// 两个 proto 的 go_package 相同,最终生成到同一 Go 包pb无跨包 import。
package svc;
option go_package = "example.com/demo/s07_ext_same_pkg/pb";
import "ext.proto";
service QueryService {
rpc Query(ext.ExtReq) returns (ext.ExtReply);
}

View File

@@ -0,0 +1,78 @@
#!/usr/bin/env bash
# Scenario 08: compare old vs new goctl output — external proto, different Go package
# Usage: bash compare.sh
# Requires: go install github.com/zeromicro/go-zero/tools/goctl@latest (auto-installed)
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GOCTL_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
NEW_GOCTL="$GOCTL_ROOT/bin/goctl"
OLD_GOCTL="$(go env GOPATH)/bin/goctl"
OUT_OLD="$SCRIPT_DIR/output_old"
OUT_NEW="$SCRIPT_DIR/output_new"
verify_build() {
local dir="$1" label="$2"
echo "Verifying $label ..."
cd "$dir"
go mod tidy
if go build ./...; then
echo "$label: build passed"
else
echo "$label: build failed"
exit 1
fi
cd "$SCRIPT_DIR"
}
# Install released goctl and build local goctl
echo ">>> Installing goctl@latest ..."
go install github.com/zeromicro/go-zero/tools/goctl@latest
echo ">>> Building local goctl ..."
go build -o "$NEW_GOCTL" "$GOCTL_ROOT"
# Generate with old goctl
echo ">>> Generating with old goctl ..."
rm -rf "$OUT_OLD" && mkdir -p "$OUT_OLD/pb"
(cd "$OUT_OLD" && go mod init example.com/demo/s08_ext_diff_pkg > /dev/null 2>&1)
cd "$SCRIPT_DIR"
set +e
"$OLD_GOCTL" rpc protoc service.proto \
--go_out="$OUT_OLD/pb" \
--go-grpc_out="$OUT_OLD/pb" \
--zrpc_out="$OUT_OLD/rpc" \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
--proto_path=. \
--proto_path=./ext_protos
GEN_STATUS=$?
set -e
if [ "$GEN_STATUS" -ne 0 ]; then
echo " ⚠️ old goctl does not support this feature (expected)"
else
verify_build "$OUT_OLD" "old"
fi
# Generate with new goctl
echo ">>> Generating with new goctl ..."
rm -rf "$OUT_NEW" && mkdir -p "$OUT_NEW/pb"
(cd "$OUT_NEW" && go mod init example.com/demo/s08_ext_diff_pkg > /dev/null 2>&1)
cd "$SCRIPT_DIR"
"$NEW_GOCTL" rpc protoc service.proto \
--go_out="$OUT_NEW/pb" \
--go-grpc_out="$OUT_NEW/pb" \
--zrpc_out="$OUT_NEW/rpc" \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
--proto_path=. \
--proto_path=./ext_protos
verify_build "$OUT_NEW" "new"
# Diff old vs new (exclude go.mod / go.sum)
echo ""
echo ">>> Diff (old vs new):"
if diff -rq --exclude="go.mod" --exclude="go.sum" "$OUT_OLD" "$OUT_NEW" > /dev/null 2>&1; then
echo " [identical] no differences between old and new output"
else
diff -r --exclude="go.mod" --exclude="go.sum" "$OUT_OLD" "$OUT_NEW" || true
fi

View File

@@ -0,0 +1,18 @@
syntax = "proto3";
// 场景08外部目录的 proto不同 pkg
// 位于 ext_protos/common/ 子目录,通过 --proto_path=./ext_protos 引入
// go_package 与主 proto 不同,生成跨包 import
package common;
option go_package = "example.com/demo/s08_ext_diff_pkg/pb/common";
message ExtReq {
string key = 1;
string source = 2;
}
message ExtReply {
string value = 1;
int32 code = 2;
}

View File

@@ -0,0 +1,40 @@
#!/usr/bin/env bash
# Scenario 08: external proto, different Go package
# Usage: bash gen.sh
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GOCTL_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
GOCTL="$GOCTL_ROOT/bin/goctl"
OUT="$SCRIPT_DIR/output"
# Build goctl from source
go build -o "$GOCTL" "$GOCTL_ROOT"
# Clean and initialize output directory
rm -rf "$OUT" && mkdir -p "$OUT/pb"
(cd "$OUT" && go mod init example.com/demo/s08_ext_diff_pkg > /dev/null 2>&1)
# Generate code
cd "$SCRIPT_DIR"
"$GOCTL" rpc protoc service.proto \
--go_out="$OUT/pb" \
--go-grpc_out="$OUT/pb" \
--zrpc_out="$OUT/rpc" \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
--proto_path=. \
--proto_path=./ext_protos
# Verify build
echo "Running go mod tidy..."
cd "$OUT" && go mod tidy
echo "Checking build..."
if go build ./...; then
echo "✅ Build passed"
else
echo "❌ Build failed"
exit 1
fi
echo "Done. Output: $OUT"

View File

@@ -0,0 +1,14 @@
syntax = "proto3";
// 场景08外部 proto不同 Go pkg—— 请求/响应直接使用 common.ExtReq / common.ExtReply
// types.proto go_package 与 service.proto 不同,生成的 Go 代码需要跨包 import。
package svc;
option go_package = "example.com/demo/s08_ext_diff_pkg/pb";
import "common/types.proto";
service DataService {
rpc Fetch(common.ExtReq) returns (common.ExtReply);
}

View File

@@ -0,0 +1,96 @@
#!/usr/bin/env bash
# Scenario 09: compare old vs new goctl output — google well-known types as RPC request/response
# Usage: bash compare.sh
# Requires: go install github.com/zeromicro/go-zero/tools/goctl@latest (auto-installed)
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GOCTL_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
NEW_GOCTL="$GOCTL_ROOT/bin/goctl"
OLD_GOCTL="$(go env GOPATH)/bin/goctl"
OUT_OLD="$SCRIPT_DIR/output_old"
OUT_NEW="$SCRIPT_DIR/output_new"
verify_build() {
local dir="$1" label="$2"
echo "Verifying $label ..."
cd "$dir"
go mod tidy
if go build ./...; then
echo "$label: build passed"
else
echo "$label: build failed"
exit 1
fi
cd "$SCRIPT_DIR"
}
# Find well-known types include path
PROTOC_INCLUDE=""
PROTOC_BIN="$(which protoc 2>/dev/null || true)"
if [ -n "$PROTOC_BIN" ]; then
CANDIDATE="$(cd "$(dirname "$PROTOC_BIN")/.." && pwd)/include"
[ -f "$CANDIDATE/google/protobuf/empty.proto" ] && PROTOC_INCLUDE="$CANDIDATE"
fi
if [ -z "$PROTOC_INCLUDE" ]; then
for d in /opt/homebrew/include /usr/local/include; do
[ -f "$d/google/protobuf/empty.proto" ] && PROTOC_INCLUDE="$d" && break
done
fi
if [ -z "$PROTOC_INCLUDE" ]; then
echo "Error: cannot find google/protobuf/empty.proto. Install protobuf (brew install protobuf)."
exit 1
fi
echo "Well-known types: $PROTOC_INCLUDE"
# Install released goctl and build local goctl
echo ">>> Installing goctl@latest ..."
go install github.com/zeromicro/go-zero/tools/goctl@latest
echo ">>> Building local goctl ..."
go build -o "$NEW_GOCTL" "$GOCTL_ROOT"
# Generate with old goctl
echo ">>> Generating with old goctl ..."
rm -rf "$OUT_OLD" && mkdir -p "$OUT_OLD/pb"
(cd "$OUT_OLD" && go mod init example.com/demo/s09_google_types > /dev/null 2>&1)
cd "$SCRIPT_DIR"
set +e
"$OLD_GOCTL" rpc protoc service.proto \
--go_out="$OUT_OLD/pb" \
--go-grpc_out="$OUT_OLD/pb" \
--zrpc_out="$OUT_OLD/rpc" \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
--proto_path=. \
--proto_path="$PROTOC_INCLUDE"
GEN_STATUS=$?
set -e
if [ "$GEN_STATUS" -ne 0 ]; then
echo " ⚠️ old goctl does not support this feature (expected)"
else
verify_build "$OUT_OLD" "old"
fi
# Generate with new goctl
echo ">>> Generating with new goctl ..."
rm -rf "$OUT_NEW" && mkdir -p "$OUT_NEW/pb"
(cd "$OUT_NEW" && go mod init example.com/demo/s09_google_types > /dev/null 2>&1)
cd "$SCRIPT_DIR"
"$NEW_GOCTL" rpc protoc service.proto \
--go_out="$OUT_NEW/pb" \
--go-grpc_out="$OUT_NEW/pb" \
--zrpc_out="$OUT_NEW/rpc" \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
--proto_path=. \
--proto_path="$PROTOC_INCLUDE"
verify_build "$OUT_NEW" "new"
# Diff old vs new (exclude go.mod / go.sum)
echo ""
echo ">>> Diff (old vs new):"
if diff -rq --exclude="go.mod" --exclude="go.sum" "$OUT_OLD" "$OUT_NEW" > /dev/null 2>&1; then
echo " [identical] no differences between old and new output"
else
diff -r --exclude="go.mod" --exclude="go.sum" "$OUT_OLD" "$OUT_NEW" || true
fi

View File

@@ -0,0 +1,58 @@
#!/usr/bin/env bash
# Scenario 09: google well-known types as RPC request/response
# Usage: bash gen.sh
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GOCTL_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
GOCTL="$GOCTL_ROOT/bin/goctl"
OUT="$SCRIPT_DIR/output"
# Find well-known types include path
PROTOC_INCLUDE=""
PROTOC_BIN="$(which protoc 2>/dev/null || true)"
if [ -n "$PROTOC_BIN" ]; then
CANDIDATE="$(cd "$(dirname "$PROTOC_BIN")/.." && pwd)/include"
[ -f "$CANDIDATE/google/protobuf/empty.proto" ] && PROTOC_INCLUDE="$CANDIDATE"
fi
if [ -z "$PROTOC_INCLUDE" ]; then
for d in /opt/homebrew/include /usr/local/include; do
[ -f "$d/google/protobuf/empty.proto" ] && PROTOC_INCLUDE="$d" && break
done
fi
if [ -z "$PROTOC_INCLUDE" ]; then
echo "Error: cannot find google/protobuf/empty.proto. Install protobuf (brew install protobuf)."
exit 1
fi
echo "Well-known types: $PROTOC_INCLUDE"
# Build goctl from source
go build -o "$GOCTL" "$GOCTL_ROOT"
# Clean and initialize output directory
rm -rf "$OUT" && mkdir -p "$OUT/pb"
(cd "$OUT" && go mod init example.com/demo/s09_google_types > /dev/null 2>&1)
# Generate code
cd "$SCRIPT_DIR"
"$GOCTL" rpc protoc service.proto \
--go_out="$OUT/pb" \
--go-grpc_out="$OUT/pb" \
--zrpc_out="$OUT/rpc" \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
--proto_path=. \
--proto_path="$PROTOC_INCLUDE"
# Verify build
echo "Running go mod tidy..."
cd "$OUT" && go mod tidy
echo "Checking build..."
if go build ./...; then
echo "✅ Build passed"
else
echo "❌ Build failed"
exit 1
fi
echo "Done. Output: $OUT"

View File

@@ -0,0 +1,23 @@
syntax = "proto3";
// 场景09请求/响应直接使用 google well-known types
// google.protobuf.Empty 作为返回值(常见的 void 返回模式)
// google.protobuf.Timestamp 直接作为请求/返回类型
package healthsvc;
option go_package = "example.com/demo/s09_google_types/pb";
import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";
message HealthCheckRequest {
string service = 1;
}
service HealthService {
// Ping: 无返回数据,使用 google.protobuf.Empty
rpc Ping(HealthCheckRequest) returns (google.protobuf.Empty);
// GetTime: 直接返回时间戳类型
rpc GetTime(HealthCheckRequest) returns (google.protobuf.Timestamp);
}