1// Copyright 2019 Google LLC 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 15package generator 16 17import ( 18 "context" 19 "errors" 20 "fmt" 21 "io/ioutil" 22 "log" 23 "os" 24 "path/filepath" 25 "regexp" 26 "strconv" 27 "strings" 28 29 "golang.org/x/sync/errgroup" 30) 31 32var goPkgOptRe = regexp.MustCompile(`(?m)^option go_package = (.*);`) 33 34// regenGenproto regenerates the genproto repository. 35// 36// regenGenproto recursively walks through each directory named by given 37// arguments, looking for all .proto files. (Symlinks are not followed.) Any 38// proto file without `go_package` option or whose option does not begin with 39// the genproto prefix is ignored. 40// 41// If multiple roots contain files with the same name, eg "root1/path/to/file" 42// and "root2/path/to/file", only the first file is processed; the rest are 43// ignored. 44// 45// Protoc is executed on remaining files, one invocation per set of files 46// declaring the same Go package. 47func regenGenproto(ctx context.Context, genprotoDir, googleapisDir, protoDir string) error { 48 log.Println("regenerating genproto") 49 50 // The protoc include directory is actually the "src" directory of the repo. 51 protoDir += "/src" 52 53 // Create space to put generated .pb.go's. 54 c := command("mkdir", "generated") 55 c.Stdout = os.Stdout 56 c.Stderr = os.Stderr 57 c.Dir = genprotoDir 58 if err := c.Run(); err != nil { 59 return err 60 } 61 62 // Record and map all .proto files to their Go packages. 63 seenFiles := make(map[string]bool) 64 pkgFiles := make(map[string][]string) 65 for _, root := range []string{googleapisDir, protoDir} { 66 walkFn := func(path string, info os.FileInfo, err error) error { 67 if err != nil { 68 return err 69 } 70 if !info.Mode().IsRegular() || !strings.HasSuffix(path, ".proto") { 71 return nil 72 } 73 74 switch rel, err := filepath.Rel(root, path); { 75 case err != nil: 76 return err 77 case seenFiles[rel]: 78 return nil 79 default: 80 seenFiles[rel] = true 81 } 82 83 pkg, err := goPkg(path) 84 if err != nil { 85 return err 86 } 87 pkgFiles[pkg] = append(pkgFiles[pkg], path) 88 return nil 89 } 90 if err := filepath.Walk(root, walkFn); err != nil { 91 return err 92 } 93 } 94 95 if len(pkgFiles) == 0 { 96 return errors.New("couldn't find any pkgfiles") 97 } 98 99 // Run protoc on all protos of all packages. 100 grp, _ := errgroup.WithContext(ctx) 101 for pkg, fnames := range pkgFiles { 102 if !strings.HasPrefix(pkg, "google.golang.org/genproto") { 103 continue 104 } 105 pk := pkg 106 fn := fnames 107 grp.Go(func() error { 108 log.Println("running protoc on", pk) 109 return protoc(genprotoDir, googleapisDir, protoDir, fn) 110 }) 111 } 112 if err := grp.Wait(); err != nil { 113 return err 114 } 115 116 // Move all generated content to their correct locations in the repository, 117 // because protoc puts it in a folder called generated/. 118 119 // The period at the end is analagous to * (copy everything in this dir). 120 c = command("cp", "-R", "generated/google.golang.org/genproto/.", ".") 121 c.Stdout = os.Stdout 122 c.Stderr = os.Stderr 123 c.Dir = genprotoDir 124 if err := c.Run(); err != nil { 125 return err 126 } 127 128 c = command("rm", "-rf", "generated") 129 c.Stdout = os.Stdout 130 c.Stderr = os.Stderr 131 c.Dir = genprotoDir 132 if err := c.Run(); err != nil { 133 return err 134 } 135 136 // Throw away changes to some special libs. 137 for _, lib := range []string{"googleapis/grafeas/v1", "googleapis/devtools/containeranalysis/v1"} { 138 c = command("git", "checkout", lib) 139 c.Stdout = os.Stdout 140 c.Stderr = os.Stderr 141 c.Dir = genprotoDir 142 if err := c.Run(); err != nil { 143 return err 144 } 145 146 c = command("git", "clean", "-df", lib) 147 c.Stdout = os.Stdout 148 c.Stderr = os.Stderr 149 c.Dir = genprotoDir 150 if err := c.Run(); err != nil { 151 return err 152 } 153 } 154 155 // Clean up and check it all compiles. 156 if err := vet(genprotoDir); err != nil { 157 return err 158 } 159 160 if err := build(genprotoDir); err != nil { 161 return err 162 } 163 164 return nil 165} 166 167// goPkg reports the import path declared in the given file's `go_package` 168// option. If the option is missing, goPkg returns empty string. 169func goPkg(fname string) (string, error) { 170 content, err := ioutil.ReadFile(fname) 171 if err != nil { 172 return "", err 173 } 174 175 var pkgName string 176 if match := goPkgOptRe.FindSubmatch(content); len(match) > 0 { 177 pn, err := strconv.Unquote(string(match[1])) 178 if err != nil { 179 return "", err 180 } 181 pkgName = pn 182 } 183 if p := strings.IndexRune(pkgName, ';'); p > 0 { 184 pkgName = pkgName[:p] 185 } 186 return pkgName, nil 187} 188 189// protoc executes the "protoc" command on files named in fnames, and outputs 190// to "<genprotoDir>/generated". 191func protoc(genprotoDir, googleapisDir, protoDir string, fnames []string) error { 192 args := []string{fmt.Sprintf("--go_out=plugins=grpc:%s/generated", genprotoDir), "-I", googleapisDir, "-I", protoDir} 193 args = append(args, fnames...) 194 c := command("protoc", args...) 195 c.Stdout = os.Stdout 196 c.Stderr = os.Stderr 197 c.Dir = genprotoDir 198 return c.Run() 199} 200