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// go mod tidy
6
7package modcmd
8
9import (
10	"cmd/go/internal/base"
11	"cmd/go/internal/cfg"
12	"cmd/go/internal/gover"
13	"cmd/go/internal/imports"
14	"cmd/go/internal/modload"
15	"cmd/go/internal/toolchain"
16	"context"
17	"fmt"
18
19	"golang.org/x/mod/modfile"
20)
21
22var cmdTidy = &base.Command{
23	UsageLine: "go mod tidy [-e] [-v] [-x] [-diff] [-go=version] [-compat=version]",
24	Short:     "add missing and remove unused modules",
25	Long: `
26Tidy makes sure go.mod matches the source code in the module.
27It adds any missing modules necessary to build the current module's
28packages and dependencies, and it removes unused modules that
29don't provide any relevant packages. It also adds any missing entries
30to go.sum and removes any unnecessary ones.
31
32The -v flag causes tidy to print information about removed modules
33to standard error.
34
35The -e flag causes tidy to attempt to proceed despite errors
36encountered while loading packages.
37
38The -diff flag causes tidy not to modify go.mod or go.sum but
39instead print the necessary changes as a unified diff. It exits
40with a non-zero code if the diff is not empty.
41
42The -go flag causes tidy to update the 'go' directive in the go.mod
43file to the given version, which may change which module dependencies
44are retained as explicit requirements in the go.mod file.
45(Go versions 1.17 and higher retain more requirements in order to
46support lazy module loading.)
47
48The -compat flag preserves any additional checksums needed for the
49'go' command from the indicated major Go release to successfully load
50the module graph, and causes tidy to error out if that version of the
51'go' command would load any imported package from a different module
52version. By default, tidy acts as if the -compat flag were set to the
53version prior to the one indicated by the 'go' directive in the go.mod
54file.
55
56The -x flag causes tidy to print the commands download executes.
57
58See https://golang.org/ref/mod#go-mod-tidy for more about 'go mod tidy'.
59	`,
60	Run: runTidy,
61}
62
63var (
64	tidyE      bool          // if true, report errors but proceed anyway.
65	tidyDiff   bool          // if true, do not update go.mod or go.sum and show changes. Return corresponding exit code.
66	tidyGo     goVersionFlag // go version to write to the tidied go.mod file (toggles lazy loading)
67	tidyCompat goVersionFlag // go version for which the tidied go.mod and go.sum files should be “compatible”
68)
69
70func init() {
71	cmdTidy.Flag.BoolVar(&cfg.BuildV, "v", false, "")
72	cmdTidy.Flag.BoolVar(&cfg.BuildX, "x", false, "")
73	cmdTidy.Flag.BoolVar(&tidyE, "e", false, "")
74	cmdTidy.Flag.BoolVar(&tidyDiff, "diff", false, "")
75	cmdTidy.Flag.Var(&tidyGo, "go", "")
76	cmdTidy.Flag.Var(&tidyCompat, "compat", "")
77	base.AddChdirFlag(&cmdTidy.Flag)
78	base.AddModCommonFlags(&cmdTidy.Flag)
79}
80
81// A goVersionFlag is a flag.Value representing a supported Go version.
82//
83// (Note that the -go argument to 'go mod edit' is *not* a goVersionFlag.
84// It intentionally allows newer-than-supported versions as arguments.)
85type goVersionFlag struct {
86	v string
87}
88
89func (f *goVersionFlag) String() string { return f.v }
90func (f *goVersionFlag) Get() any       { return f.v }
91
92func (f *goVersionFlag) Set(s string) error {
93	if s != "" {
94		latest := gover.Local()
95		if !modfile.GoVersionRE.MatchString(s) {
96			return fmt.Errorf("expecting a Go version like %q", latest)
97		}
98		if gover.Compare(s, latest) > 0 {
99			return fmt.Errorf("maximum supported Go version is %s", latest)
100		}
101	}
102
103	f.v = s
104	return nil
105}
106
107func runTidy(ctx context.Context, cmd *base.Command, args []string) {
108	if len(args) > 0 {
109		base.Fatalf("go: 'go mod tidy' accepts no arguments")
110	}
111
112	// Tidy aims to make 'go test' reproducible for any package in 'all', so we
113	// need to include test dependencies. For modules that specify go 1.15 or
114	// earlier this is a no-op (because 'all' saturates transitive test
115	// dependencies).
116	//
117	// However, with lazy loading (go 1.16+) 'all' includes only the packages that
118	// are transitively imported by the main module, not the test dependencies of
119	// those packages. In order to make 'go test' reproducible for the packages
120	// that are in 'all' but outside of the main module, we must explicitly
121	// request that their test dependencies be included.
122	modload.ForceUseModules = true
123	modload.RootMode = modload.NeedRoot
124
125	goVersion := tidyGo.String()
126	if goVersion != "" && gover.Compare(gover.Local(), goVersion) < 0 {
127		toolchain.SwitchOrFatal(ctx, &gover.TooNewError{
128			What:      "-go flag",
129			GoVersion: goVersion,
130		})
131	}
132
133	modload.LoadPackages(ctx, modload.PackageOpts{
134		TidyGoVersion:            tidyGo.String(),
135		Tags:                     imports.AnyTags(),
136		Tidy:                     true,
137		TidyDiff:                 tidyDiff,
138		TidyCompatibleVersion:    tidyCompat.String(),
139		VendorModulesInGOROOTSrc: true,
140		ResolveMissingImports:    true,
141		LoadTests:                true,
142		AllowErrors:              tidyE,
143		SilenceMissingStdImports: true,
144		Switcher:                 new(toolchain.Switcher),
145	}, "all")
146}
147