-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
internal/imports: test Source for go mod cache
This CL provides an implementation of the Source interface to use an index to the go module cache to satisfy imports. There is also a test. Change-Id: Ic931cb132fcf7253add7fc7faadd89726ee65567 Reviewed-on: https://go-review.googlesource.com/c/tools/+/627235 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Robert Findley <rfindley@google.com>
- Loading branch information
Showing
4 changed files
with
217 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
// Copyright 2024 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package imports | ||
|
||
import ( | ||
"context" | ||
"sync" | ||
"time" | ||
|
||
"golang.org/x/tools/internal/modindex" | ||
) | ||
|
||
// This code is here rather than in the modindex package | ||
// to avoid import loops | ||
|
||
// implements Source using modindex, so only for module cache. | ||
// | ||
// this is perhaps over-engineered. A new Index is read at first use. | ||
// And then Update is called after every 15 minutes, and a new Index | ||
// is read if the index changed. It is not clear the Mutex is needed. | ||
type IndexSource struct { | ||
modcachedir string | ||
mutex sync.Mutex | ||
ix *modindex.Index | ||
expires time.Time | ||
} | ||
|
||
// create a new Source. Called from NewView in cache/session.go. | ||
func NewIndexSource(cachedir string) *IndexSource { | ||
return &IndexSource{modcachedir: cachedir} | ||
} | ||
|
||
func (s *IndexSource) LoadPackageNames(ctx context.Context, srcDir string, paths []ImportPath) (map[ImportPath]PackageName, error) { | ||
/// This is used by goimports to resolve the package names of imports of the | ||
// current package, which is irrelevant for the module cache. | ||
return nil, nil | ||
} | ||
|
||
func (s *IndexSource) ResolveReferences(ctx context.Context, filename string, missing References) ([]*Result, error) { | ||
if err := s.maybeReadIndex(); err != nil { | ||
return nil, err | ||
} | ||
var cs []modindex.Candidate | ||
for pkg, nms := range missing { | ||
for nm := range nms { | ||
x := s.ix.Lookup(pkg, nm, false) | ||
cs = append(cs, x...) | ||
} | ||
} | ||
found := make(map[string]*Result) | ||
for _, c := range cs { | ||
var x *Result | ||
if x = found[c.ImportPath]; x == nil { | ||
x = &Result{ | ||
Import: &ImportInfo{ | ||
ImportPath: c.ImportPath, | ||
Name: "", | ||
}, | ||
Package: &PackageInfo{ | ||
Name: c.PkgName, | ||
Exports: make(map[string]bool), | ||
}, | ||
} | ||
found[c.ImportPath] = x | ||
} | ||
x.Package.Exports[c.Name] = true | ||
} | ||
var ans []*Result | ||
for _, x := range found { | ||
ans = append(ans, x) | ||
} | ||
return ans, nil | ||
} | ||
|
||
func (s *IndexSource) maybeReadIndex() error { | ||
s.mutex.Lock() | ||
defer s.mutex.Unlock() | ||
|
||
var readIndex bool | ||
if time.Now().After(s.expires) { | ||
ok, err := modindex.Update(s.modcachedir) | ||
if err != nil { | ||
return err | ||
} | ||
if ok { | ||
readIndex = true | ||
} | ||
} | ||
|
||
if readIndex || s.ix == nil { | ||
ix, err := modindex.ReadIndex(s.modcachedir) | ||
if err != nil { | ||
return err | ||
} | ||
s.ix = ix | ||
// for now refresh every 15 minutes | ||
s.expires = time.Now().Add(time.Minute * 15) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
// Copyright 2019 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package imports_test | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
"golang.org/x/tools/internal/imports" | ||
"golang.org/x/tools/internal/modindex" | ||
) | ||
|
||
// There are two cached packages, both resolving foo.Foo, | ||
// but only one resolving foo.Bar | ||
var ( | ||
foo = tpkg{ | ||
repo: "foo.com", | ||
dir: "foo@v1.0.0", | ||
syms: []string{"Foo"}, | ||
} | ||
foobar = tpkg{ | ||
repo: "bar.com", | ||
dir: "foo@v1.0.0", | ||
syms: []string{"Foo", "Bar"}, | ||
} | ||
|
||
fx = `package main | ||
var _ = foo.Foo | ||
var _ = foo.Bar | ||
` | ||
) | ||
|
||
type tpkg struct { | ||
// all packages are named foo | ||
repo string // e.g. foo.com | ||
dir string // e.g., foo@v1.0.0 | ||
syms []string // exported syms | ||
} | ||
|
||
func newpkgs(cachedir string, pks ...*tpkg) error { | ||
for _, p := range pks { | ||
fname := filepath.Join(cachedir, p.repo, p.dir, "foo.go") | ||
if err := os.MkdirAll(filepath.Dir(fname), 0755); err != nil { | ||
return err | ||
} | ||
fd, err := os.Create(fname) | ||
if err != nil { | ||
return err | ||
} | ||
fmt.Fprintf(fd, "package foo\n") | ||
for _, s := range p.syms { | ||
fmt.Fprintf(fd, "func %s() {}\n", s) | ||
} | ||
fd.Close() | ||
} | ||
return nil | ||
} | ||
|
||
func TestSource(t *testing.T) { | ||
|
||
dirs := testDirs(t) | ||
if err := newpkgs(dirs.cachedir, &foo, &foobar); err != nil { | ||
t.Fatal(err) | ||
} | ||
source := imports.NewIndexSource(dirs.cachedir) | ||
ctx := context.Background() | ||
fixes, err := imports.FixImports(ctx, "tfile.go", []byte(fx), "unused", nil, source) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
opts := imports.Options{} | ||
// ApplyFixes needs a non-nil opts | ||
got, err := imports.ApplyFixes(fixes, "tfile.go", []byte(fx), &opts, 0) | ||
|
||
fxwant := "package main\n\nimport \"bar.com/foo\"\n\nvar _ = foo.Foo\nvar _ = foo.Bar\n" | ||
if diff := cmp.Diff(string(got), fxwant); diff != "" { | ||
t.Errorf("FixImports got\n%q, wanted\n%q\ndiff is\n%s", string(got), fxwant, diff) | ||
} | ||
} | ||
|
||
type dirs struct { | ||
tmpdir string | ||
cachedir string | ||
rootdir string // goroot if we need it, which we don't | ||
} | ||
|
||
func testDirs(t *testing.T) dirs { | ||
t.Helper() | ||
dir := t.TempDir() | ||
modindex.IndexDir = func() (string, error) { return dir, nil } | ||
x := dirs{ | ||
tmpdir: dir, | ||
cachedir: filepath.Join(dir, "pkg", "mod"), | ||
rootdir: filepath.Join(dir, "root"), | ||
} | ||
if err := os.MkdirAll(x.cachedir, 0755); err != nil { | ||
t.Fatal(err) | ||
} | ||
os.MkdirAll(x.rootdir, 0755) | ||
return x | ||
} |