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