1// Copyright 2016 Google Inc. 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// +build ignore 16 17// Regen.go regenerates the genproto repository. 18// 19// Regen.go recursively walks through each directory named by given arguments, 20// looking for all .proto files. (Symlinks are not followed.) 21// If the pkg_prefix flag is not an empty string, 22// any proto file without `go_package` option 23// or whose option does not begin with the prefix is ignored. 24// If multiple roots contain files with the same name, 25// eg "root1/path/to/file" and "root2/path/to/file", 26// only the first file is processed; the rest are ignored. 27// Protoc is executed on remaining files, 28// one invocation per set of files declaring the same Go package. 29package main 30 31import ( 32 "flag" 33 "fmt" 34 "io/ioutil" 35 "log" 36 "os" 37 "os/exec" 38 "path/filepath" 39 "regexp" 40 "strconv" 41 "strings" 42) 43 44var goPkgOptRe = regexp.MustCompile(`(?m)^option go_package = (.*);`) 45 46func usage() { 47 fmt.Fprintln(os.Stderr, `usage: go run regen.go -go_out=path/to/output [-pkg_prefix=pkg/prefix] roots... 48 49Most users will not need to run this file directly. 50To regenerate this repository, run regen.sh instead.`) 51 flag.PrintDefaults() 52} 53 54func main() { 55 goOutDir := flag.String("go_out", "", "go_out argument to pass to protoc-gen-go") 56 pkgPrefix := flag.String("pkg_prefix", "", "only include proto files with go_package starting with this prefix") 57 flag.Usage = usage 58 flag.Parse() 59 60 if *goOutDir == "" { 61 log.Fatal("need go_out flag") 62 } 63 64 seenFiles := make(map[string]bool) 65 pkgFiles := make(map[string][]string) 66 for _, root := range flag.Args() { 67 walkFn := func(path string, info os.FileInfo, err error) error { 68 if err != nil { 69 return err 70 } 71 if !info.Mode().IsRegular() || !strings.HasSuffix(path, ".proto") { 72 return nil 73 } 74 75 switch rel, err := filepath.Rel(root, path); { 76 case err != nil: 77 return err 78 case seenFiles[rel]: 79 return nil 80 default: 81 seenFiles[rel] = true 82 } 83 84 pkg, err := goPkg(path) 85 if err != nil { 86 return err 87 } 88 pkgFiles[pkg] = append(pkgFiles[pkg], path) 89 return nil 90 } 91 if err := filepath.Walk(root, walkFn); err != nil { 92 log.Fatal(err) 93 } 94 } 95 for pkg, fnames := range pkgFiles { 96 if !strings.HasPrefix(pkg, *pkgPrefix) { 97 continue 98 } 99 if out, err := protoc(*goOutDir, flag.Args(), fnames); err != nil { 100 log.Fatalf("error executing protoc: %s\n%s", err, out) 101 } 102 } 103} 104 105// goPkg reports the import path declared in the given file's 106// `go_package` option. If the option is missing, goPkg returns empty string. 107func goPkg(fname string) (string, error) { 108 content, err := ioutil.ReadFile(fname) 109 if err != nil { 110 return "", err 111 } 112 113 var pkgName string 114 if match := goPkgOptRe.FindSubmatch(content); len(match) > 0 { 115 pn, err := strconv.Unquote(string(match[1])) 116 if err != nil { 117 return "", err 118 } 119 pkgName = pn 120 } 121 if p := strings.IndexRune(pkgName, ';'); p > 0 { 122 pkgName = pkgName[:p] 123 } 124 return pkgName, nil 125} 126 127// protoc executes the "protoc" command on files named in fnames, 128// passing go_out and include flags specified in goOut and includes respectively. 129// protoc returns combined output from stdout and stderr. 130func protoc(goOut string, includes, fnames []string) ([]byte, error) { 131 args := []string{"--go_out=plugins=grpc:" + goOut} 132 for _, inc := range includes { 133 args = append(args, "-I", inc) 134 } 135 args = append(args, fnames...) 136 return exec.Command("protoc", args...).CombinedOutput() 137} 138