github.com/0xKiwi/rules_go@v0.24.3/go/tools/builders/protoc.go (about)

     1  // Copyright 2017 The Bazel Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // protoc invokes the protobuf compiler and captures the resulting .pb.go file.
    16  package main
    17  
    18  import (
    19  	"bytes"
    20  	"errors"
    21  	"flag"
    22  	"fmt"
    23  	"io/ioutil"
    24  	"log"
    25  	"os"
    26  	"os/exec"
    27  	"path/filepath"
    28  	"strings"
    29  )
    30  
    31  type genFileInfo struct {
    32  	base       string       // The basename of the path
    33  	path       string       // The full path to the final file
    34  	expected   bool         // Whether the file is expected by the rules
    35  	created    bool         // Whether the file was created by protoc
    36  	from       *genFileInfo // The actual file protoc produced if not Path
    37  	unique     bool         // True if this base name is unique in expected results
    38  	ambiguious bool         // True if there were more than one possible outputs that matched this file
    39  }
    40  
    41  func run(args []string) error {
    42  	// process the args
    43  	args, err := expandParamsFiles(args)
    44  	if err != nil {
    45  		return err
    46  	}
    47  	options := multiFlag{}
    48  	descriptors := multiFlag{}
    49  	expected := multiFlag{}
    50  	imports := multiFlag{}
    51  	flags := flag.NewFlagSet("protoc", flag.ExitOnError)
    52  	protoc := flags.String("protoc", "", "The path to the real protoc.")
    53  	outPath := flags.String("out_path", "", "The base output path to write to.")
    54  	plugin := flags.String("plugin", "", "The go plugin to use.")
    55  	importpath := flags.String("importpath", "", "The importpath for the generated sources.")
    56  	flags.Var(&options, "option", "The plugin options.")
    57  	flags.Var(&descriptors, "descriptor_set", "The descriptor set to read.")
    58  	flags.Var(&expected, "expected", "The expected output files.")
    59  	flags.Var(&imports, "import", "Map a proto file to an import path.")
    60  	if err := flags.Parse(args); err != nil {
    61  		return err
    62  	}
    63  
    64  	// Output to a temporary folder and then move the contents into place below.
    65  	// This is to work around long file paths on Windows.
    66  	tmpDir, err := ioutil.TempDir("", "go_proto")
    67  	if err != nil {
    68  		return err
    69  	}
    70  	tmpDir = abs(tmpDir)        // required to work with long paths on Windows
    71  	absOutPath := abs(*outPath) // required to work with long paths on Windows
    72  	defer os.RemoveAll(tmpDir)
    73  
    74  	pluginBase := filepath.Base(*plugin)
    75  	pluginName := strings.TrimSuffix(
    76  		strings.TrimPrefix(filepath.Base(*plugin), "protoc-gen-"), ".exe")
    77  	for _, m := range imports {
    78  		options = append(options, fmt.Sprintf("M%v", m))
    79  	}
    80  	protoc_args := []string{
    81  		fmt.Sprintf("--%v_out=%v:%v", pluginName, strings.Join(options, ","), tmpDir),
    82  		"--plugin", fmt.Sprintf("%v=%v", strings.TrimSuffix(pluginBase, ".exe"), *plugin),
    83  		"--descriptor_set_in", strings.Join(descriptors, string(os.PathListSeparator)),
    84  	}
    85  	protoc_args = append(protoc_args, flags.Args()...)
    86  	cmd := exec.Command(*protoc, protoc_args...)
    87  	cmd.Stdout = os.Stdout
    88  	cmd.Stderr = os.Stderr
    89  	if err := cmd.Run(); err != nil {
    90  		return fmt.Errorf("error running protoc: %v", err)
    91  	}
    92  	// Build our file map, and test for existance
    93  	files := map[string]*genFileInfo{}
    94  	byBase := map[string]*genFileInfo{}
    95  	for _, path := range expected {
    96  		info := &genFileInfo{
    97  			path:     path,
    98  			base:     filepath.Base(path),
    99  			expected: true,
   100  			unique:   true,
   101  		}
   102  		files[info.path] = info
   103  		if byBase[info.base] != nil {
   104  			info.unique = false
   105  			byBase[info.base].unique = false
   106  		} else {
   107  			byBase[info.base] = info
   108  		}
   109  	}
   110  	// Walk the generated files
   111  	filepath.Walk(tmpDir, func(path string, f os.FileInfo, err error) error {
   112  		relPath, err := filepath.Rel(tmpDir, path)
   113  		if err != nil {
   114  			return err
   115  		}
   116  		if relPath == "." {
   117  			return nil
   118  		}
   119  
   120  		if f.IsDir() {
   121  			if err := os.Mkdir(filepath.Join(absOutPath, relPath), f.Mode()); !os.IsExist(err) {
   122  				return err
   123  			}
   124  			return nil
   125  		}
   126  
   127  		if !strings.HasSuffix(path, ".go") {
   128  			return nil
   129  		}
   130  
   131  		info := &genFileInfo{
   132  			path:    path,
   133  			base:    filepath.Base(path),
   134  			created: true,
   135  		}
   136  
   137  		if foundInfo, ok := files[relPath]; ok {
   138  			foundInfo.created = true
   139  			foundInfo.from = info
   140  			return nil
   141  		}
   142  		files[relPath] = info
   143  		copyTo := byBase[info.base]
   144  		switch {
   145  		case copyTo == nil:
   146  			// Unwanted output
   147  		case !copyTo.unique:
   148  			// not unique, no copy allowed
   149  		case copyTo.from != nil:
   150  			copyTo.ambiguious = true
   151  			info.ambiguious = true
   152  		default:
   153  			copyTo.from = info
   154  			copyTo.created = true
   155  			info.expected = true
   156  		}
   157  		return nil
   158  	})
   159  	buf := &bytes.Buffer{}
   160  	for _, f := range files {
   161  		switch {
   162  		case f.expected && !f.created:
   163  			// Some plugins only create output files if the proto source files have
   164  			// have relevant definitions (e.g., services for grpc_gateway). Create
   165  			// trivial files that the compiler will ignore for missing outputs.
   166  			data := []byte("// +build ignore\n\npackage ignore")
   167  			if err := ioutil.WriteFile(abs(f.path), data, 0644); err != nil {
   168  				return err
   169  			}
   170  		case f.expected && f.ambiguious:
   171  			fmt.Fprintf(buf, "Ambiguious output %v.\n", f.path)
   172  		case f.from != nil:
   173  			data, err := ioutil.ReadFile(f.from.path)
   174  			if err != nil {
   175  				return err
   176  			}
   177  			if err := ioutil.WriteFile(abs(f.path), data, 0644); err != nil {
   178  				return err
   179  			}
   180  		case !f.expected:
   181  			//fmt.Fprintf(buf, "Unexpected output %v.\n", f.path)
   182  		}
   183  		if buf.Len() > 0 {
   184  			fmt.Fprintf(buf, "Check that the go_package option is %q.", *importpath)
   185  			return errors.New(buf.String())
   186  		}
   187  	}
   188  
   189  	return nil
   190  }
   191  
   192  func main() {
   193  	if err := run(os.Args[1:]); err != nil {
   194  		log.Fatal(err)
   195  	}
   196  }