Skip to content

Commit

Permalink
feat: add aliasfix to fix migrated import paths (#6502)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
codyoss committed Aug 24, 2022
1 parent 75065bc commit 7a0e411
Show file tree
Hide file tree
Showing 18 changed files with 1,146 additions and 0 deletions.
14 changes: 14 additions & 0 deletions internal/aliasfix/README.md
@@ -0,0 +1,14 @@
# aliasfix

A tool to migrate client library imports from go-genproto to the new stubs
located in google-cloud-go.

## Usage

Make sure you dependencies for the cloud client library you depend on and
go-genproto are up to date.

```bash
go run cloud.google.com/go/aliasfix .
go mod tidy
```
13 changes: 13 additions & 0 deletions internal/aliasfix/go.mod
@@ -0,0 +1,13 @@
module cloud.google.com/go/aliasfix

go 1.19

require (
github.com/google/go-cmp v0.5.8
golang.org/x/tools v0.1.12
)

require (
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
)
8 changes: 8 additions & 0 deletions internal/aliasfix/go.sum
@@ -0,0 +1,8 @@
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
126 changes: 126 additions & 0 deletions internal/aliasfix/main.go
@@ -0,0 +1,126 @@
// Copyright 2022 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.

//go:build go1.18
// +build go1.18

package main

import (
"bytes"
"flag"
"go/ast"
"go/format"
"go/parser"
"go/token"
"io"
"io/fs"
"log"
"os"
"path/filepath"
"strconv"
"strings"

"golang.org/x/tools/imports"
)

var (
fset = token.NewFileSet()
)

func main() {
flag.Parse()
path := flag.Arg(0)
if path == "" {
log.Fatalf("expected one argument -- path to the directory needing updates")
}
if err := processPath(path); err != nil {
log.Fatal(err)
}
}

func processPath(path string) error {
dir, err := os.Stat(path)
if err != nil {
return err
}
if dir.IsDir() {
filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error {
if err == nil && !d.IsDir() && strings.HasSuffix(d.Name(), ".go") {
err = processFile(path, nil)
}
if err != nil {
return err
}
return nil
})
} else {
if err := processFile(path, nil); err != nil {
return err
}
}
return nil
}

// processFile checks to see if the given file needs any imports rewritten and
// does so if needed. Note an io.Writer is injected here for testability.
func processFile(name string, w io.Writer) error {
if w == nil {
file, err := os.Open(name)
if err != nil {
return err
}
defer file.Close()
w = file
}
f, err := parser.ParseFile(fset, name, nil, parser.ParseComments)
if err != nil {
return err
}
var modified bool
for _, imp := range f.Imports {
importPath, err := strconv.Unquote(imp.Path.Value)
if err != nil {
return err
}
if pkg, ok := m[importPath]; ok && pkg.migrated {
oldNamespace := importPath[strings.LastIndex(importPath, "/")+1:]
newNamespace := pkg.importPath[strings.LastIndex(pkg.importPath, "/")+1:]
if imp.Name == nil && oldNamespace != newNamespace {
// use old namespace for fewer diffs
imp.Name = ast.NewIdent(oldNamespace)
} else if imp.Name != nil && imp.Name.Name == newNamespace {
// no longer need named import if matching named import
imp.Name = nil
}
imp.EndPos = imp.End()
imp.Path.Value = strconv.Quote(pkg.importPath)
modified = true
}
}
if modified {
var buf bytes.Buffer
if err := format.Node(&buf, fset, f); err != nil {
return err
}
b, err := imports.Process(name, buf.Bytes(), nil)
if err != nil {
return err
}
if _, err := w.Write(b); err != nil {
return err
}
}
return nil
}
106 changes: 106 additions & 0 deletions internal/aliasfix/main_test.go
@@ -0,0 +1,106 @@
// Copyright 2022 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.

//go:build go1.18
// +build go1.18

package main

import (
"bytes"
"flag"
"os"
"path/filepath"
"testing"

"github.com/google/go-cmp/cmp"
)

var updateGoldens bool

func TestMain(m *testing.M) {
flag.BoolVar(&updateGoldens, "update-goldens", false, "Update the golden files")
flag.Parse()
os.Exit(m.Run())
}

func TestGolden(t *testing.T) {
tests := []struct {
name string
fileName string
modified bool
}{
{
name: "replace single import",
fileName: "input1",
modified: true,
},
{
name: "replace multi-import",
fileName: "input2",
modified: true,
},
{
name: "no replaces",
fileName: "input3",
modified: false,
},
{
name: "replace single, renamed matching new namespace",
fileName: "input4",
modified: true,
},
{
name: "replace multi-import, renamed non-matching",
fileName: "input5",
modified: true,
},
{
name: "not-migrated",
fileName: "input6",
modified: false,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
m["example.com/old/foo"] = pkg{
importPath: "example.com/new/foopb",
migrated: tc.modified,
}
var w bytes.Buffer
if updateGoldens {
if err := processFile(filepath.Join("testdata", tc.fileName), nil); err != nil {
t.Fatal(err)
}
return
}
if err := processFile(filepath.Join("testdata", tc.fileName), &w); err != nil {
t.Fatal(err)
}
want, err := os.ReadFile(filepath.Join("testdata", "golden", tc.fileName))
if err != nil {
t.Fatalf("ReadFile: %v", err)
}
if !tc.modified {
if len(w.Bytes()) != 0 {
t.Fatalf("source modified:\n%s", w.Bytes())
}
return
}
if diff := cmp.Diff(want, w.Bytes()); diff != "" {
t.Errorf("bytes mismatch (-want +got):\n%s", diff)
}
})
}
}

0 comments on commit 7a0e411

Please sign in to comment.