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" |
| 15 | "path/filepath" |
| 16 | "regexp" |
| 17 | "strings" |
| 18 | |
| 19 | gengogrpc "github.com/golang/protobuf/v2/cmd/protoc-gen-go-grpc/internal_gengogrpc" |
| 20 | gengo "github.com/golang/protobuf/v2/cmd/protoc-gen-go/internal_gengo" |
| 21 | "github.com/golang/protobuf/v2/protogen" |
| 22 | ) |
| 23 | |
| 24 | func init() { |
| 25 | // When the environment variable RUN_AS_PROTOC_PLUGIN is set, |
| 26 | // we skip running main and instead act as a protoc plugin. |
| 27 | // This allows the binary to pass itself to protoc. |
| 28 | if os.Getenv("RUN_AS_PROTOC_PLUGIN") != "" { |
| 29 | protogen.Run(nil, func(gen *protogen.Plugin) error { |
| 30 | for _, file := range gen.Files { |
| 31 | if file.Generate { |
| 32 | gengo.GenerateFile(gen, file) |
| 33 | gengogrpc.GenerateFile(gen, file) |
| 34 | } |
| 35 | } |
| 36 | return nil |
| 37 | }) |
| 38 | os.Exit(0) |
| 39 | } |
| 40 | } |
| 41 | |
| 42 | var ( |
| 43 | run bool |
| 44 | protoRoot string |
| 45 | repoRoot string |
| 46 | ) |
| 47 | |
| 48 | func main() { |
| 49 | flag.BoolVar(&run, "execute", false, "Write generated files to destination.") |
| 50 | flag.StringVar(&protoRoot, "protoroot", os.Getenv("PROTOBUF_ROOT"), "The root of the protobuf source tree.") |
| 51 | flag.Parse() |
| 52 | if protoRoot == "" { |
| 53 | panic("protobuf source root is not set") |
| 54 | } |
| 55 | |
| 56 | // Determine repository root path. |
| 57 | out, err := exec.Command("git", "rev-parse", "--show-toplevel").CombinedOutput() |
| 58 | check(err) |
| 59 | repoRoot = strings.TrimSpace(string(out)) |
| 60 | |
| 61 | generateLocalProtos() |
| 62 | generateRemoteProtos() |
| 63 | } |
| 64 | |
| 65 | func generateLocalProtos() { |
| 66 | tmpDir, err := ioutil.TempDir(repoRoot, "tmp") |
| 67 | check(err) |
| 68 | defer os.RemoveAll(tmpDir) |
| 69 | |
| 70 | // Generate all local proto files (except version-locked files). |
| 71 | dirs := []struct { |
| 72 | path string |
| 73 | annotateFor map[string]bool |
| 74 | }{ |
| 75 | {"cmd/protoc-gen-go/testdata", map[string]bool{"annotations/annotations.proto": true}}, |
| 76 | {"cmd/protoc-gen-go-grpc/testdata", nil}, |
| 77 | {"internal/testprotos", nil}, |
| 78 | {"encoding/testprotos", nil}, |
| 79 | {"reflect/protoregistry/testprotos", nil}, |
| 80 | } |
| 81 | semVerRx := regexp.MustCompile(`v[0-9]+\.[0-9]+\.[0-9]+`) |
| 82 | for _, d := range dirs { |
| 83 | dstDir := filepath.Join(tmpDir, filepath.FromSlash(d.path)) |
| 84 | check(os.MkdirAll(dstDir, 0775)) |
| 85 | |
| 86 | srcDir := filepath.Join(repoRoot, filepath.FromSlash(d.path)) |
| 87 | filepath.Walk(srcDir, func(srcPath string, _ os.FileInfo, _ error) error { |
| 88 | if !strings.HasSuffix(srcPath, ".proto") || semVerRx.MatchString(srcPath) { |
| 89 | return nil |
| 90 | } |
| 91 | relPath, err := filepath.Rel(srcDir, srcPath) |
| 92 | check(err) |
| 93 | |
| 94 | // Emit a .meta file for certain files. |
| 95 | var opts string |
| 96 | if d.annotateFor[filepath.ToSlash(relPath)] { |
| 97 | opts = ",annotate_code" |
| 98 | } |
| 99 | |
| 100 | protoc("-I"+filepath.Join(protoRoot, "src"), "-I"+srcDir, "--go_out=paths=source_relative"+opts+":"+dstDir, relPath) |
| 101 | return nil |
| 102 | }) |
| 103 | } |
| 104 | |
| 105 | syncOutput(repoRoot, tmpDir) |
| 106 | } |
| 107 | |
| 108 | func generateRemoteProtos() { |
| 109 | tmpDir, err := ioutil.TempDir(repoRoot, "tmp") |
| 110 | check(err) |
| 111 | defer os.RemoveAll(tmpDir) |
| 112 | |
| 113 | // Generate all remote proto files. |
| 114 | files := []struct{ prefix, path string }{ |
| 115 | {"", "conformance/conformance.proto"}, |
| 116 | {"src", "google/protobuf/any.proto"}, |
| 117 | {"src", "google/protobuf/api.proto"}, |
| 118 | {"src", "google/protobuf/descriptor.proto"}, |
| 119 | {"src", "google/protobuf/duration.proto"}, |
| 120 | {"src", "google/protobuf/empty.proto"}, |
| 121 | {"src", "google/protobuf/field_mask.proto"}, |
| 122 | {"src", "google/protobuf/source_context.proto"}, |
| 123 | {"src", "google/protobuf/struct.proto"}, |
| 124 | {"src", "google/protobuf/timestamp.proto"}, |
| 125 | {"src", "google/protobuf/type.proto"}, |
| 126 | {"src", "google/protobuf/wrappers.proto"}, |
| 127 | {"src", "google/protobuf/compiler/plugin.proto"}, |
| 128 | } |
| 129 | for _, f := range files { |
| 130 | protoc("-I"+filepath.Join(protoRoot, f.prefix), "--go_out="+tmpDir, f.path) |
| 131 | } |
| 132 | |
| 133 | // Determine the module path. |
| 134 | cmd := exec.Command("go", "list", "-m", "-f", "{{.Path}}") |
| 135 | cmd.Dir = repoRoot |
| 136 | out, err := cmd.CombinedOutput() |
| 137 | check(err) |
| 138 | modulePath := strings.TrimSpace(string(out)) |
| 139 | |
| 140 | syncOutput(repoRoot, filepath.Join(tmpDir, modulePath)) |
| 141 | } |
| 142 | |
| 143 | func protoc(args ...string) { |
| 144 | cmd := exec.Command("protoc", "--plugin=protoc-gen-go="+os.Args[0]) |
| 145 | cmd.Args = append(cmd.Args, args...) |
| 146 | cmd.Env = append(os.Environ(), "RUN_AS_PROTOC_PLUGIN=1") |
| 147 | cmd.Env = append(cmd.Env, "PROTOC_GEN_GO_ENABLE_REFLECT=1") |
| 148 | out, err := cmd.CombinedOutput() |
| 149 | if err != nil { |
| 150 | fmt.Printf("executing: %v\n%s\n", strings.Join(cmd.Args, " "), out) |
| 151 | } |
| 152 | check(err) |
| 153 | } |
| 154 | |
| 155 | func syncOutput(dstDir, srcDir string) { |
| 156 | filepath.Walk(srcDir, func(srcPath string, _ os.FileInfo, _ error) error { |
| 157 | if !strings.HasSuffix(srcPath, ".go") && !strings.HasSuffix(srcPath, ".meta") { |
| 158 | return nil |
| 159 | } |
| 160 | relPath, err := filepath.Rel(srcDir, srcPath) |
| 161 | check(err) |
| 162 | dstPath := filepath.Join(dstDir, relPath) |
| 163 | |
| 164 | if run { |
| 165 | fmt.Println("#", relPath) |
| 166 | b, err := ioutil.ReadFile(srcPath) |
| 167 | check(err) |
| 168 | check(os.MkdirAll(filepath.Dir(dstPath), 0775)) |
| 169 | check(ioutil.WriteFile(dstPath, b, 0664)) |
| 170 | } else { |
| 171 | cmd := exec.Command("diff", dstPath, srcPath, "-N", "-u") |
| 172 | cmd.Stdout = os.Stdout |
| 173 | cmd.Run() |
| 174 | } |
| 175 | return nil |
| 176 | }) |
| 177 | } |
| 178 | |
| 179 | func check(err error) { |
| 180 | if err != nil { |
| 181 | panic(err) |
| 182 | } |
| 183 | } |