Skip to content

Commit a7fc710

Browse files
author
Bryan C. Mills
committed
cmd/go/internal/modget: support the suffix '@patch' in 'go get'
As of this change, an explicit '@patch' suffix is to '-u=patch' as '@latest' is to '-u'. RELNOTE='go get' in module mode now supports the version suffix '@patch'. Fixes #26812 Change-Id: Ib5eee40de640440f7470d37a574b311ef8a67f67 Reviewed-on: https://go-review.googlesource.com/c/go/+/167747 Run-TryBot: Bryan C. Mills <[email protected]> Reviewed-by: Jay Conrod <[email protected]>
1 parent d6b2b35 commit a7fc710

14 files changed

+444
-83
lines changed

src/cmd/go/alldocs.go

Lines changed: 12 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cmd/go/internal/modget/get.go

Lines changed: 129 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"cmd/go/internal/work"
2222
"fmt"
2323
"os"
24-
pathpkg "path"
2524
"path/filepath"
2625
"strings"
2726
)
@@ -49,8 +48,6 @@ suffix to the package argument, as in 'go get golang.org/x/[email protected]'.
4948
For modules stored in source control repositories, the version suffix can
5049
also be a commit hash, branch identifier, or other syntax known to the
5150
source control system, as in 'go get golang.org/x/text@master'.
52-
The version suffix @latest explicitly requests the default behavior
53-
described above.
5451
5552
If a module under consideration is already a dependency of the current
5653
development module, then get will update the required version.
@@ -59,6 +56,13 @@ downgrades the dependency. The version suffix @none indicates that the
5956
dependency should be removed entirely, downgrading or removing modules
6057
depending on it as needed.
6158
59+
The version suffix @latest explicitly requests the latest minor release of the
60+
given path.
61+
62+
The suffix @patch requests the latest patch release: if the path is already in
63+
the build list, the selected version will have the same minor version.
64+
If the path is not already in the build list, @patch is equivalent to @latest.
65+
6266
Although get defaults to using the latest version of the module containing
6367
a named package, it does not use the latest version of that module's
6468
dependencies. Instead it prefers to use the specific dependency versions
@@ -72,9 +76,11 @@ The -u flag instructs get to update dependencies to use newer minor or
7276
patch releases when available. Continuing the previous example,
7377
'go get -u A' will use the latest A with B v1.3.1 (not B v1.2.3).
7478
75-
The -u=patch flag (not -u patch) instructs get to update dependencies
76-
to use newer patch releases when available. Continuing the previous example,
77-
'go get -u=patch A' will use the latest A with B v1.2.4 (not B v1.2.3).
79+
The -u=patch flag (not -u patch) also instructs get to update dependencies,
80+
but changes the default to select patch releases.
81+
Continuing the previous example,
82+
'go get -u=patch A@latest' will use the latest A with B v1.2.4 (not B v1.2.3),
83+
while 'go get -u=patch A' will use a patch release of A instead.
7884
7985
In general, adding a new dependency may require upgrading
8086
existing dependencies to keep a working build, and 'go get' does
@@ -165,6 +171,9 @@ func (v *upgradeFlag) Set(s string) error {
165171
if s == "false" {
166172
s = ""
167173
}
174+
if s == "true" {
175+
s = "latest"
176+
}
168177
*v = upgradeFlag(s)
169178
return nil
170179
}
@@ -180,12 +189,12 @@ func init() {
180189

181190
// A task holds the state for processing a single get argument (path@vers).
182191
type task struct {
183-
arg string // original argument
184-
index int
192+
arg string // original argument
185193
path string // package path part of arg
186194
forceModulePath bool // path must be interpreted as a module path
187195
vers string // version part of arg
188196
m module.Version // module version indicated by argument
197+
prevM module.Version // module version from initial build list
189198
req []module.Version // m's requirement list (not upgraded)
190199
}
191200

@@ -196,7 +205,7 @@ func runGet(cmd *base.Command, args []string) {
196205
}
197206

198207
switch getU {
199-
case "", "patch", "true":
208+
case "", "latest", "patch":
200209
// ok
201210
default:
202211
base.Fatalf("go get: unknown upgrade flag -u=%s", getU)
@@ -230,6 +239,7 @@ func runGet(cmd *base.Command, args []string) {
230239
// and a list of install targets (for the "go install" at the end).
231240
var tasks []*task
232241
var install []string
242+
var needModule []*task
233243
for _, arg := range search.CleanPatterns(args) {
234244
// Argument is module query path@vers, or else path with implicit @latest.
235245
path := arg
@@ -245,6 +255,12 @@ func runGet(cmd *base.Command, args []string) {
245255
install = append(install, path)
246256
}
247257

258+
// If the user runs 'go get -u=patch some/module', update some/module to a
259+
// patch release, not a minor version.
260+
if vers == "" && getU != "" {
261+
vers = string(getU)
262+
}
263+
248264
// Deciding which module to upgrade/downgrade for a particular argument is difficult.
249265
// Patterns only make it more difficult.
250266
// We impose restrictions to avoid needing to interlace pattern expansion,
@@ -271,25 +287,43 @@ func runGet(cmd *base.Command, args []string) {
271287
// - Import paths without patterns are left as is, for resolution by getQuery (eventually modload.Import).
272288
//
273289
if search.IsRelativePath(path) {
274-
// Check that this relative pattern only matches directories in the current module,
275-
// and then record the current module as the target.
276-
dir := path
277-
if i := strings.Index(path, "..."); i >= 0 {
278-
dir, _ = pathpkg.Split(path[:i])
279-
}
280-
abs, err := filepath.Abs(dir)
281-
if err != nil {
282-
base.Errorf("go get %s: %v", arg, err)
283-
continue
290+
t := &task{arg: arg, path: modload.Target.Path, vers: "", prevM: modload.Target, forceModulePath: true}
291+
292+
// If the path is relative, always upgrade the entire main module.
293+
// (TODO(golang.org/issue/26902): maybe we should upgrade the modules
294+
// containing the dependencies of the requested packages instead.)
295+
//
296+
// If the path is explicit, at least check that it is a package in the main module.
297+
if len(args) > 0 {
298+
if *getM {
299+
base.Errorf("go get %s: -m requires a module path, but a relative path must be a package in the main module", arg)
300+
continue
301+
}
302+
303+
pkgPath := modload.DirImportPath(filepath.FromSlash(path))
304+
if pkgs := modload.TargetPackages(pkgPath); len(pkgs) == 0 {
305+
if strings.Contains(path, "...") {
306+
fmt.Fprintf(os.Stderr, "go get %s: warning: pattern patched no packages", arg)
307+
} else {
308+
abs, err := filepath.Abs(path)
309+
if err != nil {
310+
abs = path
311+
}
312+
base.Errorf("go get %s: path %s is not in module rooted at %s", arg, abs, modload.ModRoot())
313+
}
314+
continue
315+
}
284316
}
285-
if !str.HasFilePathPrefix(abs, modload.ModRoot()) {
286-
base.Errorf("go get %s: directory %s is outside module root %s", arg, abs, modload.ModRoot())
287-
continue
317+
318+
switch vers {
319+
case "", "latest", "patch":
320+
tasks = append(tasks, t)
321+
default:
322+
base.Errorf("go get %s: can't request explicit version of path in main module", arg)
288323
}
289-
// TODO: Check if abs is inside a nested module.
290-
tasks = append(tasks, &task{arg: arg, path: modload.Target.Path, vers: ""})
291324
continue
292325
}
326+
293327
if path == "all" {
294328
// TODO: If *getM, should this be the module pattern "all"?
295329

@@ -306,30 +340,19 @@ func runGet(cmd *base.Command, args []string) {
306340
m := modload.PackageModule(pkg)
307341
if m.Path != "" && !seen[m] {
308342
seen[m] = true
309-
tasks = append(tasks, &task{arg: arg, path: m.Path, vers: "latest", forceModulePath: true})
343+
tasks = append(tasks, &task{arg: arg, path: m.Path, vers: vers, prevM: m, forceModulePath: true})
310344
}
311345
}
312346
continue
313347
}
314-
if search.IsMetaPackage(path) {
315-
// Already handled "all", so this must be "std" or "cmd",
316-
// which are entirely in the standard library.
317-
if path != arg {
318-
base.Errorf("go get %s: cannot use pattern %q with explicit version", arg, arg)
319-
}
320-
if *getM {
321-
base.Errorf("go get %s: cannot use pattern %q with -m", arg, arg)
322-
continue
323-
}
324-
continue
325-
}
348+
326349
if strings.Contains(path, "...") {
327350
// Apply to modules in build list matched by pattern (golang.org/x/...), if any.
328351
match := search.MatchPattern(path)
329352
matched := false
330353
for _, m := range modload.BuildList() {
331354
if match(m.Path) || str.HasPathPrefix(path, m.Path) {
332-
tasks = append(tasks, &task{arg: arg, path: m.Path, vers: vers, forceModulePath: true})
355+
tasks = append(tasks, &task{arg: arg, path: m.Path, vers: vers, prevM: m, forceModulePath: true})
333356
matched = true
334357
}
335358
}
@@ -345,10 +368,66 @@ func runGet(cmd *base.Command, args []string) {
345368
continue
346369
}
347370
}
348-
tasks = append(tasks, &task{arg: arg, path: path, vers: vers})
371+
t := &task{arg: arg, path: path, vers: vers}
372+
if vers == "patch" {
373+
if *getM {
374+
for _, m := range modload.BuildList() {
375+
if m.Path == path {
376+
t.prevM = m
377+
break
378+
}
379+
}
380+
tasks = append(tasks, t)
381+
} else {
382+
// We need to know the module containing t so that we can restrict the patch to its minor version.
383+
needModule = append(needModule, t)
384+
}
385+
} else {
386+
// The requested version of path doesn't depend on the existing version,
387+
// so don't bother resolving it.
388+
tasks = append(tasks, t)
389+
}
349390
}
350391
base.ExitIfErrors()
351392

393+
if len(needModule) > 0 {
394+
paths := make([]string, len(needModule))
395+
for i, t := range needModule {
396+
paths[i] = t.path
397+
}
398+
matches := modload.ImportPaths(paths)
399+
if len(matches) != len(paths) {
400+
base.Fatalf("go get: internal error: ImportPaths resolved %d paths to %d matches", len(paths), len(matches))
401+
}
402+
403+
for i, match := range matches {
404+
t := needModule[i]
405+
if len(match.Pkgs) == 0 {
406+
// Let modload.Query resolve the path during task processing.
407+
tasks = append(tasks, t)
408+
continue
409+
}
410+
411+
allStd := true
412+
for _, pkg := range match.Pkgs {
413+
m := modload.PackageModule(pkg)
414+
if m.Path == "" {
415+
// pkg is in the standard library.
416+
} else {
417+
allStd = false
418+
tasks = append(tasks, &task{arg: t.arg, path: pkg, vers: t.vers, prevM: m})
419+
}
420+
}
421+
if allStd {
422+
if *getM {
423+
base.Errorf("go get %s: cannot use pattern %q with -m", t.arg, t.arg)
424+
} else if t.path != t.arg {
425+
base.Errorf("go get %s: cannot use pattern %q with explicit version", t.arg, t.arg)
426+
}
427+
}
428+
}
429+
}
430+
352431
// Now we've reduced the upgrade/downgrade work to a list of path@vers pairs (tasks).
353432
// Resolve each one in parallel.
354433
reqs := modload.Reqs()
@@ -363,7 +442,7 @@ func runGet(cmd *base.Command, args []string) {
363442
t.m = module.Version{Path: t.path, Version: "none"}
364443
return
365444
}
366-
m, err := getQuery(t.path, t.vers, t.forceModulePath)
445+
m, err := getQuery(t.path, t.vers, t.prevM, t.forceModulePath)
367446
if err != nil {
368447
base.Errorf("go get %v: %v", t.arg, err)
369448
return
@@ -412,7 +491,6 @@ func runGet(cmd *base.Command, args []string) {
412491
upgraded, err := mvs.UpgradeAll(upgradeTarget, &upgrader{
413492
Reqs: modload.Reqs(),
414493
targets: named,
415-
patch: getU == "patch",
416494
tasks: byPath,
417495
})
418496
if err != nil {
@@ -554,9 +632,16 @@ func runGet(cmd *base.Command, args []string) {
554632
// to determine the underlying module version being requested.
555633
// If forceModulePath is set, getQuery must interpret path
556634
// as a module path.
557-
func getQuery(path, vers string, forceModulePath bool) (module.Version, error) {
558-
if vers == "" {
635+
func getQuery(path, vers string, prevM module.Version, forceModulePath bool) (module.Version, error) {
636+
switch vers {
637+
case "":
559638
vers = "latest"
639+
case "patch":
640+
if prevM.Version == "" {
641+
vers = "latest"
642+
} else {
643+
vers = semver.MajorMinor(prevM.Version)
644+
}
560645
}
561646

562647
// First choice is always to assume path is a module path.
@@ -625,7 +710,7 @@ func (u *upgrader) Upgrade(m module.Version) (module.Version, error) {
625710
// only ever returns untagged versions,
626711
// which is not what we want.
627712
query := "latest"
628-
if u.patch {
713+
if getU == "patch" {
629714
// For patch upgrade, query "v1.2".
630715
query = semver.MajorMinor(m.Version)
631716
}

src/cmd/go/internal/modload/load.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ func loadAll(testAll bool) []string {
339339
if !testAll {
340340
loaded.testRoots = true
341341
}
342-
all := TargetPackages()
342+
all := TargetPackages("...")
343343
loaded.load(func() []string { return all })
344344
WriteGoMod()
345345

@@ -357,10 +357,11 @@ func loadAll(testAll bool) []string {
357357
// Only "ignore" and malformed build tag requirements are considered false.
358358
var anyTags = map[string]bool{"*": true}
359359

360-
// TargetPackages returns the list of packages in the target (top-level) module,
361-
// under all build tag settings.
362-
func TargetPackages() []string {
363-
return matchPackages("...", anyTags, false, []module.Version{Target})
360+
// TargetPackages returns the list of packages in the target (top-level) module
361+
// matching pattern, which may be relative to the working directory, under all
362+
// build tag settings.
363+
func TargetPackages(pattern string) []string {
364+
return matchPackages(pattern, anyTags, false, []module.Version{Target})
364365
}
365366

366367
// BuildList returns the module build list,
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
patch.example.com/depofdirectpatch v1.0.0
2+
written by hand
3+
4+
-- .mod --
5+
module patch.example.com/depofdirectpatch
6+
-- .info --
7+
{"Version":"v1.0.0"}
8+
-- go.mod --
9+
module patch.example.com/depofdirectpatch
10+
-- depofdirectpatch.go --
11+
package depofdirectpatch
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
patch.example.com/depofdirectpatch v1.0.1
2+
written by hand
3+
4+
-- .mod --
5+
module patch.example.com/depofdirectpatch
6+
-- .info --
7+
{"Version":"v1.0.1"}
8+
-- go.mod --
9+
module patch.example.com/depofdirectpatch
10+
-- depofdirectpatch.go --
11+
package depofdirectpatch

0 commit comments

Comments
 (0)