Skip to content

Commit 5948bee

Browse files
authored
feat(internal/gapicgen): only update relevant gapic files (#4066)
1 parent 33dd86d commit 5948bee

File tree

7 files changed

+331
-104
lines changed

7 files changed

+331
-104
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright 2021 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+
15+
// Package gocmd provides helers for invoking Go tooling.
16+
package gocmd
17+
18+
import (
19+
"errors"
20+
"fmt"
21+
"log"
22+
"os"
23+
"os/exec"
24+
"strings"
25+
26+
"cloud.google.com/go/internal/gapicgen/execv"
27+
)
28+
29+
var (
30+
// ErrBuildConstraint is returned when the Go command returns this error.
31+
ErrBuildConstraint error = errors.New("build constraints exclude all Go files")
32+
)
33+
34+
// ModTidy tidies go.mod file in the specified directory
35+
func ModTidy(dir string) error {
36+
log.Printf("[%s] running go mod tidy", dir)
37+
c := execv.Command("go", "mod", "tidy")
38+
c.Dir = dir
39+
c.Env = []string{
40+
fmt.Sprintf("PATH=%s", os.Getenv("PATH")), // TODO(deklerk): Why do we need to do this? Doesn't seem to be necessary in other exec.Commands.
41+
fmt.Sprintf("HOME=%s", os.Getenv("HOME")), // TODO(deklerk): Why do we need to do this? Doesn't seem to be necessary in other exec.Commands.
42+
}
43+
return c.Run()
44+
}
45+
46+
// ListModName finds a modules name for a given directory.
47+
func ListModName(dir string) (string, error) {
48+
modC := execv.Command("go", "list", "-m")
49+
modC.Dir = dir
50+
mod, err := modC.Output()
51+
return string(mod), err
52+
}
53+
54+
// ListModDirName finds the directory in which the module resides. Returns
55+
// ErrBuildConstraint if all files in a module are constrained.
56+
func ListModDirName(dir string) (string, error) {
57+
var out []byte
58+
var err error
59+
c := execv.Command("go", "list", "-f", "'{{.Module.Dir}}'")
60+
c.Dir = dir
61+
if out, err = c.Output(); err != nil {
62+
if ee, ok := err.(*exec.ExitError); ok {
63+
if strings.Contains(string(ee.Stderr), "build constraints exclude all Go files") {
64+
return "", ErrBuildConstraint
65+
}
66+
}
67+
return "", err
68+
}
69+
return strings.Trim(strings.TrimSpace(string(out)), "'"), nil
70+
}
71+
72+
// Build attempts to build all packages recursively from the given directory.
73+
func Build(dir string) error {
74+
log.Println("building generated code")
75+
c := execv.Command("go", "build", "./...")
76+
c.Dir = dir
77+
return c.Run()
78+
}
79+
80+
// Vet runs linters on all .go files recursively from the given directory.
81+
func Vet(dir string) error {
82+
log.Println("vetting generated code")
83+
c := execv.Command("goimports", "-w", ".")
84+
c.Dir = dir
85+
if err := c.Run(); err != nil {
86+
return err
87+
}
88+
89+
c = execv.Command("gofmt", "-s", "-d", "-w", "-l", ".")
90+
c.Dir = dir
91+
return c.Run()
92+
}

internal/gapicgen/generator/gapics.go

Lines changed: 92 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ import (
2424
"strings"
2525

2626
"cloud.google.com/go/internal/gapicgen/execv"
27+
"cloud.google.com/go/internal/gapicgen/execv/gocmd"
2728
"cloud.google.com/go/internal/gapicgen/gensnippets"
29+
"cloud.google.com/go/internal/gapicgen/git"
2830
"gopkg.in/yaml.v2"
2931
)
3032

@@ -37,10 +39,11 @@ type GapicGenerator struct {
3739
gapicToGenerate string
3840
regenOnly bool
3941
onlyGenerateGapic bool
42+
modifiedPkgs []string
4043
}
4144

4245
// NewGapicGenerator creates a GapicGenerator.
43-
func NewGapicGenerator(c *Config) *GapicGenerator {
46+
func NewGapicGenerator(c *Config, modifiedPkgs []string) *GapicGenerator {
4447
return &GapicGenerator{
4548
googleapisDir: c.GoogleapisDir,
4649
protoDir: c.ProtoDir,
@@ -49,6 +52,7 @@ func NewGapicGenerator(c *Config) *GapicGenerator {
4952
gapicToGenerate: c.GapicToGenerate,
5053
regenOnly: c.RegenOnly,
5154
onlyGenerateGapic: c.OnlyGenerateGapic,
55+
modifiedPkgs: modifiedPkgs,
5256
}
5357
}
5458

@@ -72,6 +76,16 @@ func (g *GapicGenerator) Regen(ctx context.Context) error {
7276
return err
7377
}
7478

79+
// TODO(codyoss): Remove once https://github.com/googleapis/gapic-generator-go/pull/606
80+
// is released.
81+
if err := gocmd.Vet(g.googleCloudDir); err != nil {
82+
return err
83+
}
84+
85+
if err := g.resetUnknownVersion(); err != nil {
86+
return err
87+
}
88+
7589
if g.regenOnly {
7690
return nil
7791
}
@@ -94,11 +108,11 @@ func (g *GapicGenerator) Regen(ctx context.Context) error {
94108
return err
95109
}
96110

97-
if err := vet(g.googleCloudDir); err != nil {
111+
if err := gocmd.Vet(g.googleCloudDir); err != nil {
98112
return err
99113
}
100114

101-
if err := build(g.googleCloudDir); err != nil {
115+
if err := gocmd.Build(g.googleCloudDir); err != nil {
102116
return err
103117
}
104118

@@ -124,33 +138,19 @@ func (g *GapicGenerator) regenSnippets(ctx context.Context) error {
124138
if err := replaceAllForSnippets(g.googleCloudDir, snippetDir); err != nil {
125139
return err
126140
}
127-
if err := goModTidy(snippetDir); err != nil {
141+
if err := gocmd.ModTidy(snippetDir); err != nil {
128142
return err
129143
}
130144
return nil
131145
}
132146

133-
func goModTidy(dir string) error {
134-
log.Printf("[%s] running go mod tidy", dir)
135-
c := execv.Command("go", "mod", "tidy")
136-
c.Dir = dir
137-
c.Env = []string{
138-
fmt.Sprintf("PATH=%s", os.Getenv("PATH")), // TODO(deklerk): Why do we need to do this? Doesn't seem to be necessary in other exec.Commands.
139-
fmt.Sprintf("HOME=%s", os.Getenv("HOME")), // TODO(deklerk): Why do we need to do this? Doesn't seem to be necessary in other exec.Commands.
140-
}
141-
return c.Run()
142-
}
143-
144147
func replaceAllForSnippets(googleCloudDir, snippetDir string) error {
145148
return execv.ForEachMod(googleCloudDir, func(dir string) error {
146149
if dir == snippetDir {
147150
return nil
148151
}
149152

150-
// Get the module name in this dir.
151-
modC := execv.Command("go", "list", "-m")
152-
modC.Dir = dir
153-
mod, err := modC.Output()
153+
mod, err := gocmd.ListModName(dir)
154154
if err != nil {
155155
return err
156156
}
@@ -208,23 +208,60 @@ go mod edit -dropreplace "google.golang.org/genproto"
208208
return c.Run()
209209
}
210210

211+
// resetUnknownVersion resets doc.go files that have only had their version
212+
// changed to UNKNOWN by the generator.
213+
func (g *GapicGenerator) resetUnknownVersion() error {
214+
files, err := git.FindModifiedFiles(g.googleCloudDir)
215+
if err != nil {
216+
return err
217+
}
218+
219+
for _, file := range files {
220+
if !strings.HasSuffix(file, "doc.go") {
221+
continue
222+
}
223+
diff, err := git.FileDiff(g.googleCloudDir, file)
224+
if err != nil {
225+
return err
226+
}
227+
// More than one diff, don't reset.
228+
if strings.Count(diff, "@@") != 2 {
229+
log.Println(diff)
230+
continue
231+
}
232+
// Not related to version, don't reset.
233+
if !strings.Contains(diff, "+const versionClient = \"UNKNOWN\"") {
234+
continue
235+
}
236+
237+
if err := git.ResetFile(g.googleCloudDir, file); err != nil {
238+
return err
239+
}
240+
}
241+
return nil
242+
}
243+
211244
// setVersion updates the versionClient constant in all .go files. It may create
212245
// .backup files on certain systems (darwin), and so should be followed by a
213246
// clean-up of .backup files.
214247
func (g *GapicGenerator) setVersion() error {
248+
dirs, err := g.findModifiedDirs()
249+
if err != nil {
250+
return err
251+
}
215252
log.Println("updating client version")
216-
// TODO(deklerk): Migrate this all to Go instead of using bash.
217-
218-
c := execv.Command("bash", "-c", `
253+
for _, dir := range dirs {
254+
c := execv.Command("bash", "-c", `
219255
ver=$(date +%Y%m%d)
220-
git ls-files -mo | while read modified; do
221-
dir=${modified%/*.*}
222-
find . -path "*/$dir/doc.go" -exec sed -i.backup -e "s/^const versionClient.*/const versionClient = \"$ver\"/" '{}' +;
223-
done
256+
find . -path "*/doc.go" -exec sed -i.backup -e "s/^const versionClient.*/const versionClient = \"$ver\"/" '{}' +;
224257
find . -name '*.backup' -delete
225258
`)
226-
c.Dir = g.googleCloudDir
227-
return c.Run()
259+
c.Dir = dir
260+
if err := c.Run(); err != nil {
261+
return err
262+
}
263+
}
264+
return nil
228265
}
229266

230267
// microgen runs the microgenerator on a single microgen config.
@@ -501,3 +538,30 @@ func (g *GapicGenerator) parseAPIShortnames(confs []*microgenConfig, manualEntri
501538
}
502539
return shortnames, nil
503540
}
541+
542+
func (g *GapicGenerator) findModifiedDirs() ([]string, error) {
543+
log.Println("finding modifiled directories")
544+
files, err := git.FindModifiedAndUntrackedFiles(g.googleCloudDir)
545+
if err != nil {
546+
return nil, err
547+
}
548+
dirs := map[string]bool{}
549+
for _, file := range files {
550+
dir := filepath.Dir(filepath.Join(g.googleCloudDir, file))
551+
dirs[dir] = true
552+
}
553+
554+
// Add modified dirs from genproto. Sometimes only a request struct will be
555+
// updated, in these cases we should still make modifications the
556+
// corresponding gapic directories.
557+
for _, pkg := range g.modifiedPkgs {
558+
dir := filepath.Join(g.googleCloudDir, pkg)
559+
dirs[dir] = true
560+
}
561+
562+
var dirList []string
563+
for dir := range dirs {
564+
dirList = append(dirList, dir)
565+
}
566+
return dirList, nil
567+
}

internal/gapicgen/generator/generator.go

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,9 @@ import (
2121
"context"
2222
"fmt"
2323
"io/ioutil"
24-
"log"
2524
"os"
2625
"path/filepath"
2726

28-
"cloud.google.com/go/internal/gapicgen/execv"
2927
"cloud.google.com/go/internal/gapicgen/git"
3028
)
3129

@@ -49,10 +47,6 @@ func Generate(ctx context.Context, conf *Config) ([]*git.ChangeInfo, error) {
4947
return nil, fmt.Errorf("error generating genproto (may need to check logs for more errors): %v", err)
5048
}
5149
}
52-
gapicGenerator := NewGapicGenerator(conf)
53-
if err := gapicGenerator.Regen(ctx); err != nil {
54-
return nil, fmt.Errorf("error generating gapics (may need to check logs for more errors): %v", err)
55-
}
5650

5751
var changes []*git.ChangeInfo
5852
if !conf.LocalMode {
@@ -65,6 +59,17 @@ func Generate(ctx context.Context, conf *Config) ([]*git.ChangeInfo, error) {
6559
return nil, err
6660
}
6761
}
62+
var modifiedPkgs []string
63+
for _, v := range changes {
64+
if v.Package != "" {
65+
modifiedPkgs = append(modifiedPkgs, v.PackageDir)
66+
}
67+
}
68+
69+
gapicGenerator := NewGapicGenerator(conf, modifiedPkgs)
70+
if err := gapicGenerator.Regen(ctx); err != nil {
71+
return nil, fmt.Errorf("error generating gapics (may need to check logs for more errors): %v", err)
72+
}
6873

6974
return changes, nil
7075
}
@@ -81,7 +86,7 @@ func gatherChanges(googleapisDir, genprotoDir string) ([]*git.ChangeInfo, error)
8186
}
8287
gapicPkgs := make(map[string]string)
8388
for _, v := range microgenGapicConfigs {
84-
gapicPkgs[v.inputDirectoryPath] = git.ParseConventionalCommitPkg(v.importPath)
89+
gapicPkgs[v.inputDirectoryPath] = v.importPath
8590
}
8691
changes, err := git.ParseChangeInfo(googleapisDir, commits, gapicPkgs)
8792
if err != nil {
@@ -112,25 +117,3 @@ func recordGoogleapisHash(googleapisDir, genprotoDir string) error {
112117
}
113118
return nil
114119
}
115-
116-
// build attempts to build all packages recursively from the given directory.
117-
func build(dir string) error {
118-
log.Println("building generated code")
119-
c := execv.Command("go", "build", "./...")
120-
c.Dir = dir
121-
return c.Run()
122-
}
123-
124-
// vet runs linters on all .go files recursively from the given directory.
125-
func vet(dir string) error {
126-
log.Println("vetting generated code")
127-
c := execv.Command("goimports", "-w", ".")
128-
c.Dir = dir
129-
if err := c.Run(); err != nil {
130-
return err
131-
}
132-
133-
c = execv.Command("gofmt", "-s", "-d", "-w", "-l", ".")
134-
c.Dir = dir
135-
return c.Run()
136-
}

internal/gapicgen/generator/genproto.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"strings"
2727

2828
"cloud.google.com/go/internal/gapicgen/execv"
29+
"cloud.google.com/go/internal/gapicgen/execv/gocmd"
2930
"cloud.google.com/go/internal/gapicgen/git"
3031
"golang.org/x/sync/errgroup"
3132
)
@@ -131,11 +132,11 @@ func (g *GenprotoGenerator) Regen(ctx context.Context) error {
131132
return err
132133
}
133134

134-
if err := vet(g.genprotoDir); err != nil {
135+
if err := gocmd.Vet(g.genprotoDir); err != nil {
135136
return err
136137
}
137138

138-
if err := build(g.genprotoDir); err != nil {
139+
if err := gocmd.Build(g.genprotoDir); err != nil {
139140
return err
140141
}
141142

0 commit comments

Comments
 (0)