github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/go/modload/import.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 modload 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "go/build" 12 "io/fs" 13 "os" 14 pathpkg "path" 15 "path/filepath" 16 "sort" 17 "strings" 18 19 "github.com/go-asm/go/cmd/go/cfg" 20 "github.com/go-asm/go/cmd/go/fsys" 21 "github.com/go-asm/go/cmd/go/gover" 22 "github.com/go-asm/go/cmd/go/modfetch" 23 "github.com/go-asm/go/cmd/go/modindex" 24 "github.com/go-asm/go/cmd/go/par" 25 "github.com/go-asm/go/cmd/go/search" 26 "github.com/go-asm/go/cmd/go/str" 27 28 "golang.org/x/mod/module" 29 ) 30 31 type ImportMissingError struct { 32 Path string 33 Module module.Version 34 QueryErr error 35 36 ImportingMainModule module.Version 37 38 // isStd indicates whether we would expect to find the package in the standard 39 // library. This is normally true for all dotless import paths, but replace 40 // directives can cause us to treat the replaced paths as also being in 41 // modules. 42 isStd bool 43 44 // importerGoVersion is the version the module containing the import error 45 // specified. It is only set when isStd is true. 46 importerGoVersion string 47 48 // replaced the highest replaced version of the module where the replacement 49 // contains the package. replaced is only set if the replacement is unused. 50 replaced module.Version 51 52 // newMissingVersion is set to a newer version of Module if one is present 53 // in the build list. When set, we can't automatically upgrade. 54 newMissingVersion string 55 } 56 57 func (e *ImportMissingError) Error() string { 58 if e.Module.Path == "" { 59 if e.isStd { 60 msg := fmt.Sprintf("package %s is not in std (%s)", e.Path, filepath.Join(cfg.GOROOT, "src", e.Path)) 61 if e.importerGoVersion != "" { 62 msg += fmt.Sprintf("\nnote: imported by a module that requires go %s", e.importerGoVersion) 63 } 64 return msg 65 } 66 if e.QueryErr != nil && e.QueryErr != ErrNoModRoot { 67 return fmt.Sprintf("cannot find module providing package %s: %v", e.Path, e.QueryErr) 68 } 69 if cfg.BuildMod == "mod" || (cfg.BuildMod == "readonly" && allowMissingModuleImports) { 70 return "cannot find module providing package " + e.Path 71 } 72 73 if e.replaced.Path != "" { 74 suggestArg := e.replaced.Path 75 if !module.IsZeroPseudoVersion(e.replaced.Version) { 76 suggestArg = e.replaced.String() 77 } 78 return fmt.Sprintf("module %s provides package %s and is replaced but not required; to add it:\n\tgo get %s", e.replaced.Path, e.Path, suggestArg) 79 } 80 81 message := fmt.Sprintf("no required module provides package %s", e.Path) 82 if e.QueryErr != nil { 83 return fmt.Sprintf("%s: %v", message, e.QueryErr) 84 } 85 if e.ImportingMainModule.Path != "" && e.ImportingMainModule != MainModules.ModContainingCWD() { 86 return fmt.Sprintf("%s; to add it:\n\tcd %s\n\tgo get %s", message, MainModules.ModRoot(e.ImportingMainModule), e.Path) 87 } 88 return fmt.Sprintf("%s; to add it:\n\tgo get %s", message, e.Path) 89 } 90 91 if e.newMissingVersion != "" { 92 return fmt.Sprintf("package %s provided by %s at latest version %s but not at required version %s", e.Path, e.Module.Path, e.Module.Version, e.newMissingVersion) 93 } 94 95 return fmt.Sprintf("missing module for import: %s@%s provides %s", e.Module.Path, e.Module.Version, e.Path) 96 } 97 98 func (e *ImportMissingError) Unwrap() error { 99 return e.QueryErr 100 } 101 102 func (e *ImportMissingError) ImportPath() string { 103 return e.Path 104 } 105 106 // An AmbiguousImportError indicates an import of a package found in multiple 107 // modules in the build list, or found in both the main module and its vendor 108 // directory. 109 type AmbiguousImportError struct { 110 importPath string 111 Dirs []string 112 Modules []module.Version // Either empty or 1:1 with Dirs. 113 } 114 115 func (e *AmbiguousImportError) ImportPath() string { 116 return e.importPath 117 } 118 119 func (e *AmbiguousImportError) Error() string { 120 locType := "modules" 121 if len(e.Modules) == 0 { 122 locType = "directories" 123 } 124 125 var buf strings.Builder 126 fmt.Fprintf(&buf, "ambiguous import: found package %s in multiple %s:", e.importPath, locType) 127 128 for i, dir := range e.Dirs { 129 buf.WriteString("\n\t") 130 if i < len(e.Modules) { 131 m := e.Modules[i] 132 buf.WriteString(m.Path) 133 if m.Version != "" { 134 fmt.Fprintf(&buf, " %s", m.Version) 135 } 136 fmt.Fprintf(&buf, " (%s)", dir) 137 } else { 138 buf.WriteString(dir) 139 } 140 } 141 142 return buf.String() 143 } 144 145 // A DirectImportFromImplicitDependencyError indicates a package directly 146 // imported by a package or test in the main module that is satisfied by a 147 // dependency that is not explicit in the main module's go.mod file. 148 type DirectImportFromImplicitDependencyError struct { 149 ImporterPath string 150 ImportedPath string 151 Module module.Version 152 } 153 154 func (e *DirectImportFromImplicitDependencyError) Error() string { 155 return fmt.Sprintf("package %s imports %s from implicitly required module; to add missing requirements, run:\n\tgo get %s@%s", e.ImporterPath, e.ImportedPath, e.Module.Path, e.Module.Version) 156 } 157 158 func (e *DirectImportFromImplicitDependencyError) ImportPath() string { 159 return e.ImporterPath 160 } 161 162 // ImportMissingSumError is reported in readonly mode when we need to check 163 // if a module contains a package, but we don't have a sum for its .zip file. 164 // We might need sums for multiple modules to verify the package is unique. 165 // 166 // TODO(#43653): consolidate multiple errors of this type into a single error 167 // that suggests a 'go get' command for root packages that transitively import 168 // packages from modules with missing sums. load.CheckPackageErrors would be 169 // a good place to consolidate errors, but we'll need to attach the import 170 // stack here. 171 type ImportMissingSumError struct { 172 importPath string 173 found bool 174 mods []module.Version 175 importer, importerVersion string // optional, but used for additional context 176 importerIsTest bool 177 } 178 179 func (e *ImportMissingSumError) Error() string { 180 var importParen string 181 if e.importer != "" { 182 importParen = fmt.Sprintf(" (imported by %s)", e.importer) 183 } 184 var message string 185 if e.found { 186 message = fmt.Sprintf("missing go.sum entry needed to verify package %s%s is provided by exactly one module", e.importPath, importParen) 187 } else { 188 message = fmt.Sprintf("missing go.sum entry for module providing package %s%s", e.importPath, importParen) 189 } 190 var hint string 191 if e.importer == "" { 192 // Importing package is unknown, or the missing package was named on the 193 // command line. Recommend 'go mod download' for the modules that could 194 // provide the package, since that shouldn't change go.mod. 195 if len(e.mods) > 0 { 196 args := make([]string, len(e.mods)) 197 for i, mod := range e.mods { 198 args[i] = mod.Path 199 } 200 hint = fmt.Sprintf("; to add:\n\tgo mod download %s", strings.Join(args, " ")) 201 } 202 } else { 203 // Importing package is known (common case). Recommend 'go get' on the 204 // current version of the importing package. 205 tFlag := "" 206 if e.importerIsTest { 207 tFlag = " -t" 208 } 209 version := "" 210 if e.importerVersion != "" { 211 version = "@" + e.importerVersion 212 } 213 hint = fmt.Sprintf("; to add:\n\tgo get%s %s%s", tFlag, e.importer, version) 214 } 215 return message + hint 216 } 217 218 func (e *ImportMissingSumError) ImportPath() string { 219 return e.importPath 220 } 221 222 type invalidImportError struct { 223 importPath string 224 err error 225 } 226 227 func (e *invalidImportError) ImportPath() string { 228 return e.importPath 229 } 230 231 func (e *invalidImportError) Error() string { 232 return e.err.Error() 233 } 234 235 func (e *invalidImportError) Unwrap() error { 236 return e.err 237 } 238 239 // importFromModules finds the module and directory in the dependency graph of 240 // rs containing the package with the given import path. If mg is nil, 241 // importFromModules attempts to locate the module using only the main module 242 // and the roots of rs before it loads the full graph. 243 // 244 // The answer must be unique: importFromModules returns an error if multiple 245 // modules are observed to provide the same package. 246 // 247 // importFromModules can return a module with an empty m.Path, for packages in 248 // the standard library. 249 // 250 // importFromModules can return an empty directory string, for fake packages 251 // like "C" and "unsafe". 252 // 253 // If the package is not present in any module selected from the requirement 254 // graph, importFromModules returns an *ImportMissingError. 255 // 256 // If the package is present in exactly one module, importFromModules will 257 // return the module, its root directory, and a list of other modules that 258 // lexically could have provided the package but did not. 259 // 260 // If skipModFile is true, the go.mod file for the package is not loaded. This 261 // allows 'go mod tidy' to preserve a minor checksum-preservation bug 262 // (https://go.dev/issue/56222) for modules with 'go' versions between 1.17 and 263 // 1.20, preventing unnecessary go.sum churn and network access in those 264 // modules. 265 func importFromModules(ctx context.Context, path string, rs *Requirements, mg *ModuleGraph, skipModFile bool) (m module.Version, modroot, dir string, altMods []module.Version, err error) { 266 invalidf := func(format string, args ...interface{}) (module.Version, string, string, []module.Version, error) { 267 return module.Version{}, "", "", nil, &invalidImportError{ 268 importPath: path, 269 err: fmt.Errorf(format, args...), 270 } 271 } 272 273 if strings.Contains(path, "@") { 274 return invalidf("import path %q should not have @version", path) 275 } 276 if build.IsLocalImport(path) { 277 return invalidf("%q is relative, but relative import paths are not supported in module mode", path) 278 } 279 if filepath.IsAbs(path) { 280 return invalidf("%q is not a package path; see 'go help packages'", path) 281 } 282 if search.IsMetaPackage(path) { 283 return invalidf("%q is not an importable package; see 'go help packages'", path) 284 } 285 286 if path == "C" { 287 // There's no directory for import "C". 288 return module.Version{}, "", "", nil, nil 289 } 290 // Before any further lookup, check that the path is valid. 291 if err := module.CheckImportPath(path); err != nil { 292 return module.Version{}, "", "", nil, &invalidImportError{importPath: path, err: err} 293 } 294 295 // Check each module on the build list. 296 var dirs, roots []string 297 var mods []module.Version 298 299 // Is the package in the standard library? 300 pathIsStd := search.IsStandardImportPath(path) 301 if pathIsStd && modindex.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) { 302 for _, mainModule := range MainModules.Versions() { 303 if MainModules.InGorootSrc(mainModule) { 304 if dir, ok, err := dirInModule(path, MainModules.PathPrefix(mainModule), MainModules.ModRoot(mainModule), true); err != nil { 305 return module.Version{}, MainModules.ModRoot(mainModule), dir, nil, err 306 } else if ok { 307 return mainModule, MainModules.ModRoot(mainModule), dir, nil, nil 308 } 309 } 310 } 311 dir := filepath.Join(cfg.GOROOTsrc, path) 312 modroot = cfg.GOROOTsrc 313 if str.HasPathPrefix(path, "cmd") { 314 modroot = filepath.Join(cfg.GOROOTsrc, "cmd") 315 } 316 dirs = append(dirs, dir) 317 roots = append(roots, modroot) 318 mods = append(mods, module.Version{}) 319 } 320 // -mod=vendor is special. 321 // Everything must be in the main modules or the main module's or workspace's vendor directory. 322 if cfg.BuildMod == "vendor" { 323 var mainErr error 324 for _, mainModule := range MainModules.Versions() { 325 modRoot := MainModules.ModRoot(mainModule) 326 if modRoot != "" { 327 dir, mainOK, err := dirInModule(path, MainModules.PathPrefix(mainModule), modRoot, true) 328 if mainErr == nil { 329 mainErr = err 330 } 331 if mainOK { 332 mods = append(mods, mainModule) 333 dirs = append(dirs, dir) 334 roots = append(roots, modRoot) 335 } 336 } 337 } 338 339 if HasModRoot() { 340 vendorDir := VendorDir() 341 dir, vendorOK, _ := dirInModule(path, "", vendorDir, false) 342 if vendorOK { 343 readVendorList(vendorDir) 344 // TODO(#60922): It's possible for a package to manually have been added to the 345 // vendor directory, causing the dirInModule to succeed, but no vendorPkgModule 346 // to exist, causing an empty module path to be reported. Do better checking 347 // here. 348 mods = append(mods, vendorPkgModule[path]) 349 dirs = append(dirs, dir) 350 roots = append(roots, vendorDir) 351 } 352 } 353 354 if len(dirs) > 1 { 355 return module.Version{}, "", "", nil, &AmbiguousImportError{importPath: path, Dirs: dirs} 356 } 357 358 if mainErr != nil { 359 return module.Version{}, "", "", nil, mainErr 360 } 361 362 if len(dirs) == 0 { 363 return module.Version{}, "", "", nil, &ImportMissingError{Path: path} 364 } 365 366 return mods[0], roots[0], dirs[0], nil, nil 367 } 368 369 // Iterate over possible modules for the path, not all selected modules. 370 // Iterating over selected modules would make the overall loading time 371 // O(M × P) for M modules providing P imported packages, whereas iterating 372 // over path prefixes is only O(P × k) with maximum path depth k. For 373 // large projects both M and P may be very large (note that M ≤ P), but k 374 // will tend to remain smallish (if for no other reason than filesystem 375 // path limitations). 376 // 377 // We perform this iteration either one or two times. If mg is initially nil, 378 // then we first attempt to load the package using only the main module and 379 // its root requirements. If that does not identify the package, or if mg is 380 // already non-nil, then we attempt to load the package using the full 381 // requirements in mg. 382 for { 383 var sumErrMods, altMods []module.Version 384 for prefix := path; prefix != "."; prefix = pathpkg.Dir(prefix) { 385 if gover.IsToolchain(prefix) { 386 // Do not use the synthetic "go" module for "go/ast". 387 continue 388 } 389 var ( 390 v string 391 ok bool 392 ) 393 if mg == nil { 394 v, ok = rs.rootSelected(prefix) 395 } else { 396 v, ok = mg.Selected(prefix), true 397 } 398 if !ok || v == "none" { 399 continue 400 } 401 m := module.Version{Path: prefix, Version: v} 402 403 root, isLocal, err := fetch(ctx, m) 404 if err != nil { 405 if sumErr := (*sumMissingError)(nil); errors.As(err, &sumErr) { 406 // We are missing a sum needed to fetch a module in the build list. 407 // We can't verify that the package is unique, and we may not find 408 // the package at all. Keep checking other modules to decide which 409 // error to report. Multiple sums may be missing if we need to look in 410 // multiple nested modules to resolve the import; we'll report them all. 411 sumErrMods = append(sumErrMods, m) 412 continue 413 } 414 // Report fetch error. 415 // Note that we don't know for sure this module is necessary, 416 // but it certainly _could_ provide the package, and even if we 417 // continue the loop and find the package in some other module, 418 // we need to look at this module to make sure the import is 419 // not ambiguous. 420 return module.Version{}, "", "", nil, err 421 } 422 if dir, ok, err := dirInModule(path, m.Path, root, isLocal); err != nil { 423 return module.Version{}, "", "", nil, err 424 } else if ok { 425 mods = append(mods, m) 426 roots = append(roots, root) 427 dirs = append(dirs, dir) 428 } else { 429 altMods = append(altMods, m) 430 } 431 } 432 433 if len(mods) > 1 { 434 // We produce the list of directories from longest to shortest candidate 435 // module path, but the AmbiguousImportError should report them from 436 // shortest to longest. Reverse them now. 437 for i := 0; i < len(mods)/2; i++ { 438 j := len(mods) - 1 - i 439 mods[i], mods[j] = mods[j], mods[i] 440 roots[i], roots[j] = roots[j], roots[i] 441 dirs[i], dirs[j] = dirs[j], dirs[i] 442 } 443 return module.Version{}, "", "", nil, &AmbiguousImportError{importPath: path, Dirs: dirs, Modules: mods} 444 } 445 446 if len(sumErrMods) > 0 { 447 for i := 0; i < len(sumErrMods)/2; i++ { 448 j := len(sumErrMods) - 1 - i 449 sumErrMods[i], sumErrMods[j] = sumErrMods[j], sumErrMods[i] 450 } 451 return module.Version{}, "", "", nil, &ImportMissingSumError{ 452 importPath: path, 453 mods: sumErrMods, 454 found: len(mods) > 0, 455 } 456 } 457 458 if len(mods) == 1 { 459 // We've found the unique module containing the package. 460 // However, in order to actually compile it we need to know what 461 // Go language version to use, which requires its go.mod file. 462 // 463 // If the module graph is pruned and this is a test-only dependency 464 // of a package in "all", we didn't necessarily load that file 465 // when we read the module graph, so do it now to be sure. 466 if !skipModFile && cfg.BuildMod != "vendor" && mods[0].Path != "" && !MainModules.Contains(mods[0].Path) { 467 if _, err := goModSummary(mods[0]); err != nil { 468 return module.Version{}, "", "", nil, err 469 } 470 } 471 return mods[0], roots[0], dirs[0], altMods, nil 472 } 473 474 if mg != nil { 475 // We checked the full module graph and still didn't find the 476 // requested package. 477 var queryErr error 478 if !HasModRoot() { 479 queryErr = ErrNoModRoot 480 } 481 return module.Version{}, "", "", nil, &ImportMissingError{Path: path, QueryErr: queryErr, isStd: pathIsStd} 482 } 483 484 // So far we've checked the root dependencies. 485 // Load the full module graph and try again. 486 mg, err = rs.Graph(ctx) 487 if err != nil { 488 // We might be missing one or more transitive (implicit) dependencies from 489 // the module graph, so we can't return an ImportMissingError here — one 490 // of the missing modules might actually contain the package in question, 491 // in which case we shouldn't go looking for it in some new dependency. 492 return module.Version{}, "", "", nil, err 493 } 494 } 495 } 496 497 // queryImport attempts to locate a module that can be added to the current 498 // build list to provide the package with the given import path. 499 // 500 // Unlike QueryPattern, queryImport prefers to add a replaced version of a 501 // module *before* checking the proxies for a version to add. 502 func queryImport(ctx context.Context, path string, rs *Requirements) (module.Version, error) { 503 // To avoid spurious remote fetches, try the latest replacement for each 504 // module (golang.org/issue/26241). 505 var mods []module.Version 506 if MainModules != nil { // TODO(#48912): Ensure MainModules exists at this point, and remove the check. 507 for mp, mv := range MainModules.HighestReplaced() { 508 if !maybeInModule(path, mp) { 509 continue 510 } 511 if mv == "" { 512 // The only replacement is a wildcard that doesn't specify a version, so 513 // synthesize a pseudo-version with an appropriate major version and a 514 // timestamp below any real timestamp. That way, if the main module is 515 // used from within some other module, the user will be able to upgrade 516 // the requirement to any real version they choose. 517 if _, pathMajor, ok := module.SplitPathVersion(mp); ok && len(pathMajor) > 0 { 518 mv = module.ZeroPseudoVersion(pathMajor[1:]) 519 } else { 520 mv = module.ZeroPseudoVersion("v0") 521 } 522 } 523 mg, err := rs.Graph(ctx) 524 if err != nil { 525 return module.Version{}, err 526 } 527 if gover.ModCompare(mp, mg.Selected(mp), mv) >= 0 { 528 // We can't resolve the import by adding mp@mv to the module graph, 529 // because the selected version of mp is already at least mv. 530 continue 531 } 532 mods = append(mods, module.Version{Path: mp, Version: mv}) 533 } 534 } 535 536 // Every module path in mods is a prefix of the import path. 537 // As in QueryPattern, prefer the longest prefix that satisfies the import. 538 sort.Slice(mods, func(i, j int) bool { 539 return len(mods[i].Path) > len(mods[j].Path) 540 }) 541 for _, m := range mods { 542 root, isLocal, err := fetch(ctx, m) 543 if err != nil { 544 if sumErr := (*sumMissingError)(nil); errors.As(err, &sumErr) { 545 return module.Version{}, &ImportMissingSumError{importPath: path} 546 } 547 return module.Version{}, err 548 } 549 if _, ok, err := dirInModule(path, m.Path, root, isLocal); err != nil { 550 return m, err 551 } else if ok { 552 if cfg.BuildMod == "readonly" { 553 return module.Version{}, &ImportMissingError{Path: path, replaced: m} 554 } 555 return m, nil 556 } 557 } 558 if len(mods) > 0 && module.CheckPath(path) != nil { 559 // The package path is not valid to fetch remotely, 560 // so it can only exist in a replaced module, 561 // and we know from the above loop that it is not. 562 replacement := Replacement(mods[0]) 563 return module.Version{}, &PackageNotInModuleError{ 564 Mod: mods[0], 565 Query: "latest", 566 Pattern: path, 567 Replacement: replacement, 568 } 569 } 570 571 if search.IsStandardImportPath(path) { 572 // This package isn't in the standard library, isn't in any module already 573 // in the build list, and isn't in any other module that the user has 574 // shimmed in via a "replace" directive. 575 // Moreover, the import path is reserved for the standard library, so 576 // QueryPattern cannot possibly find a module containing this package. 577 // 578 // Instead of trying QueryPattern, report an ImportMissingError immediately. 579 return module.Version{}, &ImportMissingError{Path: path, isStd: true} 580 } 581 582 if (cfg.BuildMod == "readonly" || cfg.BuildMod == "vendor") && !allowMissingModuleImports { 583 // In readonly mode, we can't write go.mod, so we shouldn't try to look up 584 // the module. If readonly mode was enabled explicitly, include that in 585 // the error message. 586 // In vendor mode, we cannot use the network or module cache, so we 587 // shouldn't try to look up the module 588 var queryErr error 589 if cfg.BuildModExplicit { 590 queryErr = fmt.Errorf("import lookup disabled by -mod=%s", cfg.BuildMod) 591 } else if cfg.BuildModReason != "" { 592 queryErr = fmt.Errorf("import lookup disabled by -mod=%s\n\t(%s)", cfg.BuildMod, cfg.BuildModReason) 593 } 594 return module.Version{}, &ImportMissingError{Path: path, QueryErr: queryErr} 595 } 596 597 // Look up module containing the package, for addition to the build list. 598 // Goal is to determine the module, download it to dir, 599 // and return m, dir, ImportMissingError. 600 fmt.Fprintf(os.Stderr, "go: finding module for package %s\n", path) 601 602 mg, err := rs.Graph(ctx) 603 if err != nil { 604 return module.Version{}, err 605 } 606 607 candidates, err := QueryPackages(ctx, path, "latest", mg.Selected, CheckAllowed) 608 if err != nil { 609 if errors.Is(err, fs.ErrNotExist) { 610 // Return "cannot find module providing package […]" instead of whatever 611 // low-level error QueryPattern produced. 612 return module.Version{}, &ImportMissingError{Path: path, QueryErr: err} 613 } else { 614 return module.Version{}, err 615 } 616 } 617 618 candidate0MissingVersion := "" 619 for i, c := range candidates { 620 if v := mg.Selected(c.Mod.Path); gover.ModCompare(c.Mod.Path, v, c.Mod.Version) > 0 { 621 // QueryPattern proposed that we add module c.Mod to provide the package, 622 // but we already depend on a newer version of that module (and that 623 // version doesn't have the package). 624 // 625 // This typically happens when a package is present at the "@latest" 626 // version (e.g., v1.0.0) of a module, but we have a newer version 627 // of the same module in the build list (e.g., v1.0.1-beta), and 628 // the package is not present there. 629 if i == 0 { 630 candidate0MissingVersion = v 631 } 632 continue 633 } 634 return c.Mod, nil 635 } 636 return module.Version{}, &ImportMissingError{ 637 Path: path, 638 Module: candidates[0].Mod, 639 newMissingVersion: candidate0MissingVersion, 640 } 641 } 642 643 // maybeInModule reports whether, syntactically, 644 // a package with the given import path could be supplied 645 // by a module with the given module path (mpath). 646 func maybeInModule(path, mpath string) bool { 647 return mpath == path || 648 len(path) > len(mpath) && path[len(mpath)] == '/' && path[:len(mpath)] == mpath 649 } 650 651 var ( 652 haveGoModCache par.Cache[string, bool] // dir → bool 653 haveGoFilesCache par.ErrCache[string, bool] // dir → haveGoFiles 654 ) 655 656 // dirInModule locates the directory that would hold the package named by the given path, 657 // if it were in the module with module path mpath and root mdir. 658 // If path is syntactically not within mpath, 659 // or if mdir is a local file tree (isLocal == true) and the directory 660 // that would hold path is in a sub-module (covered by a go.mod below mdir), 661 // dirInModule returns "", false, nil. 662 // 663 // Otherwise, dirInModule returns the name of the directory where 664 // Go source files would be expected, along with a boolean indicating 665 // whether there are in fact Go source files in that directory. 666 // A non-nil error indicates that the existence of the directory and/or 667 // source files could not be determined, for example due to a permission error. 668 func dirInModule(path, mpath, mdir string, isLocal bool) (dir string, haveGoFiles bool, err error) { 669 // Determine where to expect the package. 670 if path == mpath { 671 dir = mdir 672 } else if mpath == "" { // vendor directory 673 dir = filepath.Join(mdir, path) 674 } else if len(path) > len(mpath) && path[len(mpath)] == '/' && path[:len(mpath)] == mpath { 675 dir = filepath.Join(mdir, path[len(mpath)+1:]) 676 } else { 677 return "", false, nil 678 } 679 680 // Check that there aren't other modules in the way. 681 // This check is unnecessary inside the module cache 682 // and important to skip in the vendor directory, 683 // where all the module trees have been overlaid. 684 // So we only check local module trees 685 // (the main module, and any directory trees pointed at by replace directives). 686 if isLocal { 687 for d := dir; d != mdir && len(d) > len(mdir); { 688 haveGoMod := haveGoModCache.Do(d, func() bool { 689 fi, err := fsys.Stat(filepath.Join(d, "go.mod")) 690 return err == nil && !fi.IsDir() 691 }) 692 693 if haveGoMod { 694 return "", false, nil 695 } 696 parent := filepath.Dir(d) 697 if parent == d { 698 // Break the loop, as otherwise we'd loop 699 // forever if d=="." and mdir=="". 700 break 701 } 702 d = parent 703 } 704 } 705 706 // Now committed to returning dir (not ""). 707 708 // Are there Go source files in the directory? 709 // We don't care about build tags, not even "go:build ignore". 710 // We're just looking for a plausible directory. 711 haveGoFiles, err = haveGoFilesCache.Do(dir, func() (bool, error) { 712 // modindex.GetPackage will return ErrNotIndexed for any directories which 713 // are reached through a symlink, so that they will be handled by 714 // fsys.IsDirWithGoFiles below. 715 if ip, err := modindex.GetPackage(mdir, dir); err == nil { 716 return ip.IsDirWithGoFiles() 717 } else if !errors.Is(err, modindex.ErrNotIndexed) { 718 return false, err 719 } 720 return fsys.IsDirWithGoFiles(dir) 721 }) 722 723 return dir, haveGoFiles, err 724 } 725 726 // fetch downloads the given module (or its replacement) 727 // and returns its location. 728 // 729 // The isLocal return value reports whether the replacement, 730 // if any, is local to the filesystem. 731 func fetch(ctx context.Context, mod module.Version) (dir string, isLocal bool, err error) { 732 if modRoot := MainModules.ModRoot(mod); modRoot != "" { 733 return modRoot, true, nil 734 } 735 if r := Replacement(mod); r.Path != "" { 736 if r.Version == "" { 737 dir = r.Path 738 if !filepath.IsAbs(dir) { 739 dir = filepath.Join(replaceRelativeTo(), dir) 740 } 741 // Ensure that the replacement directory actually exists: 742 // dirInModule does not report errors for missing modules, 743 // so if we don't report the error now, later failures will be 744 // very mysterious. 745 if _, err := fsys.Stat(dir); err != nil { 746 // TODO(bcmills): We should also read dir/go.mod here and check its Go version, 747 // and return a gover.TooNewError if appropriate. 748 749 if os.IsNotExist(err) { 750 // Semantically the module version itself “exists” — we just don't 751 // have its source code. Remove the equivalence to os.ErrNotExist, 752 // and make the message more concise while we're at it. 753 err = fmt.Errorf("replacement directory %s does not exist", r.Path) 754 } else { 755 err = fmt.Errorf("replacement directory %s: %w", r.Path, err) 756 } 757 return dir, true, module.VersionError(mod, err) 758 } 759 return dir, true, nil 760 } 761 mod = r 762 } 763 764 if mustHaveSums() && !modfetch.HaveSum(mod) { 765 return "", false, module.VersionError(mod, &sumMissingError{}) 766 } 767 768 dir, err = modfetch.Download(ctx, mod) 769 return dir, false, err 770 } 771 772 // mustHaveSums reports whether we require that all checksums 773 // needed to load or build packages are already present in the go.sum file. 774 func mustHaveSums() bool { 775 return HasModRoot() && cfg.BuildMod == "readonly" && !inWorkspaceMode() 776 } 777 778 type sumMissingError struct { 779 suggestion string 780 } 781 782 func (e *sumMissingError) Error() string { 783 return "missing go.sum entry" + e.suggestion 784 }