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