internal/fetch: use a ModuleGetter for FetchLocalModule
For golang/go#47780
Change-Id: I1259649c8a1d452ffad5e8a2a92943801a24b306
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/343212
Trust: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Julie Qiu <julie@golang.org>
diff --git a/internal/fetch/fetch.go b/internal/fetch/fetch.go
index 54496a5..b54dd6f 100644
--- a/internal/fetch/fetch.go
+++ b/internal/fetch/fetch.go
@@ -94,10 +94,12 @@
// Info returns basic information about the module.
Info(ctx context.Context, path, version string) (*proxy.VersionInfo, error)
// Mod returns the contents of the module's go.mod file.
+ // If the file does not exist, it returns a synthesized one.
Mod(ctx context.Context, path, version string) ([]byte, error)
// Zip returns a reader for the module's zip file.
Zip(ctx context.Context, path, version string) (*zip.Reader, error)
// ZipSize returns the approximate size of the zip file in bytes.
+ // It is used only for load-shedding.
ZipSize(ctx context.Context, path, version string) (int64, error)
}
@@ -160,6 +162,19 @@
}
func fetchModule(ctx context.Context, fr *FetchResult, mg ModuleGetter, sourceClient *source.Client) (*FetchInfo, error) {
+ // If the module path is empty, get it from the go.mod file. This should only happen when fetching
+ // a local module.
+ if fr.ModulePath == "" {
+ goModBytes, err := mg.Mod(ctx, fr.ModulePath, fr.RequestedVersion)
+ if err != nil {
+ return nil, err
+ }
+ fr.ModulePath = modfile.ModulePath(goModBytes)
+ if fr.ModulePath == "" {
+ return nil, fmt.Errorf("go.mod has no module path: %w", derrors.BadModule)
+ }
+ }
+
info, err := GetInfo(ctx, fr.ModulePath, fr.RequestedVersion, mg)
if err != nil {
return nil, err
diff --git a/internal/fetch/fetchlocal.go b/internal/fetch/fetchlocal.go
index 84340db..be9cb9e 100644
--- a/internal/fetch/fetchlocal.go
+++ b/internal/fetch/fetchlocal.go
@@ -8,17 +8,17 @@
"archive/zip"
"bytes"
"context"
+ "errors"
"fmt"
"io"
"io/ioutil"
- "net/http"
"os"
"path/filepath"
"strings"
"time"
- "golang.org/x/mod/modfile"
"golang.org/x/pkgsite/internal/derrors"
+ "golang.org/x/pkgsite/internal/proxy"
"golang.org/x/pkgsite/internal/source"
)
@@ -29,96 +29,58 @@
LocalCommitTime = time.Time{}
)
+// A directoryModuleGetter is a ModuleGetter whose source is a directory in the file system that contains
+// a module's files.
+type directoryModuleGetter struct {
+ dir string // the directory containing the module's files
+}
+
+// NewDirectoryModuleGetter returns a ModuleGetter for reading a module from a directory.
+func NewDirectoryModuleGetter(dir string) ModuleGetter {
+ return &directoryModuleGetter{dir: dir}
+}
+
+// Info returns basic information about the module.
+func (g *directoryModuleGetter) Info(ctx context.Context, path, version string) (*proxy.VersionInfo, error) {
+ return &proxy.VersionInfo{
+ Version: LocalVersion,
+ Time: LocalCommitTime,
+ }, nil
+}
+
+// Mod returns the contents of the module's go.mod file.
+// If the file does not exist, it returns a synthesized one.
+func (g *directoryModuleGetter) Mod(ctx context.Context, path, version string) ([]byte, error) {
+ data, err := ioutil.ReadFile(filepath.Join(g.dir, "go.mod"))
+ if errors.Is(err, os.ErrNotExist) {
+ if path == "" {
+ return nil, fmt.Errorf("no module path: %w", derrors.BadModule)
+ }
+ return []byte(fmt.Sprintf("module %s\n", path)), nil
+ }
+ return data, err
+}
+
+// Zip returns a reader for the module's zip file.
+func (g *directoryModuleGetter) Zip(ctx context.Context, path, version string) (*zip.Reader, error) {
+ return createZipReader(g.dir, path, LocalVersion)
+}
+
+// ZipSize returns the approximate size of the zip file in bytes.
+func (g *directoryModuleGetter) ZipSize(ctx context.Context, path, version string) (int64, error) {
+ return 0, errors.New("directoryModuleGetter.ZipSize unimplemented")
+}
+
// FetchLocalModule fetches a module from a local directory and process its contents
// to return an internal.Module and other related information. modulePath is not necessary
// if the module has a go.mod file, but if both exist, then they must match.
// FetchResult.Error should be checked to verify that the fetch succeeded. Even if the
// error is non-nil the result may contain useful data.
func FetchLocalModule(ctx context.Context, modulePath, localPath string, sourceClient *source.Client) *FetchResult {
- fr := &FetchResult{
- ModulePath: modulePath,
- RequestedVersion: LocalVersion,
- ResolvedVersion: LocalVersion,
- Defer: func() {},
- }
-
- var fi *FetchInfo
- defer func() {
- if fr.Error != nil {
- derrors.Wrap(&fr.Error, "FetchLocalModule(%q, %q)", modulePath, localPath)
- fr.Status = derrors.ToStatus(fr.Error)
- }
- if fr.Status == 0 {
- fr.Status = http.StatusOK
- }
- if fi != nil {
- finishFetchInfo(fi, fr.Status, fr.Error)
- }
- }()
-
- info, err := os.Stat(localPath)
- if err != nil {
- fr.Error = fmt.Errorf("%s: %w", err.Error(), derrors.NotFound)
- return fr
- }
-
- if !info.IsDir() {
- fr.Error = fmt.Errorf("%s not a directory: %w", localPath, derrors.NotFound)
- return fr
- }
-
- fi = &FetchInfo{
- ModulePath: fr.ModulePath,
- Version: fr.ResolvedVersion,
- Start: time.Now(),
- }
- startFetchInfo(fi)
-
- // Options for module path are either the modulePath parameter or go.mod file.
- // Accepted cases:
- // - Both are given and are the same.
- // - Only one is given. Note that: if modulePath is given and there's no go.mod
- // file, then the package is assumed to be using GOPATH.
- // Errors:
- // - Both are given and are different.
- // - Neither is given.
- if goModBytes, err := ioutil.ReadFile(filepath.Join(localPath, "go.mod")); err != nil {
- fr.GoModPath = modulePath
- fr.HasGoMod = false
- } else {
- fr.HasGoMod = true
- fr.GoModPath = modfile.ModulePath(goModBytes)
- if fr.GoModPath != modulePath && modulePath != "" {
- fr.Error = fmt.Errorf("module path=%s, go.mod path=%s: %w", modulePath, fr.GoModPath, derrors.AlternativeModule)
- return fr
- }
- }
-
- if fr.GoModPath == "" {
- fr.Error = fmt.Errorf("no module path: %w", derrors.BadModule)
- return fr
- }
- fr.ModulePath = fr.GoModPath
-
- zipReader, err := createZipReader(localPath, fr.GoModPath, LocalVersion)
- if err != nil {
- fr.Error = fmt.Errorf("couldn't create a zip: %s, %w", err.Error(), derrors.BadModule)
- return fr
- }
-
- mod, pvs, err := processZipFile(ctx, fr.GoModPath, LocalVersion, LocalVersion, LocalCommitTime, zipReader, sourceClient)
- if err != nil {
- fr.Error = err
- return fr
- }
- mod.HasGoMod = fr.HasGoMod
- fr.Module = mod
- fr.PackageVersionStates = pvs
- fr.Module.SourceInfo = nil // version is not known, so even if info is found it most likely is wrong.
- for _, state := range fr.PackageVersionStates {
- if state.Status != http.StatusOK {
- fr.Status = derrors.ToStatus(derrors.HasIncompletePackages)
- }
+ g := NewDirectoryModuleGetter(localPath)
+ fr := FetchModule(ctx, modulePath, LocalVersion, g, sourceClient)
+ if fr.Error != nil {
+ fr.Error = fmt.Errorf("FetchLocalModule(%q, %q): %w", modulePath, localPath, fr.Error)
}
return fr
}