"cmd/go/internal/work"
"fmt"
"os"
- pathpkg "path"
"path/filepath"
"strings"
)
For modules stored in source control repositories, the version suffix can
also be a commit hash, branch identifier, or other syntax known to the
source control system, as in 'go get golang.org/x/text@master'.
-The version suffix @latest explicitly requests the default behavior
-described above.
If a module under consideration is already a dependency of the current
development module, then get will update the required version.
dependency should be removed entirely, downgrading or removing modules
depending on it as needed.
+The version suffix @latest explicitly requests the latest minor release of the
+given path.
+
+The suffix @patch requests the latest patch release: if the path is already in
+the build list, the selected version will have the same minor version.
+If the path is not already in the build list, @patch is equivalent to @latest.
+
Although get defaults to using the latest version of the module containing
a named package, it does not use the latest version of that module's
dependencies. Instead it prefers to use the specific dependency versions
patch releases when available. Continuing the previous example,
'go get -u A' will use the latest A with B v1.3.1 (not B v1.2.3).
-The -u=patch flag (not -u patch) instructs get to update dependencies
-to use newer patch releases when available. Continuing the previous example,
-'go get -u=patch A' will use the latest A with B v1.2.4 (not B v1.2.3).
+The -u=patch flag (not -u patch) also instructs get to update dependencies,
+but changes the default to select patch releases.
+Continuing the previous example,
+'go get -u=patch A@latest' will use the latest A with B v1.2.4 (not B v1.2.3),
+while 'go get -u=patch A' will use a patch release of A instead.
In general, adding a new dependency may require upgrading
existing dependencies to keep a working build, and 'go get' does
if s == "false" {
s = ""
}
+ if s == "true" {
+ s = "latest"
+ }
*v = upgradeFlag(s)
return nil
}
// A task holds the state for processing a single get argument (path@vers).
type task struct {
- arg string // original argument
- index int
+ arg string // original argument
path string // package path part of arg
forceModulePath bool // path must be interpreted as a module path
vers string // version part of arg
m module.Version // module version indicated by argument
+ prevM module.Version // module version from initial build list
req []module.Version // m's requirement list (not upgraded)
}
}
switch getU {
- case "", "patch", "true":
+ case "", "latest", "patch":
// ok
default:
base.Fatalf("go get: unknown upgrade flag -u=%s", getU)
// and a list of install targets (for the "go install" at the end).
var tasks []*task
var install []string
+ var needModule []*task
for _, arg := range search.CleanPatterns(args) {
// Argument is module query path@vers, or else path with implicit @latest.
path := arg
install = append(install, path)
}
+ // If the user runs 'go get -u=patch some/module', update some/module to a
+ // patch release, not a minor version.
+ if vers == "" && getU != "" {
+ vers = string(getU)
+ }
+
// Deciding which module to upgrade/downgrade for a particular argument is difficult.
// Patterns only make it more difficult.
// We impose restrictions to avoid needing to interlace pattern expansion,
// - Import paths without patterns are left as is, for resolution by getQuery (eventually modload.Import).
//
if search.IsRelativePath(path) {
- // Check that this relative pattern only matches directories in the current module,
- // and then record the current module as the target.
- dir := path
- if i := strings.Index(path, "..."); i >= 0 {
- dir, _ = pathpkg.Split(path[:i])
- }
- abs, err := filepath.Abs(dir)
- if err != nil {
- base.Errorf("go get %s: %v", arg, err)
- continue
+ t := &task{arg: arg, path: modload.Target.Path, vers: "", prevM: modload.Target, forceModulePath: true}
+
+ // If the path is relative, always upgrade the entire main module.
+ // (TODO(golang.org/issue/26902): maybe we should upgrade the modules
+ // containing the dependencies of the requested packages instead.)
+ //
+ // If the path is explicit, at least check that it is a package in the main module.
+ if len(args) > 0 {
+ if *getM {
+ base.Errorf("go get %s: -m requires a module path, but a relative path must be a package in the main module", arg)
+ continue
+ }
+
+ pkgPath := modload.DirImportPath(filepath.FromSlash(path))
+ if pkgs := modload.TargetPackages(pkgPath); len(pkgs) == 0 {
+ if strings.Contains(path, "...") {
+ fmt.Fprintf(os.Stderr, "go get %s: warning: pattern patched no packages", arg)
+ } else {
+ abs, err := filepath.Abs(path)
+ if err != nil {
+ abs = path
+ }
+ base.Errorf("go get %s: path %s is not in module rooted at %s", arg, abs, modload.ModRoot())
+ }
+ continue
+ }
}
- if !str.HasFilePathPrefix(abs, modload.ModRoot()) {
- base.Errorf("go get %s: directory %s is outside module root %s", arg, abs, modload.ModRoot())
- continue
+
+ switch vers {
+ case "", "latest", "patch":
+ tasks = append(tasks, t)
+ default:
+ base.Errorf("go get %s: can't request explicit version of path in main module", arg)
}
- // TODO: Check if abs is inside a nested module.
- tasks = append(tasks, &task{arg: arg, path: modload.Target.Path, vers: ""})
continue
}
+
if path == "all" {
// TODO: If *getM, should this be the module pattern "all"?
m := modload.PackageModule(pkg)
if m.Path != "" && !seen[m] {
seen[m] = true
- tasks = append(tasks, &task{arg: arg, path: m.Path, vers: "latest", forceModulePath: true})
+ tasks = append(tasks, &task{arg: arg, path: m.Path, vers: vers, prevM: m, forceModulePath: true})
}
}
continue
}
- if search.IsMetaPackage(path) {
- // Already handled "all", so this must be "std" or "cmd",
- // which are entirely in the standard library.
- if path != arg {
- base.Errorf("go get %s: cannot use pattern %q with explicit version", arg, arg)
- }
- if *getM {
- base.Errorf("go get %s: cannot use pattern %q with -m", arg, arg)
- continue
- }
- continue
- }
+
if strings.Contains(path, "...") {
// Apply to modules in build list matched by pattern (golang.org/x/...), if any.
match := search.MatchPattern(path)
matched := false
for _, m := range modload.BuildList() {
if match(m.Path) || str.HasPathPrefix(path, m.Path) {
- tasks = append(tasks, &task{arg: arg, path: m.Path, vers: vers, forceModulePath: true})
+ tasks = append(tasks, &task{arg: arg, path: m.Path, vers: vers, prevM: m, forceModulePath: true})
matched = true
}
}
continue
}
}
- tasks = append(tasks, &task{arg: arg, path: path, vers: vers})
+ t := &task{arg: arg, path: path, vers: vers}
+ if vers == "patch" {
+ if *getM {
+ for _, m := range modload.BuildList() {
+ if m.Path == path {
+ t.prevM = m
+ break
+ }
+ }
+ tasks = append(tasks, t)
+ } else {
+ // We need to know the module containing t so that we can restrict the patch to its minor version.
+ needModule = append(needModule, t)
+ }
+ } else {
+ // The requested version of path doesn't depend on the existing version,
+ // so don't bother resolving it.
+ tasks = append(tasks, t)
+ }
}
base.ExitIfErrors()
+ if len(needModule) > 0 {
+ paths := make([]string, len(needModule))
+ for i, t := range needModule {
+ paths[i] = t.path
+ }
+ matches := modload.ImportPaths(paths)
+ if len(matches) != len(paths) {
+ base.Fatalf("go get: internal error: ImportPaths resolved %d paths to %d matches", len(paths), len(matches))
+ }
+
+ for i, match := range matches {
+ t := needModule[i]
+ if len(match.Pkgs) == 0 {
+ // Let modload.Query resolve the path during task processing.
+ tasks = append(tasks, t)
+ continue
+ }
+
+ allStd := true
+ for _, pkg := range match.Pkgs {
+ m := modload.PackageModule(pkg)
+ if m.Path == "" {
+ // pkg is in the standard library.
+ } else {
+ allStd = false
+ tasks = append(tasks, &task{arg: t.arg, path: pkg, vers: t.vers, prevM: m})
+ }
+ }
+ if allStd {
+ if *getM {
+ base.Errorf("go get %s: cannot use pattern %q with -m", t.arg, t.arg)
+ } else if t.path != t.arg {
+ base.Errorf("go get %s: cannot use pattern %q with explicit version", t.arg, t.arg)
+ }
+ }
+ }
+ }
+
// Now we've reduced the upgrade/downgrade work to a list of path@vers pairs (tasks).
// Resolve each one in parallel.
reqs := modload.Reqs()
t.m = module.Version{Path: t.path, Version: "none"}
return
}
- m, err := getQuery(t.path, t.vers, t.forceModulePath)
+ m, err := getQuery(t.path, t.vers, t.prevM, t.forceModulePath)
if err != nil {
base.Errorf("go get %v: %v", t.arg, err)
return
upgraded, err := mvs.UpgradeAll(upgradeTarget, &upgrader{
Reqs: modload.Reqs(),
targets: named,
- patch: getU == "patch",
tasks: byPath,
})
if err != nil {
// to determine the underlying module version being requested.
// If forceModulePath is set, getQuery must interpret path
// as a module path.
-func getQuery(path, vers string, forceModulePath bool) (module.Version, error) {
- if vers == "" {
+func getQuery(path, vers string, prevM module.Version, forceModulePath bool) (module.Version, error) {
+ switch vers {
+ case "":
vers = "latest"
+ case "patch":
+ if prevM.Version == "" {
+ vers = "latest"
+ } else {
+ vers = semver.MajorMinor(prevM.Version)
+ }
}
// First choice is always to assume path is a module path.
// only ever returns untagged versions,
// which is not what we want.
query := "latest"
- if u.patch {
+ if getU == "patch" {
// For patch upgrade, query "v1.2".
query = semver.MajorMinor(m.Version)
}
--- /dev/null
+env GO111MODULE=on
+
+# Initially, we are at v1.0.0 for all dependencies.
+cp go.mod go.mod.orig
+go list -m all
+stdout '^patch.example.com/direct v1.0.0'
+stdout '^patch.example.com/indirect v1.0.0'
+! stdout '^patch.example.com/depofdirectpatch'
+
+# get -m -u=patch, with no arguments, should patch-update all dependencies,
+# pulling in transitive dependencies and also patching those.
+#
+# TODO(golang.org/issue/26902): We should not update transitive dependencies
+# that don't affect the transitive import graph of the main module in any way.
+cp go.mod.orig go.mod
+go get -m -u=patch
+go list -m all
+stdout '^patch.example.com/direct v1.0.1'
+stdout '^patch.example.com/indirect v1.0.1'
+stdout '^patch.example.com/depofdirectpatch v1.0.1' # TODO: leave at v1.0.0
+
+# 'get -m all@patch' should be equivalent to 'get -u=patch -m all'
+cp go.mod.orig go.mod
+go get -m all@patch
+go list -m all
+stdout '^patch.example.com/direct v1.0.1'
+stdout '^patch.example.com/indirect v1.0.1'
+stdout '^patch.example.com/depofdirectpatch v1.0.0'
+
+# Requesting the direct dependency with -u=patch but without an explicit version
+# should patch-update it and its dependencies.
+cp go.mod.orig go.mod
+go get -m -u=patch patch.example.com/direct
+go list -m all
+stdout '^patch.example.com/direct v1.0.1'
+stdout '^patch.example.com/indirect v1.0.1'
+stdout '^patch.example.com/depofdirectpatch v1.0.1' # TODO: leave at v1.0.0
+
+# Requesting only the indirect dependency should not update the direct one.
+cp go.mod.orig go.mod
+go get -m -u=patch patch.example.com/indirect
+go list -m all
+stdout '^patch.example.com/direct v1.0.0'
+stdout '^patch.example.com/indirect v1.0.1'
+! stdout '^patch.example.com/depofdirectpatch'
+
+# @patch should apply only to the specific module.
+# but the result must reflect its upgraded requirements.
+cp go.mod.orig go.mod
+go get -m patch.example.com/direct@patch
+go list -m all
+stdout '^patch.example.com/direct v1.0.1'
+stdout '^patch.example.com/indirect v1.0.0'
+stdout '^patch.example.com/depofdirectpatch v1.0.0'
+
+# An explicit @patch should override a general -u.
+cp go.mod.orig go.mod
+go get -m -u patch.example.com/direct@patch
+go list -m all
+stdout '^patch.example.com/direct v1.0.1'
+stdout '^patch.example.com/indirect v1.1.0'
+stdout '^patch.example.com/depofdirectpatch v1.0.1'
+
+# An explicit @latest should override a general -u=patch.
+cp go.mod.orig go.mod
+go get -m -u=patch patch.example.com/direct@latest
+go list -m all
+stdout '^patch.example.com/direct v1.1.0'
+stdout '^patch.example.com/indirect v1.0.1'
+! stdout '^patch.example.com/depofdirectpatch'
+
+# Standard-library modules cannot be upgraded explicitly.
+cp go.mod.orig go.mod
+! go get -m std@patch
+stderr 'explicit requirement on standard-library module std not allowed'
+
+
+-- go.mod --
+module x
+
+require patch.example.com/direct v1.0.0
+
+-- main.go --
+package x
+import _ "patch.example.com/direct"
--- /dev/null
+env GO111MODULE=on
+
+# Initially, we are at v1.0.0 for all dependencies.
+cp go.mod go.mod.orig
+go list -m all
+stdout '^patch.example.com/direct v1.0.0'
+stdout '^patch.example.com/indirect v1.0.0'
+! stdout '^patch.example.com/depofdirectpatch'
+
+# get -u=patch, with no arguments, should patch-update all dependencies,
+# pulling in transitive dependencies and also patching those.
+#
+# TODO(golang.org/issue/26902): We should not update dependencies
+# that don't affect the transitive import graph of the main module in any way.
+cp go.mod.orig go.mod
+go get -u=patch
+go list -m all
+stdout '^patch.example.com/direct v1.0.1'
+stdout '^patch.example.com/indirect v1.0.1'
+stdout '^patch.example.com/depofdirectpatch v1.0.1' # TODO: leave at v1.0.0
+
+# 'get all@patch' should be equivalent to 'get -u=patch all'
+cp go.mod.orig go.mod
+go get all@patch
+go list -m all
+stdout '^patch.example.com/direct v1.0.1'
+stdout '^patch.example.com/indirect v1.0.1'
+stdout '^patch.example.com/depofdirectpatch v1.0.0'
+
+# Requesting the direct dependency with -u=patch but without an explicit version
+# should patch-update it and its dependencies.
+cp go.mod.orig go.mod
+go get -u=patch patch.example.com/direct
+go list -m all
+stdout '^patch.example.com/direct v1.0.1'
+stdout '^patch.example.com/indirect v1.0.1'
+stdout '^patch.example.com/depofdirectpatch v1.0.1' # TODO: leave at v1.0.0
+
+# Requesting only the indirect dependency should not update the direct one.
+cp go.mod.orig go.mod
+go get -u=patch patch.example.com/indirect
+go list -m all
+stdout '^patch.example.com/direct v1.0.0'
+stdout '^patch.example.com/indirect v1.0.1'
+! stdout '^patch.example.com/depofdirectpatch'
+
+# @patch should apply only to the specific module,
+# but the result must reflect its upgraded requirements.
+cp go.mod.orig go.mod
+go get patch.example.com/direct@patch
+go list -m all
+stdout '^patch.example.com/direct v1.0.1'
+stdout '^patch.example.com/indirect v1.0.0'
+stdout '^patch.example.com/depofdirectpatch v1.0.0'
+
+# An explicit @patch should override a general -u.
+cp go.mod.orig go.mod
+go get -u patch.example.com/direct@patch
+go list -m all
+stdout '^patch.example.com/direct v1.0.1'
+stdout '^patch.example.com/indirect v1.1.0'
+stdout '^patch.example.com/depofdirectpatch v1.0.1'
+
+# An explicit @latest should override a general -u=patch.
+cp go.mod.orig go.mod
+go get -u=patch patch.example.com/direct@latest
+go list -m all
+stdout '^patch.example.com/direct v1.1.0'
+stdout '^patch.example.com/indirect v1.0.1'
+! stdout '^patch.example.com/depofdirectpatch'
+
+# Standard-library packages cannot be upgraded explicitly.
+cp go.mod.orig go.mod
+! go get cmd/vet@patch
+stderr 'cannot use pattern .* with explicit version'
+
+# However, standard-library packages without explicit versions are fine.
+go get -u=patch -d cmd/get
+
+
+-- go.mod --
+module x
+
+require patch.example.com/direct v1.0.0
+
+-- main.go --
+package x
+import _ "patch.example.com/direct"