Joe Tsai | 1905843 | 2019-02-27 21:46:29 -0800 | [diff] [blame] | 1 | // Copyright 2019 The Go Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
| 5 | //go:generate go run . -execute |
| 6 | |
| 7 | package main |
| 8 | |
| 9 | import ( |
| 10 | "flag" |
| 11 | "fmt" |
| 12 | "io/ioutil" |
| 13 | "os" |
| 14 | "os/exec" |
Joe Tsai | 1af1de0 | 2019-03-01 16:12:32 -0800 | [diff] [blame] | 15 | "path" |
Joe Tsai | 1905843 | 2019-02-27 21:46:29 -0800 | [diff] [blame] | 16 | "path/filepath" |
| 17 | "regexp" |
| 18 | "strings" |
| 19 | |
| 20 | gengogrpc "github.com/golang/protobuf/v2/cmd/protoc-gen-go-grpc/internal_gengogrpc" |
| 21 | gengo "github.com/golang/protobuf/v2/cmd/protoc-gen-go/internal_gengo" |
| 22 | "github.com/golang/protobuf/v2/protogen" |
Joe Tsai | 1af1de0 | 2019-03-01 16:12:32 -0800 | [diff] [blame] | 23 | "github.com/golang/protobuf/v2/reflect/protoreflect" |
Joe Tsai | 1905843 | 2019-02-27 21:46:29 -0800 | [diff] [blame] | 24 | ) |
| 25 | |
| 26 | func init() { |
| 27 | // When the environment variable RUN_AS_PROTOC_PLUGIN is set, |
| 28 | // we skip running main and instead act as a protoc plugin. |
| 29 | // This allows the binary to pass itself to protoc. |
| 30 | if os.Getenv("RUN_AS_PROTOC_PLUGIN") != "" { |
| 31 | protogen.Run(nil, func(gen *protogen.Plugin) error { |
| 32 | for _, file := range gen.Files { |
| 33 | if file.Generate { |
| 34 | gengo.GenerateFile(gen, file) |
| 35 | gengogrpc.GenerateFile(gen, file) |
Joe Tsai | 1af1de0 | 2019-03-01 16:12:32 -0800 | [diff] [blame] | 36 | generateDescriptorFields(gen, file) |
Joe Tsai | 1905843 | 2019-02-27 21:46:29 -0800 | [diff] [blame] | 37 | } |
| 38 | } |
| 39 | return nil |
| 40 | }) |
| 41 | os.Exit(0) |
| 42 | } |
| 43 | } |
| 44 | |
| 45 | var ( |
| 46 | run bool |
| 47 | protoRoot string |
| 48 | repoRoot string |
Joe Tsai | 1af1de0 | 2019-03-01 16:12:32 -0800 | [diff] [blame] | 49 | |
| 50 | generatedPreamble = []string{ |
| 51 | "// Copyright 2019 The Go Authors. All rights reserved.", |
| 52 | "// Use of this source code is governed by a BSD-style.", |
| 53 | "// license that can be found in the LICENSE file.", |
| 54 | "", |
| 55 | "// Code generated by generate-protos. DO NOT EDIT.", |
| 56 | "", |
| 57 | } |
Joe Tsai | 1905843 | 2019-02-27 21:46:29 -0800 | [diff] [blame] | 58 | ) |
| 59 | |
| 60 | func main() { |
| 61 | flag.BoolVar(&run, "execute", false, "Write generated files to destination.") |
| 62 | flag.StringVar(&protoRoot, "protoroot", os.Getenv("PROTOBUF_ROOT"), "The root of the protobuf source tree.") |
| 63 | flag.Parse() |
| 64 | if protoRoot == "" { |
| 65 | panic("protobuf source root is not set") |
| 66 | } |
| 67 | |
| 68 | // Determine repository root path. |
| 69 | out, err := exec.Command("git", "rev-parse", "--show-toplevel").CombinedOutput() |
| 70 | check(err) |
| 71 | repoRoot = strings.TrimSpace(string(out)) |
| 72 | |
| 73 | generateLocalProtos() |
| 74 | generateRemoteProtos() |
| 75 | } |
| 76 | |
| 77 | func generateLocalProtos() { |
| 78 | tmpDir, err := ioutil.TempDir(repoRoot, "tmp") |
| 79 | check(err) |
| 80 | defer os.RemoveAll(tmpDir) |
| 81 | |
| 82 | // Generate all local proto files (except version-locked files). |
| 83 | dirs := []struct { |
| 84 | path string |
| 85 | annotateFor map[string]bool |
| 86 | }{ |
| 87 | {"cmd/protoc-gen-go/testdata", map[string]bool{"annotations/annotations.proto": true}}, |
| 88 | {"cmd/protoc-gen-go-grpc/testdata", nil}, |
| 89 | {"internal/testprotos", nil}, |
| 90 | {"encoding/testprotos", nil}, |
| 91 | {"reflect/protoregistry/testprotos", nil}, |
| 92 | } |
| 93 | semVerRx := regexp.MustCompile(`v[0-9]+\.[0-9]+\.[0-9]+`) |
| 94 | for _, d := range dirs { |
| 95 | dstDir := filepath.Join(tmpDir, filepath.FromSlash(d.path)) |
| 96 | check(os.MkdirAll(dstDir, 0775)) |
| 97 | |
| 98 | srcDir := filepath.Join(repoRoot, filepath.FromSlash(d.path)) |
| 99 | filepath.Walk(srcDir, func(srcPath string, _ os.FileInfo, _ error) error { |
| 100 | if !strings.HasSuffix(srcPath, ".proto") || semVerRx.MatchString(srcPath) { |
| 101 | return nil |
| 102 | } |
| 103 | relPath, err := filepath.Rel(srcDir, srcPath) |
| 104 | check(err) |
| 105 | |
| 106 | // Emit a .meta file for certain files. |
| 107 | var opts string |
| 108 | if d.annotateFor[filepath.ToSlash(relPath)] { |
| 109 | opts = ",annotate_code" |
| 110 | } |
| 111 | |
| 112 | protoc("-I"+filepath.Join(protoRoot, "src"), "-I"+srcDir, "--go_out=paths=source_relative"+opts+":"+dstDir, relPath) |
| 113 | return nil |
| 114 | }) |
| 115 | } |
| 116 | |
| 117 | syncOutput(repoRoot, tmpDir) |
| 118 | } |
| 119 | |
| 120 | func generateRemoteProtos() { |
| 121 | tmpDir, err := ioutil.TempDir(repoRoot, "tmp") |
| 122 | check(err) |
| 123 | defer os.RemoveAll(tmpDir) |
| 124 | |
| 125 | // Generate all remote proto files. |
| 126 | files := []struct{ prefix, path string }{ |
| 127 | {"", "conformance/conformance.proto"}, |
| 128 | {"src", "google/protobuf/any.proto"}, |
| 129 | {"src", "google/protobuf/api.proto"}, |
| 130 | {"src", "google/protobuf/descriptor.proto"}, |
| 131 | {"src", "google/protobuf/duration.proto"}, |
| 132 | {"src", "google/protobuf/empty.proto"}, |
| 133 | {"src", "google/protobuf/field_mask.proto"}, |
| 134 | {"src", "google/protobuf/source_context.proto"}, |
| 135 | {"src", "google/protobuf/struct.proto"}, |
| 136 | {"src", "google/protobuf/timestamp.proto"}, |
| 137 | {"src", "google/protobuf/type.proto"}, |
| 138 | {"src", "google/protobuf/wrappers.proto"}, |
| 139 | {"src", "google/protobuf/compiler/plugin.proto"}, |
| 140 | } |
| 141 | for _, f := range files { |
| 142 | protoc("-I"+filepath.Join(protoRoot, f.prefix), "--go_out="+tmpDir, f.path) |
| 143 | } |
| 144 | |
| 145 | // Determine the module path. |
| 146 | cmd := exec.Command("go", "list", "-m", "-f", "{{.Path}}") |
| 147 | cmd.Dir = repoRoot |
| 148 | out, err := cmd.CombinedOutput() |
| 149 | check(err) |
| 150 | modulePath := strings.TrimSpace(string(out)) |
| 151 | |
| 152 | syncOutput(repoRoot, filepath.Join(tmpDir, modulePath)) |
| 153 | } |
| 154 | |
| 155 | func protoc(args ...string) { |
| 156 | cmd := exec.Command("protoc", "--plugin=protoc-gen-go="+os.Args[0]) |
| 157 | cmd.Args = append(cmd.Args, args...) |
| 158 | cmd.Env = append(os.Environ(), "RUN_AS_PROTOC_PLUGIN=1") |
| 159 | cmd.Env = append(cmd.Env, "PROTOC_GEN_GO_ENABLE_REFLECT=1") |
| 160 | out, err := cmd.CombinedOutput() |
| 161 | if err != nil { |
| 162 | fmt.Printf("executing: %v\n%s\n", strings.Join(cmd.Args, " "), out) |
| 163 | } |
| 164 | check(err) |
| 165 | } |
| 166 | |
Joe Tsai | 1af1de0 | 2019-03-01 16:12:32 -0800 | [diff] [blame] | 167 | // generateDescriptorFields generates an internal package for descriptor.proto. |
| 168 | func generateDescriptorFields(gen *protogen.Plugin, file *protogen.File) { |
| 169 | if file.Desc.Path() != "google/protobuf/descriptor.proto" { |
| 170 | return |
| 171 | } |
| 172 | |
| 173 | const importPath = "github.com/golang/protobuf/v2/internal/descfield" |
| 174 | g := gen.NewGeneratedFile(importPath+"/field_gen.go", importPath) |
| 175 | for _, s := range generatedPreamble { |
| 176 | g.P(s) |
| 177 | } |
| 178 | g.P("// Package descfield contains constants for field numbers in descriptor.proto.") |
| 179 | g.P("package ", path.Base(importPath)) |
| 180 | g.P("") |
| 181 | |
| 182 | var processMessages func([]*protogen.Message) |
| 183 | processMessages = func(messages []*protogen.Message) { |
| 184 | for _, message := range messages { |
| 185 | g.P("// Field numbers for ", message.Desc.FullName(), ".") |
| 186 | g.P("const (") |
| 187 | for _, field := range message.Fields { |
| 188 | fd := field.Desc |
| 189 | typeName := fd.Kind().String() |
| 190 | switch fd.Kind() { |
| 191 | case protoreflect.EnumKind: |
| 192 | typeName = string(fd.EnumType().FullName()) |
| 193 | case protoreflect.MessageKind, protoreflect.GroupKind: |
| 194 | typeName = string(fd.MessageType().FullName()) |
| 195 | } |
| 196 | g.P(message.GoIdent.GoName, "_", field.GoName, "=", fd.Number(), "// ", fd.Cardinality(), " ", typeName) |
| 197 | } |
| 198 | g.P(")") |
| 199 | processMessages(message.Messages) |
| 200 | } |
| 201 | } |
| 202 | processMessages(file.Messages) |
| 203 | } |
| 204 | |
Joe Tsai | 1905843 | 2019-02-27 21:46:29 -0800 | [diff] [blame] | 205 | func syncOutput(dstDir, srcDir string) { |
| 206 | filepath.Walk(srcDir, func(srcPath string, _ os.FileInfo, _ error) error { |
| 207 | if !strings.HasSuffix(srcPath, ".go") && !strings.HasSuffix(srcPath, ".meta") { |
| 208 | return nil |
| 209 | } |
| 210 | relPath, err := filepath.Rel(srcDir, srcPath) |
| 211 | check(err) |
| 212 | dstPath := filepath.Join(dstDir, relPath) |
| 213 | |
| 214 | if run { |
| 215 | fmt.Println("#", relPath) |
| 216 | b, err := ioutil.ReadFile(srcPath) |
| 217 | check(err) |
| 218 | check(os.MkdirAll(filepath.Dir(dstPath), 0775)) |
| 219 | check(ioutil.WriteFile(dstPath, b, 0664)) |
| 220 | } else { |
| 221 | cmd := exec.Command("diff", dstPath, srcPath, "-N", "-u") |
| 222 | cmd.Stdout = os.Stdout |
| 223 | cmd.Run() |
| 224 | } |
| 225 | return nil |
| 226 | }) |
| 227 | } |
| 228 | |
| 229 | func check(err error) { |
| 230 | if err != nil { |
| 231 | panic(err) |
| 232 | } |
| 233 | } |