Skip to content

Commit 5e769f6

Browse files
committed
internal/localdatasource: use getters
Instead of loading a list of modules initially, a local datasource now takes a list of ModuleGetters, which are called on demand when a module is requested. For golang/go#47780 Change-Id: Ica3cc6d47de01ec78c451c0ef54b9ba0e0c5a96e Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/343591 Trust: Jonathan Amsterdam <[email protected]> Run-TryBot: Jonathan Amsterdam <[email protected]> TryBot-Result: kokoro <[email protected]> Reviewed-by: Julie Qiu <[email protected]>
1 parent f4073fd commit 5e769f6

File tree

3 files changed

+127
-109
lines changed

3 files changed

+127
-109
lines changed

cmd/pkgsite/main.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,12 @@ import (
3030
"github.com/google/safehtml/template"
3131
"golang.org/x/pkgsite/internal"
3232
"golang.org/x/pkgsite/internal/dcensus"
33+
"golang.org/x/pkgsite/internal/fetch"
3334
"golang.org/x/pkgsite/internal/frontend"
3435
"golang.org/x/pkgsite/internal/localdatasource"
3536
"golang.org/x/pkgsite/internal/log"
3637
"golang.org/x/pkgsite/internal/middleware"
38+
"golang.org/x/pkgsite/internal/source"
3739
)
3840

3941
const defaultAddr = "localhost:8080" // default webserver address
@@ -53,7 +55,7 @@ func main() {
5355
paths = "."
5456
}
5557

56-
lds := localdatasource.New()
58+
lds := localdatasource.New(source.NewClient(time.Second))
5759
dsg := func(context.Context) internal.DataSource { return lds }
5860
server, err := frontend.NewServer(frontend.ServerConfig{
5961
DataSourceGetter: dsg,
@@ -78,15 +80,20 @@ func load(ctx context.Context, ds *localdatasource.DataSource, pathList string)
7880
paths := strings.Split(pathList, ",")
7981
loaded := len(paths)
8082
for _, path := range paths {
81-
var err error
83+
var (
84+
mg fetch.ModuleGetter
85+
err error
86+
)
8287
if *gopathMode {
83-
err = ds.LoadFromGOPATH(ctx, path)
88+
mg, err = localdatasource.NewGOPATHModuleGetter(path)
8489
} else {
85-
err = ds.Load(ctx, path)
90+
mg, err = fetch.NewDirectoryModuleGetter("", path)
8691
}
8792
if err != nil {
8893
log.Error(ctx, err)
8994
loaded--
95+
} else {
96+
ds.AddModuleGetter(mg)
9097
}
9198
}
9299

internal/localdatasource/datasource.go

Lines changed: 88 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ package localdatasource
99

1010
import (
1111
"context"
12+
"errors"
1213
"fmt"
1314
"os"
1415
"path"
1516
"path/filepath"
1617
"strings"
1718
"sync"
18-
"time"
1919

2020
"golang.org/x/pkgsite/internal"
2121
"golang.org/x/pkgsite/internal/derrors"
@@ -29,66 +29,97 @@ type DataSource struct {
2929
sourceClient *source.Client
3030

3131
mu sync.Mutex
32+
getters []fetch.ModuleGetter
3233
loadedModules map[string]*internal.Module
3334
}
3435

3536
// New creates and returns a new local datasource that bypasses license
3637
// checks by default.
37-
func New() *DataSource {
38+
func New(sc *source.Client) *DataSource {
3839
return &DataSource{
39-
sourceClient: source.NewClient(1 * time.Minute),
40+
sourceClient: sc,
4041
loadedModules: make(map[string]*internal.Module),
4142
}
4243
}
4344

44-
// Load loads a module from the given local path. Loading is required before
45-
// being able to display the module.
46-
func (ds *DataSource) Load(ctx context.Context, localPath string) (err error) {
47-
defer derrors.Wrap(&err, "Load(%q)", localPath)
48-
return ds.fetch(ctx, "", localPath)
45+
// AddModuleGetter adds a module getter to the DataSource. To look up a module,
46+
// the getters are tried in the order they were added until the desired module
47+
// is found.
48+
func (ds *DataSource) AddModuleGetter(g fetch.ModuleGetter) {
49+
ds.mu.Lock()
50+
defer ds.mu.Unlock()
51+
ds.getters = append(ds.getters, g)
4952
}
5053

51-
// LoadFromGOPATH loads a module from GOPATH using the given import path. The full
52-
// path of the module should be GOPATH/src/importPath. If several GOPATHs exist, the
53-
// module is loaded from the first one that contains the import path. Loading is required
54-
// before being able to display the module.
55-
func (ds *DataSource) LoadFromGOPATH(ctx context.Context, importPath string) (err error) {
56-
defer derrors.Wrap(&err, "LoadFromGOPATH(%q)", importPath)
57-
58-
path := getFullPath(importPath)
59-
if path == "" {
60-
return fmt.Errorf("path %s doesn't exist: %w", importPath, derrors.NotFound)
54+
// getModule gets the module at the given path and version. It first checks the
55+
// cache, and if it isn't there it then tries to fetch it.
56+
func (ds *DataSource) getModule(ctx context.Context, path, version string) (*internal.Module, error) {
57+
if m := ds.getFromCache(path, version); m != nil {
58+
return m, nil
6159
}
60+
m, err := ds.fetch(ctx, path, version)
61+
if err != nil {
62+
return nil, err
63+
}
64+
ds.mu.Lock()
65+
defer ds.mu.Unlock()
66+
ds.loadedModules[m.ModulePath+"@"+m.Version] = m
67+
return m, nil
68+
}
6269

63-
return ds.fetch(ctx, importPath, path)
70+
// getFromCache returns a module from the cache if it is present, and nil otherwise.
71+
func (ds *DataSource) getFromCache(path, version string) *internal.Module {
72+
ds.mu.Lock()
73+
defer ds.mu.Unlock()
74+
// Look for an exact match first.
75+
if m := ds.loadedModules[path+"@"+version]; m != nil {
76+
return m
77+
}
78+
// Look for the module path with LocalVersion, as for a directory-based or GOPATH-mode module.
79+
return ds.loadedModules[path+"@"+fetch.LocalVersion]
6480
}
6581

66-
// fetch fetches a module using FetchLocalModule and adds it to the datasource.
67-
// If the fetching fails, an error is returned.
68-
func (ds *DataSource) fetch(ctx context.Context, modulePath, localPath string) error {
69-
fr := fetch.FetchLocalModule(ctx, modulePath, localPath, ds.sourceClient)
70-
if fr.Error != nil {
71-
return fr.Error
82+
// fetch fetches a module using the configured ModuleGetters.
83+
// It tries each getter in turn until it finds one that has the module.
84+
func (ds *DataSource) fetch(ctx context.Context, modulePath, version string) (*internal.Module, error) {
85+
for _, g := range ds.getters {
86+
fr := fetch.FetchModule(ctx, modulePath, version, g, ds.sourceClient)
87+
if fr.Error == nil {
88+
adjust(fr.Module)
89+
return fr.Module, nil
90+
}
91+
if !errors.Is(fr.Error, derrors.NotFound) {
92+
return nil, fr.Error
93+
}
7294
}
95+
return nil, fmt.Errorf("%s@%s: %w", modulePath, version, derrors.NotFound)
96+
}
7397

74-
fr.Module.IsRedistributable = true
75-
for _, unit := range fr.Module.Units {
98+
func adjust(m *internal.Module) {
99+
m.IsRedistributable = true
100+
for _, unit := range m.Units {
76101
unit.IsRedistributable = true
77102
}
78-
79-
for _, unit := range fr.Module.Units {
103+
for _, unit := range m.Units {
80104
for _, d := range unit.Documentation {
81105
unit.BuildContexts = append(unit.BuildContexts, internal.BuildContext{
82106
GOOS: d.GOOS,
83107
GOARCH: d.GOARCH,
84108
})
85109
}
86110
}
111+
}
87112

88-
ds.mu.Lock()
89-
defer ds.mu.Unlock()
90-
ds.loadedModules[fr.ModulePath] = fr.Module
91-
return nil
113+
// NewGOPATHModuleGetter returns a module getter that uses the GOPATH
114+
// environment variable to find the module with the given import path.
115+
func NewGOPATHModuleGetter(importPath string) (_ fetch.ModuleGetter, err error) {
116+
defer derrors.Wrap(&err, "NewGOPATHModuleGetter(%q)", importPath)
117+
118+
dir := getFullPath(importPath)
119+
if dir == "" {
120+
return nil, fmt.Errorf("path %s doesn't exist: %w", importPath, derrors.NotFound)
121+
}
122+
return fetch.NewDirectoryModuleGetter(importPath, dir)
92123
}
93124

94125
// getFullPath takes an import path, tests it relative to each GOPATH, and returns
@@ -111,51 +142,30 @@ func getFullPath(modulePath string) string {
111142
func (ds *DataSource) GetUnit(ctx context.Context, pathInfo *internal.UnitMeta, fields internal.FieldSet, bc internal.BuildContext) (_ *internal.Unit, err error) {
112143
defer derrors.Wrap(&err, "GetUnit(%q, %q)", pathInfo.Path, pathInfo.ModulePath)
113144

114-
modulepath := pathInfo.ModulePath
115-
path := pathInfo.Path
116-
117-
ds.mu.Lock()
118-
defer ds.mu.Unlock()
119-
if ds.loadedModules[modulepath] == nil {
120-
return nil, fmt.Errorf("%s not loaded: %w", modulepath, derrors.NotFound)
145+
module, err := ds.getModule(ctx, pathInfo.ModulePath, pathInfo.Version)
146+
if err != nil {
147+
return nil, err
121148
}
122-
123-
module := ds.loadedModules[modulepath]
124149
for _, unit := range module.Units {
125-
if unit.Path == path {
150+
if unit.Path == pathInfo.Path {
126151
return unit, nil
127152
}
128153
}
129154

130-
return nil, fmt.Errorf("%s not found: %w", path, derrors.NotFound)
155+
return nil, fmt.Errorf("import path %s not found in module %s: %w", pathInfo.Path, pathInfo.ModulePath, derrors.NotFound)
131156
}
132157

133158
// GetUnitMeta returns information about a path.
134159
func (ds *DataSource) GetUnitMeta(ctx context.Context, path, requestedModulePath, requestedVersion string) (_ *internal.UnitMeta, err error) {
135160
defer derrors.Wrap(&err, "GetUnitMeta(%q, %q, %q)", path, requestedModulePath, requestedVersion)
136161

137-
if requestedModulePath == internal.UnknownModulePath {
138-
requestedModulePath, err = ds.findModule(path)
139-
if err != nil {
140-
return nil, err
141-
}
142-
}
143-
144-
ds.mu.Lock()
145-
module := ds.loadedModules[requestedModulePath]
146-
ds.mu.Unlock()
147-
if module == nil {
148-
return nil, fmt.Errorf("%s not loaded: %w", requestedModulePath, derrors.NotFound)
162+
module, err := ds.findModule(ctx, path, requestedModulePath, requestedVersion)
163+
if err != nil {
164+
return nil, err
149165
}
150-
151166
um := &internal.UnitMeta{
152-
Path: path,
153-
ModuleInfo: internal.ModuleInfo{
154-
ModulePath: requestedModulePath,
155-
Version: fetch.LocalVersion,
156-
CommitTime: fetch.LocalCommitTime,
157-
IsRedistributable: module.IsRedistributable,
158-
},
167+
Path: path,
168+
ModuleInfo: module.ModuleInfo,
159169
}
160170

161171
for _, u := range module.Units {
@@ -168,23 +178,26 @@ func (ds *DataSource) GetUnitMeta(ctx context.Context, path, requestedModulePath
168178
return um, nil
169179
}
170180

171-
// findModule finds the longest module path in loadedModules containing the given
172-
// package path. It iteratively checks parent directories to find an import path.
173-
// Returns an error if no module is found.
174-
func (ds *DataSource) findModule(pkgPath string) (_ string, err error) {
181+
// findModule finds the module with longest module path containing the given
182+
// package path. It returns an error if no module is found.
183+
func (ds *DataSource) findModule(ctx context.Context, pkgPath, modulePath, version string) (_ *internal.Module, err error) {
175184
defer derrors.Wrap(&err, "findModule(%q)", pkgPath)
176185

177-
pkgPath = strings.TrimLeft(pkgPath, "/")
186+
if modulePath != internal.UnknownModulePath {
187+
return ds.getModule(ctx, modulePath, version)
188+
}
178189

179-
ds.mu.Lock()
180-
defer ds.mu.Unlock()
190+
pkgPath = strings.TrimLeft(pkgPath, "/")
181191
for modulePath := pkgPath; modulePath != "" && modulePath != "."; modulePath = path.Dir(modulePath) {
182-
if ds.loadedModules[modulePath] != nil {
183-
return modulePath, nil
192+
m, err := ds.getModule(ctx, modulePath, version)
193+
if err == nil {
194+
return m, nil
195+
}
196+
if !errors.Is(err, derrors.NotFound) {
197+
return nil, err
184198
}
185199
}
186-
187-
return "", fmt.Errorf("%s not loaded: %w", pkgPath, derrors.NotFound)
200+
return nil, fmt.Errorf("could not find module for import path %s: %w", pkgPath, derrors.NotFound)
188201
}
189202

190203
// GetLatestInfo is not implemented.

0 commit comments

Comments
 (0)