Source file src/cmd/go/internal/modload/list.go

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package modload
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"encoding/json"
    11  	"errors"
    12  	"fmt"
    13  	"io"
    14  	"os"
    15  	"runtime"
    16  	"strings"
    17  
    18  	"cmd/go/internal/base"
    19  	"cmd/go/internal/cfg"
    20  	"cmd/go/internal/gover"
    21  	"cmd/go/internal/modfetch/codehost"
    22  	"cmd/go/internal/modinfo"
    23  	"cmd/go/internal/search"
    24  	"cmd/internal/par"
    25  	"cmd/internal/pkgpattern"
    26  
    27  	"golang.org/x/mod/module"
    28  )
    29  
    30  type ListMode int
    31  
    32  const (
    33  	ListU ListMode = 1 << iota
    34  	ListRetracted
    35  	ListDeprecated
    36  	ListVersions
    37  	ListRetractedVersions
    38  )
    39  
    40  // ListModules returns a description of the modules matching args, if known,
    41  // along with any error preventing additional matches from being identified.
    42  //
    43  // The returned slice can be nonempty even if the error is non-nil.
    44  func ListModules(ctx context.Context, args []string, mode ListMode, reuseFile string) ([]*modinfo.ModulePublic, error) {
    45  	var reuse map[module.Version]*modinfo.ModulePublic
    46  	if reuseFile != "" {
    47  		data, err := os.ReadFile(reuseFile)
    48  		if err != nil {
    49  			return nil, err
    50  		}
    51  		dec := json.NewDecoder(bytes.NewReader(data))
    52  		reuse = make(map[module.Version]*modinfo.ModulePublic)
    53  		for {
    54  			var m modinfo.ModulePublic
    55  			if err := dec.Decode(&m); err != nil {
    56  				if err == io.EOF {
    57  					break
    58  				}
    59  				return nil, fmt.Errorf("parsing %s: %v", reuseFile, err)
    60  			}
    61  			if m.Origin == nil {
    62  				continue
    63  			}
    64  			m.Reuse = true
    65  			reuse[module.Version{Path: m.Path, Version: m.Version}] = &m
    66  			if m.Query != "" {
    67  				reuse[module.Version{Path: m.Path, Version: m.Query}] = &m
    68  			}
    69  		}
    70  	}
    71  
    72  	rs, mods, err := listModules(ctx, LoadModFile(ctx), args, mode, reuse)
    73  
    74  	type token struct{}
    75  	sem := make(chan token, runtime.GOMAXPROCS(0))
    76  	if mode != 0 {
    77  		for _, m := range mods {
    78  			if m.Reuse {
    79  				continue
    80  			}
    81  			add := func(m *modinfo.ModulePublic) {
    82  				sem <- token{}
    83  				go func() {
    84  					if mode&ListU != 0 {
    85  						addUpdate(ctx, m)
    86  					}
    87  					if mode&ListVersions != 0 {
    88  						addVersions(ctx, m, mode&ListRetractedVersions != 0)
    89  					}
    90  					if mode&ListRetracted != 0 {
    91  						addRetraction(ctx, m)
    92  					}
    93  					if mode&ListDeprecated != 0 {
    94  						addDeprecation(ctx, m)
    95  					}
    96  					<-sem
    97  				}()
    98  			}
    99  
   100  			add(m)
   101  			if m.Replace != nil {
   102  				add(m.Replace)
   103  			}
   104  		}
   105  	}
   106  	// Fill semaphore channel to wait for all tasks to finish.
   107  	for n := cap(sem); n > 0; n-- {
   108  		sem <- token{}
   109  	}
   110  
   111  	if err == nil {
   112  		requirements = rs
   113  		// TODO(#61605): The extra ListU clause fixes a problem with Go 1.21rc3
   114  		// where "go mod tidy" and "go list -m -u all" fight over whether the go.sum
   115  		// should be considered up-to-date. The fix for now is to always treat the
   116  		// go.sum as up-to-date during list -m -u. Probably the right fix is more targeted,
   117  		// but in general list -u is looking up other checksums in the checksum database
   118  		// that won't be necessary later, so it makes sense not to write the go.sum back out.
   119  		if !ExplicitWriteGoMod && mode&ListU == 0 {
   120  			err = commitRequirements(ctx, WriteOpts{})
   121  		}
   122  	}
   123  	return mods, err
   124  }
   125  
   126  func listModules(ctx context.Context, rs *Requirements, args []string, mode ListMode, reuse map[module.Version]*modinfo.ModulePublic) (_ *Requirements, mods []*modinfo.ModulePublic, mgErr error) {
   127  	if len(args) == 0 {
   128  		var ms []*modinfo.ModulePublic
   129  		for _, m := range MainModules.Versions() {
   130  			if gover.IsToolchain(m.Path) {
   131  				continue
   132  			}
   133  			ms = append(ms, moduleInfo(ctx, rs, m, mode, reuse))
   134  		}
   135  		return rs, ms, nil
   136  	}
   137  
   138  	needFullGraph := false
   139  	for _, arg := range args {
   140  		if strings.Contains(arg, `\`) {
   141  			base.Fatalf("go: module paths never use backslash")
   142  		}
   143  		if search.IsRelativePath(arg) {
   144  			base.Fatalf("go: cannot use relative path %s to specify module", arg)
   145  		}
   146  		if arg == "all" || strings.Contains(arg, "...") {
   147  			needFullGraph = true
   148  			if !HasModRoot() {
   149  				base.Fatalf("go: cannot match %q: %v", arg, ErrNoModRoot)
   150  			}
   151  			continue
   152  		}
   153  		if path, vers, found := strings.Cut(arg, "@"); found {
   154  			if vers == "upgrade" || vers == "patch" {
   155  				if _, ok := rs.rootSelected(path); !ok || rs.pruning == unpruned {
   156  					needFullGraph = true
   157  					if !HasModRoot() {
   158  						base.Fatalf("go: cannot match %q: %v", arg, ErrNoModRoot)
   159  					}
   160  				}
   161  			}
   162  			continue
   163  		}
   164  		if _, ok := rs.rootSelected(arg); !ok || rs.pruning == unpruned {
   165  			needFullGraph = true
   166  			if mode&ListVersions == 0 && !HasModRoot() {
   167  				base.Fatalf("go: cannot match %q without -versions or an explicit version: %v", arg, ErrNoModRoot)
   168  			}
   169  		}
   170  	}
   171  
   172  	var mg *ModuleGraph
   173  	if needFullGraph {
   174  		rs, mg, mgErr = expandGraph(ctx, rs)
   175  	}
   176  
   177  	matchedModule := map[module.Version]bool{}
   178  	for _, arg := range args {
   179  		if path, vers, found := strings.Cut(arg, "@"); found {
   180  			var current string
   181  			if mg == nil {
   182  				current, _ = rs.rootSelected(path)
   183  			} else {
   184  				current = mg.Selected(path)
   185  			}
   186  			if current == "none" && mgErr != nil {
   187  				if vers == "upgrade" || vers == "patch" {
   188  					// The module graph is incomplete, so we don't know what version we're
   189  					// actually upgrading from.
   190  					// mgErr is already set, so just skip this module.
   191  					continue
   192  				}
   193  			}
   194  
   195  			allowed := CheckAllowed
   196  			if IsRevisionQuery(path, vers) || mode&ListRetracted != 0 {
   197  				// Allow excluded and retracted versions if the user asked for a
   198  				// specific revision or used 'go list -retracted'.
   199  				allowed = nil
   200  			}
   201  			info, err := queryReuse(ctx, path, vers, current, allowed, reuse)
   202  			if err != nil {
   203  				var origin *codehost.Origin
   204  				if info != nil {
   205  					origin = info.Origin
   206  				}
   207  				mods = append(mods, &modinfo.ModulePublic{
   208  					Path:    path,
   209  					Version: vers,
   210  					Error:   modinfoError(path, vers, err),
   211  					Origin:  origin,
   212  				})
   213  				continue
   214  			}
   215  
   216  			// Indicate that m was resolved from outside of rs by passing a nil
   217  			// *Requirements instead.
   218  			var noRS *Requirements
   219  
   220  			mod := moduleInfo(ctx, noRS, module.Version{Path: path, Version: info.Version}, mode, reuse)
   221  			if vers != mod.Version {
   222  				mod.Query = vers
   223  			}
   224  			mod.Origin = info.Origin
   225  			mods = append(mods, mod)
   226  			continue
   227  		}
   228  
   229  		// Module path or pattern.
   230  		var match func(string) bool
   231  		if arg == "all" {
   232  			match = func(p string) bool { return !gover.IsToolchain(p) }
   233  		} else if strings.Contains(arg, "...") {
   234  			mp := pkgpattern.MatchPattern(arg)
   235  			match = func(p string) bool { return mp(p) && !gover.IsToolchain(p) }
   236  		} else {
   237  			var v string
   238  			if mg == nil {
   239  				var ok bool
   240  				v, ok = rs.rootSelected(arg)
   241  				if !ok {
   242  					// We checked rootSelected(arg) in the earlier args loop, so if there
   243  					// is no such root we should have loaded a non-nil mg.
   244  					panic(fmt.Sprintf("internal error: root requirement expected but not found for %v", arg))
   245  				}
   246  			} else {
   247  				v = mg.Selected(arg)
   248  			}
   249  			if v == "none" && mgErr != nil {
   250  				// mgErr is already set, so just skip this module.
   251  				continue
   252  			}
   253  			if v != "none" {
   254  				mods = append(mods, moduleInfo(ctx, rs, module.Version{Path: arg, Version: v}, mode, reuse))
   255  			} else if cfg.BuildMod == "vendor" {
   256  				// In vendor mode, we can't determine whether a missing module is “a
   257  				// known dependency” because the module graph is incomplete.
   258  				// Give a more explicit error message.
   259  				mods = append(mods, &modinfo.ModulePublic{
   260  					Path:  arg,
   261  					Error: modinfoError(arg, "", errors.New("can't resolve module using the vendor directory\n\t(Use -mod=mod or -mod=readonly to bypass.)")),
   262  				})
   263  			} else if mode&ListVersions != 0 {
   264  				// Don't make the user provide an explicit '@latest' when they're
   265  				// explicitly asking what the available versions are. Instead, return a
   266  				// module with version "none", to which we can add the requested list.
   267  				mods = append(mods, &modinfo.ModulePublic{Path: arg})
   268  			} else {
   269  				mods = append(mods, &modinfo.ModulePublic{
   270  					Path:  arg,
   271  					Error: modinfoError(arg, "", errors.New("not a known dependency")),
   272  				})
   273  			}
   274  			continue
   275  		}
   276  
   277  		var matches []module.Version
   278  		for _, m := range mg.BuildList() {
   279  			if match(m.Path) {
   280  				if !matchedModule[m] {
   281  					matchedModule[m] = true
   282  					matches = append(matches, m)
   283  				}
   284  			}
   285  		}
   286  
   287  		if len(matches) == 0 {
   288  			fmt.Fprintf(os.Stderr, "warning: pattern %q matched no module dependencies\n", arg)
   289  		}
   290  
   291  		q := par.NewQueue(runtime.GOMAXPROCS(0))
   292  		fetchedMods := make([]*modinfo.ModulePublic, len(matches))
   293  		for i, m := range matches {
   294  			q.Add(func() {
   295  				fetchedMods[i] = moduleInfo(ctx, rs, m, mode, reuse)
   296  			})
   297  		}
   298  		<-q.Idle()
   299  		mods = append(mods, fetchedMods...)
   300  	}
   301  
   302  	return rs, mods, mgErr
   303  }
   304  
   305  // modinfoError wraps an error to create an error message in
   306  // modinfo.ModuleError with minimal redundancy.
   307  func modinfoError(path, vers string, err error) *modinfo.ModuleError {
   308  	var nerr *NoMatchingVersionError
   309  	var merr *module.ModuleError
   310  	if errors.As(err, &nerr) {
   311  		// NoMatchingVersionError contains the query, so we don't mention the
   312  		// query again in ModuleError.
   313  		err = &module.ModuleError{Path: path, Err: err}
   314  	} else if !errors.As(err, &merr) {
   315  		// If the error does not contain path and version, wrap it in a
   316  		// module.ModuleError.
   317  		err = &module.ModuleError{Path: path, Version: vers, Err: err}
   318  	}
   319  
   320  	return &modinfo.ModuleError{Err: err.Error()}
   321  }
   322  

View as plain text