aboutsummaryrefslogtreecommitdiff
path: root/go/tools/builders/protoc.go
diff options
context:
space:
mode:
Diffstat (limited to 'go/tools/builders/protoc.go')
-rw-r--r--go/tools/builders/protoc.go219
1 files changed, 219 insertions, 0 deletions
diff --git a/go/tools/builders/protoc.go b/go/tools/builders/protoc.go
new file mode 100644
index 00000000..46a9f012
--- /dev/null
+++ b/go/tools/builders/protoc.go
@@ -0,0 +1,219 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// protoc invokes the protobuf compiler and captures the resulting .pb.go file.
+package main
+
+import (
+ "bytes"
+ "errors"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strings"
+)
+
+type genFileInfo struct {
+ base string // The basename of the path
+ path string // The full path to the final file
+ expected bool // Whether the file is expected by the rules
+ created bool // Whether the file was created by protoc
+ from *genFileInfo // The actual file protoc produced if not Path
+ unique bool // True if this base name is unique in expected results
+ ambiguious bool // True if there were more than one possible outputs that matched this file
+}
+
+func run(args []string) error {
+ // process the args
+ args, useParamFile, err := expandParamsFiles(args)
+ if err != nil {
+ return err
+ }
+ options := multiFlag{}
+ descriptors := multiFlag{}
+ expected := multiFlag{}
+ imports := multiFlag{}
+ flags := flag.NewFlagSet("protoc", flag.ExitOnError)
+ protoc := flags.String("protoc", "", "The path to the real protoc.")
+ outPath := flags.String("out_path", "", "The base output path to write to.")
+ plugin := flags.String("plugin", "", "The go plugin to use.")
+ importpath := flags.String("importpath", "", "The importpath for the generated sources.")
+ flags.Var(&options, "option", "The plugin options.")
+ flags.Var(&descriptors, "descriptor_set", "The descriptor set to read.")
+ flags.Var(&expected, "expected", "The expected output files.")
+ flags.Var(&imports, "import", "Map a proto file to an import path.")
+ if err := flags.Parse(args); err != nil {
+ return err
+ }
+
+ // Output to a temporary folder and then move the contents into place below.
+ // This is to work around long file paths on Windows.
+ tmpDir, err := ioutil.TempDir("", "go_proto")
+ if err != nil {
+ return err
+ }
+ tmpDir = abs(tmpDir) // required to work with long paths on Windows
+ absOutPath := abs(*outPath) // required to work with long paths on Windows
+ defer os.RemoveAll(tmpDir)
+
+ pluginBase := filepath.Base(*plugin)
+ pluginName := strings.TrimSuffix(
+ strings.TrimPrefix(filepath.Base(*plugin), "protoc-gen-"), ".exe")
+ for _, m := range imports {
+ options = append(options, fmt.Sprintf("M%v", m))
+ }
+ if runtime.GOOS == "windows" {
+ // Turn the plugin path into raw form, since we're handing it off to a non-go binary.
+ // This is required to work with long paths on Windows.
+ *plugin = "\\\\?\\" + abs(*plugin)
+ }
+ protoc_args := []string{
+ fmt.Sprintf("--%v_out=%v:%v", pluginName, strings.Join(options, ","), tmpDir),
+ "--plugin", fmt.Sprintf("%v=%v", strings.TrimSuffix(pluginBase, ".exe"), *plugin),
+ "--descriptor_set_in", strings.Join(descriptors, string(os.PathListSeparator)),
+ }
+ protoc_args = append(protoc_args, flags.Args()...)
+
+ var cmd *exec.Cmd
+ if useParamFile {
+ paramFile, err := ioutil.TempFile(tmpDir, "protoc-*.params")
+ if err != nil {
+ return fmt.Errorf("error creating param file for protoc: %v", err)
+ }
+ for _, arg := range protoc_args {
+ _, err := fmt.Fprintln(paramFile, arg)
+ if err != nil {
+ return fmt.Errorf("error writing param file for protoc: %v", err)
+ }
+ }
+ cmd = exec.Command(*protoc, "@"+paramFile.Name())
+ } else {
+ cmd = exec.Command(*protoc, protoc_args...)
+ }
+
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("error running protoc: %v", err)
+ }
+ // Build our file map, and test for existance
+ files := map[string]*genFileInfo{}
+ byBase := map[string]*genFileInfo{}
+ for _, path := range expected {
+ info := &genFileInfo{
+ path: path,
+ base: filepath.Base(path),
+ expected: true,
+ unique: true,
+ }
+ files[info.path] = info
+ if byBase[info.base] != nil {
+ info.unique = false
+ byBase[info.base].unique = false
+ } else {
+ byBase[info.base] = info
+ }
+ }
+ // Walk the generated files
+ filepath.Walk(tmpDir, func(path string, f os.FileInfo, err error) error {
+ relPath, err := filepath.Rel(tmpDir, path)
+ if err != nil {
+ return err
+ }
+ if relPath == "." {
+ return nil
+ }
+
+ if f.IsDir() {
+ if err := os.Mkdir(filepath.Join(absOutPath, relPath), f.Mode()); !os.IsExist(err) {
+ return err
+ }
+ return nil
+ }
+
+ if !strings.HasSuffix(path, ".go") {
+ return nil
+ }
+
+ info := &genFileInfo{
+ path: path,
+ base: filepath.Base(path),
+ created: true,
+ }
+
+ if foundInfo, ok := files[relPath]; ok {
+ foundInfo.created = true
+ foundInfo.from = info
+ return nil
+ }
+ files[relPath] = info
+ copyTo := byBase[info.base]
+ switch {
+ case copyTo == nil:
+ // Unwanted output
+ case !copyTo.unique:
+ // not unique, no copy allowed
+ case copyTo.from != nil:
+ copyTo.ambiguious = true
+ info.ambiguious = true
+ default:
+ copyTo.from = info
+ copyTo.created = true
+ info.expected = true
+ }
+ return nil
+ })
+ buf := &bytes.Buffer{}
+ for _, f := range files {
+ switch {
+ case f.expected && !f.created:
+ // Some plugins only create output files if the proto source files have
+ // have relevant definitions (e.g., services for grpc_gateway). Create
+ // trivial files that the compiler will ignore for missing outputs.
+ data := []byte("// +build ignore\n\npackage ignore")
+ if err := ioutil.WriteFile(abs(f.path), data, 0644); err != nil {
+ return err
+ }
+ case f.expected && f.ambiguious:
+ fmt.Fprintf(buf, "Ambiguious output %v.\n", f.path)
+ case f.from != nil:
+ data, err := ioutil.ReadFile(f.from.path)
+ if err != nil {
+ return err
+ }
+ if err := ioutil.WriteFile(abs(f.path), data, 0644); err != nil {
+ return err
+ }
+ case !f.expected:
+ //fmt.Fprintf(buf, "Unexpected output %v.\n", f.path)
+ }
+ if buf.Len() > 0 {
+ fmt.Fprintf(buf, "Check that the go_package option is %q.", *importpath)
+ return errors.New(buf.String())
+ }
+ }
+
+ return nil
+}
+
+func main() {
+ if err := run(os.Args[1:]); err != nil {
+ log.Fatal(err)
+ }
+}