├── .github ├── CODEOWNERS └── workflows │ ├── docker.yaml │ └── needs-triage.yaml ├── Dockerfile ├── README.md ├── go.mod ├── main.go └── ownership.yaml /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @github/dev-frameworks 2 | -------------------------------------------------------------------------------- /.github/workflows/docker.yaml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | push: {} 8 | pull_request: {} 9 | 10 | jobs: 11 | build: 12 | name: build 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v2 17 | 18 | - name: Build Docker Image 19 | run: docker build -t proto-gen-go . 20 | -------------------------------------------------------------------------------- /.github/workflows/needs-triage.yaml: -------------------------------------------------------------------------------- 1 | name: Triage new issue 2 | on: 3 | issues: 4 | types: 5 | - opened 6 | - transferred 7 | jobs: 8 | label_issues: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | issues: write 12 | steps: 13 | - name: Label triage 14 | uses: andymckay/labeler@e6c4322d0397f3240f0e7e30a33b5c5df2d39e90 15 | with: 16 | add-labels: "needs triage, needs refinement" 17 | ignore-if-labeled: false 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # This Dockerfile produces an image that runs the protocol compiler 2 | # to generate Go declarations for messages and Twirp RPC interfaces. 3 | # 4 | # For build reproducibility, it is explicit about the versions of its 5 | # dependencies, which include: 6 | # - the golang base docker image (linux, go, git), 7 | # - protoc, 8 | # - Go packages (protoc-gen-go and protoc-gen-twirp), 9 | # - apt packages (unzip). 10 | 11 | FROM golang:1.23 12 | 13 | WORKDIR /work 14 | 15 | RUN apt-get update && \ 16 | apt-get install -y unzip=6.0-28 && \ 17 | curl --location --silent -o protoc.zip https://github.com/protocolbuffers/protobuf/releases/download/v29.3/protoc-29.3-linux-x86_64.zip && \ 18 | unzip protoc.zip -d /usr/local/ && \ 19 | rm -fr protoc.zip 20 | 21 | RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.4 && \ 22 | go install github.com/twitchtv/twirp/protoc-gen-twirp@v8.1.3+incompatible && \ 23 | go install github.com/github/twirp-ruby/protoc-gen-twirp_ruby@v1.10.0 && \ 24 | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1 25 | 26 | ENTRYPOINT ["protoc"] 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # proto-gen-go 2 | 3 | [![Docker](https://github.com/github/proto-gen-go/actions/workflows/docker.yaml/badge.svg)](https://github.com/github/proto-gen-go/actions/workflows/docker.yaml) 4 | 5 | This tool is a thin wrapper around protoc, the protocol compiler. It 6 | makes it easy to reliably generate and update Go definitions for 7 | messages and services defined in .proto files. It uses a docker 8 | container with explicitly versioned dependencies to ensure maximum 9 | reproducibility and minimum side effects. 10 | 11 | In your Go project's proto directory, add a `gen.go` file with the following contents: 12 | 13 | ```go 14 | package proto 15 | //go:generate sh -c "go run github.com/github/proto-gen-go@ [flags] [--] [protoc flags] [proto files]" 16 | ``` 17 | 18 | (The `go run module@version` command requires Go 1.23 or later.) 19 | 20 | Now, when you run `go generate` in your proto directory, the script 21 | will re-run the protocol compiler on all .proto files, and generate go 22 | files into the obvious relative locations. Commit them along with your 23 | source code. 24 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/github/proto-gen-go 2 | 3 | go 1.23 4 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // The proto-gen-go command runs an explicitly versioned protoc 2 | // command, with Go and Twirp plugins, inside a container, to generate 3 | // Go declarations for protocol messages and Twirp RPC interfaces in a 4 | // set of .proto files. Run this program manually (or via Make) after 5 | // changing your .proto files. 6 | // 7 | // Usage: 8 | // 9 | // $ go run github.com/github/proto-gen-go@ [flags] [--] [protoc-flags] [proto files] 10 | // 11 | // When invoked from build scripts, it is best to use an explicit 12 | // module version (not 'latest') to ensure build reproducibility. 13 | // All of the tool's own dependencies are explicitly versioned. 14 | // 15 | // If you add this special comment to a Go source file in your proto/ directory: 16 | // 17 | // package proto 18 | // //go:generate sh -c "go run github.com/github/proto-gen-go@ ..." 19 | // 20 | // then you'll be able to update your generated code by running this 21 | // command from the root: 22 | // 23 | // $ go generate ./proto 24 | // 25 | // All flags and arguments are passed directly to protoc. Assuming a 26 | // go:generate directive in the proto/ directory, typical arguments are: 27 | // 28 | // --proto_path=$(pwd) Root of proto import tree; absolute path recommended. 29 | // --go_out=.. Root of tree for generated files for messages. 30 | // --twirp_out=. Root of tree for generated files for Twirp services. 31 | // --go_opt=paths=source_relative Generated filenames mirror source file names. 32 | // messages.proto services.proto List of proto files. 33 | // 34 | // Protoc is quite particular about the use of absolute vs. relative 35 | // paths, which is why the example above used "sh -c", to allow 36 | // arguments to reference $(pwd). 37 | // 38 | // This program uses Docker to ensure maximum reproducibility and 39 | // minimum side effects. In particular: 40 | // - Thanks to volume mounts, the program can only change files 41 | // beneath $(pwd); changes outside this tree are not reflected 42 | // outside the container. If you want the command to write the 43 | // generated files outside the proto/ tree, you'll need to use 44 | // 'cd .. && go run ...' and adjust the flags accordingly. 45 | // - By always running protoc on Linux, we needn't worry about 46 | // downloading an appropriate executable. 47 | package main 48 | 49 | import ( 50 | "bytes" 51 | _ "embed" 52 | "flag" 53 | "fmt" 54 | "log" 55 | "os" 56 | "os/exec" 57 | "strings" 58 | ) 59 | 60 | // dockerfile contains the docker specification for our versioned dependencies 61 | //go:embed Dockerfile 62 | var dockerfile string 63 | 64 | func main() { 65 | log.SetPrefix("proto-gen-go: ") 66 | log.SetFlags(0) 67 | flag.Parse() 68 | 69 | pwd, err := os.Getwd() 70 | if err != nil { 71 | log.Fatal(err) 72 | } 73 | 74 | // Build the protoc container image specified by the Dockerfile. 75 | // The dockerized program assumes linux/amd64, and the --platform flag enables 76 | // dynamic binary translation on M1 hardware. 77 | // The docker context is empty. 78 | log.Printf("building protoc container image...") 79 | cmd := exec.Command("docker", "build", "--platform=linux/amd64", "-q", "-") 80 | cmd.Stdin = strings.NewReader(dockerfile) 81 | cmd.Stderr = os.Stderr 82 | cmd.Stdout = new(bytes.Buffer) 83 | if err := cmd.Run(); err != nil { 84 | log.Fatalf("docker build failed: %v", err) 85 | } 86 | id := strings.TrimSpace(fmt.Sprint(cmd.Stdout)) // docker image id 87 | 88 | // Log the command, neatly. 89 | protocArgs := flag.Args() 90 | cmdstr := "protoc " + strings.ReplaceAll(strings.Join(protocArgs, " "), pwd, "$(pwd)") 91 | log.Println(cmdstr) 92 | 93 | // Run protoc, in a container. 94 | // We assume pwd does not conflict with some critical part 95 | // of the docker image, and volume-mount it. 96 | cmd = exec.Command("docker", "run", "-v", pwd+":"+pwd, "--platform=linux/amd64", id) 97 | cmd.Args = append(cmd.Args, protocArgs...) 98 | cmd.Stderr = os.Stderr 99 | cmd.Stdout = os.Stderr 100 | if err := cmd.Run(); err != nil { 101 | log.Fatalf("protoc command failed: %v", err) 102 | } 103 | log.Println("done") 104 | } 105 | -------------------------------------------------------------------------------- /ownership.yaml: -------------------------------------------------------------------------------- 1 | version: 1 2 | ownership: 3 | - name: proto-gen-go 4 | description: A 'go generate' script to generate Go files from .proto interfaces 5 | kind: code 6 | tier: 2 7 | maintainer: dougnutz 8 | exec_sponsor: smeirsha 9 | team: github/dev-frameworks 10 | team_slack: dx-dev-frameworks 11 | repo: https://github.com/github/proto-gen-go 12 | qos: best_effort 13 | sev2: 14 | issue: https://github.com/github/proto-gen-go/issues 15 | tta: 1 business day 16 | sev3: 17 | issue: https://github.com/github/proto-gen-go/issues 18 | tta: 4 weeks 19 | --------------------------------------------------------------------------------