// if those packages are not found in existing dependencies of the main module.
import (
- "bytes"
"context"
"errors"
"fmt"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/fsys"
+ "cmd/go/internal/gover"
"cmd/go/internal/imports"
"cmd/go/internal/modfetch"
+ "cmd/go/internal/modindex"
"cmd/go/internal/mvs"
"cmd/go/internal/par"
"cmd/go/internal/search"
"cmd/go/internal/str"
"golang.org/x/mod/module"
- "golang.org/x/mod/semver"
)
// loaded is the most recently-used package loader.
// PackageOpts control the behavior of the LoadPackages function.
type PackageOpts struct {
- // GoVersion is the Go version to which the go.mod file should be updated
+ // TidyGoVersion is the Go version to which the go.mod file should be updated
// after packages have been loaded.
//
- // An empty GoVersion means to use the Go version already specified in the
+ // An empty TidyGoVersion means to use the Go version already specified in the
// main module's go.mod file, or the latest Go version if there is no main
// module.
- GoVersion string
+ TidyGoVersion string
// Tags are the build tags in effect (as interpreted by the
// cmd/go/internal/imports package).
Tags map[string]bool
// Tidy, if true, requests that the build list and go.sum file be reduced to
- // the minimial dependencies needed to reproducibly reload the requested
+ // the minimal dependencies needed to reproducibly reload the requested
// packages.
Tidy bool
// SilenceUnmatchedWarnings suppresses the warnings normally emitted for
// patterns that did not match any packages.
SilenceUnmatchedWarnings bool
+
+ // Resolve the query against this module.
+ MainModule module.Version
+
+ // If Switcher is non-nil, then LoadPackages passes all encountered errors
+ // to Switcher.Error and tries Switcher.Switch before base.ExitIfErrors.
+ Switcher gover.Switcher
}
// LoadPackages identifies the set of packages matching the given patterns and
case m.IsLocal():
// Evaluate list of file system directories on first iteration.
if m.Dirs == nil {
- matchLocalDirs(ctx, m, rs)
+ matchModRoots := modRoots
+ if opts.MainModule != (module.Version{}) {
+ matchModRoots = []string{MainModules.ModRoot(opts.MainModule)}
+ }
+ matchLocalDirs(ctx, matchModRoots, m, rs)
}
// Make a copy of the directory list and translate to import paths.
// The initial roots are the packages in the main module.
// loadFromRoots will expand that to "all".
m.Errs = m.Errs[:0]
- matchPackages(ctx, m, opts.Tags, omitStd, MainModules.Versions())
+ matchModules := MainModules.Versions()
+ if opts.MainModule != (module.Version{}) {
+ matchModules = []module.Version{opts.MainModule}
+ }
+ matchPackages(ctx, m, opts.Tags, omitStd, matchModules)
} else {
// Starting with the packages in the main module,
// enumerate the full list of "all".
}
}
- initialRS := LoadModFile(ctx)
+ initialRS, err := loadModFile(ctx, &opts)
+ if err != nil {
+ base.Fatal(err)
+ }
ld := loadFromRoots(ctx, loaderParams{
PackageOpts: opts,
if !ld.SilencePackageErrors {
for _, match := range matches {
for _, err := range match.Errs {
- ld.errorf("%v\n", err)
+ ld.error(err)
}
}
}
- base.ExitIfErrors()
+ ld.exitIfErrors(ctx)
if !opts.SilenceUnmatchedWarnings {
search.WarnUnmatched(matches)
if opts.Tidy {
if cfg.BuildV {
mg, _ := ld.requirements.Graph(ctx)
-
for _, m := range initialRS.rootModules {
var unused bool
if ld.requirements.pruning == unpruned {
}
keep := keepSums(ctx, ld, ld.requirements, loadedZipSumsOnly)
- if compatDepth := pruningForGoVersion(ld.TidyCompatibleVersion); compatDepth != ld.requirements.pruning {
- compatRS := newRequirements(compatDepth, ld.requirements.rootModules, ld.requirements.direct)
- ld.checkTidyCompatibility(ctx, compatRS)
+ compatVersion := ld.TidyCompatibleVersion
+ goVersion := ld.requirements.GoVersion()
+ if compatVersion == "" {
+ if gover.Compare(goVersion, gover.GoStrictVersion) < 0 {
+ compatVersion = gover.Prev(goVersion)
+ } else {
+ // Starting at GoStrictVersion, we no longer maintain compatibility with
+ // versions older than what is listed in the go.mod file.
+ compatVersion = goVersion
+ }
+ }
+ if gover.Compare(compatVersion, goVersion) > 0 {
+ // Each version of the Go toolchain knows how to interpret go.mod and
+ // go.sum files produced by all previous versions, so a compatibility
+ // version higher than the go.mod version adds nothing.
+ compatVersion = goVersion
+ }
+ if compatPruning := pruningForGoVersion(compatVersion); compatPruning != ld.requirements.pruning {
+ compatRS := newRequirements(compatPruning, ld.requirements.rootModules, ld.requirements.direct)
+ ld.checkTidyCompatibility(ctx, compatRS, compatVersion)
for m := range keepSums(ctx, ld, compatRS, loadedZipSumsOnly) {
keep[m] = true
// loaded.requirements, but here we may have also loaded (and want to
// preserve checksums for) additional entities from compatRS, which are
// only needed for compatibility with ld.TidyCompatibleVersion.
- if err := modfetch.WriteGoSum(keep, mustHaveCompleteRequirements()); err != nil {
- base.Fatalf("go: %v", err)
+ if err := modfetch.WriteGoSum(ctx, keep, mustHaveCompleteRequirements()); err != nil {
+ base.Fatal(err)
}
}
-
- // Update the go.mod file's Go version if necessary.
- modFile := MainModules.ModFile(MainModules.mustGetSingleMainModule())
- if ld.GoVersion != "" {
- modFile.AddGoStmt(ld.GoVersion)
- }
}
// Success! Update go.mod and go.sum (if needed) and return the results.
sort.Strings(loadedPackages)
if !ExplicitWriteGoMod && opts.ResolveMissingImports {
- if err := commitRequirements(ctx); err != nil {
- base.Fatalf("go: %v", err)
+ if err := commitRequirements(ctx, WriteOpts{}); err != nil {
+ base.Fatal(err)
}
}
// matchLocalDirs is like m.MatchDirs, but tries to avoid scanning directories
// outside of the standard library and active modules.
-func matchLocalDirs(ctx context.Context, m *search.Match, rs *Requirements) {
+func matchLocalDirs(ctx context.Context, modRoots []string, m *search.Match, rs *Requirements) {
if !m.IsLocal() {
panic(fmt.Sprintf("internal error: resolveLocalDirs on non-local pattern %s", m.Pattern()))
}
modRoot := findModuleRoot(absDir)
found := false
- for _, mod := range MainModules.Versions() {
- if MainModules.ModRoot(mod) == modRoot {
+ for _, mainModuleRoot := range modRoots {
+ if mainModuleRoot == modRoot {
found = true
break
}
}
if !found && search.InDir(absDir, cfg.GOROOTsrc) == "" && pathInModuleCache(ctx, absDir, rs) == "" {
m.Dirs = []string{}
- m.AddError(fmt.Errorf("directory prefix %s outside available modules", base.ShortPath(absDir)))
+ scope := "main module or its selected dependencies"
+ if inWorkspaceMode() {
+ scope = "modules listed in go.work or their selected dependencies"
+ }
+ m.AddError(fmt.Errorf("directory prefix %s does not contain %s", base.ShortPath(absDir), scope))
return
}
}
pkgNotFoundLongestPrefix := ""
for _, mainModule := range MainModules.Versions() {
modRoot := MainModules.ModRoot(mainModule)
- if modRoot != "" && strings.HasPrefix(absDir, modRoot+string(filepath.Separator)) && !strings.Contains(absDir[len(modRoot):], "@") {
- suffix := filepath.ToSlash(absDir[len(modRoot):])
- if strings.HasPrefix(suffix, "/vendor/") {
+ if modRoot != "" && str.HasFilePathPrefix(absDir, modRoot) && !strings.Contains(absDir[len(modRoot):], "@") {
+ suffix := filepath.ToSlash(str.TrimFilePathPrefix(absDir, modRoot))
+ if pkg, found := strings.CutPrefix(suffix, "vendor/"); found {
if cfg.BuildMod != "vendor" {
return "", fmt.Errorf("without -mod=vendor, directory %s has no package path", absDir)
}
- readVendorList(mainModule)
- pkg := strings.TrimPrefix(suffix, "/vendor/")
+ readVendorList(VendorDir())
if _, ok := vendorPkgModule[pkg]; !ok {
return "", fmt.Errorf("directory %s is not a package listed in vendor/modules.txt", absDir)
}
mainModulePrefix := MainModules.PathPrefix(mainModule)
if mainModulePrefix == "" {
- pkg := strings.TrimPrefix(suffix, "/")
+ pkg := suffix
if pkg == "builtin" {
// "builtin" is a pseudo-package with a real source file.
// It's not included in "std", so it shouldn't resolve from "."
return pkg, nil
}
- pkg := mainModulePrefix + suffix
+ pkg := pathpkg.Join(mainModulePrefix, suffix)
if _, ok, err := dirInModule(pkg, mainModulePrefix, modRoot, true); err != nil {
return "", err
} else if !ok {
pkg := pathInModuleCache(ctx, absDir, rs)
if pkg == "" {
- return "", fmt.Errorf("directory %s outside available modules", base.ShortPath(absDir))
+ dirstr := fmt.Sprintf("directory %s", base.ShortPath(absDir))
+ if dirstr == "directory ." {
+ dirstr = "current directory"
+ }
+ if inWorkspaceMode() {
+ if mr := findModuleRoot(absDir); mr != "" {
+ return "", fmt.Errorf("%s is contained in a module that is not one of the workspace modules listed in go.work. You can add the module to the workspace using:\n\tgo work use %s", dirstr, base.ShortPath(mr))
+ }
+ return "", fmt.Errorf("%s outside modules listed in go.work or their selected dependencies", dirstr)
+ }
+ return "", fmt.Errorf("%s outside main module or its selected dependencies", dirstr)
}
return pkg, nil
}
// if dir is in the module cache copy of a module in our build list.
func pathInModuleCache(ctx context.Context, dir string, rs *Requirements) string {
tryMod := func(m module.Version) (string, bool) {
+ if gover.IsToolchain(m.Path) {
+ return "", false
+ }
var root string
var err error
if repl := Replacement(m); repl.Path != "" && repl.Version == "" {
root = filepath.Join(replaceRelativeTo(), root)
}
} else if repl.Path != "" {
- root, err = modfetch.DownloadDir(repl)
+ root, err = modfetch.DownloadDir(ctx, repl)
} else {
- root, err = modfetch.DownloadDir(m)
+ root, err = modfetch.DownloadDir(ctx, m)
}
if err != nil {
return "", false
tags := imports.Tags()
imports, testImports, err := imports.ScanFiles(gofiles, tags)
if err != nil {
- base.Fatalf("go: %v", err)
+ base.Fatal(err)
}
loaded = loadFromRoots(ctx, loaderParams{
},
})
requirements = loaded.requirements
+
+ if !ExplicitWriteGoMod {
+ if err := commitRequirements(ctx, WriteOpts{}); err != nil {
+ base.Fatal(err)
+ }
+ }
}
// DirImportPath returns the effective import path for dir,
if dir == modRoot {
return mms.PathPrefix(v), v
}
- if strings.HasPrefix(dir, modRoot+string(filepath.Separator)) {
+ if str.HasFilePathPrefix(dir, modRoot) {
pathPrefix := MainModules.PathPrefix(v)
if pathPrefix > longestPrefix {
longestPrefix = pathPrefix
longestPrefixVersion = v
- suffix := filepath.ToSlash(dir[len(modRoot):])
- if strings.HasPrefix(suffix, "/vendor/") {
- longestPrefixPath = strings.TrimPrefix(suffix, "/vendor/")
+ suffix := filepath.ToSlash(str.TrimFilePathPrefix(dir, modRoot))
+ if strings.HasPrefix(suffix, "vendor/") {
+ longestPrefixPath = suffix[len("vendor/"):]
+ continue
}
- longestPrefixPath = mms.PathPrefix(v) + suffix
+ longestPrefixPath = pathpkg.Join(mms.PathPrefix(v), suffix)
}
}
}
return ".", module.Version{}
}
-// ImportMap returns the actual package import path
-// for an import path found in source code.
-// If the given import path does not appear in the source code
-// for the packages that have been loaded, ImportMap returns the empty string.
-func ImportMap(path string) string {
- pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
- if !ok {
- return ""
- }
- return pkg.path
-}
-
-// PackageDir returns the directory containing the source code
-// for the package named by the import path.
-func PackageDir(path string) string {
- pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
- if !ok {
- return ""
- }
- return pkg.dir
-}
-
// PackageModule returns the module providing the package named by the import path.
func PackageModule(path string) module.Version {
- pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
+ pkg, ok := loaded.pkgCache.Get(path)
if !ok {
return module.Version{}
}
if parentIsStd {
path = loaded.stdVendor(parentPath, path)
}
- pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
+ pkg, ok := loaded.pkgCache.Get(path)
if !ok {
// The loader should have found all the relevant paths.
// There are a few exceptions, though:
// transitively *imported by* the packages and tests in the main module.)
allClosesOverTests bool
+ // skipImportModFiles indicates whether we may skip loading go.mod files
+ // for imported packages (as in 'go mod tidy' in Go 1.17–1.20).
+ skipImportModFiles bool
+
work *par.Queue
// reset on each iteration
roots []*loadPkg
- pkgCache *par.Cache // package path (string) → *loadPkg
+ pkgCache *par.Cache[string, *loadPkg]
pkgs []*loadPkg // transitive closure of loaded packages and tests; populated in buildStacks
}
}
ld.roots = nil
- ld.pkgCache = new(par.Cache)
+ ld.pkgCache = new(par.Cache[string, *loadPkg])
ld.pkgs = nil
}
-// errorf reports an error via either os.Stderr or base.Errorf,
+// error reports an error via either os.Stderr or base.Error,
// according to whether ld.AllowErrors is set.
-func (ld *loader) errorf(format string, args ...interface{}) {
+func (ld *loader) error(err error) {
if ld.AllowErrors {
- fmt.Fprintf(os.Stderr, format, args...)
+ fmt.Fprintf(os.Stderr, "go: %v\n", err)
+ } else if ld.Switcher != nil {
+ ld.Switcher.Error(err)
} else {
- base.Errorf(format, args...)
+ base.Error(err)
}
}
+// switchIfErrors switches toolchains if a switch is needed.
+func (ld *loader) switchIfErrors(ctx context.Context) {
+ if ld.Switcher != nil {
+ ld.Switcher.Switch(ctx)
+ }
+}
+
+// exitIfErrors switches toolchains if a switch is needed
+// or else exits if any errors have been reported.
+func (ld *loader) exitIfErrors(ctx context.Context) {
+ ld.switchIfErrors(ctx)
+ base.ExitIfErrors()
+}
+
+// goVersion reports the Go version that should be used for the loader's
+// requirements: ld.TidyGoVersion if set, or ld.requirements.GoVersion()
+// otherwise.
+func (ld *loader) goVersion() string {
+ if ld.TidyGoVersion != "" {
+ return ld.TidyGoVersion
+ }
+ return ld.requirements.GoVersion()
+}
+
// A loadPkg records information about a single loaded package.
type loadPkg struct {
// Populated at construction time:
// An atomicLoadPkgFlags stores a loadPkgFlags for which individual flags can be
// added atomically.
type atomicLoadPkgFlags struct {
- bits int32
+ bits atomic.Int32
}
// update sets the given flags in af (in addition to any flags already set).
// flags were newly-set.
func (af *atomicLoadPkgFlags) update(flags loadPkgFlags) (old loadPkgFlags) {
for {
- old := atomic.LoadInt32(&af.bits)
+ old := af.bits.Load()
new := old | int32(flags)
- if new == old || atomic.CompareAndSwapInt32(&af.bits, old, new) {
+ if new == old || af.bits.CompareAndSwap(old, new) {
return loadPkgFlags(old)
}
}
// has reports whether all of the flags in cond are set in af.
func (af *atomicLoadPkgFlags) has(cond loadPkgFlags) bool {
- return loadPkgFlags(atomic.LoadInt32(&af.bits))&cond == cond
+ return loadPkgFlags(af.bits.Load())&cond == cond
}
// isTest reports whether pkg is a test of another package.
work: par.NewQueue(runtime.GOMAXPROCS(0)),
}
- if ld.GoVersion == "" {
- ld.GoVersion = MainModules.GoVersion()
-
- if ld.Tidy && semver.Compare("v"+ld.GoVersion, "v"+LatestGoVersion()) > 0 {
- ld.errorf("go: go.mod file indicates go %s, but maximum version supported by tidy is %s\n", ld.GoVersion, LatestGoVersion())
- base.ExitIfErrors()
- }
- }
-
- if ld.Tidy {
- if ld.TidyCompatibleVersion == "" {
- ld.TidyCompatibleVersion = priorGoVersion(ld.GoVersion)
- } else if semver.Compare("v"+ld.TidyCompatibleVersion, "v"+ld.GoVersion) > 0 {
- // Each version of the Go toolchain knows how to interpret go.mod and
- // go.sum files produced by all previous versions, so a compatibility
- // version higher than the go.mod version adds nothing.
- ld.TidyCompatibleVersion = ld.GoVersion
- }
- }
-
- if semver.Compare("v"+ld.GoVersion, narrowAllVersionV) < 0 && !ld.UseVendorAll {
- // The module's go version explicitly predates the change in "all" for graph
- // pruning, so continue to use the older interpretation.
- ld.allClosesOverTests = true
- }
-
- var err error
- ld.requirements, err = convertPruning(ctx, ld.requirements, pruningForGoVersion(ld.GoVersion))
- if err != nil {
- ld.errorf("go: %v\n", err)
- }
-
if ld.requirements.pruning == unpruned {
// If the module graph does not support pruning, we assume that we will need
// the full module graph in order to load package dependencies.
var err error
ld.requirements, _, err = expandGraph(ctx, ld.requirements)
if err != nil {
- ld.errorf("go: %v\n", err)
+ ld.error(err)
}
}
+ ld.exitIfErrors(ctx)
+
+ updateGoVersion := func() {
+ goVersion := ld.goVersion()
+
+ if ld.requirements.pruning != workspace {
+ var err error
+ ld.requirements, err = convertPruning(ctx, ld.requirements, pruningForGoVersion(goVersion))
+ if err != nil {
+ ld.error(err)
+ ld.exitIfErrors(ctx)
+ }
+ }
+
+ // If the module's Go version omits go.sum entries for go.mod files for test
+ // dependencies of external packages, avoid loading those files in the first
+ // place.
+ ld.skipImportModFiles = ld.Tidy && gover.Compare(goVersion, gover.TidyGoModSumVersion) < 0
+
+ // If the module's go version explicitly predates the change in "all" for
+ // graph pruning, continue to use the older interpretation.
+ ld.allClosesOverTests = gover.Compare(goVersion, gover.NarrowAllVersion) < 0 && !ld.UseVendorAll
+ }
for {
ld.reset()
+ updateGoVersion()
// Load the root packages and their imports.
// Note: the returned roots can change on each iteration,
changed, err := ld.updateRequirements(ctx)
if err != nil {
- ld.errorf("go: %v\n", err)
+ ld.error(err)
break
}
if changed {
- // Don't resolve missing imports until the module graph have stabilized.
+ // Don't resolve missing imports until the module graph has stabilized.
// If the roots are still changing, they may turn out to specify a
// requirement on the missing package(s), and we would rather use a
// version specified by a new root than add a new dependency on an
break
}
- modAddedBy := ld.resolveMissingImports(ctx)
+ modAddedBy, err := ld.resolveMissingImports(ctx)
+ if err != nil {
+ ld.error(err)
+ break
+ }
if len(modAddedBy) == 0 {
// The roots are stable, and we've resolved all of the missing packages
// that we can.
}
toAdd := make([]module.Version, 0, len(modAddedBy))
- for m, _ := range modAddedBy {
+ for m := range modAddedBy {
toAdd = append(toAdd, m)
}
- module.Sort(toAdd) // to make errors deterministic
+ gover.ModSort(toAdd) // to make errors deterministic
// We ran updateRequirements before resolving missing imports and it didn't
// make any changes, so we know that the requirement graph is already
// are more descriptive.
if err, ok := err.(*mvs.BuildListError); ok {
if pkg := modAddedBy[err.Module()]; pkg != nil {
- ld.errorf("go: %s: %v\n", pkg.stackText(), err.Err)
+ ld.error(fmt.Errorf("%s: %w", pkg.stackText(), err.Err))
break
}
}
- ld.errorf("go: %v\n", err)
+ ld.error(err)
break
}
if reflect.DeepEqual(rs.rootModules, ld.requirements.rootModules) {
}
ld.requirements = rs
}
- base.ExitIfErrors() // TODO(bcmills): Is this actually needed?
+ ld.exitIfErrors(ctx)
// Tidy the build list, if applicable, before we report errors.
// (The process of tidying may remove errors from irrelevant dependencies.)
if ld.Tidy {
rs, err := tidyRoots(ctx, ld.requirements, ld.pkgs)
if err != nil {
- ld.errorf("go: %v\n", err)
- }
-
- if ld.requirements.pruning == pruned {
- // We continuously add tidy roots to ld.requirements during loading, so at
- // this point the tidy roots should be a subset of the roots of
- // ld.requirements, ensuring that no new dependencies are brought inside
- // the graph-pruning horizon.
- // If that is not the case, there is a bug in the loading loop above.
- for _, m := range rs.rootModules {
- if v, ok := ld.requirements.rootSelected(m.Path); !ok || v != m.Version {
- ld.errorf("go: internal error: a requirement on %v is needed but was not added during package loading\n", m)
- base.ExitIfErrors()
+ ld.error(err)
+ } else {
+ if ld.TidyGoVersion != "" {
+ // Attempt to switch to the requested Go version. We have been using its
+ // pruning and semantics all along, but there may have been — and may
+ // still be — requirements on higher versions in the graph.
+ tidy := overrideRoots(ctx, rs, []module.Version{{Path: "go", Version: ld.TidyGoVersion}})
+ mg, err := tidy.Graph(ctx)
+ if err != nil {
+ ld.error(err)
+ }
+ if v := mg.Selected("go"); v == ld.TidyGoVersion {
+ rs = tidy
+ } else {
+ conflict := Conflict{
+ Path: mg.g.FindPath(func(m module.Version) bool {
+ return m.Path == "go" && m.Version == v
+ })[1:],
+ Constraint: module.Version{Path: "go", Version: ld.TidyGoVersion},
+ }
+ msg := conflict.Summary()
+ if cfg.BuildV {
+ msg = conflict.String()
+ }
+ ld.error(errors.New(msg))
+ }
+ }
+
+ if ld.requirements.pruning == pruned {
+ // We continuously add tidy roots to ld.requirements during loading, so
+ // at this point the tidy roots (other than possibly the "go" version
+ // edited above) should be a subset of the roots of ld.requirements,
+ // ensuring that no new dependencies are brought inside the
+ // graph-pruning horizon.
+ // If that is not the case, there is a bug in the loading loop above.
+ for _, m := range rs.rootModules {
+ if m.Path == "go" && ld.TidyGoVersion != "" {
+ continue
+ }
+ if v, ok := ld.requirements.rootSelected(m.Path); !ok || v != m.Version {
+ ld.error(fmt.Errorf("internal error: a requirement on %v is needed but was not added during package loading (selected %s)", m, v))
+ }
}
}
+
+ ld.requirements = rs
}
- ld.requirements = rs
+
+ ld.exitIfErrors(ctx)
}
// Report errors, if any.
}
}
- if ld.SilencePackageErrors {
- continue
+ if stdErr := (*ImportMissingError)(nil); errors.As(pkg.err, &stdErr) && stdErr.isStd {
+ // Add importer go version information to import errors of standard
+ // library packages arising from newer releases.
+ if importer := pkg.stack; importer != nil {
+ if v, ok := rawGoVersion.Load(importer.mod); ok && gover.Compare(gover.Local(), v.(string)) < 0 {
+ stdErr.importerGoVersion = v.(string)
+ }
+ }
+ if ld.SilenceMissingStdImports {
+ continue
+ }
}
- if stdErr := (*ImportMissingError)(nil); errors.As(pkg.err, &stdErr) &&
- stdErr.isStd && ld.SilenceMissingStdImports {
+ if ld.SilencePackageErrors {
continue
}
if ld.SilenceNoGoErrors && errors.Is(pkg.err, imports.ErrNoGo) {
continue
}
- ld.errorf("%s: %v\n", pkg.stackText(), pkg.err)
+ ld.error(fmt.Errorf("%s: %w", pkg.stackText(), pkg.err))
}
ld.checkMultiplePaths()
//
// In particular:
//
-// - Modules that provide packages directly imported from the main module are
-// marked as direct, and are promoted to explicit roots. If a needed root
-// cannot be promoted due to -mod=readonly or -mod=vendor, the importing
-// package is marked with an error.
+// - Modules that provide packages directly imported from the main module are
+// marked as direct, and are promoted to explicit roots. If a needed root
+// cannot be promoted due to -mod=readonly or -mod=vendor, the importing
+// package is marked with an error.
//
-// - If ld scanned the "all" pattern independent of build constraints, it is
-// guaranteed to have seen every direct import. Module dependencies that did
-// not provide any directly-imported package are then marked as indirect.
+// - If ld scanned the "all" pattern independent of build constraints, it is
+// guaranteed to have seen every direct import. Module dependencies that did
+// not provide any directly-imported package are then marked as indirect.
//
-// - Root dependencies are updated to their selected versions.
+// - Root dependencies are updated to their selected versions.
//
// The "changed" return value reports whether the update changed the selected
// version of any module that either provided a loaded package or may now
}
}
+ var maxTooNew *gover.TooNewError
for _, pkg := range ld.pkgs {
+ if pkg.err != nil {
+ if tooNew := (*gover.TooNewError)(nil); errors.As(pkg.err, &tooNew) {
+ if maxTooNew == nil || gover.Compare(tooNew.GoVersion, maxTooNew.GoVersion) > 0 {
+ maxTooNew = tooNew
+ }
+ }
+ }
if pkg.mod.Version != "" || !MainModules.Contains(pkg.mod.Path) {
continue
}
+
for _, dep := range pkg.imports {
if !dep.fromExternalModule() {
continue
}
+ if inWorkspaceMode() {
+ // In workspace mode / workspace pruning mode, the roots are the main modules
+ // rather than the main module's direct dependencies. The check below on the selected
+ // roots does not apply.
+ if cfg.BuildMod == "vendor" {
+ // In workspace vendor mode, we don't need to load the requirements of the workspace
+ // modules' dependencies so the check below doesn't work. But that's okay, because
+ // checking whether modules are required directly for the purposes of pruning is
+ // less important in vendor mode: if we were able to load the package, we have
+ // everything we need to build the package, and dependencies' tests are pruned out
+ // of the vendor directory anyway.
+ continue
+ }
+ if mg, err := rs.Graph(ctx); err != nil {
+ return false, err
+ } else if _, ok := mg.RequiredBy(dep.mod); !ok {
+ // dep.mod is not an explicit dependency, but needs to be.
+ // See comment on error returned below.
+ pkg.err = &DirectImportFromImplicitDependencyError{
+ ImporterPath: pkg.path,
+ ImportedPath: dep.path,
+ Module: dep.mod,
+ }
+ }
+ continue
+ }
+
if pkg.err == nil && cfg.BuildMod != "mod" {
if v, ok := rs.rootSelected(dep.mod.Path); !ok || v != dep.mod.Version {
// dep.mod is not an explicit dependency, but needs to be.
direct[dep.mod.Path] = true
}
}
+ if maxTooNew != nil {
+ return false, maxTooNew
+ }
var addRoots []module.Version
if ld.Tidy {
return false, err
}
- if rs != ld.requirements && !reflect.DeepEqual(rs.rootModules, ld.requirements.rootModules) {
+ if rs.GoVersion() != ld.requirements.GoVersion() {
+ // A change in the selected Go version may or may not affect the set of
+ // loaded packages, but in some cases it can change the meaning of the "all"
+ // pattern, the level of pruning in the module graph, and even the set of
+ // packages present in the standard library. If it has changed, it's best to
+ // reload packages once more to be sure everything is stable.
+ changed = true
+ } else if rs != ld.requirements && !reflect.DeepEqual(rs.rootModules, ld.requirements.rootModules) {
// The roots of the module graph have changed in some way (not just the
// "direct" markings). Check whether the changes affected any of the loaded
// packages.
//
// In some sense, we can think of this as ‘upgraded the module providing
// pkg.path from "none" to a version higher than "none"’.
- if _, _, _, err = importFromModules(ctx, pkg.path, rs, nil); err == nil {
+ if _, _, _, _, err = importFromModules(ctx, pkg.path, rs, nil, ld.skipImportModFiles); err == nil {
changed = true
break
}
// The newly-resolved packages are added to the addedModuleFor map, and
// resolveMissingImports returns a map from each new module version to
// the first missing package that module would resolve.
-func (ld *loader) resolveMissingImports(ctx context.Context) (modAddedBy map[module.Version]*loadPkg) {
+func (ld *loader) resolveMissingImports(ctx context.Context) (modAddedBy map[module.Version]*loadPkg, err error) {
type pkgMod struct {
pkg *loadPkg
mod *module.Version
<-ld.work.Idle()
modAddedBy = map[module.Version]*loadPkg{}
+
+ var (
+ maxTooNew *gover.TooNewError
+ maxTooNewPkg *loadPkg
+ )
+ for _, pm := range pkgMods {
+ if tooNew := (*gover.TooNewError)(nil); errors.As(pm.pkg.err, &tooNew) {
+ if maxTooNew == nil || gover.Compare(tooNew.GoVersion, maxTooNew.GoVersion) > 0 {
+ maxTooNew = tooNew
+ maxTooNewPkg = pm.pkg
+ }
+ }
+ }
+ if maxTooNew != nil {
+ fmt.Fprintf(os.Stderr, "go: toolchain upgrade needed to resolve %s\n", maxTooNewPkg.path)
+ return nil, maxTooNew
+ }
+
for _, pm := range pkgMods {
pkg, mod := pm.pkg, *pm.mod
if mod.Path == "" {
}
}
- return modAddedBy
+ return modAddedBy, nil
}
// pkg locates the *loadPkg for path, creating and queuing it for loading if
panic("internal error: (*loader).pkg called with pkgImportsLoaded flag set")
}
- pkg := ld.pkgCache.Do(path, func() interface{} {
+ pkg := ld.pkgCache.Do(path, func() *loadPkg {
pkg := &loadPkg{
path: path,
}
ld.work.Add(func() { ld.load(ctx, pkg) })
return pkg
- }).(*loadPkg)
+ })
ld.applyPkgFlags(ctx, pkg, flags)
return pkg
// If the main module is tidy and the package is in "all" — or if we're
// lucky — we can identify all of its imports without actually loading the
// full module graph.
- m, _, _, err := importFromModules(ctx, path, ld.requirements, nil)
+ m, _, _, _, err := importFromModules(ctx, path, ld.requirements, nil, ld.skipImportModFiles)
if err != nil {
var missing *ImportMissingError
if errors.As(err, &missing) && ld.ResolveMissingImports {
for m := range need {
toAdd = append(toAdd, m)
}
- module.Sort(toAdd)
+ gover.ModSort(toAdd)
rs, err := updateRoots(ctx, ld.requirements.direct, ld.requirements, nil, toAdd, ld.AssumeRootsImported)
if err != nil {
// We are missing some root dependency, and for some reason we can't load
// enough of the module dependency graph to add the missing root. Package
// loading is doomed to fail, so fail quickly.
- ld.errorf("go: %v\n", err)
- base.ExitIfErrors()
+ ld.error(err)
+ ld.exitIfErrors(ctx)
return false
}
if reflect.DeepEqual(rs.rootModules, ld.requirements.rootModules) {
// load loads an individual package.
func (ld *loader) load(ctx context.Context, pkg *loadPkg) {
- if strings.Contains(pkg.path, "@") {
- // Leave for error during load.
- return
- }
- if build.IsLocalImport(pkg.path) || filepath.IsAbs(pkg.path) {
- // Leave for error during load.
- // (Module mode does not allow local imports.)
- return
- }
-
- if search.IsMetaPackage(pkg.path) {
- pkg.err = &invalidImportError{
- importPath: pkg.path,
- err: fmt.Errorf("%q is not an importable package; see 'go help packages'", pkg.path),
- }
- return
- }
-
var mg *ModuleGraph
if ld.requirements.pruning == unpruned {
var err error
}
}
- pkg.mod, pkg.dir, pkg.altMods, pkg.err = importFromModules(ctx, pkg.path, ld.requirements, mg)
+ var modroot string
+ pkg.mod, modroot, pkg.dir, pkg.altMods, pkg.err = importFromModules(ctx, pkg.path, ld.requirements, mg, ld.skipImportModFiles)
if pkg.dir == "" {
return
}
// We can't scan standard packages for gccgo.
} else {
var err error
- imports, testImports, err = scanDir(pkg.dir, ld.Tags)
+ imports, testImports, err = scanDir(modroot, pkg.dir, ld.Tags)
if err != nil {
pkg.err = err
return
func (ld *loader) checkMultiplePaths() {
mods := ld.requirements.rootModules
if cached := ld.requirements.graph.Load(); cached != nil {
- if mg := cached.(cachedGraph).mg; mg != nil {
+ if mg := cached.mg; mg != nil {
mods = mg.BuildList()
}
}
if prev, ok := firstPath[src]; !ok {
firstPath[src] = mod.Path
} else if prev != mod.Path {
- ld.errorf("go: %s@%s used for two different module paths (%s and %s)\n", src.Path, src.Version, prev, mod.Path)
+ ld.error(fmt.Errorf("%s@%s used for two different module paths (%s and %s)", src.Path, src.Version, prev, mod.Path))
}
}
}
// checkTidyCompatibility emits an error if any package would be loaded from a
// different module under rs than under ld.requirements.
-func (ld *loader) checkTidyCompatibility(ctx context.Context, rs *Requirements) {
+func (ld *loader) checkTidyCompatibility(ctx context.Context, rs *Requirements, compatVersion string) {
+ goVersion := rs.GoVersion()
suggestUpgrade := false
suggestEFlag := false
suggestFixes := func() {
fmt.Fprintln(os.Stderr)
goFlag := ""
- if ld.GoVersion != MainModules.GoVersion() {
- goFlag = " -go=" + ld.GoVersion
+ if goVersion != MainModules.GoVersion() {
+ goFlag = " -go=" + goVersion
}
compatFlag := ""
- if ld.TidyCompatibleVersion != priorGoVersion(ld.GoVersion) {
- compatFlag = " -compat=" + ld.TidyCompatibleVersion
+ if compatVersion != gover.Prev(goVersion) {
+ compatFlag = " -compat=" + compatVersion
}
if suggestUpgrade {
eDesc := ""
eDesc = ", leaving some packages unresolved"
eFlag = " -e"
}
- fmt.Fprintf(os.Stderr, "To upgrade to the versions selected by go %s%s:\n\tgo mod tidy%s -go=%s && go mod tidy%s -go=%s%s\n", ld.TidyCompatibleVersion, eDesc, eFlag, ld.TidyCompatibleVersion, eFlag, ld.GoVersion, compatFlag)
+ fmt.Fprintf(os.Stderr, "To upgrade to the versions selected by go %s%s:\n\tgo mod tidy%s -go=%s && go mod tidy%s -go=%s%s\n", compatVersion, eDesc, eFlag, compatVersion, eFlag, goVersion, compatFlag)
} else if suggestEFlag {
// If some packages are missing but no package is upgraded, then we
// shouldn't suggest upgrading to the Go 1.16 versions explicitly — that
// wouldn't actually fix anything for Go 1.16 users, and *would* break
// something for Go 1.17 users.
- fmt.Fprintf(os.Stderr, "To proceed despite packages unresolved in go %s:\n\tgo mod tidy -e%s%s\n", ld.TidyCompatibleVersion, goFlag, compatFlag)
+ fmt.Fprintf(os.Stderr, "To proceed despite packages unresolved in go %s:\n\tgo mod tidy -e%s%s\n", compatVersion, goFlag, compatFlag)
}
- fmt.Fprintf(os.Stderr, "If reproducibility with go %s is not needed:\n\tgo mod tidy%s -compat=%s\n", ld.TidyCompatibleVersion, goFlag, ld.GoVersion)
+ fmt.Fprintf(os.Stderr, "If reproducibility with go %s is not needed:\n\tgo mod tidy%s -compat=%s\n", compatVersion, goFlag, goVersion)
// TODO(#46141): Populate the linked wiki page.
fmt.Fprintf(os.Stderr, "For other options, see:\n\thttps://golang.org/doc/modules/pruning\n")
mg, err := rs.Graph(ctx)
if err != nil {
- ld.errorf("go: error loading go %s module graph: %v\n", ld.TidyCompatibleVersion, err)
+ ld.error(fmt.Errorf("error loading go %s module graph: %w", compatVersion, err))
+ ld.switchIfErrors(ctx)
suggestFixes()
+ ld.exitIfErrors(ctx)
return
}
pkg := pkg
ld.work.Add(func() {
- mod, _, _, err := importFromModules(ctx, pkg.path, rs, mg)
+ mod, _, _, _, err := importFromModules(ctx, pkg.path, rs, mg, ld.skipImportModFiles)
if mod != pkg.mod {
mismatches := <-mismatchMu
mismatches[pkg] = mismatch{mod: mod, err: err}
for _, m := range ld.requirements.rootModules {
if v := mg.Selected(m.Path); v != m.Version {
fmt.Fprintln(os.Stderr)
- base.Fatalf("go: internal error: failed to diagnose selected-version mismatch for module %s: go %s selects %s, but go %s selects %s\n\tPlease report this at https://golang.org/issue.", m.Path, ld.GoVersion, m.Version, ld.TidyCompatibleVersion, v)
+ base.Fatalf("go: internal error: failed to diagnose selected-version mismatch for module %s: go %s selects %s, but go %s selects %s\n\tPlease report this at https://golang.org/issue.", m.Path, goVersion, m.Version, compatVersion, v)
}
}
return
if pkg.isTest() {
// We already did (or will) report an error for the package itself,
- // so don't report a duplicate (and more vebose) error for its test.
+ // so don't report a duplicate (and more verbose) error for its test.
if _, ok := mismatches[pkg.testOf]; !ok {
base.Fatalf("go: internal error: mismatch recorded for test %s, but not its non-test package", pkg.path)
}
Path: pkg.mod.Path,
Version: mg.Selected(pkg.mod.Path),
}
- ld.errorf("%s loaded from %v,\n\tbut go %s would fail to locate it in %s\n", pkg.stackText(), pkg.mod, ld.TidyCompatibleVersion, selected)
+ ld.error(fmt.Errorf("%s loaded from %v,\n\tbut go %s would fail to locate it in %s", pkg.stackText(), pkg.mod, compatVersion, selected))
} else {
if ambiguous := (*AmbiguousImportError)(nil); errors.As(mismatch.err, &ambiguous) {
// TODO: Is this check needed?
}
- ld.errorf("%s loaded from %v,\n\tbut go %s would fail to locate it:\n\t%v\n", pkg.stackText(), pkg.mod, ld.TidyCompatibleVersion, mismatch.err)
+ ld.error(fmt.Errorf("%s loaded from %v,\n\tbut go %s would fail to locate it:\n\t%v", pkg.stackText(), pkg.mod, compatVersion, mismatch.err))
}
suggestEFlag = true
// pkg.err should have already been logged elsewhere — along with a
// stack trace — so log only the import path and non-error info here.
suggestUpgrade = true
- ld.errorf("%s failed to load from any module,\n\tbut go %s would load it from %v\n", pkg.path, ld.TidyCompatibleVersion, mismatch.mod)
+ ld.error(fmt.Errorf("%s failed to load from any module,\n\tbut go %s would load it from %v", pkg.path, compatVersion, mismatch.mod))
case pkg.mod != mismatch.mod:
// The package is loaded successfully by both Go versions, but from a
// unnoticed!) variations in behavior between builds with different
// toolchains.
suggestUpgrade = true
- ld.errorf("%s loaded from %v,\n\tbut go %s would select %v\n", pkg.stackText(), pkg.mod, ld.TidyCompatibleVersion, mismatch.mod.Version)
+ ld.error(fmt.Errorf("%s loaded from %v,\n\tbut go %s would select %v\n", pkg.stackText(), pkg.mod, compatVersion, mismatch.mod.Version))
default:
base.Fatalf("go: internal error: mismatch recorded for package %s, but no differences found", pkg.path)
}
}
+ ld.switchIfErrors(ctx)
suggestFixes()
- base.ExitIfErrors()
+ ld.exitIfErrors(ctx)
}
// scanDir is like imports.ScanDir but elides known magic imports from the list,
// during "go vendor", we look into "// +build appengine" files and
// may see these legacy imports. We drop them so that the module
// search does not look for modules to try to satisfy them.
-func scanDir(dir string, tags map[string]bool) (imports_, testImports []string, err error) {
+func scanDir(modroot string, dir string, tags map[string]bool) (imports_, testImports []string, err error) {
+ if ip, mierr := modindex.GetPackage(modroot, dir); mierr == nil {
+ imports_, testImports, err = ip.ScanDir(tags)
+ goto Happy
+ } else if !errors.Is(mierr, modindex.ErrNotIndexed) {
+ return nil, nil, mierr
+ }
+
imports_, testImports, err = imports.ScanDir(dir, tags)
+Happy:
filter := func(x []string) []string {
w := 0
// other2 tested by
// other2.test imports
// pkg
-//
func (pkg *loadPkg) stackText() string {
var stack []*loadPkg
for p := pkg; p != nil; p = p.stack {
stack = append(stack, p)
}
- var buf bytes.Buffer
+ var buf strings.Builder
for i := len(stack) - 1; i >= 0; i-- {
p := stack[i]
fmt.Fprint(&buf, p.path)
// If there is no reason for the package to be in the current build,
// Why returns an empty string.
func Why(path string) string {
- pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
+ pkg, ok := loaded.pkgCache.Get(path)
if !ok {
return ""
}
// WhyDepth returns 0.
func WhyDepth(path string) int {
n := 0
- pkg, _ := loaded.pkgCache.Get(path).(*loadPkg)
+ pkg, _ := loaded.pkgCache.Get(path)
for p := pkg; p != nil; p = p.stack {
n++
}