cmd/go/internal/modget: resolve paths at the requested versions

Previously, we resolved each argument to 'go get' to a package path or
module path based on what was in the build list at existing versions,
even if the argument specified a different version explicitly. That
resulted in bugs like #37438, in which we variously resolved the wrong
version or guessed the wrong argument type for what is unambiguously a
package argument at the requested version.

We were also using a two-step upgrade/downgrade algorithm, which could
not only upgrade more that is strictly necessary, but could also
unintentionally upgrade *above* the requested versions during the
downgrade step.

This change instead uses an iterative approach, with an explicit
disambiguation step for the (rare) cases where an argument could match
the same package path in multiple modules. We use a hook in the
package loader to halt package loading as soon as an incorrect version
is found — preventing over-resolving — and verify that the result
after applying downgrades successfully obtained the requested versions
of all modules.

Making 'go get' be correct and usable is especially important now that
we are defaulting to read-only mode (#40728), for which we are
recommending 'go get' more heavily.

While I'm in here refactoring, I'm also reworking the API boundary
between the modget and modload packages. Previously, the modget
package edited the build list directly, and the modload package
accepted the edited build list without validation. For lazy loading
(#36460), the modload package will need to maintain additional
metadata about the requirement graph, so it needs tighter control over
the changes to the build list.

As of this change, modget no longer invokes MVS directly, but instead
goes through the modload package. The resulting API gives clearer
reasons in case of updates, which we can use to emit more useful
errors.

Fixes #37438
Updates #36460
Updates #40728

Change-Id: I596f0020f3795870dec258147e6fc26a3292c93a
Reviewed-on: https://go-review.googlesource.com/c/go/+/263267
Trust: Bryan C. Mills <bcmills@google.com>
Trust: Jay Conrod <jayconrod@google.com>
Run-TryBot: Bryan C. Mills <bcmills@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
This commit is contained in:
Bryan C. Mills 2020-09-18 12:10:58 -04:00
parent 67bf1c9979
commit 06538fa723
42 changed files with 2668 additions and 1009 deletions

View File

@ -110,6 +110,16 @@ Do not send CLs removing the interior tags from such phrases.
See <code>go</code> <code>help</code> <code>environment</code> for details.
</p>
<h4 id="go-get"><code>go</code> <code>get</code></h4>
<p><!-- golang.org/cl/263267 -->
<code>go</code> <code>get</code> <code>example.com/mod@patch</code> now
requires that some version of <code>example.com/mod</code> already be
required by the main module.
(However, <code>go</code> <code>get</code> <code>-u=patch</code> continues
to patch even newly-added dependencies.)
</p>
<h4 id="all-pattern">The <code>all</code> pattern</h4>
<p><!-- golang.org/cl/240623 -->

View File

@ -617,15 +617,15 @@
// dependency should be removed entirely, downgrading or removing modules
// depending on it as needed.
//
// The version suffix @latest explicitly requests the latest minor release of the
// The version suffix @latest explicitly requests the latest minor release ofthe
// module named by the given path. The suffix @upgrade is like @latest but
// will not downgrade a module if it is already required at a revision or
// pre-release version newer than the latest released version. The suffix
// @patch requests the latest patch release: the latest released version
// with the same major and minor version numbers as the currently required
// version. Like @upgrade, @patch will not downgrade a module already required
// at a newer version. If the path is not already required, @upgrade and @patch
// are equivalent to @latest.
// at a newer version. If the path is not already required, @upgrade is
// equivalent to @latest, and @patch is disallowed.
//
// Although get defaults to using the latest version of the module containing
// a named package, it does not use the latest version of that module's

File diff suppressed because it is too large Load Diff

View File

@ -1,202 +0,0 @@
// Copyright 2020 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 modget
import (
"context"
"errors"
"cmd/go/internal/base"
"cmd/go/internal/modload"
"cmd/go/internal/mvs"
"golang.org/x/mod/module"
)
// An upgrader adapts an underlying mvs.Reqs to apply an
// upgrade policy to a list of targets and their dependencies.
type upgrader struct {
mvs.Reqs
// cmdline maps a module path to a query made for that module at a
// specific target version. Each query corresponds to a module
// matched by a command line argument.
cmdline map[string]*query
// upgrade is a set of modules providing dependencies of packages
// matched by command line arguments. If -u or -u=patch is set,
// these modules are upgraded accordingly.
upgrade map[string]bool
}
// newUpgrader creates an upgrader. cmdline contains queries made at
// specific versions for modules matched by command line arguments. pkgs
// is the set of packages matched by command line arguments. If -u or -u=patch
// is set, modules providing dependencies of pkgs are upgraded accordingly.
func newUpgrader(cmdline map[string]*query, pkgs map[string]bool) *upgrader {
u := &upgrader{
Reqs: modload.Reqs(),
cmdline: cmdline,
}
if getU != "" {
u.upgrade = make(map[string]bool)
// Traverse package import graph.
// Initialize work queue with root packages.
seen := make(map[string]bool)
var work []string
add := func(path string) {
if !seen[path] {
seen[path] = true
work = append(work, path)
}
}
for pkg := range pkgs {
add(pkg)
}
for len(work) > 0 {
pkg := work[0]
work = work[1:]
m := modload.PackageModule(pkg)
u.upgrade[m.Path] = true
// testImports is empty unless test imports were actually loaded,
// i.e., -t was set or "all" was one of the arguments.
imports, testImports := modload.PackageImports(pkg)
for _, imp := range imports {
add(imp)
}
for _, imp := range testImports {
add(imp)
}
}
}
return u
}
// Required returns the requirement list for m.
// For the main module, we override requirements with the modules named
// one the command line, and we include new requirements. Otherwise,
// we defer to u.Reqs.
func (u *upgrader) Required(m module.Version) ([]module.Version, error) {
rs, err := u.Reqs.Required(m)
if err != nil {
return nil, err
}
if m != modload.Target {
return rs, nil
}
overridden := make(map[string]bool)
for i, m := range rs {
if q := u.cmdline[m.Path]; q != nil && q.m.Version != "none" {
rs[i] = q.m
overridden[q.m.Path] = true
}
}
for _, q := range u.cmdline {
if !overridden[q.m.Path] && q.m.Path != modload.Target.Path && q.m.Version != "none" {
rs = append(rs, q.m)
}
}
return rs, nil
}
// Upgrade returns the desired upgrade for m.
//
// If m was requested at a specific version on the command line, then
// Upgrade returns that version.
//
// If -u is set and m provides a dependency of a package matched by
// command line arguments, then Upgrade may provider a newer tagged version.
// If m is a tagged version, then Upgrade will return the latest tagged
// version (with the same minor version number if -u=patch).
// If m is a pseudo-version, then Upgrade returns the latest tagged version
// only if that version has a time-stamp newer than m. This special case
// prevents accidental downgrades when already using a pseudo-version
// newer than the latest tagged version.
//
// If none of the above cases apply, then Upgrade returns m.
func (u *upgrader) Upgrade(m module.Version) (module.Version, error) {
// Allow pkg@vers on the command line to override the upgrade choice v.
// If q's version is < m.Version, then we're going to downgrade anyway,
// and it's cleaner to avoid moving back and forth and picking up
// extraneous other newer dependencies.
// If q's version is > m.Version, then we're going to upgrade past
// m.Version anyway, and again it's cleaner to avoid moving back and forth
// picking up extraneous other newer dependencies.
if q := u.cmdline[m.Path]; q != nil {
return q.m, nil
}
if !u.upgrade[m.Path] {
// Not involved in upgrade. Leave alone.
return m, nil
}
// Run query required by upgrade semantics.
// Note that Query "latest" is not the same as using repo.Latest,
// which may return a pseudoversion for the latest commit.
// Query "latest" returns the newest tagged version or the newest
// prerelease version if there are no non-prereleases, or repo.Latest
// if there aren't any tagged versions.
// If we're querying "upgrade" or "patch", Query will compare the current
// version against the chosen version and will return the current version
// if it is newer.
info, err := modload.Query(context.TODO(), m.Path, string(getU), m.Version, checkAllowedOrCurrent(m.Version))
if err != nil {
// Report error but return m, to let version selection continue.
// (Reporting the error will fail the command at the next base.ExitIfErrors.)
// Special case: if the error is for m.Version itself and m.Version has a
// replacement, then keep it and don't report the error: the fact that the
// version is invalid is likely the reason it was replaced to begin with.
var vErr *module.InvalidVersionError
if errors.As(err, &vErr) && vErr.Version == m.Version && modload.Replacement(m).Path != "" {
return m, nil
}
// Special case: if the error is "no matching versions" then don't
// even report the error. Because Query does not consider pseudo-versions,
// it may happen that we have a pseudo-version but during -u=patch
// the query v0.0 matches no versions (not even the one we're using).
var noMatch *modload.NoMatchingVersionError
if !errors.As(err, &noMatch) {
base.Errorf("go get: upgrading %s@%s: %v", m.Path, m.Version, err)
}
return m, nil
}
if info.Version != m.Version {
logOncef("go: %s %s => %s", m.Path, getU, info.Version)
}
return module.Version{Path: m.Path, Version: info.Version}, nil
}
// buildListForLostUpgrade returns the build list for the module graph
// rooted at lost. Unlike mvs.BuildList, the target module (lost) is not
// treated specially. The returned build list may contain a newer version
// of lost.
//
// buildListForLostUpgrade is used after a downgrade has removed a module
// requested at a specific version. This helps us understand the requirements
// implied by each downgrade.
func buildListForLostUpgrade(lost module.Version, reqs mvs.Reqs) ([]module.Version, error) {
return mvs.BuildList(lostUpgradeRoot, &lostUpgradeReqs{Reqs: reqs, lost: lost})
}
var lostUpgradeRoot = module.Version{Path: "lost-upgrade-root", Version: ""}
type lostUpgradeReqs struct {
mvs.Reqs
lost module.Version
}
func (r *lostUpgradeReqs) Required(mod module.Version) ([]module.Version, error) {
if mod == lostUpgradeRoot {
return []module.Version{r.lost}, nil
}
return r.Reqs.Required(mod)
}

View File

@ -0,0 +1,353 @@
// Copyright 2020 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 modget
import (
"fmt"
"path/filepath"
"regexp"
"strings"
"sync"
"cmd/go/internal/base"
"cmd/go/internal/modload"
"cmd/go/internal/search"
"cmd/go/internal/str"
"golang.org/x/mod/module"
)
// A query describes a command-line argument and the modules and/or packages
// to which that argument may resolve..
type query struct {
// raw is the original argument, to be printed in error messages.
raw string
// rawVersion is the portion of raw corresponding to version, if any
rawVersion string
// pattern is the part of the argument before "@" (or the whole argument
// if there is no "@"), which may match either packages (preferred) or
// modules (if no matching packages).
//
// The pattern may also be "-u", for the synthetic query representing the -u
// (“upgrade”)flag.
pattern string
// patternIsLocal indicates whether pattern is restricted to match only paths
// local to the main module, such as absolute filesystem paths or paths
// beginning with './'.
//
// A local pattern must resolve to one or more packages in the main module.
patternIsLocal bool
// version is the part of the argument after "@", or an implied
// "upgrade" or "patch" if there is no "@". version specifies the
// module version to get.
version string
// matchWildcard, if non-nil, reports whether pattern, which must be a
// wildcard (with the substring "..."), matches the given package or module
// path.
matchWildcard func(path string) bool
// canMatchWildcard, if non-nil, reports whether the module with the given
// path could lexically contain a package matching pattern, which must be a
// wildcard.
canMatchWildcardInModule func(mPath string) bool
// conflict is the first query identified as incompatible with this one.
// conflict forces one or more of the modules matching this query to a
// version that does not match version.
conflict *query
// candidates is a list of sets of alternatives for a path that matches (or
// contains packages that match) the pattern. The query can be resolved by
// choosing exactly one alternative from each set in the list.
//
// A path-literal query results in only one set: the path itself, which
// may resolve to either a package path or a module path.
//
// A wildcard query results in one set for each matching module path, each
// module for which the matching version contains at least one matching
// package, and (if no other modules match) one candidate set for the pattern
// overall if no existing match is identified in the build list.
//
// A query for pattern "all" results in one set for each package transitively
// imported by the main module.
//
// The special query for the "-u" flag results in one set for each
// otherwise-unconstrained package that has available upgrades.
candidates []pathSet
candidatesMu sync.Mutex
// pathSeen ensures that only one pathSet is added to the query per
// unique path.
pathSeen sync.Map
// resolved contains the set of modules whose versions have been determined by
// this query, in the order in which they were determined.
//
// The resolver examines the candidate sets for each query, resolving one
// module per candidate set in a way that attempts to avoid obvious conflicts
// between the versions resolved by different queries.
resolved []module.Version
// matchesPackages is true if the resolved modules provide at least one
// package mathcing q.pattern.
matchesPackages bool
}
// A pathSet describes the possible options for resolving a specific path
// to a package and/or module.
type pathSet struct {
// path is a package (if "all" or "-u" or a non-wildcard) or module (if
// wildcard) path that could be resolved by adding any of the modules in this
// set. For a wildcard pattern that so far matches no packages, the path is
// the wildcard pattern itself.
//
// Each path must occur only once in a query's candidate sets, and the path is
// added implicitly to each pathSet returned to pathOnce.
path string
// pkgMods is a set of zero or more modules, each of which contains the
// package with the indicated path. Due to the requirement that imports be
// unambiguous, only one such module can be in the build list, and all others
// must be excluded.
pkgMods []module.Version
// mod is either the zero Version, or a module that does not contain any
// packages matching the query but for which the module path itself
// matches the query pattern.
//
// We track this module separately from pkgMods because, all else equal, we
// prefer to match a query to a package rather than just a module. Also,
// unlike the modules in pkgMods, this module does not inherently exclude
// any other module in pkgMods.
mod module.Version
err error
}
// errSet returns a pathSet containing the given error.
func errSet(err error) pathSet { return pathSet{err: err} }
// newQuery returns a new query parsed from the raw argument,
// which must be either path or path@version.
func newQuery(raw string) (*query, error) {
pattern := raw
rawVers := ""
if i := strings.Index(raw, "@"); i >= 0 {
pattern, rawVers = raw[:i], raw[i+1:]
if strings.Contains(rawVers, "@") || rawVers == "" {
return nil, fmt.Errorf("invalid module version syntax %q", raw)
}
}
// If no version suffix is specified, assume @upgrade.
// If -u=patch was specified, assume @patch instead.
version := rawVers
if version == "" {
if getU.version == "" {
version = "upgrade"
} else {
version = getU.version
}
}
q := &query{
raw: raw,
rawVersion: rawVers,
pattern: pattern,
patternIsLocal: filepath.IsAbs(pattern) || search.IsRelativePath(pattern),
version: version,
}
if strings.Contains(q.pattern, "...") {
q.matchWildcard = search.MatchPattern(q.pattern)
q.canMatchWildcardInModule = search.TreeCanMatchPattern(q.pattern)
}
if err := q.validate(); err != nil {
return q, err
}
return q, nil
}
// validate reports a non-nil error if q is not sensible and well-formed.
func (q *query) validate() error {
if q.patternIsLocal {
if q.rawVersion != "" {
return fmt.Errorf("can't request explicit version %q of path %q in main module", q.rawVersion, q.pattern)
}
return nil
}
if q.pattern == "all" {
// If there is no main module, "all" is not meaningful.
if !modload.HasModRoot() {
return fmt.Errorf(`cannot match "all": working directory is not part of a module`)
}
if !versionOkForMainModule(q.version) {
// TODO(bcmills): "all@none" seems like a totally reasonable way to
// request that we remove all module requirements, leaving only the main
// module and standard library. Perhaps we should implement that someday.
return &modload.QueryMatchesMainModuleError{
Pattern: q.pattern,
Query: q.version,
}
}
}
if search.IsMetaPackage(q.pattern) && q.pattern != "all" {
if q.pattern != q.raw {
return fmt.Errorf("can't request explicit version of standard-library pattern %q", q.pattern)
}
}
return nil
}
// String returns the original argument from which q was parsed.
func (q *query) String() string { return q.raw }
// ResolvedString returns a string describing m as a resolved match for q.
func (q *query) ResolvedString(m module.Version) string {
if m.Path != q.pattern {
if m.Version != q.version {
return fmt.Sprintf("%v (matching %s@%s)", m, q.pattern, q.version)
}
return fmt.Sprintf("%v (matching %v)", m, q)
}
if m.Version != q.version {
return fmt.Sprintf("%s@%s (%s)", q.pattern, q.version, m.Version)
}
return q.String()
}
// isWildcard reports whether q is a pattern that can match multiple paths.
func (q *query) isWildcard() bool {
return q.matchWildcard != nil || (q.patternIsLocal && strings.Contains(q.pattern, "..."))
}
// matchesPath reports whether the given path matches q.pattern.
func (q *query) matchesPath(path string) bool {
if q.matchWildcard != nil {
return q.matchWildcard(path)
}
return path == q.pattern
}
// canMatchInModule reports whether the given module path can potentially
// contain q.pattern.
func (q *query) canMatchInModule(mPath string) bool {
if q.canMatchWildcardInModule != nil {
return q.canMatchWildcardInModule(mPath)
}
return str.HasPathPrefix(q.pattern, mPath)
}
// pathOnce invokes f to generate the pathSet for the given path,
// if one is still needed.
//
// Note that, unlike sync.Once, pathOnce does not guarantee that a concurrent
// call to f for the given path has completed on return.
//
// pathOnce is safe for concurrent use by multiple goroutines, but note that
// multiple concurrent calls will result in the sets being added in
// nondeterministic order.
func (q *query) pathOnce(path string, f func() pathSet) {
if _, dup := q.pathSeen.LoadOrStore(path, nil); dup {
return
}
cs := f()
if len(cs.pkgMods) > 0 || cs.mod != (module.Version{}) || cs.err != nil {
cs.path = path
q.candidatesMu.Lock()
q.candidates = append(q.candidates, cs)
q.candidatesMu.Unlock()
}
}
// reportError logs err concisely using base.Errorf.
func reportError(q *query, err error) {
errStr := err.Error()
// If err already mentions all of the relevant parts of q, just log err to
// reduce stutter. Otherwise, log both q and err.
//
// TODO(bcmills): Use errors.As to unpack these errors instead of parsing
// strings with regular expressions.
patternRE := regexp.MustCompile("(?m)(?:[ \t(\"`]|^)" + regexp.QuoteMeta(q.pattern) + "(?:[ @:)\"`]|$)")
if patternRE.MatchString(errStr) {
if q.rawVersion == "" {
base.Errorf("go get: %s", errStr)
return
}
versionRE := regexp.MustCompile("(?m)(?:[ @(\"`]|^)" + regexp.QuoteMeta(q.version) + "(?:[ :)\"`]|$)")
if versionRE.MatchString(errStr) {
base.Errorf("go get: %s", errStr)
return
}
}
base.Errorf("go get %s: %s", q, errStr)
}
func reportConflict(pq *query, m module.Version, conflict versionReason) {
if pq.conflict != nil {
// We've already reported a conflict for the proposed query.
// Don't report it again, even if it has other conflicts.
return
}
pq.conflict = conflict.reason
proposed := versionReason{
version: m.Version,
reason: pq,
}
if pq.isWildcard() && !conflict.reason.isWildcard() {
// Prefer to report the specific path first and the wildcard second.
proposed, conflict = conflict, proposed
}
reportError(pq, &conflictError{
mPath: m.Path,
proposed: proposed,
conflict: conflict,
})
}
type conflictError struct {
mPath string
proposed versionReason
conflict versionReason
}
func (e *conflictError) Error() string {
argStr := func(q *query, v string) string {
if v != q.version {
return fmt.Sprintf("%s@%s (%s)", q.pattern, q.version, v)
}
return q.String()
}
pq := e.proposed.reason
rq := e.conflict.reason
modDetail := ""
if e.mPath != pq.pattern {
modDetail = fmt.Sprintf("for module %s, ", e.mPath)
}
return fmt.Sprintf("%s%s conflicts with %s",
modDetail,
argStr(pq, e.proposed.version),
argStr(rq, e.conflict.version))
}
func versionOkForMainModule(version string) bool {
return version == "upgrade" || version == "patch"
}

View File

@ -12,6 +12,7 @@ import (
"context"
"fmt"
"os"
"strings"
"golang.org/x/mod/module"
)
@ -27,6 +28,11 @@ import (
//
var buildList []module.Version
// capVersionSlice returns s with its cap reduced to its length.
func capVersionSlice(s []module.Version) []module.Version {
return s[:len(s):len(s)]
}
// LoadAllModules loads and returns the list of modules matching the "all"
// module pattern, starting with the Target module and in a deterministic
// (stable) order, without loading any packages.
@ -35,21 +41,21 @@ var buildList []module.Version
// LoadAllModules need only be called if LoadPackages is not,
// typically in commands that care about modules but no particular package.
//
// The caller must not modify the returned list.
// The caller must not modify the returned list, but may append to it.
func LoadAllModules(ctx context.Context) []module.Version {
LoadModFile(ctx)
ReloadBuildList()
WriteGoMod()
return buildList
return capVersionSlice(buildList)
}
// LoadedModules returns the list of module requirements loaded or set by a
// previous call (typically LoadAllModules or LoadPackages), starting with the
// Target module and in a deterministic (stable) order.
//
// The caller must not modify the returned list.
// The caller must not modify the returned list, but may append to it.
func LoadedModules() []module.Version {
return buildList
return capVersionSlice(buildList)
}
// Selected returns the selected version of the module with the given path, or
@ -74,6 +80,147 @@ func SetBuildList(list []module.Version) {
buildList = append([]module.Version{}, list...)
}
// EditBuildList edits the global build list by first adding every module in add
// to the existing build list, then adjusting versions (and adding or removing
// requirements as needed) until every module in mustSelect is selected at the
// given version.
//
// (Note that the newly-added modules might not be selected in the resulting
// build list: they could be lower than existing requirements or conflict with
// versions in mustSelect.)
//
// After performing the requested edits, EditBuildList returns the updated build
// list.
//
// If the versions listed in mustSelect are mutually incompatible (due to one of
// the listed modules requiring a higher version of another), EditBuildList
// returns a *ConstraintError and leaves the build list in its previous state.
func EditBuildList(ctx context.Context, add, mustSelect []module.Version) error {
var upgraded = capVersionSlice(buildList)
if len(add) > 0 {
// First, upgrade the build list with any additions.
// In theory we could just append the additions to the build list and let
// mvs.Downgrade take care of resolving the upgrades too, but the
// diagnostics from Upgrade are currently much better in case of errors.
var err error
upgraded, err = mvs.Upgrade(Target, &mvsReqs{buildList: upgraded}, add...)
if err != nil {
return err
}
}
downgraded, err := mvs.Downgrade(Target, &mvsReqs{buildList: append(upgraded, mustSelect...)}, mustSelect...)
if err != nil {
return err
}
final, err := mvs.Upgrade(Target, &mvsReqs{buildList: downgraded}, mustSelect...)
if err != nil {
return err
}
selected := make(map[string]module.Version, len(final))
for _, m := range final {
selected[m.Path] = m
}
inconsistent := false
for _, m := range mustSelect {
s, ok := selected[m.Path]
if !ok {
if m.Version != "none" {
panic(fmt.Sprintf("internal error: mvs.BuildList lost %v", m))
}
continue
}
if s.Version != m.Version {
inconsistent = true
break
}
}
if !inconsistent {
buildList = final
return nil
}
// We overshot one or more of the modules in mustSelected, which means that
// Downgrade removed something in mustSelect because it conflicted with
// something else in mustSelect.
//
// Walk the requirement graph to find the conflict.
//
// TODO(bcmills): Ideally, mvs.Downgrade (or a replacement for it) would do
// this directly.
reqs := &mvsReqs{buildList: final}
reason := map[module.Version]module.Version{}
for _, m := range mustSelect {
reason[m] = m
}
queue := mustSelect[:len(mustSelect):len(mustSelect)]
for len(queue) > 0 {
var m module.Version
m, queue = queue[0], queue[1:]
required, err := reqs.Required(m)
if err != nil {
return err
}
for _, r := range required {
if _, ok := reason[r]; !ok {
reason[r] = reason[m]
queue = append(queue, r)
}
}
}
var conflicts []Conflict
for _, m := range mustSelect {
s, ok := selected[m.Path]
if !ok {
if m.Version != "none" {
panic(fmt.Sprintf("internal error: mvs.BuildList lost %v", m))
}
continue
}
if s.Version != m.Version {
conflicts = append(conflicts, Conflict{
Source: reason[s],
Dep: s,
Constraint: m,
})
}
}
return &ConstraintError{
Conflicts: conflicts,
}
}
// A ConstraintError describes inconsistent constraints in EditBuildList
type ConstraintError struct {
// Conflict lists the source of the conflict for each version in mustSelect
// that could not be selected due to the requirements of some other version in
// mustSelect.
Conflicts []Conflict
}
func (e *ConstraintError) Error() string {
b := new(strings.Builder)
b.WriteString("version constraints conflict:")
for _, c := range e.Conflicts {
fmt.Fprintf(b, "\n\t%v requires %v, but %v is requested", c.Source, c.Dep, c.Constraint)
}
return b.String()
}
// A Conflict documents that Source requires Dep, which conflicts with Constraint.
// (That is, Dep has the same module path as Constraint but a higher version.)
type Conflict struct {
Source module.Version
Dep module.Version
Constraint module.Version
}
// ReloadBuildList resets the state of loaded packages, then loads and returns
// the build list set in SetBuildList.
func ReloadBuildList() []module.Version {
@ -84,7 +231,7 @@ func ReloadBuildList() []module.Version {
listRoots: func() []string { return nil },
allClosesOverTests: index.allPatternClosesOverTests(), // but doesn't matter because the root list is empty.
})
return buildList
return capVersionSlice(buildList)
}
// TidyBuildList trims the build list to the minimal requirements needed to

View File

@ -188,9 +188,9 @@ func (e *invalidImportError) Unwrap() error {
// importFromBuildList can return an empty directory string, for fake packages
// like "C" and "unsafe".
//
// If the package cannot be found in the current build list,
// If the package cannot be found in buildList,
// importFromBuildList returns an *ImportMissingError.
func importFromBuildList(ctx context.Context, path string) (m module.Version, dir string, err error) {
func importFromBuildList(ctx context.Context, path string, buildList []module.Version) (m module.Version, dir string, err error) {
if strings.Contains(path, "@") {
return module.Version{}, "", fmt.Errorf("import path should not have @version")
}

View File

@ -141,6 +141,15 @@ type PackageOpts struct {
// if the flag is set to "readonly" (the default) or "vendor".
ResolveMissingImports bool
// AllowPackage, if non-nil, is called after identifying the module providing
// each package. If AllowPackage returns a non-nil error, that error is set
// for the package, and the imports and test of that package will not be
// loaded.
//
// AllowPackage may be invoked concurrently by multiple goroutines,
// and may be invoked multiple times for a given package path.
AllowPackage func(ctx context.Context, path string, mod module.Version) error
// LoadTests loads the test dependencies of each package matching a requested
// pattern. If ResolveMissingImports is also true, test dependencies will be
// resolved if missing.
@ -550,6 +559,7 @@ func DirImportPath(dir string) string {
func TargetPackages(ctx context.Context, pattern string) *search.Match {
// TargetPackages is relative to the main module, so ensure that the main
// module is a thing that can contain packages.
LoadModFile(ctx)
ModRoot()
m := search.NewMatch(pattern)
@ -1042,7 +1052,7 @@ func (ld *loader) load(pkg *loadPkg) {
return
}
pkg.mod, pkg.dir, pkg.err = importFromBuildList(context.TODO(), pkg.path)
pkg.mod, pkg.dir, pkg.err = importFromBuildList(context.TODO(), pkg.path, buildList)
if pkg.dir == "" {
return
}
@ -1058,6 +1068,11 @@ func (ld *loader) load(pkg *loadPkg) {
// to scanning source code for imports).
ld.applyPkgFlags(pkg, pkgInAll)
}
if ld.AllowPackage != nil {
if err := ld.AllowPackage(context.TODO(), pkg.path, pkg.mod); err != nil {
pkg.err = err
}
}
imports, testImports, err := scanDir(pkg.dir, ld.Tags)
if err != nil {

View File

@ -99,8 +99,16 @@ func versions(ctx context.Context, path string, allowed AllowedFunc) ([]string,
// Previous returns the tagged version of m.Path immediately prior to
// m.Version, or version "none" if no prior version is tagged.
//
// Since the version of Target is not found in the version list,
// it has no previous version.
func (*mvsReqs) Previous(m module.Version) (module.Version, error) {
// TODO(golang.org/issue/38714): thread tracing context through MVS.
if m == Target {
return module.Version{Path: m.Path, Version: "none"}, nil
}
list, err := versions(context.TODO(), m.Path, CheckAllowed)
if err != nil {
if errors.Is(err, os.ErrNotExist) {

View File

@ -47,8 +47,9 @@ import (
// with non-prereleases preferred over prereleases.
// - a repository commit identifier or tag, denoting that commit.
//
// current denotes the current version of the module; it may be "" if the
// current version is unknown or should not be considered. If query is
// current denotes the currently-selected version of the module; it may be
// "none" if no version is currently selected, or "" if the currently-selected
// version is unknown or should not be considered. If query is
// "upgrade" or "patch", current will be returned if it is a newer
// semantic version or a chronologically later pseudo-version than the
// version that would otherwise be chosen. This prevents accidental downgrades
@ -98,7 +99,7 @@ func queryProxy(ctx context.Context, proxy, path, query, current string, allowed
ctx, span := trace.StartSpan(ctx, "modload.queryProxy "+path+" "+query)
defer span.Done()
if current != "" && !semver.IsValid(current) {
if current != "" && current != "none" && !semver.IsValid(current) {
return nil, fmt.Errorf("invalid previous version %q", current)
}
if cfg.BuildMod == "vendor" {
@ -109,7 +110,7 @@ func queryProxy(ctx context.Context, proxy, path, query, current string, allowed
}
if path == Target.Path {
if query != "latest" && query != "upgrade" && query != "patch" {
if query != "upgrade" && query != "patch" {
return nil, &QueryMatchesMainModuleError{Pattern: path, Query: query}
}
if err := allowed(ctx, Target); err != nil {
@ -236,7 +237,7 @@ func queryProxy(ctx context.Context, proxy, path, query, current string, allowed
}
}
if (query == "upgrade" || query == "patch") && current != "" {
if (query == "upgrade" || query == "patch") && current != "" && current != "none" {
// "upgrade" and "patch" may stay on the current version if allowed.
if err := allowed(ctx, module.Version{Path: path, Version: current}); errors.Is(err, ErrDisallowed) {
return nil, err
@ -323,7 +324,7 @@ func newQueryMatcher(path string, query, current string, allowed AllowedFunc) (*
qm.mayUseLatest = true
case query == "upgrade":
if current == "" {
if current == "" || current == "none" {
qm.mayUseLatest = true
} else {
qm.mayUseLatest = modfetch.IsPseudoVersion(current)
@ -331,6 +332,9 @@ func newQueryMatcher(path string, query, current string, allowed AllowedFunc) (*
}
case query == "patch":
if current == "none" {
return nil, &NoPatchBaseError{path}
}
if current == "" {
qm.mayUseLatest = true
} else {
@ -554,6 +558,9 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
if i := strings.Index(pattern, "..."); i >= 0 {
base = pathpkg.Dir(pattern[:i+3])
if base == "." {
return nil, nil, &WildcardInFirstElementError{Pattern: pattern, Query: query}
}
match = func(mod module.Version, root string, isLocal bool) *search.Match {
m := search.NewMatch(pattern)
matchPackages(ctx, m, imports.AnyTags(), omitStd, []module.Version{mod})
@ -578,7 +585,7 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
if HasModRoot() {
m := match(Target, modRoot, true)
if len(m.Pkgs) > 0 {
if query != "latest" && query != "upgrade" && query != "patch" {
if query != "upgrade" && query != "patch" {
return nil, nil, &QueryMatchesPackagesInMainModuleError{
Pattern: pattern,
Query: query,
@ -598,7 +605,7 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
return nil, nil, err
}
if query != "latest" && query != "upgrade" && query != "patch" && matchPattern(Target.Path) {
if query != "upgrade" && query != "patch" && matchPattern(Target.Path) {
if err := allowed(ctx, Target); err == nil {
modOnly = &QueryResult{
Mod: Target,
@ -724,6 +731,7 @@ func queryPrefixModules(ctx context.Context, candidateModules []string, queryMod
var (
noPackage *PackageNotInModuleError
noVersion *NoMatchingVersionError
noPatchBase *NoPatchBaseError
notExistErr error
)
for _, r := range results {
@ -740,6 +748,10 @@ func queryPrefixModules(ctx context.Context, candidateModules []string, queryMod
if noVersion == nil {
noVersion = rErr
}
case *NoPatchBaseError:
if noPatchBase == nil {
noPatchBase = rErr
}
default:
if errors.Is(rErr, fs.ErrNotExist) {
if notExistErr == nil {
@ -771,6 +783,8 @@ func queryPrefixModules(ctx context.Context, candidateModules []string, queryMod
err = noPackage
case noVersion != nil:
err = noVersion
case noPatchBase != nil:
err = noPatchBase
case notExistErr != nil:
err = notExistErr
default:
@ -795,12 +809,34 @@ type NoMatchingVersionError struct {
func (e *NoMatchingVersionError) Error() string {
currentSuffix := ""
if (e.query == "upgrade" || e.query == "patch") && e.current != "" {
if (e.query == "upgrade" || e.query == "patch") && e.current != "" && e.current != "none" {
currentSuffix = fmt.Sprintf(" (current version is %s)", e.current)
}
return fmt.Sprintf("no matching versions for query %q", e.query) + currentSuffix
}
// A NoPatchBaseError indicates that Query was called with the query "patch"
// but with a current version of "" or "none".
type NoPatchBaseError struct {
path string
}
func (e *NoPatchBaseError) Error() string {
return fmt.Sprintf(`can't query version "patch" of module %s: no existing version is required`, e.path)
}
// A WildcardInFirstElementError indicates that a pattern passed to QueryPattern
// had a wildcard in its first path element, and therefore had no pattern-prefix
// modules to search in.
type WildcardInFirstElementError struct {
Pattern string
Query string
}
func (e *WildcardInFirstElementError) Error() string {
return fmt.Sprintf("no modules to query for %s@%s because first path element contains a wildcard", e.Pattern, e.Query)
}
// A PackageNotInModuleError indicates that QueryPattern found a candidate
// module at the requested version, but that module did not contain any packages
// matching the requested pattern.

View File

@ -156,7 +156,7 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
isLocal = true
} else {
var err error
needSum := true
const needSum = true
root, isLocal, err = fetch(ctx, mod, needSum)
if err != nil {
m.AddError(err)
@ -174,3 +174,46 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
return
}
// MatchInModule identifies the packages matching the given pattern within the
// given module version, which does not need to be in the build list or module
// requirement graph.
//
// If m is the zero module.Version, MatchInModule matches the pattern
// against the standard library (std and cmd) in GOROOT/src.
func MatchInModule(ctx context.Context, pattern string, m module.Version, tags map[string]bool) *search.Match {
match := search.NewMatch(pattern)
if m == (module.Version{}) {
matchPackages(ctx, match, tags, includeStd, nil)
}
LoadModFile(ctx)
if !match.IsLiteral() {
matchPackages(ctx, match, tags, omitStd, []module.Version{m})
return match
}
const needSum = true
root, isLocal, err := fetch(ctx, m, needSum)
if err != nil {
match.Errs = []error{err}
return match
}
dir, haveGoFiles, err := dirInModule(pattern, m.Path, root, isLocal)
if err != nil {
match.Errs = []error{err}
return match
}
if haveGoFiles {
if _, _, err := scanDir(dir, tags); err != imports.ErrNoGo {
// ErrNoGo indicates that the directory is not actually a Go package,
// perhaps due to the tags in use. Any other non-nil error indicates a
// problem with one or more of the Go source files, but such an error does
// not stop the package from existing, so it has no impact on matching.
match.Pkgs = []string{pattern}
}
}
return match
}

View File

@ -3,5 +3,7 @@
[!net] skip
env GO111MODULE=off
go get -u -n .../
! stderr 'duplicate loads of' # make sure old packages are removed from cache

View File

@ -2,7 +2,7 @@ env GO111MODULE=on
# explicit get should report errors about bad names
! go get appengine
stderr '^go get appengine: package appengine is not in GOROOT \(.*\)$'
stderr '^go get: malformed module path "appengine": missing dot in first path element$'
! go get x/y.z
stderr 'malformed module path "x/y.z": missing dot in first path element'

View File

@ -5,7 +5,7 @@ env GO111MODULE=on
# to resolve an external module.
cd dir
! go get .
stderr 'go get \.: path .* is not a package in module rooted at .*[/\\]dir$'
stderr 'go get: \. \(.*[/\\]dir\) is not a package in module rooted at .*[/\\]dir$'
! go list
! stderr 'cannot find module providing package'
stderr '^no Go files in '$WORK'[/\\]gopath[/\\]src[/\\]dir$'

View File

@ -101,11 +101,11 @@ stderr '^rsc.io/quote@v1.999.999: reading .*/v1.999.999.info: 404 Not Found$'
! go mod download -json bad/path
stdout '^\t"Error": "module bad/path: not a known dependency"'
# download main module returns an error
# download main module produces a warning or error
go mod download m
stderr '^go mod download: skipping argument m that resolves to the main module\n'
go mod download m@latest
stderr '^go mod download: skipping argument m@latest that resolves to the main module\n'
! go mod download m@latest
stderr 'm@latest: can''t request version "latest" of the main module \(m\)'
# download updates go.mod and populates go.sum
cd update

View File

@ -1,23 +1,22 @@
go mod tidy
cp go.mod go.mod.orig
# If there is no sensible *package* meaning for 'm/p', perhaps it should refer
# to *module* m/p?
# Today, it still seems to refer to the package.
# If there is no sensible *package* meaning for 'm/p', it should refer
# to *module* m/p.
! go get -d m/p@v0.1.0
stderr 'go get m/p@v0.1.0: module m/p@latest found \(v0.1.0, replaced by ./mp01\), but does not contain package m/p'
cmp go.mod.orig go.mod
# TODO(#37438): If we add v0.2.0 before this point, we end up (somehow!)
# resolving m/p@v0.1.0 as *both* a module and a package.
go get -d m/p # @latest
go list -m all
stdout '^m/p v0.3.0 '
! stdout '^m '
cp go.mod.orig go.mod
go mod edit -replace=m@v0.2.0=./m02
go mod edit -replace=m/p@v0.2.0=./mp02
# The argument 'm/p' in 'go get m/p' refers to *package* m/p,
go get -d m/p@v0.1.0
go list -m all
stdout '^m/p v0.1.0 '
! stdout '^m '
# When feasible, the argument 'm/p' in 'go get m/p' refers to *package* m/p,
# which is in module m.
#
# (It only refers to *module* m/p if there is no such package at the
@ -26,7 +25,7 @@ go mod edit -replace=m/p@v0.2.0=./mp02
go get -d m/p@v0.2.0
go list -m all
stdout '^m v0.2.0 '
stdout '^m/p v0.1.0 '
stdout '^m/p v0.1.0 ' # unchanged from the previous case
# Repeating the above with module m/p already in the module graph does not
# change its meaning.
@ -36,26 +35,6 @@ go list -m all
stdout '^m v0.2.0 '
stdout '^m/p v0.1.0 '
# TODO(#37438): If we add v0.3.0 before this point, we get a totally bogus error
# today, because 'go get' ends up attempting to resolve package 'm/p' without a
# specific version and can't find it if module m no longer contains v0.3.0.
cp go.mod.orig go.mod
go mod edit -replace=m@v0.3.0=./m03
go mod edit -replace=m/p@v0.3.0=./mp03
! go get -d m/p@v0.2.0
stderr 'go get m/p@v0.2.0: module m/p@latest found \(v0.3.0, replaced by ./mp03\), but does not contain package m/p$'
# If there is no sensible package meaning for 'm/p', perhaps it should refer
# to *module* m/p?
# Today, it still seems to refer to the package.
! go get -d m/p@v0.3.0
stderr '^go get m/p@v0.3.0: module m/p@latest found \(v0\.3\.0, replaced by \./mp03\), but does not contain package m/p$'
-- go.mod --
module example.com
@ -63,7 +42,11 @@ go 1.16
replace (
m v0.1.0 => ./m01
m v0.2.0 => ./m02
m v0.3.0 => ./m03
m/p v0.1.0 => ./mp01
m/p v0.2.0 => ./mp02
m/p v0.3.0 => ./mp03
)
-- m01/go.mod --
module m

View File

@ -9,7 +9,7 @@ cp go.mod go.mod.orig
# TODO(#27899): Should we automatically upgrade example.net/m to v0.2.0
# to resolve the conflict?
! go get -d example.net/m/p@v1.0.0
stderr '^go get example.net/m/p@v1.0.0: ambiguous import: found package example.net/m/p in multiple modules:\n\texample.net/m v0.1.0 \(.*[/\\]m1[/\\]p\)\n\texample.net/m/p v1.0.0 \(.*[/\\]p0\)\n\z'
stderr '^example.net/m/p: ambiguous import: found package example.net/m/p in multiple modules:\n\texample.net/m v0.1.0 \(.*[/\\]m1[/\\]p\)\n\texample.net/m/p v1.0.0 \(.*[/\\]p0\)\n\z'
cmp go.mod go.mod.orig
# Upgrading both modules simultaneously resolves the ambiguous upgrade.

View File

@ -15,22 +15,20 @@ stdout '^example.net/ambiguous/nested v0.1.0$'
# From an initial state that already depends on the shorter path,
# the same 'go get' command attempts to add the longer path and fails.
#
# TODO(bcmills): What should really happen here?
# Should we match the versioned package path against the existing package
# (reducing unexpected errors), or give it the same meaning regardless of the
# initial state?
# the same 'go get' command should (somewhat arbitrarily) keep the
# existing path, since it is a valid interpretation of the command.
cp go.mod.orig go.mod
go mod edit -require=example.net/ambiguous@v0.1.0
! go get -d example.net/ambiguous/nested/pkg@v0.1.0
stderr '^go get example.net/ambiguous/nested/pkg@v0.1.0: ambiguous import: found package example.net/ambiguous/nested/pkg in multiple modules:\n\texample.net/ambiguous v0.1.0 \(.*\)\n\texample.net/ambiguous/nested v0.1.0 \(.*\)\n\z'
go get -d example.net/ambiguous/nested/pkg@v0.1.0
go list -m all
stdout '^example.net/ambiguous v0.1.0$'
! stdout '^example.net/ambiguous/nested '
# The user should be able to fix the aforementioned failure by explicitly
# upgrading the conflicting module.
# The user should be able to make the command unambiguous by explicitly
# upgrading the conflicting module...
go get -d example.net/ambiguous@v0.2.0 example.net/ambiguous/nested/pkg@v0.1.0
go list -m all
@ -39,38 +37,26 @@ stdout '^example.net/ambiguous v0.2.0$'
# ...or by explicitly NOT adding the conflicting module.
#
# BUG(#37438): Today, this does not work: explicit module version constraints do
# not affect the package-to-module mapping during package upgrades, so the
# arguments are interpreted as specifying conflicting versions of the longer
# module path.
cp go.mod.orig go.mod
go mod edit -require=example.net/ambiguous@v0.1.0
! go get -d example.net/ambiguous/nested/pkg@v0.1.0 example.net/ambiguous/nested@none
stderr '^go get: conflicting versions for module example.net/ambiguous/nested: v0.1.0 and none$'
# go list -m all
# ! stdout '^example.net/ambiguous/nested '
# stdout '^example.net/ambiguous v0.1.0$'
go get -d example.net/ambiguous/nested/pkg@v0.1.0 example.net/ambiguous/nested@none
go list -m all
! stdout '^example.net/ambiguous/nested '
stdout '^example.net/ambiguous v0.1.0$'
# The user should also be able to fix it by *downgrading* the conflicting module
# away.
#
# BUG(#37438): Today, this does not work: the "ambiguous import" error causes
# 'go get' to fail before applying the requested downgrade.
cp go.mod.orig go.mod
go mod edit -require=example.net/ambiguous@v0.1.0
! go get -d example.net/ambiguous@none example.net/ambiguous/nested/pkg@v0.1.0
stderr '^go get example.net/ambiguous/nested/pkg@v0.1.0: ambiguous import: found package example.net/ambiguous/nested/pkg in multiple modules:\n\texample.net/ambiguous v0.1.0 \(.*\)\n\texample.net/ambiguous/nested v0.1.0 \(.*\)\n\z'
# go list -m all
# stdout '^example.net/ambiguous/nested v0.1.0$'
# !stdout '^example.net/ambiguous '
go get -d example.net/ambiguous@none example.net/ambiguous/nested/pkg@v0.1.0
go list -m all
stdout '^example.net/ambiguous/nested v0.1.0$'
! stdout '^example.net/ambiguous '
# In contrast, if we do the same thing tacking a wildcard pattern ('/...') on

View File

@ -19,7 +19,8 @@ go list -m all
stdout 'rsc.io/quote v1.5.1'
stdout 'rsc.io/sampler v1.3.0'
! go get rsc.io/sampler@v1.0.0 rsc.io/quote@v1.5.2 golang.org/x/text@none
stderr 'go get: inconsistent versions:\n\trsc.io/quote@v1.5.2 requires golang.org/x/text@v0.0.0-20170915032832-14c0d48ead0c \(not golang.org/x/text@none\), rsc.io/sampler@v1.3.0 \(not rsc.io/sampler@v1.0.0\)'
stderr '^go get: rsc.io/quote@v1.5.2 requires rsc.io/sampler@v1.3.0, not rsc.io/sampler@v1.0.0$'
stderr '^go get: rsc.io/quote@v1.5.2 requires golang.org/x/text@v0.0.0-20170915032832-14c0d48ead0c, not golang.org/x/text@none$'
go list -m all
stdout 'rsc.io/quote v1.5.1'
stdout 'rsc.io/sampler v1.3.0'

View File

@ -5,12 +5,12 @@ cp go.mod go.mod.orig
# rather than a "matched no packages" warning.
! go get example.net/pkgadded@v1.1.0 example.net/pkgadded/subpkg/...
stderr '^go get: conflicting versions for module example\.net/pkgadded: v1\.1\.0 and v1\.2\.0$'
stderr '^go get: example.net/pkgadded@v1.1.0 conflicts with example.net/pkgadded/subpkg/...@upgrade \(v1.2.0\)$'
! stderr 'matched no packages'
cmp go.mod.orig go.mod
# A wildcard pattern should match a package in a module with that path.
# A wildcard pattern should match the pattern with that path.
go get example.net/pkgadded/...@v1.0.0
go list -m all
@ -23,8 +23,8 @@ cp go.mod.orig go.mod
# package, then 'go get' should fail with a useful error message.
! go get example.net/pkgadded@v1.0.0 .
stderr -count=1 '^go: found example.net/pkgadded/subpkg in example.net/pkgadded v1\.2\.0$' # TODO: We shouldn't even try v1.2.0.
stderr '^example.com/m imports\n\texample.net/pkgadded/subpkg: cannot find module providing package example.net/pkgadded/subpkg$'
! stderr 'example.net/pkgadded v1\.2\.0'
cmp go.mod.orig go.mod
go get example.net/pkgadded@v1.0.0

View File

@ -0,0 +1,69 @@
cp go.mod go.mod.orig
# The -u flag should not (even temporarily) upgrade modules whose versions are
# determined by explicit queries to any version other than the explicit one.
# Otherwise, 'go get -u' could introduce spurious dependencies.
go get -d -u example.net/a@v0.1.0 example.net/b@v0.1.0
go list -m all
stdout '^example.net/a v0.1.0 '
stdout '^example.net/b v0.1.0 '
! stdout '^example.net/c '
# TODO(bcmills): This property does not yet hold for modules added for
# missing packages when the newly-added module matches a wildcard.
cp go.mod.orig go.mod
go get -d -u example.net/a@v0.1.0 example.net/b/...@v0.1.0
go list -m all
stdout '^example.net/a v0.1.0 '
stdout '^example.net/b v0.1.0 '
stdout '^example.net/c ' # BUG, but a minor and rare one
-- go.mod --
module example
go 1.15
replace (
example.net/a v0.1.0 => ./a1
example.net/b v0.1.0 => ./b1
example.net/b v0.2.0 => ./b2
example.net/c v0.1.0 => ./c1
example.net/c v0.2.0 => ./c1
)
-- a1/go.mod --
module example.net/a
go 1.15
// example.net/a needs a dependency on example.net/b, but lacks a requirement
// on it (perhaps due to a missed file in a VCS commit).
-- a1/a.go --
package a
import _ "example.net/b"
-- b1/go.mod --
module example.net/b
go 1.15
-- b1/b.go --
package b
-- b2/go.mod --
module example.net/b
go 1.15
require example.net/c v0.1.0
-- b2/b.go --
package b
-- c1/go.mod --
module example.net/c
go 1.15

View File

@ -0,0 +1,37 @@
# Regression test for https://golang.org/issue/37438.
#
# If a path exists at the requested version, but does not exist at the
# version of the module that is already required and does not exist at
# the version that would be selected by 'go mod tidy', then
# 'go get foo@requested' should resolve the requested version,
# not error out on the (unrelated) latest one.
go get -d example.net/a/p@v0.2.0
-- go.mod --
module example
go 1.15
require example.net/a v0.1.0
replace (
example.net/a v0.1.0 => ./a1
example.net/a v0.2.0 => ./a2
example.net/a v0.3.0 => ./a1
)
-- a1/go.mod --
module example.net/a
go 1.15
-- a1/README --
package example.net/a/p does not exist at this version.
-- a2/go.mod --
module example.net/a
go 1.15
-- a2/p/p.go --
// Package p exists only at v0.2.0.
package p

View File

@ -3,13 +3,13 @@ cp go.mod.orig go.mod
# relative and absolute paths must be within the main module.
! go get -d ..
stderr '^go get \.\.: path '$WORK'[/\\]gopath is not a package in module rooted at '$WORK'[/\\]gopath[/\\]src$'
stderr '^go get: \.\. \('$WORK'[/\\]gopath\) is not within module rooted at '$WORK'[/\\]gopath[/\\]src$'
! go get -d $WORK
stderr '^go get '$WORK': path '$WORK' is not a package in module rooted at '$WORK'[/\\]gopath[/\\]src$'
stderr '^go get: '$WORK' is not within module rooted at '$WORK'[/\\]gopath[/\\]src$'
! go get -d ../...
stderr '^go get: pattern \.\./\.\.\.: directory prefix \.\. outside available modules$'
stderr '^go get: \.\./\.\.\. \('$WORK'[/\\]gopath([/\\]...)?\) is not within module rooted at '$WORK'[/\\]gopath[/\\]src$'
! go get -d $WORK/...
stderr '^go get: pattern '$WORK'[/\\]\.\.\.: directory prefix \.\.[/\\]\.\. outside available modules$'
stderr '^go get: '$WORK'[/\\]\.\.\. is not within module rooted at '$WORK'[/\\]gopath[/\\]src$'
# @patch and @latest within the main module refer to the current version.
# The main module won't be upgraded, but missing dependencies will be added.
@ -22,21 +22,21 @@ go get -d rsc.io/x@patch
grep 'rsc.io/quote v1.5.2' go.mod
cp go.mod.orig go.mod
# The main module cannot be updated to @latest, which is a specific version.
! go get -d rsc.io/x@latest
stderr '^go get rsc.io/x@latest: can.t request explicit version of path in main module$'
# The main module cannot be updated to a specific version.
! go get -d rsc.io/x@v0.1.0
stderr '^go get rsc.io/x@v0.1.0: can.t request explicit version of path in main module$'
! go get -d rsc.io/x@v0.1.0
stderr '^go get rsc.io/x@v0.1.0: can.t request explicit version of path in main module$'
# Upgrading a package pattern not contained in the main module should not
# attempt to upgrade the main module.
go get -d rsc.io/quote/...@v1.5.1
grep 'rsc.io/quote v1.5.1' go.mod
# The main module cannot be updated to a specific version.
! go get -d rsc.io/x@v0.1.0
stderr '^go get: package rsc.io/x is in the main module, so can''t request version v0.1.0$'
# The main module cannot be updated to @latest, which is a specific version.
! go get -d rsc.io/x/...@latest
stderr '^go get: pattern rsc.io/x/... matches package rsc.io/x in the main module, so can''t request version latest$'
-- go.mod.orig --
module rsc.io

View File

@ -28,7 +28,9 @@ go list -m all
stdout 'example.com/join/subpkg v1.0.0'
# A 'go get' that simultaneously upgrades away conflicting package definitions is not ambiguous.
go get example.com/join/subpkg@v1.1.0
# (A wildcard pattern applies to both packages and modules,
# because we define wildcard matching to apply after version resolution.)
go get example.com/join/subpkg/...@v1.1.0
# A 'go get' without an upgrade should find the package.
rm go.mod

View File

@ -11,5 +11,4 @@ go mod init m
cmp stderr stderr-expected
-- stderr-expected --
go get: inconsistent versions:
example.com/newcycle/a@v1.0.0 requires example.com/newcycle/a@v1.0.1 (not example.com/newcycle/a@v1.0.0)
go get: example.com/newcycle/a@v1.0.0 requires example.com/newcycle/a@v1.0.1, not example.com/newcycle/a@v1.0.0

View File

@ -0,0 +1,130 @@
# This test examines the behavior of 'go get …@patch'
# See also mod_upgrade_patch.txt (focused on "-u=patch" specifically)
# and mod_get_patchmod.txt (focused on module/package ambiguities).
cp go.mod go.mod.orig
# example.net/b@patch refers to the patch for the version of b that was selected
# at the start of 'go get', not the version after applying other changes.
! go get -d example.net/a@v0.2.0 example.net/b@patch
stderr '^go get: example.net/a@v0.2.0 requires example.net/b@v0.2.0, not example.net/b@patch \(v0.1.1\)$'
cmp go.mod go.mod.orig
# -u=patch changes the default version for other arguments to '@patch',
# but they continue to be resolved against the originally-selected version,
# not the updated one.
#
# TODO(#42360): Reconsider the change in defaults.
! go get -d -u=patch example.net/a@v0.2.0 example.net/b
stderr '^go get: example.net/a@v0.2.0 requires example.net/b@v0.2.0, not example.net/b@patch \(v0.1.1\)$'
cmp go.mod go.mod.orig
# -u=patch refers to the patches for the selected versions of dependencies *after*
# applying other version changes, not the versions that were selected at the start.
# However, it should not patch versions determined by explicit arguments.
go get -d -u=patch example.net/a@v0.2.0
go list -m all
stdout '^example.net/a v0.2.0 '
stdout '^example.net/b v0.2.1 '
# "-u=patch all" should be equivalent to "all@patch", and should fail if the
# patched versions result in a higher-than-patch upgrade.
cp go.mod.orig go.mod
! go get -u=patch all
stderr '^go get: example.net/a@v0.1.1 \(matching all@patch\) requires example.net/b@v0.2.0, not example.net/b@v0.1.1 \(matching all@patch\)$'
cmp go.mod go.mod.orig
# On the other hand, "-u=patch ./..." should patch-upgrade dependencies until
# they reach a fixed point, even if that results in higher-than-patch upgrades.
go get -u=patch ./...
go list -m all
stdout '^example.net/a v0.1.1 '
stdout '^example.net/b v0.2.1 '
-- go.mod --
module example
go 1.16
require (
example.net/a v0.1.0
example.net/b v0.1.0 // indirect
)
replace (
example.net/a v0.1.0 => ./a10
example.net/a v0.1.1 => ./a11
example.net/a v0.2.0 => ./a20
example.net/a v0.2.1 => ./a21
example.net/b v0.1.0 => ./b
example.net/b v0.1.1 => ./b
example.net/b v0.2.0 => ./b
example.net/b v0.2.1 => ./b
example.net/b v0.3.0 => ./b
example.net/b v0.3.1 => ./b
)
-- example.go --
package example
import _ "example.net/a"
-- a10/go.mod --
module example.net/a
go 1.16
require example.net/b v0.1.0
-- a10/a.go --
package a
import _ "example.net/b"
-- a11/go.mod --
module example.net/a
go 1.16
require example.net/b v0.2.0 // upgraded
-- a11/a.go --
package a
import _ "example.net/b"
-- a20/go.mod --
module example.net/a
go 1.16
require example.net/b v0.2.0
-- a20/a.go --
package a
import _ "example.net/b"
-- a21/go.mod --
module example.net/a
go 1.16
require example.net/b v0.2.0 // not upgraded
-- a21/a.go --
package a
import _ "example.net/b"
-- b/go.mod --
module example.net/b
go 1.16
-- b/b.go --
package b

View File

@ -0,0 +1,84 @@
# -u=patch will patch dependencies as far as possible, but not so far that they
# conflict with other command-line arguments.
go list -m all
stdout '^example.net/a v0.1.0 '
stdout '^example.net/b v0.1.0 '
go get -d -u=patch example.net/a@v0.2.0
go list -m all
stdout '^example.net/a v0.2.0 '
stdout '^example.net/b v0.1.1 ' # not v0.1.2, which requires …/a v0.3.0.
-- go.mod --
module example
go 1.16
require (
example.net/a v0.1.0
example.net/b v0.1.0 // indirect
)
replace (
example.net/a v0.1.0 => ./a
example.net/a v0.2.0 => ./a
example.net/a v0.3.0 => ./a
example.net/b v0.1.0 => ./b10
example.net/b v0.1.1 => ./b11
example.net/b v0.1.2 => ./b12
)
-- example.go --
package example
import _ "example.net/a"
-- a/go.mod --
module example.net/a
go 1.16
require example.net/b v0.1.0
-- a/a.go --
package a
import _ "example.net/b"
-- b10/go.mod --
module example.net/b
go 1.16
require example.net/a v0.1.0
-- b10/b.go --
package b
-- b10/b_test.go --
package b_test
import _ "example.net/a"
-- b11/go.mod --
module example.net/b
go 1.16
require example.net/a v0.2.0
-- b11/b.go --
package b
-- b11/b_test.go --
package b_test
import _ "example.net/a"
-- b12/go.mod --
module example.net/b
go 1.16
require example.net/a v0.3.0
-- b12/b.go --
package b
-- b12/b_test.go --
package b_test
import _ "example.net/a"

View File

@ -0,0 +1,64 @@
# If a patch of a module requires a higher version of itself,
# it should be reported as its own conflict.
#
# This case is weird and unlikely to occur often at all, but it should not
# spuriously succeed.
# (It used to print v0.1.1 but then silently upgrade to v0.2.0.)
! go get example.net/a@patch
stderr '^go get: example.net/a@patch \(v0.1.1\) requires example.net/a@v0.2.0, not example.net/a@patch \(v0.1.1\)$' # TODO: A mention of b v0.1.0 would be nice.
-- go.mod --
module example
go 1.16
require example.net/a v0.1.0
replace (
example.net/a v0.1.0 => ./a10
example.net/a v0.1.1 => ./a11
example.net/a v0.2.0 => ./a20
example.net/b v0.1.0 => ./b10
)
-- example.go --
package example
import _ "example.net/a"
-- a10/go.mod --
module example.net/a
go 1.16
-- a10/a.go --
package a
-- a11/go.mod --
module example.net/a
go 1.16
require example.net/b v0.1.0
-- a11/a.go --
package a
import _ "example.net/b"
-- a20/go.mod --
module example.net/a
go 1.16
-- a20/a.go --
package a
-- b10/go.mod --
module example.net/b
go 1.16
require example.net/a v0.2.0
-- b10/b.go --
package b
import _ "example.net/a"

View File

@ -4,20 +4,41 @@ go get -d example.net/pkgremoved@v0.1.0
go list example.net/pkgremoved
stdout '^example.net/pkgremoved'
cp go.mod go.mod.orig
# When we resolve a new dependency on example.net/other,
# it will change the meaning of the path "example.net/pkgremoved"
# from a package (at v0.1.0) to only a module (at v0.2.0).
#
# If we simultaneously 'get' that module at the query "patch", the module should
# be upgraded to its patch release (v0.2.1) even though it no longer matches a
# package.
#
# BUG(#37438): Today, the pattern is only interpreted as its initial kind
# (a package), so the 'go get' invocation fails.
# be constrained to the latest patch of its originally-selected version (v0.1.0),
# not upgraded to the latest patch of the new transitive dependency.
! go get -d example.net/pkgremoved@patch example.net/other@v0.1.0
stderr '^go get: example.net/other@v0.1.0 requires example.net/pkgremoved@v0.2.0, not example.net/pkgremoved@patch \(v0.1.1\)$'
cmp go.mod.orig go.mod
stderr '^go get example.net/pkgremoved@patch: module example.net/pkgremoved@latest found \(v0.2.1, replaced by ./pr2\), but does not contain package example.net/pkgremoved$'
# However, we should be able to patch from a package to a module and vice-versa.
# Package to module ...
go get -d example.net/pkgremoved@v0.3.0
go list example.net/pkgremoved
stdout 'example.net/pkgremoved'
go get -d example.net/pkgremoved@patch
! go list example.net/pkgremoved
# ... and module to package.
go get -d example.net/pkgremoved@v0.4.0
! go list example.net/pkgremoved
go get -d example.net/pkgremoved@patch
go list example.net/pkgremoved
stdout 'example.net/pkgremoved'
-- go.mod --
@ -27,10 +48,18 @@ go 1.16
replace (
example.net/other v0.1.0 => ./other
example.net/pkgremoved v0.1.0 => ./pr1
example.net/pkgremoved v0.1.1 => ./pr1
example.net/pkgremoved v0.2.0 => ./pr2
example.net/pkgremoved v0.2.1 => ./pr2
example.net/pkgremoved v0.1.0 => ./prpkg
example.net/pkgremoved v0.1.1 => ./prpkg
example.net/pkgremoved v0.2.0 => ./prmod
example.net/pkgremoved v0.2.1 => ./prmod
example.net/pkgremoved v0.3.0 => ./prpkg
example.net/pkgremoved v0.3.1 => ./prmod
example.net/pkgremoved v0.4.0 => ./prmod
example.net/pkgremoved v0.4.1 => ./prpkg
)
-- other/go.mod --
module example.net/other
@ -40,13 +69,14 @@ go 1.16
require example.net/pkgremoved v0.2.0
-- other/other.go --
package other
-- pr1/go.mod --
-- prpkg/go.mod --
module example.net/pkgremoved
go 1.16
-- pr1/pkgremoved.go --
-- prpkg/pkgremoved.go --
package pkgremoved
-- pr2/go.mod --
-- prmod/go.mod --
module example.net/pkgremoved
-- pr2/README.txt --
Package pkgremoved was removed in v0.2.0.
-- prmod/README.txt --
Package pkgremoved was removed in v0.2.0 and v0.3.1,
and added in v0.1.0 and v0.4.1.

View File

@ -10,21 +10,27 @@ grep 'require rsc.io/quote' go.mod
cp go.mod.orig go.mod
! go get -d rsc.io/quote/x...
stderr 'go get rsc.io/quote/x...: module rsc.io/quote@upgrade found \(v1.5.2\), but does not contain packages matching rsc.io/quote/x...'
stderr 'go get: module rsc.io/quote@upgrade found \(v1.5.2\), but does not contain packages matching rsc.io/quote/x...'
! grep 'require rsc.io/quote' go.mod
! go get -d rsc.io/quote/x/...
stderr 'go get rsc.io/quote/x/...: module rsc.io/quote@upgrade found \(v1.5.2\), but does not contain packages matching rsc.io/quote/x/...'
stderr 'go get: module rsc.io/quote@upgrade found \(v1.5.2\), but does not contain packages matching rsc.io/quote/x/...'
! grep 'require rsc.io/quote' go.mod
# If a pattern matches no packages within a module, the module should not
# be upgraded, even if the module path matches the pattern.
# be upgraded, even if the module path is a prefix of the pattern.
cp go.mod.orig go.mod
go mod edit -require example.com/nest@v1.0.0
go get -d example.com/nest/sub/y...
grep 'example.com/nest/sub v1.0.0' go.mod
grep 'example.com/nest v1.0.0' go.mod
# However, if the pattern matches the module path itself, the module
# should be upgraded even if it contains no matching packages.
go get -d example.com/n...t
grep 'example.com/nest v1.1.0' go.mod
grep 'example.com/nest/sub v1.0.0' go.mod
-- go.mod.orig --
module m

View File

@ -82,7 +82,7 @@ cp go.mod.orig go.mod
! go list example
stderr '^package example is not in GOROOT \(.*\)$'
! go get -d example
stderr '^go get example: package example is not in GOROOT \(.*\)$'
stderr '^go get: malformed module path "example": missing dot in first path element$'
go mod edit -replace example@v0.1.0=./example

View File

@ -0,0 +1,157 @@
cp go.mod go.mod.orig
# 'go get' on a package already provided by the build list should update
# the module already in the build list, not fail with an ambiguous import error.
go get -d example.net/split/nested@patch
go list -m all
stdout '^example.net/split v0.2.1 '
! stdout '^example.net/split/nested'
# We should get the same behavior if we use a pattern that matches only that package.
cp go.mod.orig go.mod
go get -d example.net/split/nested/...@patch
go list -m all
stdout '^example.net/split v0.2.1 '
! stdout '^example.net/split/nested'
# If we request a version for which the package only exists in one particular module,
# we should add that one particular module but not resolve import ambiguities.
#
# In particular, if the module that previously provided the package has a
# matching version, but does not itself match the pattern and contains no
# matching packages, we should not change its version. (We should *not* downgrade
# module example.net/split to v0.1.0, despite the fact that
# example.net/split v0.2.0 currently provides the package with the requested path.)
#
# TODO(#27899): Maybe we should resolve the ambiguities by upgrading.
cp go.mod.orig go.mod
! go get -d example.net/split/nested@v0.1.0
stderr '^example.net/split/nested: ambiguous import: found package example.net/split/nested in multiple modules:\n\texample.net/split v0.2.0 \(.*split.2[/\\]nested\)\n\texample.net/split/nested v0.1.0 \(.*nested.1\)$'
# A wildcard that matches packages in some module at its selected version
# but not at the requested version should fail.
#
# We can't set the module to the selected version, because that version doesn't
# even match the query: if we ran the same query twice, we wouldn't consider the
# module to match the wildcard during the second call, so why should we consider
# it to match during the first one? ('go get' should be idempotent, and if we
# did that then it would not be.)
#
# But we also can't leave it where it is: the user requested that we set everything
# matching the pattern to the given version, and right now we have packages
# that match the pattern but *not* the version.
#
# That only leaves two options: we can set the module to an arbitrary version
# (perhaps 'latest' or 'none'), or we can report an error and the let the user
# disambiguate. We would rather not choose arbitrarily, so we do the latter.
#
# TODO(#27899): Should we instead upgrade or downgrade to an arbirary version?
! go get -d example.net/split/nested/...@v0.1.0
stderr '^go get: example.net/split/nested/\.\.\.@v0.1.0 matches packages in example.net/split@v0.2.0 but not example.net/split@v0.1.0: specify a different version for module example.net/split$'
cmp go.mod go.mod.orig
# If another argument resolves the ambiguity, we should be ok again.
go get -d example.net/split@none example.net/split/nested@v0.1.0
go list -m all
! stdout '^example.net/split '
stdout '^example.net/split/nested v0.1.0 '
cp go.mod.orig go.mod
go get -d example.net/split@v0.3.0 example.net/split/nested@v0.1.0
go list -m all
stdout '^example.net/split v0.3.0 '
stdout '^example.net/split/nested v0.1.0 '
# If a pattern applies to modules and to packages, we should set all matching
# modules to the version indicated by the pattern, and also resolve packages
# to match the pattern if possible.
cp go.mod.orig go.mod
go get -d example.net/split/nested@v0.0.0
go get -d example.net/...@v0.1.0
go list -m all
stdout '^example.net/split v0.1.0 '
stdout '^example.net/split/nested v0.1.0 '
go get -d example.net/...
go list -m all
stdout '^example.net/split v0.3.0 '
stdout '^example.net/split/nested v0.2.0 '
# @none applies to all matching module paths,
# regardless of whether they contain any packages.
go get -d example.net/...@none
go list -m all
! stdout '^example.net'
# Starting from no dependencies, a wildcard can resolve to an empty module with
# the same prefix even if it contains no packages.
go get -d example.net/...@none
go get -d example.net/split/...@v0.1.0
go list -m all
stdout '^example.net/split v0.1.0 '
-- go.mod --
module m
go 1.16
require example.net/split v0.2.0
replace (
example.net/split v0.1.0 => ./split.1
example.net/split v0.2.0 => ./split.2
example.net/split v0.2.1 => ./split.2
example.net/split v0.3.0 => ./split.3
example.net/split/nested v0.0.0 => ./nested.0
example.net/split/nested v0.1.0 => ./nested.1
example.net/split/nested v0.2.0 => ./nested.2
)
-- split.1/go.mod --
module example.net/split
go 1.16
-- split.2/go.mod --
module example.net/split
go 1.16
-- split.2/nested/nested.go --
package nested
-- split.3/go.mod --
module example.net/split
go 1.16
-- nested.0/go.mod --
module example.net/split/nested
go 1.16
-- nested.1/go.mod --
module example.net/split/nested
go 1.16
-- nested.1/nested.go --
package nested
-- nested.2/go.mod --
module example.net/split/nested
go 1.16
-- nested.2/nested.go --
package nested

View File

@ -0,0 +1,95 @@
# This test covers a crazy edge-case involving wildcards and multiple passes of
# patch-upgrades, but if we get it right we probably get many other edge-cases
# right too.
go list -m all
stdout '^example.net/a v0.1.0 '
! stdout '^example.net/b '
# Requesting pattern example.../b by itself fails: there is no such module
# already in the build list, and the wildcard in the first element prevents us
# from attempting to resolve a new module whose path is a prefix of the pattern.
! go get -d -u=patch example.../b@upgrade
stderr '^go get: no modules to query for example\.\.\./b@upgrade because first path element contains a wildcard$'
# Patching . causes a patch to example.net/a, which introduces a new match
# for example.net/b/..., which is itself patched and causes another upgrade to
# example.net/a, which is then patched again.
go get -d -u=patch . example.../b@upgrade
go list -m all
stdout '^example.net/a v0.2.1 ' # upgraded by dependency of b and -u=patch
stdout '^example.net/b v0.2.0 ' # introduced by patch of a and upgraded by wildcard
-- go.mod --
module example
go 1.16
require example.net/a v0.1.0
replace (
example.net/a v0.1.0 => ./a10
example.net/a v0.1.1 => ./a11
example.net/a v0.2.0 => ./a20
example.net/a v0.2.1 => ./a20
example.net/b v0.1.0 => ./b1
example.net/b v0.1.1 => ./b1
example.net/b v0.2.0 => ./b2
)
-- example.go --
package example
import _ "example.net/a"
-- a10/go.mod --
module example.net/a
go 1.16
-- a10/a.go --
package a
-- a11/go.mod --
module example.net/a
go 1.16
require example.net/b v0.1.0
-- a11/a.go --
package a
-- a11/unimported/unimported.go --
package unimported
import _ "example.net/b"
-- a20/go.mod --
module example.net/a
go 1.16
-- a20/a.go --
package a
-- b1/go.mod --
module example.net/b
go 1.16
-- b1/b.go --
package b
-- b2/go.mod --
module example.net/b
go 1.16
require example.net/a v0.2.0
-- b2/b.go --
package b
-- b2/b_test.go --
package b_test
import _ "example.net/a"

View File

@ -222,7 +222,7 @@ stdout 'github.com/pierrec/lz4 v2.0.5\+incompatible'
# not resolve to a pseudo-version with a different major version.
cp go.mod.orig go.mod
! go get -d github.com/pierrec/lz4@v2.0.8
stderr 'go get github.com/pierrec/lz4@v2.0.8: github.com/pierrec/lz4@v2.0.8: invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v2'
stderr 'go get: github.com/pierrec/lz4@v2.0.8: invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v2'
# An invalid +incompatible suffix for a canonical version should error out,
# not resolve to a pseudo-version.

View File

@ -69,14 +69,13 @@ import (
func Test(t *testing.T) {}
-- update-main-expected --
go: example.com/badchain/c upgrade => v1.1.0
go get: example.com/badchain/c@v1.0.0 updating to
example.com/badchain/c@v1.1.0: parsing go.mod:
module declares its path as: badchain.example.com/c
but was required as: example.com/badchain/c
-- update-a-expected --
go: example.com/badchain/a upgrade => v1.1.0
go get: example.com/badchain/a@v1.1.0 requires
go get: example.com/badchain/a@v1.0.0 updating to
example.com/badchain/a@v1.1.0 requires
example.com/badchain/b@v1.1.0 requires
example.com/badchain/c@v1.1.0: parsing go.mod:
module declares its path as: badchain.example.com/c

View File

@ -130,12 +130,12 @@ stderr 'cannot find main module'
# 'go get -u all' upgrades the transitive import graph of the main module,
# which is empty.
! go get -u all
stderr 'go get all: cannot match "all": working directory is not part of a module'
stderr 'go get: cannot match "all": working directory is not part of a module'
# 'go get' should check the proposed module graph for consistency,
# even though we won't write it anywhere.
! go get -d example.com/printversion@v1.0.0 example.com/version@none
stderr 'inconsistent versions'
stderr '^go get: example.com/printversion@v1.0.0 requires example.com/version@v1.0.0, not example.com/version@none$'
# 'go get -d' should download and extract the source code needed to build the requested version.
rm -r $GOPATH/pkg/mod/example.com

View File

@ -8,7 +8,7 @@ go mod download example.com/join@v1.1.0
env GOPROXY=file:///$WORK/badproxy
cp go.mod.orig go.mod
! go get -d example.com/join/subpkg
stderr 'go get example.com/join/subpkg: example.com/join/subpkg@v0.0.0-20190624000000-123456abcdef: .*'
stderr 'go get: example.com/join/subpkg@v0.0.0-20190624000000-123456abcdef: .*'
# If @v/list is empty, the 'go' command should still try to resolve
# other module paths.
@ -40,7 +40,7 @@ env GOPROXY=file:///$WORK/gatekeeper
chmod 0000 $WORK/gatekeeper/example.com/join/subpkg/@latest
cp go.mod.orig go.mod
! go get -d example.com/join/subpkg
stderr 'go get example.com/join/subpkg: module example.com/join/subpkg: (invalid response from proxy ".+": json: invalid character .+|reading file://.*/gatekeeper/example.com/join/subpkg/@latest: .+)'
stderr 'go get: module example.com/join/subpkg: (invalid response from proxy ".+": json: invalid character .+|reading file://.*/gatekeeper/example.com/join/subpkg/@latest: .+)'
-- go.mod.orig --
module example.com/othermodule

View File

@ -19,7 +19,7 @@ stdout '^rsc.io/quote v1.5.1$'
# get excluded version
cp go.exclude.mod go.exclude.mod.orig
! go get -modfile=go.exclude.mod -d rsc.io/quote@v1.5.0
stderr '^go get rsc.io/quote@v1.5.0: rsc.io/quote@v1.5.0: excluded by go.mod$'
stderr '^go get: rsc.io/quote@v1.5.0: excluded by go.mod$'
# get non-excluded version
cp go.exclude.mod.orig go.exclude.mod

View File

@ -29,7 +29,7 @@ go list -m vcs-test.golang.org/git/retract-pseudo.git
stdout '^vcs-test.golang.org/git/retract-pseudo.git v1.0.1-0.20201009173747-713affd19d7b$'
! go get -d vcs-test.golang.org/git/retract-pseudo.git@v1.0.1-0.20201009173747-64c061ed4371
stderr '^go get vcs-test.golang.org/git/retract-pseudo.git@v1.0.1-0.20201009173747-64c061ed4371: vcs-test.golang.org/git/retract-pseudo.git@v1.0.1-0.20201009173747-64c061ed4371: invalid pseudo-version: tag \(v1.0.0\) found on revision 64c061ed4371 is already canonical, so should not be replaced with a pseudo-version derived from that tag$'
stderr '^go get: vcs-test.golang.org/git/retract-pseudo.git@v1.0.1-0.20201009173747-64c061ed4371: invalid pseudo-version: tag \(v1.0.0\) found on revision 64c061ed4371 is already canonical, so should not be replaced with a pseudo-version derived from that tag$'
-- retract-pseudo.sh --
#!/bin/bash

View File

@ -9,7 +9,7 @@ env dbname=localhost.localdev/sumdb
cp go.mod.orig go.mod
env GOSUMDB=$sumdb' '$proxy/sumdb-wrong
! go get -d rsc.io/quote
stderr 'go get rsc.io/quote: rsc.io/quote@v1.5.2: verifying module: checksum mismatch'
stderr 'go get: rsc.io/quote@v1.5.2: verifying module: checksum mismatch'
stderr 'downloaded: h1:3fEy'
stderr 'localhost.localdev/sumdb: h1:wrong'
stderr 'SECURITY ERROR\nThis download does NOT match the one reported by the checksum server.'

View File

@ -13,7 +13,7 @@ env GOPATH=$WORK/gopath1
[windows] env GOPROXY=file:///$WORK/sumproxy,https://proxy.golang.org
[!windows] env GOPROXY=file://$WORK/sumproxy,https://proxy.golang.org
! go get -d golang.org/x/text@v0.3.2
stderr '^go get golang.org/x/text@v0.3.2: golang.org/x/text@v0.3.2: verifying module: golang.org/x/text@v0.3.2: reading file://.*/sumdb/sum.golang.org/lookup/golang.org/x/text@v0.3.2: (no such file or directory|.*cannot find the path specified.*)'
stderr '^go get: golang.org/x/text@v0.3.2: verifying module: golang.org/x/text@v0.3.2: reading file://.*/sumdb/sum.golang.org/lookup/golang.org/x/text@v0.3.2: (no such file or directory|.*cannot find the path specified.*)'
# If the proxy does not claim to support the database,
# checksum verification should fall through to the next proxy,

View File

@ -9,6 +9,11 @@ stdout '^patch.example.com/direct v1.0.0'
stdout '^patch.example.com/indirect v1.0.0'
! stdout '^patch.example.com/depofdirectpatch'
# @patch should be rejected for modules not already in the build list.
! go get -d patch.example.com/depofdirectpatch@patch
stderr '^go get: can''t query version "patch" of module patch.example.com/depofdirectpatch: no existing version is required$'
cmp go.mod.orig go.mod
# get -u=patch, with no arguments, should patch-update all dependencies
# of the package in the current directory, pulling in transitive dependencies
# and also patching those.
@ -19,7 +24,7 @@ stdout '^patch.example.com/direct v1.0.1'
stdout '^patch.example.com/indirect v1.0.1'
stdout '^patch.example.com/depofdirectpatch v1.0.0'
# 'get all@patch' should be equivalent to 'get -u=patch all'
# 'get all@patch' should patch the modules that provide packages in 'all'.
cp go.mod.orig go.mod
go get -d all@patch
go list -m all
@ -27,6 +32,15 @@ stdout '^patch.example.com/direct v1.0.1'
stdout '^patch.example.com/indirect v1.0.1'
stdout '^patch.example.com/depofdirectpatch v1.0.0'
# ...but 'all@patch' should fail if any of the affected modules do not already
# have a selected version.
cp go.mod.orig go.mod
go mod edit -droprequire=patch.example.com/direct
cp go.mod go.mod.dropped
! go get -d all@patch
stderr '^go get all@patch: can''t query version "patch" of module patch.example.com/direct: no existing version is required$'
cmp go.mod.dropped go.mod
# Requesting the direct dependency with -u=patch but without an explicit version
# should patch-update it and its dependencies.
cp go.mod.orig go.mod
@ -69,10 +83,10 @@ stdout '^patch.example.com/direct v1.1.0'
stdout '^patch.example.com/indirect v1.0.1'
! stdout '^patch.example.com/depofdirectpatch'
# Standard-library packages cannot be upgraded explicitly.
# Standard library packages cannot be upgraded explicitly.
cp go.mod.orig go.mod
! go get cmd/vet@patch
stderr 'cannot use pattern .* with explicit version'
stderr 'go get: can''t request explicit version "patch" of standard library package cmd/vet$'
# However, standard-library packages without explicit versions are fine.
go get -d -u=patch -d cmd/go
@ -85,6 +99,7 @@ go get -d example.com/noroot@patch
go list -m all
stdout '^example.com/noroot v1.0.1$'
-- go.mod --
module x