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
5package modload
6
7import (
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/pkgpattern"
25
26	"golang.org/x/mod/module"
27)
28
29type ListMode int
30
31const (
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.
43func 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
125func 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.
296func 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}
311