github.com/gernest/nezuko@v0.1.2/internal/modget/get.go (about) 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 // Package modget implements the module-aware ``go get'' command. 6 package modget 7 8 import ( 9 "fmt" 10 pathpkg "path" 11 "path/filepath" 12 "strings" 13 14 "github.com/gernest/nezuko/internal/base" 15 "github.com/gernest/nezuko/internal/cfg" 16 "github.com/gernest/nezuko/internal/get" 17 "github.com/gernest/nezuko/internal/load" 18 "github.com/gernest/nezuko/internal/modfetch" 19 "github.com/gernest/nezuko/internal/modload" 20 "github.com/gernest/nezuko/internal/module" 21 "github.com/gernest/nezuko/internal/mvs" 22 "github.com/gernest/nezuko/internal/par" 23 "github.com/gernest/nezuko/internal/search" 24 "github.com/gernest/nezuko/internal/semver" 25 "github.com/gernest/nezuko/internal/str" 26 ) 27 28 var CmdGet = &base.Command{ 29 // Note: -d -m -u are listed explicitly because they are the most common get flags. 30 // Do not send CLs removing them because they're covered by [get flags]. 31 UsageLine: "z get [-d] [-m] [-u] [-v] [-insecure] [build flags] [packages]", 32 Short: "add dependencies to current module and install them", 33 Long: ` 34 Get resolves and adds dependencies to the current development module 35 and then builds and installs them. 36 37 The first step is to resolve which dependencies to add. 38 39 For each named package or package pattern, get must decide which version of 40 the corresponding module to use. By default, get chooses the latest tagged 41 release version, such as v0.4.5 or v1.2.3. If there are no tagged release 42 versions, get chooses the latest tagged prerelease version, such as 43 v0.0.1-pre1. If there are no tagged versions at all, get chooses the latest 44 known commit. 45 46 This default version selection can be overridden by adding an @version 47 suffix to the package argument, as in 'go get golang.org/x/text@v0.3.0'. 48 For modules stored in source control repositories, the version suffix can 49 also be a commit hash, branch identifier, or other syntax known to the 50 source control system, as in 'go get golang.org/x/text@master'. 51 The version suffix @latest explicitly requests the default behavior 52 described above. 53 54 If a module under consideration is already a dependency of the current 55 development module, then get will update the required version. 56 Specifying a version earlier than the current required version is valid and 57 downgrades the dependency. The version suffix @none indicates that the 58 dependency should be removed entirely, downgrading or removing modules 59 depending on it as needed. 60 61 Although get defaults to using the latest version of the module containing 62 a named package, it does not use the latest version of that module's 63 dependencies. Instead it prefers to use the specific dependency versions 64 requested by that module. For example, if the latest A requires module 65 B v1.2.3, while B v1.2.4 and v1.3.1 are also available, then 'go get A' 66 will use the latest A but then use B v1.2.3, as requested by A. (If there 67 are competing requirements for a particular module, then 'go get' resolves 68 those requirements by taking the maximum requested version.) 69 70 The -u flag instructs get to update dependencies to use newer minor or 71 patch releases when available. Continuing the previous example, 72 'go get -u A' will use the latest A with B v1.3.1 (not B v1.2.3). 73 74 The -u=patch flag (not -u patch) instructs get to update dependencies 75 to use newer patch releases when available. Continuing the previous example, 76 'go get -u=patch A' will use the latest A with B v1.2.4 (not B v1.2.3). 77 78 In general, adding a new dependency may require upgrading 79 existing dependencies to keep a working build, and 'go get' does 80 this automatically. Similarly, downgrading one dependency may 81 require downgrading other dependencies, and 'go get' does 82 this automatically as well. 83 84 The -m flag instructs get to stop here, after resolving, upgrading, 85 and downgrading modules and updating z.mod. When using -m, 86 each specified package path must be a module path as well, 87 not the import path of a package below the module root. 88 89 The -insecure flag permits fetching from repositories and resolving 90 custom domains using insecure schemes such as HTTP. Use with caution. 91 92 The second step is to download (if needed), build, and install 93 the named packages. 94 95 If an argument names a module but not a package (because there is no 96 Go source code in the module's root directory), then the install step 97 is skipped for that argument, instead of causing a build failure. 98 For example 'go get golang.org/x/perf' succeeds even though there 99 is no code corresponding to that import path. 100 101 Note that package patterns are allowed and are expanded after resolving 102 the module versions. For example, 'go get golang.org/x/perf/cmd/...' 103 adds the latest golang.org/x/perf and then installs the commands in that 104 latest version. 105 106 The -d flag instructs get to download the source code needed to build 107 the named packages, including downloading necessary dependencies, 108 but not to build and install them. 109 110 With no package arguments, 'go get' applies to the main module, 111 and to the Go package in the current directory, if any. In particular, 112 'go get -u' and 'go get -u=patch' update all the dependencies of the 113 main module. With no package arguments and also without -u, 114 'go get' is not much more than 'go install', and 'go get -d' not much 115 more than 'go list'. 116 117 For more about modules, see 'go help modules'. 118 119 For more about specifying packages, see 'go help packages'. 120 121 This text describes the behavior of get using modules to manage source 122 code and dependencies. If instead the go command is running in GOPATH 123 mode, the details of get's flags and effects change, as does 'go help get'. 124 See 'go help modules' and 'go help gopath-get'. 125 126 See also: go build, go install, go clean, z mod. 127 `, 128 } 129 130 // Note that this help text is a stopgap to make the module-aware get help text 131 // available even in non-module settings. It should be deleted when the old get 132 // is deleted. It should NOT be considered to set a precedent of having hierarchical 133 // help names with dashes. 134 var HelpModuleGet = &base.Command{ 135 UsageLine: "module-get", 136 Short: "module-aware go get", 137 Long: ` 138 The 'go get' command changes behavior depending on whether the 139 go command is running in module-aware mode or legacy GOPATH mode. 140 This help text, accessible as 'go help module-get' even in legacy GOPATH mode, 141 describes 'go get' as it operates in module-aware mode. 142 143 Usage: ` + CmdGet.UsageLine + ` 144 ` + CmdGet.Long, 145 } 146 147 var ( 148 getD = CmdGet.Flag.Bool("d", false, "") 149 getM = CmdGet.Flag.Bool("m", false, "") 150 getU upgradeFlag 151 // -insecure is get.Insecure 152 // -v is cfg.BuildV 153 ) 154 155 // upgradeFlag is a custom flag.Value for -u. 156 type upgradeFlag string 157 158 func (*upgradeFlag) IsBoolFlag() bool { return true } // allow -u 159 160 func (v *upgradeFlag) Set(s string) error { 161 if s == "false" { 162 s = "" 163 } 164 *v = upgradeFlag(s) 165 return nil 166 } 167 168 func (v *upgradeFlag) String() string { return "" } 169 170 func init() { 171 CmdGet.Run = runGet // break init loop 172 CmdGet.Flag.BoolVar(&get.Insecure, "insecure", get.Insecure, "") 173 CmdGet.Flag.Var(&getU, "u", "") 174 } 175 176 // A task holds the state for processing a single get argument (path@vers). 177 type task struct { 178 arg string // original argument 179 index int 180 path string // package path part of arg 181 forceModulePath bool // path must be interpreted as a module path 182 vers string // version part of arg 183 m module.Version // module version indicated by argument 184 req []module.Version // m's requirement list (not upgraded) 185 } 186 187 func runGet(cmd *base.Command, args []string) { 188 // -mod=readonly has no effect on "go get". 189 if cfg.BuildMod == "readonly" { 190 cfg.BuildMod = "" 191 } 192 193 switch getU { 194 case "", "patch", "true": 195 // ok 196 default: 197 base.Fatalf("z get: unknown upgrade flag -u=%s", getU) 198 } 199 200 modload.LoadBuildList() 201 202 // Do not allow any updating of z.mod until we've applied 203 // all the requested changes and checked that the result matches 204 // what was requested. 205 modload.DisallowWriteGoMod() 206 207 // Build task and install lists. 208 // The command-line arguments are of the form path@version 209 // or simply path, with implicit @latest. path@none is "downgrade away". 210 // At the end of the loop, we've resolved the list of arguments into 211 // a list of tasks (a path@vers that needs further processing) 212 // and a list of install targets (for the "go install" at the end). 213 var tasks []*task 214 var install []string 215 for _, arg := range search.CleanPatterns(args) { 216 // Argument is module query path@vers, or else path with implicit @latest. 217 path := arg 218 vers := "" 219 if i := strings.Index(arg, "@"); i >= 0 { 220 path, vers = arg[:i], arg[i+1:] 221 } 222 if strings.Contains(vers, "@") || arg != path && vers == "" { 223 base.Errorf("go get %s: invalid module version syntax", arg) 224 continue 225 } 226 if vers != "none" { 227 install = append(install, path) 228 } 229 230 // Deciding which module to upgrade/downgrade for a particular argument is difficult. 231 // Patterns only make it more difficult. 232 // We impose restrictions to avoid needing to interlace pattern expansion, 233 // like in modload.ImportPaths. 234 // Specifically, these patterns are supported: 235 // 236 // - Relative paths like ../../foo or ../../foo... are restricted to matching directories 237 // in the current module and therefore map to the current module. 238 // It's possible that the pattern matches no packages, but we will still treat it 239 // as mapping to the current module. 240 // TODO: In followup, could just expand the full list and remove the discrepancy. 241 // - The pattern "all" has its usual package meaning and maps to the list of modules 242 // from which the matched packages are drawn. This is potentially a subset of the 243 // module pattern "all". If module A requires B requires C but A does not import 244 // the parts of B that import C, the packages matched by "all" are only from A and B, 245 // so only A and B end up on the tasks list. 246 // TODO: Even in -m mode? 247 // - The patterns "std" and "cmd" expand to packages in the standard library, 248 // which aren't upgradable, so we skip over those. 249 // In -m mode they expand to non-module-paths, so they are disallowed. 250 // - Import path patterns like foo/bar... are matched against the module list, 251 // assuming any package match would imply a module pattern match. 252 // TODO: What about -m mode? 253 // - Import paths without patterns are left as is, for resolution by getQuery (eventually modload.Import). 254 // 255 if search.IsRelativePath(path) { 256 // Check that this relative pattern only matches directories in the current module, 257 // and then record the current module as the target. 258 dir := path 259 if i := strings.Index(path, "..."); i >= 0 { 260 dir, _ = pathpkg.Split(path[:i]) 261 } 262 abs, err := filepath.Abs(dir) 263 if err != nil { 264 base.Errorf("go get %s: %v", arg, err) 265 continue 266 } 267 if !str.HasFilePathPrefix(abs, modload.ModRoot()) { 268 base.Errorf("go get %s: directory %s is outside module root %s", arg, abs, modload.ModRoot()) 269 continue 270 } 271 // TODO: Check if abs is inside a nested module. 272 tasks = append(tasks, &task{arg: arg, path: modload.Target.Path, vers: ""}) 273 continue 274 } 275 if path == "all" { 276 // TODO: If *getM, should this be the module pattern "all"? 277 278 // This is the package pattern "all" not the module pattern "all": 279 // enumerate all the modules actually needed by builds of the packages 280 // in the main module, not incidental modules that happen to be 281 // in the package graph (and therefore build list). 282 // Note that LoadALL may add new modules to the build list to 283 // satisfy new imports, but vers == "latest" implicitly anyway, 284 // so we'll assume that's OK. 285 seen := make(map[module.Version]bool) 286 pkgs := modload.LoadALL() 287 for _, pkg := range pkgs { 288 m := modload.PackageModule(pkg) 289 if m.Path != "" && !seen[m] { 290 seen[m] = true 291 tasks = append(tasks, &task{arg: arg, path: m.Path, vers: "latest", forceModulePath: true}) 292 } 293 } 294 continue 295 } 296 if search.IsMetaPackage(path) { 297 // Already handled "all", so this must be "std" or "cmd", 298 // which are entirely in the standard library. 299 if path != arg { 300 base.Errorf("go get %s: cannot use pattern %q with explicit version", arg, arg) 301 } 302 if *getM { 303 base.Errorf("go get %s: cannot use pattern %q with -m", arg, arg) 304 continue 305 } 306 continue 307 } 308 if strings.Contains(path, "...") { 309 // Apply to modules in build list matched by pattern (golang.org/x/...), if any. 310 match := search.MatchPattern(path) 311 matched := false 312 for _, m := range modload.BuildList() { 313 if match(m.Path) || str.HasPathPrefix(path, m.Path) { 314 tasks = append(tasks, &task{arg: arg, path: m.Path, vers: vers, forceModulePath: true}) 315 matched = true 316 } 317 } 318 // If matched, we're done. 319 // Otherwise assume pattern is inside a single module 320 // (golang.org/x/text/unicode/...) and leave for usual lookup. 321 // Unless we're using -m. 322 if matched { 323 continue 324 } 325 if *getM { 326 base.Errorf("go get %s: pattern matches no modules in build list", arg) 327 continue 328 } 329 } 330 tasks = append(tasks, &task{arg: arg, path: path, vers: vers}) 331 } 332 base.ExitIfErrors() 333 334 // Now we've reduced the upgrade/downgrade work to a list of path@vers pairs (tasks). 335 // Resolve each one in parallel. 336 reqs := modload.Reqs() 337 var lookup par.Work 338 for _, t := range tasks { 339 lookup.Add(t) 340 } 341 lookup.Do(10, func(item interface{}) { 342 t := item.(*task) 343 if t.vers == "none" { 344 // Wait for downgrade step. 345 t.m = module.Version{Path: t.path, Version: "none"} 346 return 347 } 348 m, err := getQuery(t.path, t.vers, t.forceModulePath) 349 if err != nil { 350 base.Errorf("go get %v: %v", t.arg, err) 351 return 352 } 353 t.m = m 354 }) 355 base.ExitIfErrors() 356 357 // Now we know the specific version of each path@vers. 358 // The final build list will be the union of three build lists: 359 // 1. the original build list 360 // 2. the modules named on the command line (other than @none) 361 // 3. the upgraded requirements of those modules (if upgrading) 362 // Start building those lists. 363 // This loop collects (2). 364 // Also, because the list of paths might have named multiple packages in a single module 365 // (or even the same package multiple times), now that we know the module for each 366 // package, this loop deduplicates multiple references to a given module. 367 // (If a module is mentioned multiple times, the listed target version must be the same each time.) 368 var named []module.Version 369 byPath := make(map[string]*task) 370 for _, t := range tasks { 371 prev, ok := byPath[t.m.Path] 372 if prev != nil && prev.m != t.m { 373 base.Errorf("go get: conflicting versions for module %s: %s and %s", t.m.Path, prev.m.Version, t.m.Version) 374 byPath[t.m.Path] = nil // sentinel to stop errors 375 continue 376 } 377 if ok { 378 continue // already added 379 } 380 byPath[t.m.Path] = t 381 if t.m.Version != "none" { 382 named = append(named, t.m) 383 } 384 } 385 base.ExitIfErrors() 386 387 // If the modules named on the command line have any dependencies 388 // and we're supposed to upgrade dependencies, 389 // chase down the full list of upgraded dependencies. 390 // This turns required from a not-yet-upgraded (3) to the final (3). 391 // (See list above.) 392 var required []module.Version 393 if getU != "" { 394 upgraded, err := mvs.UpgradeAll(upgradeTarget, &upgrader{ 395 Reqs: modload.Reqs(), 396 targets: named, 397 patch: getU == "patch", 398 tasks: byPath, 399 }) 400 if err != nil { 401 base.Fatalf("go get: %v", err) 402 } 403 required = upgraded[1:] // slice off upgradeTarget 404 base.ExitIfErrors() 405 } 406 407 // Put together the final build list as described above (1) (2) (3). 408 // If we're not using -u, then len(required) == 0 and ReloadBuildList 409 // chases down the dependencies of all the named module versions 410 // in one operation. 411 var list []module.Version 412 list = append(list, modload.BuildList()...) 413 list = append(list, named...) 414 list = append(list, required...) 415 modload.SetBuildList(list) 416 modload.ReloadBuildList() // note: does not update z.mod 417 base.ExitIfErrors() 418 419 // Scan for and apply any needed downgrades. 420 var down []module.Version 421 for _, m := range modload.BuildList() { 422 t := byPath[m.Path] 423 if t != nil && semver.Compare(m.Version, t.m.Version) > 0 { 424 down = append(down, module.Version{Path: m.Path, Version: t.m.Version}) 425 } 426 } 427 if len(down) > 0 { 428 list, err := mvs.Downgrade(modload.Target, modload.Reqs(), down...) 429 if err != nil { 430 base.Fatalf("go get: %v", err) 431 } 432 modload.SetBuildList(list) 433 modload.ReloadBuildList() // note: does not update z.mod 434 } 435 base.ExitIfErrors() 436 437 // Scan for any upgrades lost by the downgrades. 438 lost := make(map[string]string) 439 for _, m := range modload.BuildList() { 440 t := byPath[m.Path] 441 if t != nil && semver.Compare(m.Version, t.m.Version) != 0 { 442 lost[m.Path] = m.Version 443 } 444 } 445 if len(lost) > 0 { 446 desc := func(m module.Version) string { 447 s := m.Path + "@" + m.Version 448 t := byPath[m.Path] 449 if t != nil && t.arg != s { 450 s += " from " + t.arg 451 } 452 return s 453 } 454 downByPath := make(map[string]module.Version) 455 for _, d := range down { 456 downByPath[d.Path] = d 457 } 458 var buf strings.Builder 459 fmt.Fprintf(&buf, "go get: inconsistent versions:") 460 for _, t := range tasks { 461 if lost[t.m.Path] == "" { 462 continue 463 } 464 // We lost t because its build list requires a newer version of something in down. 465 // Figure out exactly what. 466 // Repeatedly constructing the build list is inefficient 467 // if there are MANY command-line arguments, 468 // but at least all the necessary requirement lists are cached at this point. 469 list, err := mvs.BuildList(t.m, reqs) 470 if err != nil { 471 base.Fatalf("go get: %v", err) 472 } 473 474 fmt.Fprintf(&buf, "\n\t%s", desc(t.m)) 475 sep := " requires" 476 for _, m := range list { 477 if down, ok := downByPath[m.Path]; ok && semver.Compare(down.Version, m.Version) < 0 { 478 fmt.Fprintf(&buf, "%s %s@%s (not %s)", sep, m.Path, m.Version, desc(down)) 479 sep = "," 480 } 481 } 482 if sep != "," { 483 // We have no idea why this happened. 484 // At least report the problem. 485 fmt.Fprintf(&buf, " ended up at %v unexpectedly (please report at golang.org/issue/new)", lost[t.m.Path]) 486 } 487 } 488 base.Fatalf("%v", buf.String()) 489 } 490 491 // Everything succeeded. Update z.mod. 492 modload.AllowWriteGoMod() 493 modload.WriteGoMod() 494 modload.WriteZigBuildFile() 495 496 // If -m was specified, we're done after the module work. No download, no build. 497 if *getM { 498 return 499 } 500 501 if len(install) > 0 { 502 // All requested versions were explicitly @none. 503 // Note that 'go get -u' without any arguments results in len(install) == 1: 504 // search.CleanImportPaths returns "." for empty args. 505 // TODO(gernest): BuildInit() 506 pkgs := load.PackagesAndErrors(install) 507 var todo []*load.Package 508 for _, p := range pkgs { 509 // Ignore "no Go source files" errors for 'go get' operations on modules. 510 if p.Error != nil { 511 if len(args) == 0 && getU != "" && strings.HasPrefix(p.Error.Err, "no Go files") { 512 // Upgrading modules: skip the implicitly-requested package at the 513 // current directory, even if it is not tho module root. 514 continue 515 } 516 if strings.Contains(p.Error.Err, "cannot find module providing") && modload.ModuleInfo(p.ImportPath) != nil { 517 // Explicitly-requested module, but it doesn't contain a package at the 518 // module root. 519 continue 520 } 521 base.Errorf("%s", p.Error) 522 } 523 todo = append(todo, p) 524 } 525 base.ExitIfErrors() 526 527 // If -d was specified, we're done after the download: no build. 528 // (The load.PackagesAndErrors is what did the download 529 // of the named packages and their dependencies.) 530 if len(todo) > 0 && !*getD { 531 //TODO(gernest): install packages 532 } 533 } 534 } 535 536 // getQuery evaluates the given package path, version pair 537 // to determine the underlying module version being requested. 538 // If forceModulePath is set, getQuery must interpret path 539 // as a module path. 540 func getQuery(path, vers string, forceModulePath bool) (module.Version, error) { 541 if vers == "" { 542 vers = "latest" 543 } 544 545 // First choice is always to assume path is a module path. 546 // If that works out, we're done. 547 info, err := modload.Query(path, vers, modload.Allowed) 548 if err == nil { 549 return module.Version{Path: path, Version: info.Version}, nil 550 } 551 552 // Even if the query fails, if the path must be a real module, then report the query error. 553 if forceModulePath || *getM { 554 return module.Version{}, err 555 } 556 557 // Otherwise, try a package path. 558 m, _, err := modload.QueryPackage(path, vers, modload.Allowed) 559 return m, err 560 } 561 562 // An upgrader adapts an underlying mvs.Reqs to apply an 563 // upgrade policy to a list of targets and their dependencies. 564 // If patch=false, the upgrader implements "get -u". 565 // If patch=true, the upgrader implements "get -u=patch". 566 type upgrader struct { 567 mvs.Reqs 568 targets []module.Version 569 patch bool 570 tasks map[string]*task 571 } 572 573 // upgradeTarget is a fake "target" requiring all the modules to be upgraded. 574 var upgradeTarget = module.Version{Path: "upgrade target", Version: ""} 575 576 // Required returns the requirement list for m. 577 // Other than the upgradeTarget, we defer to u.Reqs. 578 func (u *upgrader) Required(m module.Version) ([]module.Version, error) { 579 if m == upgradeTarget { 580 return u.targets, nil 581 } 582 return u.Reqs.Required(m) 583 } 584 585 // Upgrade returns the desired upgrade for m. 586 // If m is a tagged version, then Upgrade returns the latest tagged version. 587 // If m is a pseudo-version, then Upgrade returns the latest tagged version 588 // when that version has a time-stamp newer than m. 589 // Otherwise Upgrade returns m (preserving the pseudo-version). 590 // This special case prevents accidental downgrades 591 // when already using a pseudo-version newer than the latest tagged version. 592 func (u *upgrader) Upgrade(m module.Version) (module.Version, error) { 593 // Allow pkg@vers on the command line to override the upgrade choice v. 594 // If t's version is < v, then we're going to downgrade anyway, 595 // and it's cleaner to avoid moving back and forth and picking up 596 // extraneous other newer dependencies. 597 // If t's version is > v, then we're going to upgrade past v anyway, 598 // and again it's cleaner to avoid moving back and forth picking up 599 // extraneous other newer dependencies. 600 if t := u.tasks[m.Path]; t != nil { 601 return t.m, nil 602 } 603 604 // Note that query "latest" is not the same as 605 // using repo.Latest. 606 // The query only falls back to untagged versions 607 // if nothing is tagged. The Latest method 608 // only ever returns untagged versions, 609 // which is not what we want. 610 query := "latest" 611 if u.patch { 612 // For patch upgrade, query "v1.2". 613 query = semver.MajorMinor(m.Version) 614 } 615 info, err := modload.Query(m.Path, query, modload.Allowed) 616 if err != nil { 617 // Report error but return m, to let version selection continue. 618 // (Reporting the error will fail the command at the next base.ExitIfErrors.) 619 // Special case: if the error is "no matching versions" then don't 620 // even report the error. Because Query does not consider pseudo-versions, 621 // it may happen that we have a pseudo-version but during -u=patch 622 // the query v0.0 matches no versions (not even the one we're using). 623 if !strings.Contains(err.Error(), "no matching versions") { 624 base.Errorf("go get: upgrading %s@%s: %v", m.Path, m.Version, err) 625 } 626 return m, nil 627 } 628 629 // If we're on a later prerelease, keep using it, 630 // even though normally an Upgrade will ignore prereleases. 631 if semver.Compare(info.Version, m.Version) < 0 { 632 return m, nil 633 } 634 635 // If we're on a pseudo-version chronologically after the latest tagged version, keep using it. 636 // This avoids some accidental downgrades. 637 if mTime, err := modfetch.PseudoVersionTime(m.Version); err == nil && info.Time.Before(mTime) { 638 return m, nil 639 } 640 641 return module.Version{Path: m.Path, Version: info.Version}, nil 642 }