blob: b5f69e11716e5c8597766eabb78c71685100bd4a [file] [log] [blame]
// Copyright 2021 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 fetch
import (
"context"
"errors"
"golang.org/x/pkgsite/internal"
"golang.org/x/pkgsite/internal/derrors"
"golang.org/x/pkgsite/internal/log"
"golang.org/x/pkgsite/internal/proxy"
"golang.org/x/pkgsite/internal/version"
)
// RawLatestInfo uses the proxy to get information about the raw latest version
// of modulePath.
//
// The hasGoMod function that is passed in should check if version v of the
// module has a go.mod file, using a source other than the proxy (e.g. a
// database). If it doesn't have enough information to decide, it should return
// an error that wraps derrors.NotFound.
func RawLatestInfo(ctx context.Context, modulePath string, prox *proxy.Client, hasGoMod func(v string) (bool, error)) (_ *internal.RawLatestInfo, err error) {
defer derrors.WrapStack(&err, "RawLatestInfo(%q)", modulePath)
v, err := fetchRawLatestVersion(ctx, modulePath, prox, hasGoMod)
if err != nil {
return nil, err
}
modBytes, err := prox.Mod(ctx, modulePath, v)
if err != nil {
return nil, err
}
return internal.NewRawLatestInfo(modulePath, v, modBytes)
}
// fetchRawLatestVersion uses the proxy to determine the latest
// version of a module independent of retractions or other modifications.
//
// This meaning of "latest" is defined at https://golang.org/ref/mod#version-queries.
// That definition does not deal with a subtlety involving
// incompatible versions. The actual definition is embodied in the go command's
// queryMatcher.filterVersions method. This code is a rewrite of that method at Go
// version 1.16
// (https://go.googlesource.com/go/+/refs/tags/go1.16/src/cmd/go/internal/modload/query.go#441).
func fetchRawLatestVersion(ctx context.Context, modulePath string, prox *proxy.Client, hasGoMod func(v string) (bool, error)) (v string, err error) {
defer derrors.WrapStack(&err, "fetchRawLatestVersion(%q)", modulePath)
defer func() {
log.Debugf(ctx, "fetchRawLatestVersion(%q) => (%q, %v)", modulePath, v, err)
}()
if hasGoMod == nil {
hasGoMod = func(string) (bool, error) { return false, derrors.NotFound }
}
// Prefer tagged versions to pseudoversions. Get them from the proxy's list endpoint.
taggedVersions, err := prox.Versions(ctx, modulePath)
if err != nil {
return "", err
}
// If there are no tagged versions, use the proxy's @latest endpoint.
if len(taggedVersions) == 0 {
latestInfo, err := prox.Info(ctx, modulePath, internal.LatestVersion)
if err != nil {
return "", err
}
return latestInfo.Version, nil
}
// Find the latest of all tagged versions.
latest := version.Latest(taggedVersions, nil)
// If the latest is a compatible version, use it.
if !version.IsIncompatible(latest) {
return latest, nil
}
// The latest version is incompatible. If there is a go.mod file at the
// latest compatible version, assume the module author has adopted proper
// versioning, and use that latest compatible version. Otherwise, use this
// incompatible version.
latestCompat := version.Latest(taggedVersions, func(v string) bool { return !version.IsIncompatible(v) })
if latestCompat == "" {
// No compatible versions; use the latest (incompatible) version.
return latest, nil
}
latestCompatHasGoMod, err := hasGoMod(latestCompat)
if err != nil {
if !errors.Is(err, derrors.NotFound) {
return "", err
}
// hasGoMod doesn't know; download the zip.
zr, err := prox.Zip(ctx, modulePath, latestCompat)
if err != nil {
return "", err
}
latestCompatHasGoMod = hasGoModFile(zr, modulePath, latestCompat)
}
if latestCompatHasGoMod {
return latestCompat, nil
} else {
return latest, nil
}
}