gozero 拉取远程proto makefile-proto 导入外部包

120 阅读1分钟

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