github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/go/modload/list.go (about)

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