Skip to content

Commit a287481

Browse files
committed
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 <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent 9387a39 commit a287481

File tree

4 files changed

+217
-3
lines changed

4 files changed

+217
-3
lines changed

internal/imports/source.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,5 @@ type Source interface {
5959
// candidates satisfy all missing references for that package name. It is up
6060
// to each data source to select the best result for each entry in the
6161
// missing map.
62-
ResolveReferences(ctx context.Context, filename string, missing References) (map[PackageName]*Result, error)
62+
ResolveReferences(ctx context.Context, filename string, missing References) ([]*Result, error)
6363
}

internal/imports/source_env.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func (s *ProcessEnvSource) LoadPackageNames(ctx context.Context, srcDir string,
4848
return r.loadPackageNames(unknown, srcDir)
4949
}
5050

51-
func (s *ProcessEnvSource) ResolveReferences(ctx context.Context, filename string, refs map[string]map[string]bool) (map[string]*Result, error) {
51+
func (s *ProcessEnvSource) ResolveReferences(ctx context.Context, filename string, refs map[string]map[string]bool) ([]*Result, error) {
5252
var mu sync.Mutex
5353
found := make(map[string][]pkgDistance)
5454
callback := &scanCallback{
@@ -121,5 +121,9 @@ func (s *ProcessEnvSource) ResolveReferences(ctx context.Context, filename strin
121121
if err := g.Wait(); err != nil {
122122
return nil, err
123123
}
124-
return results, nil
124+
var ans []*Result
125+
for _, x := range results {
126+
ans = append(ans, x)
127+
}
128+
return ans, nil
125129
}

internal/imports/source_modindex.go

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package imports
6+
7+
import (
8+
"context"
9+
"sync"
10+
"time"
11+
12+
"golang.org/x/tools/internal/modindex"
13+
)
14+
15+
// This code is here rather than in the modindex package
16+
// to avoid import loops
17+
18+
// implements Source using modindex, so only for module cache.
19+
//
20+
// this is perhaps over-engineered. A new Index is read at first use.
21+
// And then Update is called after every 15 minutes, and a new Index
22+
// is read if the index changed. It is not clear the Mutex is needed.
23+
type IndexSource struct {
24+
modcachedir string
25+
mutex sync.Mutex
26+
ix *modindex.Index
27+
expires time.Time
28+
}
29+
30+
// create a new Source. Called from NewView in cache/session.go.
31+
func NewIndexSource(cachedir string) *IndexSource {
32+
return &IndexSource{modcachedir: cachedir}
33+
}
34+
35+
func (s *IndexSource) LoadPackageNames(ctx context.Context, srcDir string, paths []ImportPath) (map[ImportPath]PackageName, error) {
36+
/// This is used by goimports to resolve the package names of imports of the
37+
// current package, which is irrelevant for the module cache.
38+
return nil, nil
39+
}
40+
41+
func (s *IndexSource) ResolveReferences(ctx context.Context, filename string, missing References) ([]*Result, error) {
42+
if err := s.maybeReadIndex(); err != nil {
43+
return nil, err
44+
}
45+
var cs []modindex.Candidate
46+
for pkg, nms := range missing {
47+
for nm := range nms {
48+
x := s.ix.Lookup(pkg, nm, false)
49+
cs = append(cs, x...)
50+
}
51+
}
52+
found := make(map[string]*Result)
53+
for _, c := range cs {
54+
var x *Result
55+
if x = found[c.ImportPath]; x == nil {
56+
x = &Result{
57+
Import: &ImportInfo{
58+
ImportPath: c.ImportPath,
59+
Name: "",
60+
},
61+
Package: &PackageInfo{
62+
Name: c.PkgName,
63+
Exports: make(map[string]bool),
64+
},
65+
}
66+
found[c.ImportPath] = x
67+
}
68+
x.Package.Exports[c.Name] = true
69+
}
70+
var ans []*Result
71+
for _, x := range found {
72+
ans = append(ans, x)
73+
}
74+
return ans, nil
75+
}
76+
77+
func (s *IndexSource) maybeReadIndex() error {
78+
s.mutex.Lock()
79+
defer s.mutex.Unlock()
80+
81+
var readIndex bool
82+
if time.Now().After(s.expires) {
83+
ok, err := modindex.Update(s.modcachedir)
84+
if err != nil {
85+
return err
86+
}
87+
if ok {
88+
readIndex = true
89+
}
90+
}
91+
92+
if readIndex || s.ix == nil {
93+
ix, err := modindex.ReadIndex(s.modcachedir)
94+
if err != nil {
95+
return err
96+
}
97+
s.ix = ix
98+
// for now refresh every 15 minutes
99+
s.expires = time.Now().Add(time.Minute * 15)
100+
}
101+
102+
return nil
103+
}

internal/imports/sourcex_test.go

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright 2019 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package imports_test
6+
7+
import (
8+
"context"
9+
"fmt"
10+
"os"
11+
"path/filepath"
12+
"testing"
13+
14+
"github.com/google/go-cmp/cmp"
15+
"golang.org/x/tools/internal/imports"
16+
"golang.org/x/tools/internal/modindex"
17+
)
18+
19+
// There are two cached packages, both resolving foo.Foo,
20+
// but only one resolving foo.Bar
21+
var (
22+
foo = tpkg{
23+
repo: "foo.com",
24+
25+
syms: []string{"Foo"},
26+
}
27+
foobar = tpkg{
28+
repo: "bar.com",
29+
30+
syms: []string{"Foo", "Bar"},
31+
}
32+
33+
fx = `package main
34+
var _ = foo.Foo
35+
var _ = foo.Bar
36+
`
37+
)
38+
39+
type tpkg struct {
40+
// all packages are named foo
41+
repo string // e.g. foo.com
42+
dir string // e.g., [email protected]
43+
syms []string // exported syms
44+
}
45+
46+
func newpkgs(cachedir string, pks ...*tpkg) error {
47+
for _, p := range pks {
48+
fname := filepath.Join(cachedir, p.repo, p.dir, "foo.go")
49+
if err := os.MkdirAll(filepath.Dir(fname), 0755); err != nil {
50+
return err
51+
}
52+
fd, err := os.Create(fname)
53+
if err != nil {
54+
return err
55+
}
56+
fmt.Fprintf(fd, "package foo\n")
57+
for _, s := range p.syms {
58+
fmt.Fprintf(fd, "func %s() {}\n", s)
59+
}
60+
fd.Close()
61+
}
62+
return nil
63+
}
64+
65+
func TestSource(t *testing.T) {
66+
67+
dirs := testDirs(t)
68+
if err := newpkgs(dirs.cachedir, &foo, &foobar); err != nil {
69+
t.Fatal(err)
70+
}
71+
source := imports.NewIndexSource(dirs.cachedir)
72+
ctx := context.Background()
73+
fixes, err := imports.FixImports(ctx, "tfile.go", []byte(fx), "unused", nil, source)
74+
if err != nil {
75+
t.Fatal(err)
76+
}
77+
opts := imports.Options{}
78+
// ApplyFixes needs a non-nil opts
79+
got, err := imports.ApplyFixes(fixes, "tfile.go", []byte(fx), &opts, 0)
80+
81+
fxwant := "package main\n\nimport \"bar.com/foo\"\n\nvar _ = foo.Foo\nvar _ = foo.Bar\n"
82+
if diff := cmp.Diff(string(got), fxwant); diff != "" {
83+
t.Errorf("FixImports got\n%q, wanted\n%q\ndiff is\n%s", string(got), fxwant, diff)
84+
}
85+
}
86+
87+
type dirs struct {
88+
tmpdir string
89+
cachedir string
90+
rootdir string // goroot if we need it, which we don't
91+
}
92+
93+
func testDirs(t *testing.T) dirs {
94+
t.Helper()
95+
dir := t.TempDir()
96+
modindex.IndexDir = func() (string, error) { return dir, nil }
97+
x := dirs{
98+
tmpdir: dir,
99+
cachedir: filepath.Join(dir, "pkg", "mod"),
100+
rootdir: filepath.Join(dir, "root"),
101+
}
102+
if err := os.MkdirAll(x.cachedir, 0755); err != nil {
103+
t.Fatal(err)
104+
}
105+
os.MkdirAll(x.rootdir, 0755)
106+
return x
107+
}

0 commit comments

Comments
 (0)