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 }