Skip to content

Commit b6f4866

Browse files
rscjproberts
authored andcommitted
cmd/go: add -reuse flag to make proxy invocations more efficient
The go list -m and go mod download commands now have a -reuse flag, which is passed the name of a file containing the JSON output from a previous run of the same command. (It is up to the caller to ensure that flags such as -versions or -retracted, which affect the output, are consistent between the old and new run.) The new run uses the old JSON to evaluate whether the answer is unchanged since the old run. If so, it reuses that information, avoiding a costly 'git fetch', and sets a new Reuse: true field in its own JSON output. This dance with saving the JSON output and passing it back to -reuse is not necessary on most systems, because the go command caches version control checkouts in the module cache. That cache means that a new 'git fetch' would only download the commits that are new since the previous one (often none at all). The dance becomes important only on systems that do not preserve the module cache, for example by running 'go clean -modcache' aggressively or by running in some environment that starts with an empty file system. For golang#53644. Change-Id: I447960abf8055f83cc6dbc699a9fde9931130004 Reviewed-on: https://go-review.googlesource.com/c/go/+/411398 Run-TryBot: Russ Cox <[email protected]> Reviewed-by: Bryan Mills <[email protected]>
1 parent ace7a7e commit b6f4866

File tree

15 files changed

+711
-65
lines changed

15 files changed

+711
-65
lines changed

src/cmd/go/alldocs.go

Lines changed: 25 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cmd/go/internal/list/list.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ applied to a Go struct, but now a Module struct:
223223
224224
type Module struct {
225225
Path string // module path
226+
Query string // version query corresponding to this version
226227
Version string // module version
227228
Versions []string // available module versions
228229
Replace *Module // replaced by this module
@@ -236,6 +237,8 @@ applied to a Go struct, but now a Module struct:
236237
Retracted []string // retraction information, if any (with -retracted or -u)
237238
Deprecated string // deprecation message, if any (with -u)
238239
Error *ModuleError // error loading module
240+
Origin any // provenance of module
241+
Reuse bool // reuse of old module info is safe
239242
}
240243
241244
type ModuleError struct {
@@ -312,6 +315,16 @@ that must be a module path or query and returns the specified
312315
module as a Module struct. If an error occurs, the result will
313316
be a Module struct with a non-nil Error field.
314317
318+
When using -m, the -reuse=old.json flag accepts the name of file containing
319+
the JSON output of a previous 'go list -m -json' invocation with the
320+
same set of modifier flags (such as -u, -retracted, and -versions).
321+
The go command may use this file to determine that a module is unchanged
322+
since the previous invocation and avoid redownloading information about it.
323+
Modules that are not redownloaded will be marked in the new output by
324+
setting the Reuse field to true. Normally the module cache provides this
325+
kind of reuse automatically; the -reuse flag can be useful on systems that
326+
do not preserve the module cache.
327+
315328
For more about build flags, see 'go help build'.
316329
317330
For more about specifying packages, see 'go help packages'.
@@ -337,6 +350,7 @@ var (
337350
listJsonFields jsonFlag // If not empty, only output these fields.
338351
listM = CmdList.Flag.Bool("m", false, "")
339352
listRetracted = CmdList.Flag.Bool("retracted", false, "")
353+
listReuse = CmdList.Flag.String("reuse", "", "")
340354
listTest = CmdList.Flag.Bool("test", false, "")
341355
listU = CmdList.Flag.Bool("u", false, "")
342356
listVersions = CmdList.Flag.Bool("versions", false, "")
@@ -398,6 +412,12 @@ func runList(ctx context.Context, cmd *base.Command, args []string) {
398412
if *listFmt != "" && listJson == true {
399413
base.Fatalf("go list -f cannot be used with -json")
400414
}
415+
if *listReuse != "" && !*listM {
416+
base.Fatalf("go list -reuse cannot be used without -m")
417+
}
418+
if *listReuse != "" && modload.HasModRoot() {
419+
base.Fatalf("go list -reuse cannot be used inside a module")
420+
}
401421

402422
work.BuildInit()
403423
out := newTrackingWriter(os.Stdout)
@@ -532,7 +552,10 @@ func runList(ctx context.Context, cmd *base.Command, args []string) {
532552
mode |= modload.ListRetractedVersions
533553
}
534554
}
535-
mods, err := modload.ListModules(ctx, args, mode)
555+
if *listReuse != "" && len(args) == 0 {
556+
base.Fatalf("go: list -m -reuse only has an effect with module@version arguments")
557+
}
558+
mods, err := modload.ListModules(ctx, args, mode, *listReuse)
536559
if !*listE {
537560
for _, m := range mods {
538561
if m.Error != nil {
@@ -783,7 +806,7 @@ func runList(ctx context.Context, cmd *base.Command, args []string) {
783806
if *listRetracted {
784807
mode |= modload.ListRetracted
785808
}
786-
rmods, err := modload.ListModules(ctx, args, mode)
809+
rmods, err := modload.ListModules(ctx, args, mode, *listReuse)
787810
if err != nil && !*listE {
788811
base.Errorf("go: %v", err)
789812
}

src/cmd/go/internal/modcmd/download.go

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@ import (
1313
"cmd/go/internal/base"
1414
"cmd/go/internal/cfg"
1515
"cmd/go/internal/modfetch"
16+
"cmd/go/internal/modfetch/codehost"
1617
"cmd/go/internal/modload"
1718

1819
"golang.org/x/mod/module"
1920
"golang.org/x/mod/semver"
2021
)
2122

2223
var cmdDownload = &base.Command{
23-
UsageLine: "go mod download [-x] [-json] [modules]",
24+
UsageLine: "go mod download [-x] [-json] [-reuse=old.json] [modules]",
2425
Short: "download modules to local cache",
2526
Long: `
2627
Download downloads the named modules, which can be module patterns selecting
@@ -44,6 +45,7 @@ corresponding to this Go struct:
4445
4546
type Module struct {
4647
Path string // module path
48+
Query string // version query corresponding to this version
4749
Version string // module version
4850
Error string // error loading module
4951
Info string // absolute path to cached .info file
@@ -52,8 +54,18 @@ corresponding to this Go struct:
5254
Dir string // absolute path to cached source root directory
5355
Sum string // checksum for path, version (as in go.sum)
5456
GoModSum string // checksum for go.mod (as in go.sum)
57+
Origin any // provenance of module
58+
Reuse bool // reuse of old module info is safe
5559
}
5660
61+
The -reuse flag accepts the name of file containing the JSON output of a
62+
previous 'go mod download -json' invocation. The go command may use this
63+
file to determine that a module is unchanged since the previous invocation
64+
and avoid redownloading it. Modules that are not redownloaded will be marked
65+
in the new output by setting the Reuse field to true. Normally the module
66+
cache provides this kind of reuse automatically; the -reuse flag can be
67+
useful on systems that do not preserve the module cache.
68+
5769
The -x flag causes download to print the commands download executes.
5870
5971
See https://golang.org/ref/mod#go-mod-download for more about 'go mod download'.
@@ -62,7 +74,10 @@ See https://golang.org/ref/mod#version-queries for more about version queries.
6274
`,
6375
}
6476

65-
var downloadJSON = cmdDownload.Flag.Bool("json", false, "")
77+
var (
78+
downloadJSON = cmdDownload.Flag.Bool("json", false, "")
79+
downloadReuse = cmdDownload.Flag.String("reuse", "", "")
80+
)
6681

6782
func init() {
6883
cmdDownload.Run = runDownload // break init cycle
@@ -75,13 +90,17 @@ func init() {
7590
type moduleJSON struct {
7691
Path string `json:",omitempty"`
7792
Version string `json:",omitempty"`
93+
Query string `json:",omitempty"`
7894
Error string `json:",omitempty"`
7995
Info string `json:",omitempty"`
8096
GoMod string `json:",omitempty"`
8197
Zip string `json:",omitempty"`
8298
Dir string `json:",omitempty"`
8399
Sum string `json:",omitempty"`
84100
GoModSum string `json:",omitempty"`
101+
102+
Origin *codehost.Origin `json:",omitempty"`
103+
Reuse bool `json:",omitempty"`
85104
}
86105

87106
func runDownload(ctx context.Context, cmd *base.Command, args []string) {
@@ -148,12 +167,12 @@ func runDownload(ctx context.Context, cmd *base.Command, args []string) {
148167
}
149168

150169
downloadModule := func(m *moduleJSON) {
151-
var err error
152-
_, m.Info, err = modfetch.InfoFile(m.Path, m.Version)
170+
_, file, err := modfetch.InfoFile(m.Path, m.Version)
153171
if err != nil {
154172
m.Error = err.Error()
155173
return
156174
}
175+
m.Info = file
157176
m.GoMod, err = modfetch.GoModFile(m.Path, m.Version)
158177
if err != nil {
159178
m.Error = err.Error()
@@ -179,9 +198,14 @@ func runDownload(ctx context.Context, cmd *base.Command, args []string) {
179198
}
180199

181200
var mods []*moduleJSON
201+
202+
if *downloadReuse != "" && modload.HasModRoot() {
203+
base.Fatalf("go mod download -reuse cannot be used inside a module")
204+
}
205+
182206
type token struct{}
183207
sem := make(chan token, runtime.GOMAXPROCS(0))
184-
infos, infosErr := modload.ListModules(ctx, args, 0)
208+
infos, infosErr := modload.ListModules(ctx, args, 0, *downloadReuse)
185209
if !haveExplicitArgs {
186210
// 'go mod download' is sometimes run without arguments to pre-populate the
187211
// module cache. It may fetch modules that aren't needed to build packages
@@ -209,12 +233,18 @@ func runDownload(ctx context.Context, cmd *base.Command, args []string) {
209233
m := &moduleJSON{
210234
Path: info.Path,
211235
Version: info.Version,
236+
Query: info.Query,
237+
Reuse: info.Reuse,
238+
Origin: info.Origin,
212239
}
213240
mods = append(mods, m)
214241
if info.Error != nil {
215242
m.Error = info.Error.Err
216243
continue
217244
}
245+
if m.Reuse {
246+
continue
247+
}
218248
sem <- token{}
219249
go func() {
220250
downloadModule(m)

src/cmd/go/internal/modcmd/why.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func runWhy(ctx context.Context, cmd *base.Command, args []string) {
8282
}
8383
}
8484

85-
mods, err := modload.ListModules(ctx, args, 0)
85+
mods, err := modload.ListModules(ctx, args, 0, "")
8686
if err != nil {
8787
base.Fatalf("go: %v", err)
8888
}

src/cmd/go/internal/modfetch/cache.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,26 @@ func writeDiskStat(file string, info *RevInfo) error {
573573
if file == "" {
574574
return nil
575575
}
576+
577+
if info.Origin != nil {
578+
// Clean the origin information, which might have too many
579+
// validation criteria, for example if we are saving the result of
580+
// m@master as m@pseudo-version.
581+
clean := *info
582+
info = &clean
583+
o := *info.Origin
584+
info.Origin = &o
585+
586+
// Tags never matter if you are starting with a semver version,
587+
// as we would be when finding this cache entry.
588+
o.TagSum = ""
589+
o.TagPrefix = ""
590+
// Ref doesn't matter if you have a pseudoversion.
591+
if module.IsPseudoVersion(info.Version) {
592+
o.Ref = ""
593+
}
594+
}
595+
576596
js, err := json.Marshal(info)
577597
if err != nil {
578598
return err

src/cmd/go/internal/modfetch/codehost/codehost.go

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -113,20 +113,17 @@ type Origin struct {
113113
}
114114

115115
// Checkable reports whether the Origin contains anything that can be checked.
116-
// If not, it's purely informational and should fail a CheckReuse call.
116+
// If not, the Origin is purely informational and should fail a CheckReuse call.
117117
func (o *Origin) Checkable() bool {
118118
return o.TagSum != "" || o.Ref != "" || o.Hash != ""
119119
}
120120

121-
func (o *Origin) Merge(other *Origin) {
122-
if o.TagSum == "" {
123-
o.TagPrefix = other.TagPrefix
124-
o.TagSum = other.TagSum
125-
}
126-
if o.Ref == "" {
127-
o.Ref = other.Ref
128-
o.Hash = other.Hash
129-
}
121+
// ClearCheckable clears the Origin enough to make Checkable return false.
122+
func (o *Origin) ClearCheckable() {
123+
o.TagSum = ""
124+
o.TagPrefix = ""
125+
o.Ref = ""
126+
o.Hash = ""
130127
}
131128

132129
// A Tags describes the available tags in a code repository.

src/cmd/go/internal/modfetch/codehost/git.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,8 +423,11 @@ func (r *gitRepo) stat(rev string) (info *RevInfo, err error) {
423423

424424
defer func() {
425425
if info != nil {
426-
info.Origin.Ref = ref
427426
info.Origin.Hash = info.Name
427+
// There's a ref = hash below; don't write that hash down as Origin.Ref.
428+
if ref != info.Origin.Hash {
429+
info.Origin.Ref = ref
430+
}
428431
}
429432
}()
430433

src/cmd/go/internal/modfetch/coderepo.go

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@ func (r *codeRepo) Versions(prefix string) (*Versions, error) {
153153
Err: err,
154154
}
155155
}
156+
if tags.Origin != nil {
157+
tags.Origin.Subdir = r.codeDir
158+
}
156159

157160
var list, incompatible []string
158161
for _, tag := range tags.List {
@@ -450,23 +453,26 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e
450453
}
451454

452455
origin := info.Origin
453-
if module.IsPseudoVersion(v) {
454-
// Add tags that are relevant to pseudo-version calculation to origin.
455-
prefix := ""
456-
if r.codeDir != "" {
457-
prefix = r.codeDir + "/"
458-
}
459-
if r.pathMajor != "" { // "/v2" or "/.v2"
460-
prefix += r.pathMajor[1:] + "." // += "v2."
461-
}
462-
tags, err := r.code.Tags(prefix)
463-
if err != nil {
464-
return nil, err
465-
}
456+
if origin != nil {
466457
o := *origin
467458
origin = &o
468-
origin.TagPrefix = tags.Origin.TagPrefix
469-
origin.TagSum = tags.Origin.TagSum
459+
origin.Subdir = r.codeDir
460+
if module.IsPseudoVersion(v) && (v != statVers || !strings.HasPrefix(v, "v0.0.0-")) {
461+
// Add tags that are relevant to pseudo-version calculation to origin.
462+
prefix := r.codeDir
463+
if prefix != "" {
464+
prefix += "/"
465+
}
466+
if r.pathMajor != "" { // "/v2" or "/.v2"
467+
prefix += r.pathMajor[1:] + "." // += "v2."
468+
}
469+
tags, err := r.code.Tags(prefix)
470+
if err != nil {
471+
return nil, err
472+
}
473+
origin.TagPrefix = tags.Origin.TagPrefix
474+
origin.TagSum = tags.Origin.TagSum
475+
}
470476
}
471477

472478
return &RevInfo{

0 commit comments

Comments
 (0)