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