-
Notifications
You must be signed in to change notification settings - Fork 9.4k
/
protobuf-compile.go
233 lines (203 loc) · 7.3 KB
/
protobuf-compile.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
// protobuf-compile is a helper tool for running protoc against all of the
// .proto files in this repository using specific versions of protoc and
// protoc-gen-go, to ensure consistent results across all development
// environments.
//
// protoc itself isn't a Go tool, so we need to use a custom strategy to
// install and run it. The official releases are built only for a subset of
// platforms that Go can potentially target, so this tool will fail if you
// are using a platform other than the ones this wrapper tool has explicit
// support for. In that case you'll need to either run this tool on a supported
// platform or to recreate what it does manually using a protoc you've built
// and installed yourself.
package main
import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"github.com/hashicorp/go-getter"
)
const protocVersion = "3.15.6"
// We also use protoc-gen-go and its grpc addon, but since these are Go tools
// in Go modules our version selection for these comes from our top-level
// go.mod, as with all other Go dependencies. If you want to switch to a newer
// version of either tool then you can upgrade their modules in the usual way.
const protocGenGoPackage = "github.com/golang/protobuf/protoc-gen-go"
const protocGenGoGrpcPackage = "google.golang.org/grpc/cmd/protoc-gen-go-grpc"
type protocStep struct {
DisplayName string
WorkDir string
Args []string
}
var protocSteps = []protocStep{
{
"tfplugin5 (provider wire protocol version 5)",
"internal/tfplugin5",
[]string{"--go_out=paths=source_relative,plugins=grpc:.", "./tfplugin5.proto"},
},
{
"tfplugin6 (provider wire protocol version 6)",
"internal/tfplugin6",
[]string{"--go_out=paths=source_relative,plugins=grpc:.", "./tfplugin6.proto"},
},
{
"tfplan (plan file serialization)",
"internal/plans/internal/planproto",
[]string{"--go_out=paths=source_relative:.", "planfile.proto"},
},
}
func main() {
if len(os.Args) != 2 {
log.Fatal("Usage: go run github.com/hashicorp/terraform/tools/protobuf-compile <basedir>")
}
baseDir := os.Args[1]
workDir := filepath.Join(baseDir, "tools/protobuf-compile/.workdir")
protocLocalDir := filepath.Join(workDir, "protoc-v"+protocVersion)
if _, err := os.Stat(protocLocalDir); os.IsNotExist(err) {
err := downloadProtoc(protocVersion, protocLocalDir)
if err != nil {
log.Fatal(err)
}
} else {
log.Printf("already have protoc v%s in %s", protocVersion, protocLocalDir)
}
protocExec := filepath.Join(protocLocalDir, "bin/protoc")
protocGenGoExec, err := buildProtocGenGo(workDir)
if err != nil {
log.Fatal(err)
}
_, err = buildProtocGenGoGrpc(workDir)
if err != nil {
log.Fatal(err)
}
protocExec, err = filepath.Abs(protocExec)
if err != nil {
log.Fatal(err)
}
protocGenGoExec, err = filepath.Abs(protocGenGoExec)
if err != nil {
log.Fatal(err)
}
protocGenGoGrpcExec, err := filepath.Abs(protocGenGoExec)
if err != nil {
log.Fatal(err)
}
// For all of our steps we'll run our localized protoc with our localized
// protoc-gen-go.
baseCmdLine := []string{protocExec, "--plugin=" + protocGenGoExec, "--plugin=" + protocGenGoGrpcExec}
for _, step := range protocSteps {
log.Printf("working on %s", step.DisplayName)
cmdLine := make([]string, 0, len(baseCmdLine)+len(step.Args))
cmdLine = append(cmdLine, baseCmdLine...)
cmdLine = append(cmdLine, step.Args...)
cmd := &exec.Cmd{
Path: cmdLine[0],
Args: cmdLine[1:],
Dir: step.WorkDir,
Env: os.Environ(),
Stdout: os.Stdout,
Stderr: os.Stderr,
}
err := cmd.Run()
if err != nil {
log.Printf("failed to compile: %s", err)
}
}
}
// downloadProtoc downloads the given version of protoc into the given
// directory.
func downloadProtoc(version string, localDir string) error {
protocURL, err := protocDownloadURL(version)
if err != nil {
return err
}
log.Printf("downloading and extracting protoc v%s from %s into %s", version, protocURL, localDir)
// For convenience, we'll be using go-getter to actually download this
// thing, so we need to turn the real URL into the funny sort of pseudo-URL
// thing that go-getter wants.
goGetterURL := protocURL + "?archive=zip"
err = getter.Get(localDir, goGetterURL)
if err != nil {
return fmt.Errorf("failed to download or extract the package: %s", err)
}
return nil
}
// buildProtocGenGo uses the Go toolchain to fetch the module containing
// protoc-gen-go and then build an executable into the working directory.
//
// If successful, it returns the location of the executable.
func buildProtocGenGo(workDir string) (string, error) {
exeSuffixRaw, err := exec.Command("go", "env", "GOEXE").Output()
if err != nil {
return "", fmt.Errorf("failed to determine executable suffix: %s", err)
}
exeSuffix := strings.TrimSpace(string(exeSuffixRaw))
exePath := filepath.Join(workDir, "protoc-gen-go"+exeSuffix)
log.Printf("building %s as %s", protocGenGoPackage, exePath)
cmd := exec.Command("go", "build", "-o", exePath, protocGenGoPackage)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return "", fmt.Errorf("failed to build %s: %s", protocGenGoPackage, err)
}
return exePath, nil
}
// buildProtocGenGoGrpc uses the Go toolchain to fetch the module containing
// protoc-gen-go-grpc and then build an executable into the working directory.
//
// If successful, it returns the location of the executable.
func buildProtocGenGoGrpc(workDir string) (string, error) {
exeSuffixRaw, err := exec.Command("go", "env", "GOEXE").Output()
if err != nil {
return "", fmt.Errorf("failed to determine executable suffix: %s", err)
}
exeSuffix := strings.TrimSpace(string(exeSuffixRaw))
exePath := filepath.Join(workDir, "protoc-gen-go-grpc"+exeSuffix)
log.Printf("building %s as %s", protocGenGoGrpcPackage, exePath)
cmd := exec.Command("go", "build", "-o", exePath, protocGenGoGrpcPackage)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return "", fmt.Errorf("failed to build %s: %s", protocGenGoGrpcPackage, err)
}
return exePath, nil
}
// protocDownloadURL returns the URL to try to download the protoc package
// for the current platform or an error if there's no known URL for the
// current platform.
func protocDownloadURL(version string) (string, error) {
platformKW := protocPlatform()
if platformKW == "" {
return "", fmt.Errorf("don't know where to find protoc for %s on %s", runtime.GOOS, runtime.GOARCH)
}
return fmt.Sprintf("https://github.com/protocolbuffers/protobuf/releases/download/v%s/protoc-%s-%s.zip", protocVersion, protocVersion, platformKW), nil
}
// protocPlatform returns the package name substring for the current platform
// in the naming convention used by official protoc packages, or an empty
// string if we don't know how protoc packaging would describe current
// platform.
func protocPlatform() string {
goPlatform := runtime.GOOS + "_" + runtime.GOARCH
switch goPlatform {
case "linux_amd64":
return "linux-x86_64"
case "linux_arm64":
return "linux-aarch_64"
case "darwin_amd64":
return "osx-x86_64"
case "darwin_arm64":
// As of 3.15.6 there isn't yet an osx-aarch_64 package available,
// so we'll install the x86_64 version and hope Rosetta can handle it.
return "osx-x86_64"
case "windows_amd64":
return "win64" // for some reason the windows packages don't have a CPU architecture part
default:
return ""
}
}