blob: 2c28a7ab86100bf37fbc93bdebc87c4ad15fdb48 [file] [log] [blame]
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package generator
import (
"context"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"golang.org/x/sync/errgroup"
)
var goPkgOptRe = regexp.MustCompile(`(?m)^option go_package = (.*);`)
// denylist is a set of clients to NOT generate.
var denylist = map[string]bool{
// TODO(codyoss): re-enable after issue is resolve -- https://github.com/googleapis/go-genproto/issues/357
"google.golang.org/genproto/googleapis/cloud/recommendationengine/v1beta1": true,
// These two container APIs are currently frozen. They should not be updated
// due to manual layer built on top of them.
"google.golang.org/genproto/googleapis/grafeas/v1": true,
"google.golang.org/genproto/googleapis/devtools/containeranalysis/v1": true,
}
// regenGenproto regenerates the genproto repository.
//
// regenGenproto recursively walks through each directory named by given
// arguments, looking for all .proto files. (Symlinks are not followed.) Any
// proto file without `go_package` option or whose option does not begin with
// the genproto prefix is ignored.
//
// If multiple roots contain files with the same name, eg "root1/path/to/file"
// and "root2/path/to/file", only the first file is processed; the rest are
// ignored.
//
// Protoc is executed on remaining files, one invocation per set of files
// declaring the same Go package.
func regenGenproto(ctx context.Context, genprotoDir, googleapisDir, protoDir string) error {
log.Println("regenerating genproto")
// The protoc include directory is actually the "src" directory of the repo.
protoDir += "/src"
// Create space to put generated .pb.go's.
c := command("mkdir", "generated")
c.Stdout = os.Stdout
c.Stderr = os.Stderr
c.Dir = genprotoDir
if err := c.Run(); err != nil {
return err
}
// Record and map all .proto files to their Go packages.
seenFiles := make(map[string]bool)
pkgFiles := make(map[string][]string)
for _, root := range []string{googleapisDir} {
walkFn := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.Mode().IsRegular() || !strings.HasSuffix(path, ".proto") {
return nil
}
switch rel, err := filepath.Rel(root, path); {
case err != nil:
return err
case seenFiles[rel]:
return nil
default:
seenFiles[rel] = true
}
pkg, err := goPkg(path)
if err != nil {
return err
}
pkgFiles[pkg] = append(pkgFiles[pkg], path)
return nil
}
if err := filepath.Walk(root, walkFn); err != nil {
return err
}
}
if len(pkgFiles) == 0 {
return errors.New("couldn't find any pkgfiles")
}
// Run protoc on all protos of all packages.
grp, _ := errgroup.WithContext(ctx)
for pkg, fnames := range pkgFiles {
if !strings.HasPrefix(pkg, "google.golang.org/genproto") || denylist[pkg] {
continue
}
pk := pkg
fn := fnames
grp.Go(func() error {
log.Println("running protoc on", pk)
return protoc(genprotoDir, googleapisDir, protoDir, fn)
})
}
if err := grp.Wait(); err != nil {
return err
}
// Move all generated content to their correct locations in the repository,
// because protoc puts it in a folder called generated/.
// The period at the end is analagous to * (copy everything in this dir).
c = command("cp", "-R", "generated/google.golang.org/genproto/.", ".")
c.Stdout = os.Stdout
c.Stderr = os.Stderr
c.Dir = genprotoDir
if err := c.Run(); err != nil {
return err
}
c = command("rm", "-rf", "generated")
c.Stdout = os.Stdout
c.Stderr = os.Stderr
c.Dir = genprotoDir
if err := c.Run(); err != nil {
return err
}
// Clean up and check it all compiles.
if err := vet(genprotoDir); err != nil {
return err
}
if err := build(genprotoDir); err != nil {
return err
}
return nil
}
// goPkg reports the import path declared in the given file's `go_package`
// option. If the option is missing, goPkg returns empty string.
func goPkg(fname string) (string, error) {
content, err := ioutil.ReadFile(fname)
if err != nil {
return "", err
}
var pkgName string
if match := goPkgOptRe.FindSubmatch(content); len(match) > 0 {
pn, err := strconv.Unquote(string(match[1]))
if err != nil {
return "", err
}
pkgName = pn
}
if p := strings.IndexRune(pkgName, ';'); p > 0 {
pkgName = pkgName[:p]
}
return pkgName, nil
}
// protoc executes the "protoc" command on files named in fnames, and outputs
// to "<genprotoDir>/generated".
func protoc(genprotoDir, googleapisDir, protoDir string, fnames []string) error {
args := []string{"--experimental_allow_proto3_optional", fmt.Sprintf("--go_out=plugins=grpc:%s/generated", genprotoDir), "-I", googleapisDir, "-I", protoDir}
args = append(args, fnames...)
c := command("protoc", args...)
c.Stdout = os.Stdout
c.Stderr = os.Stderr
c.Dir = genprotoDir
return c.Run()
}