Skip to content

Commit 7a0e411

Browse files
authored
feat: add aliasfix to fix migrated import paths (#6502)
This will assist us in the migration effort and is also a tool our users can use. The tool is allowlisted to only migrate specific imports, and only if we have made a configuration change to say the code has been migrated.
1 parent 75065bc commit 7a0e411

File tree

18 files changed

+1146
-0
lines changed

18 files changed

+1146
-0
lines changed

internal/aliasfix/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# aliasfix
2+
3+
A tool to migrate client library imports from go-genproto to the new stubs
4+
located in google-cloud-go.
5+
6+
## Usage
7+
8+
Make sure you dependencies for the cloud client library you depend on and
9+
go-genproto are up to date.
10+
11+
```bash
12+
go run cloud.google.com/go/aliasfix .
13+
go mod tidy
14+
```

internal/aliasfix/go.mod

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module cloud.google.com/go/aliasfix
2+
3+
go 1.19
4+
5+
require (
6+
github.com/google/go-cmp v0.5.8
7+
golang.org/x/tools v0.1.12
8+
)
9+
10+
require (
11+
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
12+
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
13+
)

internal/aliasfix/go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
2+
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
3+
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
4+
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
5+
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
6+
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
7+
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
8+
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

internal/aliasfix/main.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright 2022 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+
//go:build go1.18
16+
// +build go1.18
17+
18+
package main
19+
20+
import (
21+
"bytes"
22+
"flag"
23+
"go/ast"
24+
"go/format"
25+
"go/parser"
26+
"go/token"
27+
"io"
28+
"io/fs"
29+
"log"
30+
"os"
31+
"path/filepath"
32+
"strconv"
33+
"strings"
34+
35+
"golang.org/x/tools/imports"
36+
)
37+
38+
var (
39+
fset = token.NewFileSet()
40+
)
41+
42+
func main() {
43+
flag.Parse()
44+
path := flag.Arg(0)
45+
if path == "" {
46+
log.Fatalf("expected one argument -- path to the directory needing updates")
47+
}
48+
if err := processPath(path); err != nil {
49+
log.Fatal(err)
50+
}
51+
}
52+
53+
func processPath(path string) error {
54+
dir, err := os.Stat(path)
55+
if err != nil {
56+
return err
57+
}
58+
if dir.IsDir() {
59+
filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error {
60+
if err == nil && !d.IsDir() && strings.HasSuffix(d.Name(), ".go") {
61+
err = processFile(path, nil)
62+
}
63+
if err != nil {
64+
return err
65+
}
66+
return nil
67+
})
68+
} else {
69+
if err := processFile(path, nil); err != nil {
70+
return err
71+
}
72+
}
73+
return nil
74+
}
75+
76+
// processFile checks to see if the given file needs any imports rewritten and
77+
// does so if needed. Note an io.Writer is injected here for testability.
78+
func processFile(name string, w io.Writer) error {
79+
if w == nil {
80+
file, err := os.Open(name)
81+
if err != nil {
82+
return err
83+
}
84+
defer file.Close()
85+
w = file
86+
}
87+
f, err := parser.ParseFile(fset, name, nil, parser.ParseComments)
88+
if err != nil {
89+
return err
90+
}
91+
var modified bool
92+
for _, imp := range f.Imports {
93+
importPath, err := strconv.Unquote(imp.Path.Value)
94+
if err != nil {
95+
return err
96+
}
97+
if pkg, ok := m[importPath]; ok && pkg.migrated {
98+
oldNamespace := importPath[strings.LastIndex(importPath, "/")+1:]
99+
newNamespace := pkg.importPath[strings.LastIndex(pkg.importPath, "/")+1:]
100+
if imp.Name == nil && oldNamespace != newNamespace {
101+
// use old namespace for fewer diffs
102+
imp.Name = ast.NewIdent(oldNamespace)
103+
} else if imp.Name != nil && imp.Name.Name == newNamespace {
104+
// no longer need named import if matching named import
105+
imp.Name = nil
106+
}
107+
imp.EndPos = imp.End()
108+
imp.Path.Value = strconv.Quote(pkg.importPath)
109+
modified = true
110+
}
111+
}
112+
if modified {
113+
var buf bytes.Buffer
114+
if err := format.Node(&buf, fset, f); err != nil {
115+
return err
116+
}
117+
b, err := imports.Process(name, buf.Bytes(), nil)
118+
if err != nil {
119+
return err
120+
}
121+
if _, err := w.Write(b); err != nil {
122+
return err
123+
}
124+
}
125+
return nil
126+
}

internal/aliasfix/main_test.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright 2022 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+
//go:build go1.18
16+
// +build go1.18
17+
18+
package main
19+
20+
import (
21+
"bytes"
22+
"flag"
23+
"os"
24+
"path/filepath"
25+
"testing"
26+
27+
"github.com/google/go-cmp/cmp"
28+
)
29+
30+
var updateGoldens bool
31+
32+
func TestMain(m *testing.M) {
33+
flag.BoolVar(&updateGoldens, "update-goldens", false, "Update the golden files")
34+
flag.Parse()
35+
os.Exit(m.Run())
36+
}
37+
38+
func TestGolden(t *testing.T) {
39+
tests := []struct {
40+
name string
41+
fileName string
42+
modified bool
43+
}{
44+
{
45+
name: "replace single import",
46+
fileName: "input1",
47+
modified: true,
48+
},
49+
{
50+
name: "replace multi-import",
51+
fileName: "input2",
52+
modified: true,
53+
},
54+
{
55+
name: "no replaces",
56+
fileName: "input3",
57+
modified: false,
58+
},
59+
{
60+
name: "replace single, renamed matching new namespace",
61+
fileName: "input4",
62+
modified: true,
63+
},
64+
{
65+
name: "replace multi-import, renamed non-matching",
66+
fileName: "input5",
67+
modified: true,
68+
},
69+
{
70+
name: "not-migrated",
71+
fileName: "input6",
72+
modified: false,
73+
},
74+
}
75+
for _, tc := range tests {
76+
t.Run(tc.name, func(t *testing.T) {
77+
m["example.com/old/foo"] = pkg{
78+
importPath: "example.com/new/foopb",
79+
migrated: tc.modified,
80+
}
81+
var w bytes.Buffer
82+
if updateGoldens {
83+
if err := processFile(filepath.Join("testdata", tc.fileName), nil); err != nil {
84+
t.Fatal(err)
85+
}
86+
return
87+
}
88+
if err := processFile(filepath.Join("testdata", tc.fileName), &w); err != nil {
89+
t.Fatal(err)
90+
}
91+
want, err := os.ReadFile(filepath.Join("testdata", "golden", tc.fileName))
92+
if err != nil {
93+
t.Fatalf("ReadFile: %v", err)
94+
}
95+
if !tc.modified {
96+
if len(w.Bytes()) != 0 {
97+
t.Fatalf("source modified:\n%s", w.Bytes())
98+
}
99+
return
100+
}
101+
if diff := cmp.Diff(want, w.Bytes()); diff != "" {
102+
t.Errorf("bytes mismatch (-want +got):\n%s", diff)
103+
}
104+
})
105+
}
106+
}

0 commit comments

Comments
 (0)