blob: 869834672a6fd66a0baaac4086490d5c58150274 [file] [log] [blame]
Joe Tsai19058432019-02-27 21:46:29 -08001// 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
7package main
8
9import (
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
24func 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
42var (
43 run bool
44 protoRoot string
45 repoRoot string
46)
47
48func 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
65func 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
108func 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
143func 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
155func 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
179func check(err error) {
180 if err != nil {
181 panic(err)
182 }
183}