gozero 拉取远程proto makefile
# =============================================================================
# rpc/api:goctl 仅用 --remote/--branch 拉模板。zRPC 用 --name-from-filename(入口 = APP)。
# PROTO_DIR:go list -m,否则 $(PROTO_LOCAL_ROOT)。
# =============================================================================
GOHOSTOS := $(shell go env GOHOSTOS)
# Proto(换项目改)
PROTO_REPO ?= gitlab.okok.com/public-project/ark-biz-proto
PROTO_PATH ?= product/v1
PROTO_FILE ?= ark_biz_product.proto
PROTO_BRANCH ?= prod
# =============================================================================
# 拉取common 库
PLATFORM_COMMON_REPO ?= gitlab.okok.com/public-project/platform-common
PLATFORM_COMMON_BRANCH ?= prod
# =============================================================================
# 应用/二进制名 = 主 proto 文件名(无扩展名);覆盖:make APP=xxx
APP ?= $(basename $(PROTO_FILE))
REPO_DIRNAME := $(notdir $(abspath $(CURDIR)))
GO_MOD_BOOT ?= gitlab.okok.com/public-project/$(REPO_DIRNAME)
PROTO_LOCAL_ROOT ?= $(abspath $(CURDIR)/../ark-biz-proto)
MODULE_NAME = $(shell grep '^module' go.mod 2>/dev/null | head -1 | awk '{print $$2}')
PROTO_DIR_GO := $(shell go list -m -f '{{.Dir}}' $(PROTO_REPO) 2>/dev/null)
PROTO_DIR = $(if $(strip $(PROTO_DIR_GO)),$(PROTO_DIR_GO),$(PROTO_LOCAL_ROOT))
IMPORT_PATH := $(PROTO_REPO)/$(PROTO_PATH)
LOCAL_IMPORT_PATH = $(MODULE_NAME)/rpc/$(PROTO_PATH)
PROTO_ABS = $(PROTO_DIR)/$(PROTO_PATH)/$(PROTO_FILE)
BUILD_DIR ?= ./deploy
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "0.0.0-dev")
GIT_COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
BUILD_TIME := $(shell date '+%Y-%m-%d %H:%M:%S')
GOCTL_TEMPLATE_REPO ?= git@gitlab.okok.com:public-project/platform-gozero-template.git
GOCTL_TEMPLATE_BRANCH ?= prod
API_SPEC ?= api/$(APP).api
API_GEN_DIR ?= .
# --- 公司级 Cursor 规则(克隆 go-zero-project-guidelines → 覆盖 .cursor)暂行关闭;恢复时取消下行注释并在 cursor-update 中取消对应步骤注释 ---
# RULES_REPO ?= git@gitlab.okok.com:go-project/ark-project/global/common-project/go-zero-project-guidelines.git
# RULES_BRANCH ?= main
# RULES_TEMP_DIR := .tmp-rules
CURSOR_TARGET := .cursor
# ai-context → submodule;GitHub 慢/超时可:make cursor-update AI_CONTEXT_REPO=https://ghproxy.com/https://github.com/zeromicro/ai-context.git
AI_CONTEXT_REPO ?= https://github.com/zeromicro/ai-context.git
CURSORRULES_DIR ?= .cursorrules
BLUE := \033[0;34m
GREEN := \033[0;32m
YELLOW := \033[1;33m
RED := \033[0;31m
CYAN := \033[0;36m
NC := \033[0m
# macOS BSD sed / 其他 GNU sed
ifeq ($(GOHOSTOS),darwin)
SED_INPLACE := sed -i ''
else
SED_INPLACE := sed -i
endif
FIND_GO := find . -name '*.go' -type f -not -path './vendor/*' -not -path './.git/*'
define BANNER
@echo "$(BLUE)╔══════════════════════════════════════════════════════════════════╗$(NC)"
@echo "$(BLUE)║ $(1)$(NC)"
@echo "$(BLUE)╚══════════════════════════════════════════════════════════════════╝$(NC)"
endef
# go mod tidy 会删掉「尚无 Go import」的 proto require;补回后 go list / replace 才一致
define REAPPLY_PROTO_MOD
@if [ -d "$(PROTO_LOCAL_ROOT)" ]; then \
go mod edit -require=$(PROTO_REPO)@v0.0.0; \
go mod edit -replace=$(PROTO_REPO)=$(PROTO_LOCAL_ROOT); \
fi
endef
.PHONY: help all init import rpc api vendor tidy build run clean version \
cursor-update cursor-info cursor-clean fmt vet
.DEFAULT_GOAL := help
all: init rpc build ## 初始化工具 + rpc + 编译
##@ 帮助信息
help: ## 显示帮助
$(call BANNER,Makefile · $(APP))
@awk 'BEGIN {FS = ":.*##"; printf "使用方法: make $(GREEN)<target>$(NC)\n\n"} \
/^[a-zA-Z_-]+:.*?##/ { printf " $(GREEN)%-25s$(NC) %s\n", $$1, $$2 } \
/^##@/ { printf "\n$(YELLOW)%s$(NC)\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
##@ 环境与代码生成
init: ## 安装工具 + go.mod(缺则 init)+ 本地 proto require/replace(目录存在时)
$(call BANNER,初始化)
@go install github.com/zeromicro/go-zero/tools/goctl@v1.10.1
@go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.10
@go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1
@if [ ! -f go.mod ]; then \
echo "$(BLUE)>>>$(NC) go mod init $(GO_MOD_BOOT)"; \
go mod init "$(GO_MOD_BOOT)"; \
fi
@if [ -d "$(PROTO_LOCAL_ROOT)" ]; then \
echo "$(BLUE)>>>$(NC) go mod require+replace $(PROTO_REPO) → $(PROTO_LOCAL_ROOT)"; \
go mod edit -require=$(PROTO_REPO)@v0.0.0; \
go mod edit -replace=$(PROTO_REPO)=$(PROTO_LOCAL_ROOT); \
else \
echo "$(YELLOW)!!$(NC) 无 $(PROTO_LOCAL_ROOT),未配置 replace;克隆 proto 或设置 $(CYAN)PROTO_LOCAL_ROOT$(NC)"; \
fi
@echo "$(GREEN)✓$(NC) init 完成(未跑 tidy;需要时再 $(CYAN)make vendor$(NC))"
# import.go:业务 pb + 平台 Go 库;tidy 后删;占位符仍靠 rpc 里 sed
import: ## 拉取依赖(首次/CI:make import PROTO_BRANCH=prod)
@test -f go.mod || { echo "$(RED)✗$(NC) 无 go.mod,请先 $(CYAN)make init$(NC)"; exit 1; }
@printf 'package main\n\nimport (\n\t_ "$(IMPORT_PATH)"\n\t_ "$(PLATFORM_COMMON_REPO)"\n)\n' > import.go
@go get $(PROTO_REPO)@$(PROTO_BRANCH)
@go get $(PLATFORM_COMMON_REPO)@$(PLATFORM_COMMON_BRANCH)
@go mod tidy -v
@rm -f import.go
@echo "$(GREEN)✓$(NC) import 完成(已 tidy,import.go 已删)"
rpc: ## goctl 生成 zRPC(--remote 模板;多 service 时 -m)
@test -f go.mod || { echo "$(RED)✗$(NC) 无 go.mod,请先 $(CYAN)make init$(NC)"; exit 1; }
@if [ -z "$(strip $(MODULE_NAME))" ]; then \
echo "$(RED)✗$(NC) go.mod 无 module 行"; exit 1; \
fi
@if [ -z "$(PROTO_DIR)" ] || [ ! -d "$(PROTO_DIR)" ]; then \
echo "$(RED)✗$(NC) 无 proto 根目录($(PROTO_DIR))— $(CYAN)make init$(NC) 且保证 $(PROTO_LOCAL_ROOT) 存在"; exit 1; \
fi
@echo "$(GREEN)✓$(NC) $(PROTO_DIR) → $(PROTO_PATH)/$(PROTO_FILE)"
@TMP_OUT=$$(mktemp -d) && \
goctl rpc protoc $(PROTO_ABS) \
--proto_path=$(PROTO_DIR) \
--go_out=$$TMP_OUT \
--go-grpc_out=$$TMP_OUT \
--zrpc_out=. \
-m --module=$(MODULE_NAME) \
--name-from-filename \
--remote=$(GOCTL_TEMPLATE_REPO) \
--branch=$(GOCTL_TEMPLATE_BRANCH) \
--client=false \
--style=go_zero \
|| { echo "$(RED)✗$(NC) goctl rpc 失败(远程模板 $(GOCTL_TEMPLATE_REPO) @ $(GOCTL_TEMPLATE_BRANCH))"; exit 1; } \
&& \
rm -rf $$TMP_OUT rpc && \
$(FIND_GO) -exec $(SED_INPLACE) \
-e 's|$(LOCAL_IMPORT_PATH)|$(IMPORT_PATH)|g' \
-e 's|"$(MODULE_NAME)/var/folders/[^"]*|"$(IMPORT_PATH)|g' \
-e 's|"$(MODULE_NAME)/tmp/[^"]*|"$(IMPORT_PATH)|g' \
-e 's|"/tmp/[^"]*|"$(IMPORT_PATH)|g' \
-e 's|"[^"]*/tmp/tmp\.[^"]*|"$(IMPORT_PATH)|g' \
-e 's|__BIZ_PROTO_IMPORT__|$(PROTO_REPO)|g' \
-e 's|__PLATFORM_COMMON_IMPORT__|$(PLATFORM_COMMON_REPO)|g' \
{} + 2>/dev/null || true; \
go fmt ./...
@go mod tidy -v
$(REAPPLY_PROTO_MOD)
@echo "$(GREEN)✓$(NC) rpc — $(IMPORT_PATH)"
api: ## goctl 生成 HTTP API(--remote 模板)
@test -f "$(API_SPEC)" || { echo "$(RED)✗$(NC) 无 $(API_SPEC)"; exit 1; }
@echo "$(GREEN)✓$(NC) $(API_SPEC)"
@goctl api go --api "$(API_SPEC)" --dir "$(API_GEN_DIR)" \
--remote=$(GOCTL_TEMPLATE_REPO) \
--branch=$(GOCTL_TEMPLATE_BRANCH) \
--style go_zero \
|| { echo "$(RED)✗$(NC) goctl api 失败(远程模板 $(GOCTL_TEMPLATE_REPO) @ $(GOCTL_TEMPLATE_BRANCH))"; exit 1; }
@go mod tidy && go fmt ./...
$(REAPPLY_PROTO_MOD)
@echo "$(GREEN)✓$(NC) api 完成"
##@ 代码质量
fmt: ## go fmt ./...
@go fmt ./... && echo "$(GREEN)✓$(NC) fmt 完成"
vet: ## go vet ./...
@go vet ./... && echo "$(GREEN)✓$(NC) vet 完成"
##@ 依赖与构建
vendor: ## go mod tidy -v(tidy 后自动补回 proto require+replace)
@go mod tidy -v
$(REAPPLY_PROTO_MOD)
@echo "$(GREEN)✓$(NC) vendor / tidy 完成"
tidy: vendor ## 同 vendor(go mod tidy + 补 proto replace)
build: vendor ## 编译 → $(BUILD_DIR)/$(APP)
$(call BANNER,构建 $(APP))
@mkdir -p $(BUILD_DIR)
@go build -o $(BUILD_DIR)/$(APP) .
@echo "$(GREEN)✓$(NC) $(BUILD_DIR)/$(APP)"
run: ## 运行 RPC(main 默认 -f)
@go run .
clean: ## 删除 $(BUILD_DIR)
@rm -rf $(BUILD_DIR) && echo "$(GREEN)✓$(NC) clean 完成"
##@ 版本
version: ## 版本信息
$(call BANNER,版本信息)
@echo " $(GREEN)项目$(NC) $(APP)"
@echo " $(GREEN)版本$(NC) $(VERSION)"
@echo " $(GREEN)Git$(NC) $(GIT_COMMIT)"
@echo " $(GREEN)构建时间$(NC) $(BUILD_TIME)"
@echo " $(GREEN)Go$(NC) $(shell go version)"
##@ Cursor
cursor-update: ## ai-context submodule → $(CURSORRULES_DIR)(公司 guidelines → .cursor 已关闭,见 Makefile 顶部注释)
# 公司级 .cursor 同步(暂行关闭):
# @rm -rf $(RULES_TEMP_DIR) && \
# git clone --depth 1 --branch $(RULES_BRANCH) $(RULES_REPO) $(RULES_TEMP_DIR) 2>/dev/null \
# || { echo "$(RED)[ERROR]$(NC) 克隆失败"; exit 1; }
# @test -d "$(RULES_TEMP_DIR)/.cursor" || { echo "$(RED)[ERROR]$(NC) 无 .cursor"; rm -rf $(RULES_TEMP_DIR); exit 1; }
# @rm -rf $(CURSOR_TARGET) && cp -r $(RULES_TEMP_DIR)/.cursor $(CURSOR_TARGET) && rm -rf $(RULES_TEMP_DIR)
@echo "$(BLUE)>>>$(NC) git submodule $(AI_CONTEXT_REPO) → $(CURSORRULES_DIR)"
@if ! git rev-parse --git-dir >/dev/null 2>&1; then \
echo "$(YELLOW)!!$(NC) 非 git 仓库,跳过 ai-context submodule"; \
elif [ -f .gitmodules ] && git config -f .gitmodules --get-regexp path 2>/dev/null \
| awk -v p="$(CURSORRULES_DIR)" '$$2 == p { f=1 } END { exit !f }'; then \
git submodule update --init --remote --depth 1 "$(CURSORRULES_DIR)"; \
echo "$(GREEN)✓$(NC) $(CURSORRULES_DIR) submodule update --remote"; \
elif [ -e "$(CURSORRULES_DIR)" ]; then \
echo "$(RED)✗$(NC) 已存在 $(CURSORRULES_DIR) 且未登记为 submodule"; exit 1; \
else \
git submodule add --depth 1 "$(AI_CONTEXT_REPO)" "$(CURSORRULES_DIR)"; \
git submodule update --init --remote --depth 1 "$(CURSORRULES_DIR)"; \
echo "$(GREEN)✓$(NC) $(CURSORRULES_DIR) submodule add + update --remote"; \
fi
@echo "$(GREEN)[SUCCESS]$(NC) cursor-update 完成"
cursor-info: ## .cursor 是否已安装
@if [ -d "$(CURSOR_TARGET)" ]; then \
echo "$(GREEN)状态:$(NC) 已安装"; \
find $(CURSOR_TARGET) -name "RULE.md" -type f | sort | while read f; do echo " - $$f"; done; \
else echo "$(YELLOW)状态:$(NC) 未安装 — $(CYAN)make cursor-update$(NC)"; fi
cursor-clean: ## 删除 .cursor 与历史临时目录 .tmp-rules
@rm -rf $(CURSOR_TARGET) .tmp-rules && echo "$(GREEN)[SUCCESS]$(NC) 已清理"
项目结构,idl 放在项目内部的这种
/apps/www/go/src/newstart
├── idl
│ ├── common
│ │ └── base.proto
│ ├── order
│ │ └── v1
│ │ └── order.proto
│ └── product
│ └── v1
│ └── product.proto
└── internal
base.proto 定义
package base;
// 确保 go_package 路径能正确映射到生成的 Go 代码
option go_package = "newstart/idl/common;v1";
message BaseModel {
int32 baseCode = 1;
string baseMsg = 2;
}
order.proto 定义
package order;
option go_package="idl/order;v1";
import "common/base.proto";
message Request {
string ping = 1;
}
message Response {
string pong = 1;
base.BaseModel base = 2; // 引入公共基础模型
}
service Order {
rpc Ping(Request) returns(Response);
}
protoc 命令
VERSION=$(shell git describe --tags --always)
GOHOSTOS:=$(shell go env GOHOSTOS)
ifeq ($(GOHOSTOS), windows)
Git_Bash=$(subst \,/,$(subst cmd\,bin\bash.exe,$(dir $(shell where git))))
INTERNAL_PROTO_FILES=$(shell $(Git_Bash) -c "find internal -name *.proto 2>/dev/null || true")
API_PROTO_FILES=$(shell $(Git_Bash) -c "find idl -name *.proto 2>/dev/null || true")
else
INTERNAL_PROTO_FILES=$(shell find internal -name *.proto 2>/dev/null || true)
API_PROTO_FILES=$(shell find idl -name *.proto 2>/dev/null || true)
endif
.PHONY: kratos_api
kratos_api:
protoc --proto_path=./idl \
--proto_path=./third_party \
--go_out=Morder/v1/order.proto=./idl/order/v1,paths=source_relative:./idl \
--go-http_out=Morder/v1/order.proto=./idl/order/v1,paths=source_relative:./idl \
--go-grpc_out=Morder/v1/order.proto=./idl/order/v1,paths=source_relative:./idl \
--openapi_out=fq_schema_naming=true,default_response=false:. \
$(API_PROTO_FILES)
如果外部多项目共用common
需要在 common/base.proto 里设置全局唯一的 go_package。
如果是基于buf 管理
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@latest
buf.gen.yaml
managed:
enabled: true
override:
- file_option: go_package
value: newstart/rpc/order/v1;v1
- file_option: go_package
value: newstart/rpc/common;v1
disable:
- file_option: go_package
module: buf.build/bufbuild/protovalidate
- file_option: go_package
module: buf.build/googleapis/googleapis
plugins:
- local: protoc-gen-go
out: rpc
opt:
- paths=source_relative
- Morder/v1/order.proto=./idl/order/v1
- local: protoc-gen-go-grpc
out: rpc
opt:
- paths=source_relative
- Morder/v1/order.proto=./idl/order/v1
inputs:
- directory: ./idl
- directory: ./third_party
buf generate
go-zero的goctl是不支持外部proto导入的,如何即想用goctl又想使用buf导入 在当前项目gen目录创建项目proto,比如项目就叫newstart,那就是newstart.proto
package newstart;
option go_package="./newstart/v1";
message Request {
string ping = 1;
}
message Response {
string pong = 1;
}
service NewStart {
rpc Ping(Request) returns(Response);
}
然后
# generate rpc code
rpc_test:
goctl rpc protoc newstart.proto --go_out=./rpc --go-grpc_out=./rpc --zrpc_out=.
@rm -rf rpc
.PHONY: all
# generate all
all:
make rpc_test
buf generate