github.com/octohelm/cuemod@v0.9.4/internal/cmd/go/internals/modload/modfile.go (about) 1 // Copyright 2020 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 modload 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "os" 12 "path/filepath" 13 "strings" 14 "sync" 15 "unicode" 16 17 "github.com/octohelm/cuemod/internal/cmd/go/internals/base" 18 "github.com/octohelm/cuemod/internal/cmd/go/internals/cfg" 19 "github.com/octohelm/cuemod/internal/cmd/go/internals/fsys" 20 "github.com/octohelm/cuemod/internal/cmd/go/internals/gover" 21 "github.com/octohelm/cuemod/internal/cmd/go/internals/lockedfile" 22 "github.com/octohelm/cuemod/internal/cmd/go/internals/modfetch" 23 "github.com/octohelm/cuemod/internal/cmd/go/internals/par" 24 "github.com/octohelm/cuemod/internal/cmd/go/internals/trace" 25 26 "golang.org/x/mod/modfile" 27 "golang.org/x/mod/module" 28 ) 29 30 // ReadModFile reads and parses the mod file at gomod. ReadModFile properly applies the 31 // overlay, locks the file while reading, and applies fix, if applicable. 32 func ReadModFile(gomod string, fix modfile.VersionFixer) (data []byte, f *modfile.File, err error) { 33 gomod = base.ShortPath(gomod) // use short path in any errors 34 if gomodActual, ok := fsys.OverlayPath(gomod); ok { 35 // Don't lock go.mod if it's part of the overlay. 36 // On Plan 9, locking requires chmod, and we don't want to modify any file 37 // in the overlay. See #44700. 38 data, err = os.ReadFile(gomodActual) 39 } else { 40 data, err = lockedfile.Read(gomodActual) 41 } 42 if err != nil { 43 return nil, nil, err 44 } 45 46 f, err = modfile.Parse(gomod, data, fix) 47 if err != nil { 48 // Errors returned by modfile.Parse begin with file:line. 49 return nil, nil, fmt.Errorf("errors parsing %s:\n%w", gomod, err) 50 } 51 if f.Go != nil && gover.Compare(f.Go.Version, gover.Local()) > 0 { 52 toolchain := "" 53 if f.Toolchain != nil { 54 toolchain = f.Toolchain.Name 55 } 56 return nil, nil, &gover.TooNewError{What: gomod, GoVersion: f.Go.Version, Toolchain: toolchain} 57 } 58 if f.Module == nil { 59 // No module declaration. Must add module path. 60 return nil, nil, fmt.Errorf("error reading %s: missing module declaration. To specify the module path:\n\tgo mod edit -module=example.com/mod", gomod) 61 } 62 63 return data, f, err 64 } 65 66 // A modFileIndex is an index of data corresponding to a modFile 67 // at a specific point in time. 68 type modFileIndex struct { 69 data []byte 70 dataNeedsFix bool // true if fixVersion applied a change while parsing data 71 module module.Version 72 goVersion string // Go version (no "v" or "go" prefix) 73 toolchain string 74 require map[module.Version]requireMeta 75 replace map[module.Version]module.Version 76 exclude map[module.Version]bool 77 } 78 79 type requireMeta struct { 80 indirect bool 81 } 82 83 // A modPruning indicates whether transitive dependencies of Go 1.17 dependencies 84 // are pruned out of the module subgraph rooted at a given module. 85 // (See https://golang.org/ref/mod#graph-pruning.) 86 type modPruning uint8 87 88 const ( 89 pruned modPruning = iota // transitive dependencies of modules at go 1.17 and higher are pruned out 90 unpruned // no transitive dependencies are pruned out 91 workspace // pruned to the union of modules in the workspace 92 ) 93 94 func (p modPruning) String() string { 95 switch p { 96 case pruned: 97 return "pruned" 98 case unpruned: 99 return "unpruned" 100 case workspace: 101 return "workspace" 102 default: 103 return fmt.Sprintf("%T(%d)", p, p) 104 } 105 } 106 107 func pruningForGoVersion(goVersion string) modPruning { 108 if gover.Compare(goVersion, gover.ExplicitIndirectVersion) < 0 { 109 // The go.mod file does not duplicate relevant information about transitive 110 // dependencies, so they cannot be pruned out. 111 return unpruned 112 } 113 return pruned 114 } 115 116 // CheckAllowed returns an error equivalent to ErrDisallowed if m is excluded by 117 // the main module's go.mod or retracted by its author. Most version queries use 118 // this to filter out versions that should not be used. 119 func CheckAllowed(ctx context.Context, m module.Version) error { 120 if err := CheckExclusions(ctx, m); err != nil { 121 return err 122 } 123 if err := CheckRetractions(ctx, m); err != nil { 124 return err 125 } 126 return nil 127 } 128 129 // ErrDisallowed is returned by version predicates passed to Query and similar 130 // functions to indicate that a version should not be considered. 131 var ErrDisallowed = errors.New("disallowed module version") 132 133 // CheckExclusions returns an error equivalent to ErrDisallowed if module m is 134 // excluded by the main module's go.mod file. 135 func CheckExclusions(ctx context.Context, m module.Version) error { 136 for _, mainModule := range MainModules.Versions() { 137 if index := MainModules.Index(mainModule); index != nil && index.exclude[m] { 138 return module.VersionError(m, errExcluded) 139 } 140 } 141 return nil 142 } 143 144 var errExcluded = &excludedError{} 145 146 type excludedError struct{} 147 148 func (e *excludedError) Error() string { return "excluded by go.mod" } 149 func (e *excludedError) Is(err error) bool { return err == ErrDisallowed } 150 151 // CheckRetractions returns an error if module m has been retracted by 152 // its author. 153 func CheckRetractions(ctx context.Context, m module.Version) (err error) { 154 defer func() { 155 if retractErr := (*ModuleRetractedError)(nil); err == nil || errors.As(err, &retractErr) { 156 return 157 } 158 // Attribute the error to the version being checked, not the version from 159 // which the retractions were to be loaded. 160 if mErr := (*module.ModuleError)(nil); errors.As(err, &mErr) { 161 err = mErr.Err 162 } 163 err = &retractionLoadingError{m: m, err: err} 164 }() 165 166 if m.Version == "" { 167 // Main module, standard library, or file replacement module. 168 // Cannot be retracted. 169 return nil 170 } 171 if repl := Replacement(module.Version{Path: m.Path}); repl.Path != "" { 172 // All versions of the module were replaced. 173 // Don't load retractions, since we'd just load the replacement. 174 return nil 175 } 176 177 // Find the latest available version of the module, and load its go.mod. If 178 // the latest version is replaced, we'll load the replacement. 179 // 180 // If there's an error loading the go.mod, we'll return it here. These errors 181 // should generally be ignored by callers since they happen frequently when 182 // we're offline. These errors are not equivalent to ErrDisallowed, so they 183 // may be distinguished from retraction errors. 184 // 185 // We load the raw file here: the go.mod file may have a different module 186 // path that we expect if the module or its repository was renamed. 187 // We still want to apply retractions to other aliases of the module. 188 rm, err := queryLatestVersionIgnoringRetractions(ctx, m.Path) 189 if err != nil { 190 return err 191 } 192 summary, err := rawGoModSummary(rm) 193 if err != nil { 194 return err 195 } 196 197 var rationale []string 198 isRetracted := false 199 for _, r := range summary.retract { 200 if gover.ModCompare(m.Path, r.Low, m.Version) <= 0 && gover.ModCompare(m.Path, m.Version, r.High) <= 0 { 201 isRetracted = true 202 if r.Rationale != "" { 203 rationale = append(rationale, r.Rationale) 204 } 205 } 206 } 207 if isRetracted { 208 return module.VersionError(m, &ModuleRetractedError{Rationale: rationale}) 209 } 210 return nil 211 } 212 213 type ModuleRetractedError struct { 214 Rationale []string 215 } 216 217 func (e *ModuleRetractedError) Error() string { 218 msg := "retracted by module author" 219 if len(e.Rationale) > 0 { 220 // This is meant to be a short error printed on a terminal, so just 221 // print the first rationale. 222 msg += ": " + ShortMessage(e.Rationale[0], "retracted by module author") 223 } 224 return msg 225 } 226 227 func (e *ModuleRetractedError) Is(err error) bool { 228 return err == ErrDisallowed 229 } 230 231 type retractionLoadingError struct { 232 m module.Version 233 err error 234 } 235 236 func (e *retractionLoadingError) Error() string { 237 return fmt.Sprintf("loading module retractions for %v: %v", e.m, e.err) 238 } 239 240 func (e *retractionLoadingError) Unwrap() error { 241 return e.err 242 } 243 244 // ShortMessage returns a string from go.mod (for example, a retraction 245 // rationale or deprecation message) that is safe to print in a terminal. 246 // 247 // If the given string is empty, ShortMessage returns the given default. If the 248 // given string is too long or contains non-printable characters, ShortMessage 249 // returns a hard-coded string. 250 func ShortMessage(message, emptyDefault string) string { 251 const maxLen = 500 252 if i := strings.Index(message, "\n"); i >= 0 { 253 message = message[:i] 254 } 255 message = strings.TrimSpace(message) 256 if message == "" { 257 return emptyDefault 258 } 259 if len(message) > maxLen { 260 return "(message omitted: too long)" 261 } 262 for _, r := range message { 263 if !unicode.IsGraphic(r) && !unicode.IsSpace(r) { 264 return "(message omitted: contains non-printable characters)" 265 } 266 } 267 // NOTE: the go.mod parser rejects invalid UTF-8, so we don't check that here. 268 return message 269 } 270 271 // CheckDeprecation returns a deprecation message from the go.mod file of the 272 // latest version of the given module. Deprecation messages are comments 273 // before or on the same line as the module directives that start with 274 // "Deprecated:" and run until the end of the paragraph. 275 // 276 // CheckDeprecation returns an error if the message can't be loaded. 277 // CheckDeprecation returns "", nil if there is no deprecation message. 278 func CheckDeprecation(ctx context.Context, m module.Version) (deprecation string, err error) { 279 defer func() { 280 if err != nil { 281 err = fmt.Errorf("loading deprecation for %s: %w", m.Path, err) 282 } 283 }() 284 285 if m.Version == "" { 286 // Main module, standard library, or file replacement module. 287 // Don't look up deprecation. 288 return "", nil 289 } 290 if repl := Replacement(module.Version{Path: m.Path}); repl.Path != "" { 291 // All versions of the module were replaced. 292 // We'll look up deprecation separately for the replacement. 293 return "", nil 294 } 295 296 latest, err := queryLatestVersionIgnoringRetractions(ctx, m.Path) 297 if err != nil { 298 return "", err 299 } 300 summary, err := rawGoModSummary(latest) 301 if err != nil { 302 return "", err 303 } 304 return summary.deprecated, nil 305 } 306 307 func replacement(mod module.Version, replace map[module.Version]module.Version) (fromVersion string, to module.Version, ok bool) { 308 if r, ok := replace[mod]; ok { 309 return mod.Version, r, true 310 } 311 if r, ok := replace[module.Version{Path: mod.Path}]; ok { 312 return "", r, true 313 } 314 return "", module.Version{}, false 315 } 316 317 // Replacement returns the replacement for mod, if any. If the path in the 318 // module.Version is relative it's relative to the single main module outside 319 // workspace mode, or the workspace's directory in workspace mode. 320 func Replacement(mod module.Version) module.Version { 321 r, foundModRoot, _ := replacementFrom(mod) 322 return canonicalizeReplacePath(r, foundModRoot) 323 } 324 325 // replacementFrom returns the replacement for mod, if any, the modroot of the replacement if it appeared in a go.mod, 326 // and the source of the replacement. The replacement is relative to the go.work or go.mod file it appears in. 327 func replacementFrom(mod module.Version) (r module.Version, modroot string, fromFile string) { 328 foundFrom, found, foundModRoot := "", module.Version{}, "" 329 if MainModules == nil { 330 return module.Version{}, "", "" 331 } else if MainModules.Contains(mod.Path) && mod.Version == "" { 332 // Don't replace the workspace version of the main module. 333 return module.Version{}, "", "" 334 } 335 if _, r, ok := replacement(mod, MainModules.WorkFileReplaceMap()); ok { 336 return r, "", workFilePath 337 } 338 for _, v := range MainModules.Versions() { 339 if index := MainModules.Index(v); index != nil { 340 if from, r, ok := replacement(mod, index.replace); ok { 341 modRoot := MainModules.ModRoot(v) 342 if foundModRoot != "" && foundFrom != from && found != r { 343 base.Errorf("conflicting replacements found for %v in workspace modules defined by %v and %v", 344 mod, modFilePath(foundModRoot), modFilePath(modRoot)) 345 return found, foundModRoot, modFilePath(foundModRoot) 346 } 347 found, foundModRoot = r, modRoot 348 } 349 } 350 } 351 return found, foundModRoot, modFilePath(foundModRoot) 352 } 353 354 func replaceRelativeTo() string { 355 if workFilePath := WorkFilePath(); workFilePath != "" { 356 return filepath.Dir(workFilePath) 357 } 358 return MainModules.ModRoot(MainModules.mustGetSingleMainModule()) 359 } 360 361 // canonicalizeReplacePath ensures that relative, on-disk, replaced module paths 362 // are relative to the workspace directory (in workspace mode) or to the module's 363 // directory (in module mode, as they already are). 364 func canonicalizeReplacePath(r module.Version, modRoot string) module.Version { 365 if filepath.IsAbs(r.Path) || r.Version != "" || modRoot == "" { 366 return r 367 } 368 workFilePath := WorkFilePath() 369 if workFilePath == "" { 370 return r 371 } 372 abs := filepath.Join(modRoot, r.Path) 373 if rel, err := filepath.Rel(filepath.Dir(workFilePath), abs); err == nil { 374 return module.Version{Path: ToDirectoryPath(rel), Version: r.Version} 375 } 376 // We couldn't make the version's path relative to the workspace's path, 377 // so just return the absolute path. It's the best we can do. 378 return module.Version{Path: ToDirectoryPath(abs), Version: r.Version} 379 } 380 381 // resolveReplacement returns the module actually used to load the source code 382 // for m: either m itself, or the replacement for m (iff m is replaced). 383 // It also returns the modroot of the module providing the replacement if 384 // one was found. 385 func resolveReplacement(m module.Version) module.Version { 386 if r := Replacement(m); r.Path != "" { 387 return r 388 } 389 return m 390 } 391 392 func toReplaceMap(replacements []*modfile.Replace) map[module.Version]module.Version { 393 replaceMap := make(map[module.Version]module.Version, len(replacements)) 394 for _, r := range replacements { 395 if prev, dup := replaceMap[r.Old]; dup && prev != r.New { 396 base.Fatalf("go: conflicting replacements for %v:\n\t%v\n\t%v", r.Old, prev, r.New) 397 } 398 replaceMap[r.Old] = r.New 399 } 400 return replaceMap 401 } 402 403 // indexModFile rebuilds the index of modFile. 404 // If modFile has been changed since it was first read, 405 // modFile.Cleanup must be called before indexModFile. 406 func indexModFile(data []byte, modFile *modfile.File, mod module.Version, needsFix bool) *modFileIndex { 407 i := new(modFileIndex) 408 i.data = data 409 i.dataNeedsFix = needsFix 410 411 i.module = module.Version{} 412 if modFile.Module != nil { 413 i.module = modFile.Module.Mod 414 } 415 416 i.goVersion = "" 417 if modFile.Go == nil { 418 rawGoVersion.Store(mod, "") 419 } else { 420 i.goVersion = modFile.Go.Version 421 rawGoVersion.Store(mod, modFile.Go.Version) 422 } 423 if modFile.Toolchain != nil { 424 i.toolchain = modFile.Toolchain.Name 425 } 426 427 i.require = make(map[module.Version]requireMeta, len(modFile.Require)) 428 for _, r := range modFile.Require { 429 i.require[r.Mod] = requireMeta{indirect: r.Indirect} 430 } 431 432 i.replace = toReplaceMap(modFile.Replace) 433 434 i.exclude = make(map[module.Version]bool, len(modFile.Exclude)) 435 for _, x := range modFile.Exclude { 436 i.exclude[x.Mod] = true 437 } 438 439 return i 440 } 441 442 // modFileIsDirty reports whether the go.mod file differs meaningfully 443 // from what was indexed. 444 // If modFile has been changed (even cosmetically) since it was first read, 445 // modFile.Cleanup must be called before modFileIsDirty. 446 func (i *modFileIndex) modFileIsDirty(modFile *modfile.File) bool { 447 if i == nil { 448 return modFile != nil 449 } 450 451 if i.dataNeedsFix { 452 return true 453 } 454 455 if modFile.Module == nil { 456 if i.module != (module.Version{}) { 457 return true 458 } 459 } else if modFile.Module.Mod != i.module { 460 return true 461 } 462 463 var goV, toolchain string 464 if modFile.Go != nil { 465 goV = modFile.Go.Version 466 } 467 if modFile.Toolchain != nil { 468 toolchain = modFile.Toolchain.Name 469 } 470 471 if goV != i.goVersion || 472 toolchain != i.toolchain || 473 len(modFile.Require) != len(i.require) || 474 len(modFile.Replace) != len(i.replace) || 475 len(modFile.Exclude) != len(i.exclude) { 476 return true 477 } 478 479 for _, r := range modFile.Require { 480 if meta, ok := i.require[r.Mod]; !ok { 481 return true 482 } else if r.Indirect != meta.indirect { 483 if cfg.BuildMod == "readonly" { 484 // The module's requirements are consistent; only the "// indirect" 485 // comments that are wrong. But those are only guaranteed to be accurate 486 // after a "go mod tidy" — it's a good idea to run those before 487 // committing a change, but it's certainly not mandatory. 488 } else { 489 return true 490 } 491 } 492 } 493 494 for _, r := range modFile.Replace { 495 if r.New != i.replace[r.Old] { 496 return true 497 } 498 } 499 500 for _, x := range modFile.Exclude { 501 if !i.exclude[x.Mod] { 502 return true 503 } 504 } 505 506 return false 507 } 508 509 // rawGoVersion records the Go version parsed from each module's go.mod file. 510 // 511 // If a module is replaced, the version of the replacement is keyed by the 512 // replacement module.Version, not the version being replaced. 513 var rawGoVersion sync.Map // map[module.Version]string 514 515 // A modFileSummary is a summary of a go.mod file for which we do not need to 516 // retain complete information — for example, the go.mod file of a dependency 517 // module. 518 type modFileSummary struct { 519 module module.Version 520 goVersion string 521 toolchain string 522 pruning modPruning 523 require []module.Version 524 retract []retraction 525 deprecated string 526 } 527 528 // A retraction consists of a retracted version interval and rationale. 529 // retraction is like modfile.Retract, but it doesn't point to the syntax tree. 530 type retraction struct { 531 modfile.VersionInterval 532 Rationale string 533 } 534 535 // goModSummary returns a summary of the go.mod file for module m, 536 // taking into account any replacements for m, exclusions of its dependencies, 537 // and/or vendoring. 538 // 539 // m must be a version in the module graph, reachable from the Target module. 540 // In readonly mode, the go.sum file must contain an entry for m's go.mod file 541 // (or its replacement). goModSummary must not be called for the Target module 542 // itself, as its requirements may change. Use rawGoModSummary for other 543 // module versions. 544 // 545 // The caller must not modify the returned summary. 546 func goModSummary(m module.Version) (*modFileSummary, error) { 547 if m.Version == "" && !inWorkspaceMode() && MainModules.Contains(m.Path) { 548 panic("internal error: goModSummary called on a main module") 549 } 550 if gover.IsToolchain(m.Path) { 551 return rawGoModSummary(m) 552 } 553 554 if cfg.BuildMod == "vendor" { 555 summary := &modFileSummary{ 556 module: module.Version{Path: m.Path}, 557 } 558 559 readVendorList(VendorDir()) 560 if vendorVersion[m.Path] != m.Version { 561 // This module is not vendored, so packages cannot be loaded from it and 562 // it cannot be relevant to the build. 563 return summary, nil 564 } 565 566 // For every module other than the target, 567 // return the full list of modules from modules.txt. 568 // We don't know what versions the vendored module actually relies on, 569 // so assume that it requires everything. 570 summary.require = vendorList 571 return summary, nil 572 } 573 574 actual := resolveReplacement(m) 575 if mustHaveSums() && actual.Version != "" { 576 key := module.Version{Path: actual.Path, Version: actual.Version + "/go.mod"} 577 if !modfetch.HaveSum(key) { 578 suggestion := fmt.Sprintf(" for go.mod file; to add it:\n\tgo mod download %s", m.Path) 579 return nil, module.VersionError(actual, &sumMissingError{suggestion: suggestion}) 580 } 581 } 582 summary, err := rawGoModSummary(actual) 583 if err != nil { 584 return nil, err 585 } 586 587 if actual.Version == "" { 588 // The actual module is a filesystem-local replacement, for which we have 589 // unfortunately not enforced any sort of invariants about module lines or 590 // matching module paths. Anything goes. 591 // 592 // TODO(bcmills): Remove this special-case, update tests, and add a 593 // release note. 594 } else { 595 if summary.module.Path == "" { 596 return nil, module.VersionError(actual, errors.New("parsing go.mod: missing module line")) 597 } 598 599 // In theory we should only allow mpath to be unequal to m.Path here if the 600 // version that we fetched lacks an explicit go.mod file: if the go.mod file 601 // is explicit, then it should match exactly (to ensure that imports of other 602 // packages within the module are interpreted correctly). Unfortunately, we 603 // can't determine that information from the module proxy protocol: we'll have 604 // to leave that validation for when we load actual packages from within the 605 // module. 606 if mpath := summary.module.Path; mpath != m.Path && mpath != actual.Path { 607 return nil, module.VersionError(actual, 608 fmt.Errorf("parsing go.mod:\n"+ 609 "\tmodule declares its path as: %s\n"+ 610 "\t but was required as: %s", mpath, m.Path)) 611 } 612 } 613 614 for _, mainModule := range MainModules.Versions() { 615 if index := MainModules.Index(mainModule); index != nil && len(index.exclude) > 0 { 616 // Drop any requirements on excluded versions. 617 // Don't modify the cached summary though, since we might need the raw 618 // summary separately. 619 haveExcludedReqs := false 620 for _, r := range summary.require { 621 if index.exclude[r] { 622 haveExcludedReqs = true 623 break 624 } 625 } 626 if haveExcludedReqs { 627 s := new(modFileSummary) 628 *s = *summary 629 s.require = make([]module.Version, 0, len(summary.require)) 630 for _, r := range summary.require { 631 if !index.exclude[r] { 632 s.require = append(s.require, r) 633 } 634 } 635 summary = s 636 } 637 } 638 } 639 return summary, nil 640 } 641 642 // rawGoModSummary returns a new summary of the go.mod file for module m, 643 // ignoring all replacements that may apply to m and excludes that may apply to 644 // its dependencies. 645 // 646 // rawGoModSummary cannot be used on the main module outside of workspace mode. 647 func rawGoModSummary(m module.Version) (*modFileSummary, error) { 648 if gover.IsToolchain(m.Path) { 649 if m.Path == "go" && gover.Compare(m.Version, gover.GoStrictVersion) >= 0 { 650 // Declare that go 1.21.3 requires toolchain 1.21.3, 651 // so that go get knows that downgrading toolchain implies downgrading go 652 // and similarly upgrading go requires upgrading the toolchain. 653 return &modFileSummary{module: m, require: []module.Version{{Path: "toolchain", Version: "go" + m.Version}}}, nil 654 } 655 return &modFileSummary{module: m}, nil 656 } 657 if m.Version == "" && !inWorkspaceMode() && MainModules.Contains(m.Path) { 658 // Calling rawGoModSummary implies that we are treating m as a module whose 659 // requirements aren't the roots of the module graph and can't be modified. 660 // 661 // If we are not in workspace mode, then the requirements of the main module 662 // are the roots of the module graph and we expect them to be kept consistent. 663 panic("internal error: rawGoModSummary called on a main module") 664 } 665 if m.Version == "" && inWorkspaceMode() && m.Path == "command-line-arguments" { 666 // "go work sync" calls LoadModGraph to make sure the module graph is valid. 667 // If there are no modules in the workspace, we synthesize an empty 668 // command-line-arguments module, which rawGoModData cannot read a go.mod for. 669 return &modFileSummary{module: m}, nil 670 } 671 return rawGoModSummaryCache.Do(m, func() (*modFileSummary, error) { 672 summary := new(modFileSummary) 673 name, data, err := rawGoModData(m) 674 if err != nil { 675 return nil, err 676 } 677 f, err := modfile.ParseLax(name, data, nil) 678 if err != nil { 679 return nil, module.VersionError(m, fmt.Errorf("parsing %s: %v", base.ShortPath(name), err)) 680 } 681 if f.Module != nil { 682 summary.module = f.Module.Mod 683 summary.deprecated = f.Module.Deprecated 684 } 685 if f.Go != nil { 686 rawGoVersion.LoadOrStore(m, f.Go.Version) 687 summary.goVersion = f.Go.Version 688 summary.pruning = pruningForGoVersion(f.Go.Version) 689 } else { 690 summary.pruning = unpruned 691 } 692 if f.Toolchain != nil { 693 summary.toolchain = f.Toolchain.Name 694 } 695 if len(f.Require) > 0 { 696 summary.require = make([]module.Version, 0, len(f.Require)+1) 697 for _, req := range f.Require { 698 summary.require = append(summary.require, req.Mod) 699 } 700 } 701 if summary.goVersion != "" && gover.Compare(summary.goVersion, gover.GoStrictVersion) >= 0 { 702 if gover.Compare(summary.goVersion, gover.Local()) > 0 { 703 return nil, &gover.TooNewError{What: "module " + m.String(), GoVersion: summary.goVersion} 704 } 705 summary.require = append(summary.require, module.Version{Path: "go", Version: summary.goVersion}) 706 } 707 if len(f.Retract) > 0 { 708 summary.retract = make([]retraction, 0, len(f.Retract)) 709 for _, ret := range f.Retract { 710 summary.retract = append(summary.retract, retraction{ 711 VersionInterval: ret.VersionInterval, 712 Rationale: ret.Rationale, 713 }) 714 } 715 } 716 717 return summary, nil 718 }) 719 } 720 721 var rawGoModSummaryCache par.ErrCache[module.Version, *modFileSummary] 722 723 // rawGoModData returns the content of the go.mod file for module m, ignoring 724 // all replacements that may apply to m. 725 // 726 // rawGoModData cannot be used on the main module outside of workspace mode. 727 // 728 // Unlike rawGoModSummary, rawGoModData does not cache its results in memory. 729 // Use rawGoModSummary instead unless you specifically need these bytes. 730 func rawGoModData(m module.Version) (name string, data []byte, err error) { 731 if m.Version == "" { 732 dir := m.Path 733 if !filepath.IsAbs(dir) { 734 if inWorkspaceMode() && MainModules.Contains(m.Path) { 735 dir = MainModules.ModRoot(m) 736 } else { 737 // m is a replacement module with only a file path. 738 dir = filepath.Join(replaceRelativeTo(), dir) 739 } 740 } 741 name = filepath.Join(dir, "go.mod") 742 if gomodActual, ok := fsys.OverlayPath(name); ok { 743 // Don't lock go.mod if it's part of the overlay. 744 // On Plan 9, locking requires chmod, and we don't want to modify any file 745 // in the overlay. See #44700. 746 data, err = os.ReadFile(gomodActual) 747 } else { 748 data, err = lockedfile.Read(gomodActual) 749 } 750 if err != nil { 751 return "", nil, module.VersionError(m, fmt.Errorf("reading %s: %v", base.ShortPath(name), err)) 752 } 753 } else { 754 if !gover.ModIsValid(m.Path, m.Version) { 755 // Disallow the broader queries supported by fetch.Lookup. 756 base.Fatalf("go: internal error: %s@%s: unexpected invalid semantic version", m.Path, m.Version) 757 } 758 name = "go.mod" 759 data, err = modfetch.GoMod(context.TODO(), m.Path, m.Version) 760 } 761 return name, data, err 762 } 763 764 // queryLatestVersionIgnoringRetractions looks up the latest version of the 765 // module with the given path without considering retracted or excluded 766 // versions. 767 // 768 // If all versions of the module are replaced, 769 // queryLatestVersionIgnoringRetractions returns the replacement without making 770 // a query. 771 // 772 // If the queried latest version is replaced, 773 // queryLatestVersionIgnoringRetractions returns the replacement. 774 func queryLatestVersionIgnoringRetractions(ctx context.Context, path string) (latest module.Version, err error) { 775 return latestVersionIgnoringRetractionsCache.Do(path, func() (module.Version, error) { 776 ctx, span := trace.StartSpan(ctx, "queryLatestVersionIgnoringRetractions "+path) 777 defer span.Done() 778 779 if repl := Replacement(module.Version{Path: path}); repl.Path != "" { 780 // All versions of the module were replaced. 781 // No need to query. 782 return repl, nil 783 } 784 785 // Find the latest version of the module. 786 // Ignore exclusions from the main module's go.mod. 787 const ignoreSelected = "" 788 var allowAll AllowedFunc 789 rev, err := Query(ctx, path, "latest", ignoreSelected, allowAll) 790 if err != nil { 791 return module.Version{}, err 792 } 793 latest := module.Version{Path: path, Version: rev.Version} 794 if repl := resolveReplacement(latest); repl.Path != "" { 795 latest = repl 796 } 797 return latest, nil 798 }) 799 } 800 801 var latestVersionIgnoringRetractionsCache par.ErrCache[string, module.Version] // path → queryLatestVersionIgnoringRetractions result 802 803 // ToDirectoryPath adds a prefix if necessary so that path in unambiguously 804 // an absolute path or a relative path starting with a '.' or '..' 805 // path component. 806 func ToDirectoryPath(path string) string { 807 if modfile.IsDirectoryPath(path) { 808 return path 809 } 810 // The path is not a relative path or an absolute path, so make it relative 811 // to the current directory. 812 return "./" + filepath.ToSlash(filepath.Clean(path)) 813 }