Skip to content

Commit b5fdc90

Browse files
committed
cmd/pkgsite: add multi-module support, invalidation, and search
This change adds a new goPackagesModuleGetter, which uses x/tools/go/packages to query package information for modules requested by the pkgsite command. Additionally, add new extension interfaces that allow getters to add support for search and content invalidation. The go/packages getter uses these extensions to implement search (via a simple fuzzy-matching algorithm copied from x/tools), and invalidation via statting package files. Along the way, refactor slightly for testing ergonomics. Updates golang/go#40371 Updates golang/go#50229 Fixes golang/go#54479 Change-Id: Iea91a4d6327707733cbbc4f74a9d93052f33e295 Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/474295 TryBot-Result: kokoro <noreply+kokoro@google.com> Run-TryBot: Robert Findley <rfindley@google.com> Reviewed-by: Jamal Carvalho <jamal@golang.org>
1 parent 8e09d06 commit b5fdc90

File tree

12 files changed

+1200
-435
lines changed

12 files changed

+1200
-435
lines changed

cmd/pkgsite/main.go

+172-139
Large diffs are not rendered by default.

cmd/pkgsite/main_test.go

+63-64
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ import (
1616

1717
"github.com/google/go-cmp/cmp"
1818
"golang.org/x/net/html"
19-
"golang.org/x/pkgsite/internal"
2019
"golang.org/x/pkgsite/internal/proxy"
2120
"golang.org/x/pkgsite/internal/proxy/proxytest"
2221
"golang.org/x/pkgsite/internal/testing/htmlcheck"
22+
"golang.org/x/pkgsite/internal/testing/testhelper"
2323
)
2424

2525
var (
@@ -51,48 +51,45 @@ func TestBuildGetters(t *testing.T) {
5151
prox, teardown := proxytest.SetupTestClient(t, testModules)
5252
defer teardown()
5353

54-
localGetter := "Dir(example.com/testmod, " + abs(localModule) + ")"
54+
localGetter := "Dir(" + abs(localModule) + ")"
5555
cacheGetter := "FSProxy(" + abs(cacheDir) + ")"
5656
for _, test := range []struct {
5757
name string
58-
paths []string
59-
cmods []internal.Modver
58+
dirs []string
6059
cacheDir string
61-
prox *proxy.Client
60+
proxy *proxy.Client
6261
want []string
6362
}{
6463
{
65-
name: "local only",
66-
paths: []string{localModule},
67-
want: []string{localGetter},
64+
name: "local only",
65+
dirs: []string{localModule},
66+
want: []string{localGetter},
6867
},
6968
{
7069
name: "cache",
7170
cacheDir: cacheDir,
7271
want: []string{cacheGetter},
7372
},
7473
{
75-
name: "proxy",
76-
prox: prox,
77-
want: []string{"Proxy"},
74+
name: "proxy",
75+
proxy: prox,
76+
want: []string{"Proxy"},
7877
},
7978
{
8079
name: "all three",
81-
paths: []string{localModule},
80+
dirs: []string{localModule},
8281
cacheDir: cacheDir,
83-
prox: prox,
82+
proxy: prox,
8483
want: []string{localGetter, cacheGetter, "Proxy"},
8584
},
86-
{
87-
name: "list",
88-
paths: []string{localModule},
89-
cacheDir: cacheDir,
90-
cmods: []internal.Modver{{Path: "foo", Version: "v1.2.3"}},
91-
want: []string{localGetter, "FSProxy(" + abs(cacheDir) + ", foo@v1.2.3)"},
92-
},
9385
} {
9486
t.Run(test.name, func(t *testing.T) {
95-
getters, err := buildGetters(ctx, test.paths, false, test.cacheDir, test.cmods, test.prox)
87+
getters, err := buildGetters(ctx, getterConfig{
88+
dirs: test.dirs,
89+
pattern: "./...",
90+
modCacheDir: test.cacheDir,
91+
proxy: test.proxy,
92+
})
9693
if err != nil {
9794
t.Fatal(err)
9895
}
@@ -119,56 +116,91 @@ func TestServer(t *testing.T) {
119116
return a
120117
}
121118

122-
localModule := repoPath("internal/fetch/testdata/has_go_mod")
119+
localModule, _ := testhelper.WriteTxtarToTempDir(t, `
120+
-- go.mod --
121+
module example.com/testmod
122+
-- a.go --
123+
package a
124+
`)
123125
cacheDir := repoPath("internal/fetch/testdata/modcache")
124126
testModules := proxytest.LoadTestModules(repoPath("internal/proxy/testdata"))
125127
prox, teardown := proxytest.SetupTestClient(t, testModules)
126128
defer teardown()
127129

128-
getters, err := buildGetters(context.Background(), []string{localModule}, false, cacheDir, nil, prox)
129-
if err != nil {
130-
t.Fatal(err)
131-
}
132-
server, err := newServer(getters, prox)
133-
if err != nil {
134-
t.Fatal(err)
130+
defaultConfig := serverConfig{
131+
paths: []string{localModule},
132+
gopathMode: false,
133+
useListedMods: true,
134+
useCache: true,
135+
cacheDir: cacheDir,
136+
proxy: prox,
135137
}
136-
mux := http.NewServeMux()
137-
server.Install(mux.Handle, nil, nil)
138138

139139
modcacheChecker := in("",
140140
in(".Documentation", hasText("var V = 1")),
141141
sourceLinks(path.Join(abs(cacheDir), "modcache.com@v1.0.0"), "a.go"))
142142

143+
ctx := context.Background()
143144
for _, test := range []struct {
144145
name string
146+
cfg serverConfig
145147
url string
146148
want htmlcheck.Checker
147149
}{
148150
{
149151
"local",
152+
defaultConfig,
150153
"example.com/testmod",
151154
in("",
152155
in(".Documentation", hasText("There is no documentation for this package.")),
153156
sourceLinks(path.Join(abs(localModule), "example.com/testmod"), "a.go")),
154157
},
155158
{
156159
"modcache",
160+
defaultConfig,
157161
"modcache.com@v1.0.0",
158162
modcacheChecker,
159163
},
160164
{
161165
"modcache latest",
166+
defaultConfig,
162167
"modcache.com",
163168
modcacheChecker,
164169
},
165170
{
166171
"proxy",
172+
defaultConfig,
167173
"example.com/single/pkg",
168174
hasText("G is new in v1.1.0"),
169175
},
176+
{
177+
"search",
178+
defaultConfig,
179+
"search?q=a",
180+
in(".SearchResults",
181+
hasText("example.com/testmod"),
182+
),
183+
},
184+
{
185+
"search",
186+
defaultConfig,
187+
"search?q=zzz",
188+
in(".SearchResults",
189+
hasText("no matches"),
190+
),
191+
},
192+
// TODO(rfindley): add more tests, including a test for the standard
193+
// library once it doesn't go through the stdlib package.
194+
// See also golang/go#58923.
170195
} {
171196
t.Run(test.name, func(t *testing.T) {
197+
server, err := buildServer(ctx, test.cfg)
198+
if err != nil {
199+
t.Fatal(err)
200+
}
201+
mux := http.NewServeMux()
202+
server.Install(mux.Handle, nil, nil)
203+
172204
w := httptest.NewRecorder()
173205
mux.ServeHTTP(w, httptest.NewRequest("GET", "/"+test.url, nil))
174206
if w.Code != http.StatusOK {
@@ -203,36 +235,3 @@ func TestCollectPaths(t *testing.T) {
203235
t.Errorf("got %v, want %v", got, want)
204236
}
205237
}
206-
207-
func TestListModsForPaths(t *testing.T) {
208-
listModules = func(string) ([]listedMod, error) {
209-
return []listedMod{
210-
{
211-
internal.Modver{Path: "m1", Version: "v1.2.3"},
212-
"/dir/cache/download/m1/@v/v1.2.3.mod",
213-
false,
214-
},
215-
{
216-
internal.Modver{Path: "m2", Version: "v1.0.0"},
217-
"/repos/m2/go.mod",
218-
false,
219-
},
220-
{
221-
internal.Modver{Path: "indir", Version: "v2.3.4"},
222-
"",
223-
true,
224-
},
225-
}, nil
226-
}
227-
defer func() { listModules = _listModules }()
228-
229-
gotPaths, gotCacheMods, err := listModsForPaths([]string{"m1"}, "/dir")
230-
if err != nil {
231-
t.Fatal(err)
232-
}
233-
wantPaths := []string{"/repos/m2"}
234-
wantCacheMods := []internal.Modver{{Path: "m1", Version: "v1.2.3"}}
235-
if !cmp.Equal(gotPaths, wantPaths) || !cmp.Equal(gotCacheMods, wantCacheMods) {
236-
t.Errorf("got\n%v, %v\nwant\n%v, %v", gotPaths, gotCacheMods, wantPaths, wantCacheMods)
237-
}
238-
}

internal/datasource.go

+72-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,73 @@
44

55
package internal
66

7-
import "context"
7+
import (
8+
"context"
9+
"time"
10+
)
11+
12+
// SearchOptions provide information used by db.Search.
13+
type SearchOptions struct {
14+
// Maximum number of results to return (page size).
15+
MaxResults int
16+
17+
// Offset for DB query.
18+
Offset int
19+
20+
// Maximum number to use for total result count.
21+
MaxResultCount int
22+
23+
// If true, perform a symbol search.
24+
SearchSymbols bool
25+
26+
// SymbolFilter is the word in a search query with a # prefix.
27+
SymbolFilter string
28+
}
29+
30+
// SearchResult represents a single search result from SearchDocuments.
31+
type SearchResult struct {
32+
Name string
33+
PackagePath string
34+
ModulePath string
35+
Version string
36+
Synopsis string
37+
Licenses []string
38+
39+
CommitTime time.Time
40+
41+
// Score is used to sort items in an array of SearchResult.
42+
Score float64
43+
44+
// NumImportedBy is the number of packages that import PackagePath.
45+
NumImportedBy uint64
46+
47+
// SameModule is a list of SearchResults from the same module as this one,
48+
// with lower scores.
49+
SameModule []*SearchResult
50+
51+
// OtherMajor is a map from module paths with the same series path but at
52+
// different major versions of this module, to major version.
53+
// The major version for a non-vN module path (either 0 or 1) is computed
54+
// based on the version in search documents.
55+
OtherMajor map[string]int
56+
57+
// NumResults is the total number of packages that were returned for this
58+
// search.
59+
NumResults uint64
60+
61+
// Symbol information returned by a search request.
62+
// Only populated for symbol search mode.
63+
SymbolName string
64+
SymbolKind SymbolKind
65+
SymbolSynopsis string
66+
SymbolGOOS string
67+
SymbolGOARCH string
68+
69+
// Offset is the 0-based number of this row in the DB query results, which
70+
// is the value to use in a SQL OFFSET clause to have this row be the first
71+
// one returned.
72+
Offset int
73+
}
874

975
// DataSource is the interface used by the frontend to interact with module data.
1076
type DataSource interface {
@@ -22,10 +88,14 @@ type DataSource interface {
2288
GetUnitMeta(ctx context.Context, path, requestedModulePath, requestedVersion string) (_ *UnitMeta, err error)
2389
// GetModuleReadme gets the readme for the module.
2490
GetModuleReadme(ctx context.Context, modulePath, resolvedVersion string) (*Readme, error)
25-
2691
// GetLatestInfo gets information about the latest versions of a unit and module.
2792
// See LatestInfo for documentation.
2893
GetLatestInfo(ctx context.Context, unitPath, modulePath string, latestUnitMeta *UnitMeta) (LatestInfo, error)
94+
95+
// SupportsSearch reports whether this data source supports search.
96+
SupportsSearch() bool
97+
// Search searches for packages matching the given query.
98+
Search(ctx context.Context, q string, opts SearchOptions) (_ []*SearchResult, err error)
2999
}
30100

31101
// LatestInfo holds information about the latest versions and paths.

0 commit comments

Comments
 (0)