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