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  }