github.com/bir3/gocompiler@v0.3.205/src/cmd/gocmd/internal/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 "github.com/bir3/gocompiler/src/go/build" 12 "io/fs" 13 "os" 14 pathpkg "path" 15 "path/filepath" 16 "sort" 17 "strings" 18 19 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/cfg" 20 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/fsys" 21 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/modfetch" 22 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/modindex" 23 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/par" 24 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/search" 25 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/str" 26 27 "github.com/bir3/gocompiler/src/xvendor/golang.org/x/mod/module" 28 "github.com/bir3/gocompiler/src/xvendor/golang.org/x/mod/semver" 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 GOROOT (%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 transtively 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 module or the main module's vendor directory. 322 if cfg.BuildMod == "vendor" { 323 mainModule := MainModules.mustGetSingleMainModule() 324 modRoot := MainModules.ModRoot(mainModule) 325 var mainErr error 326 if modRoot != "" { 327 mainDir, mainOK, err := dirInModule(path, MainModules.PathPrefix(mainModule), modRoot, true) 328 mainErr = err 329 if mainOK { 330 mods = append(mods, mainModule) 331 dirs = append(dirs, mainDir) 332 roots = append(roots, modRoot) 333 } 334 vendorDir, vendorOK, _ := dirInModule(path, "", filepath.Join(modRoot, "vendor"), false) 335 if vendorOK { 336 readVendorList(mainModule) 337 mods = append(mods, vendorPkgModule[path]) 338 dirs = append(dirs, vendorDir) 339 roots = append(roots, modRoot) 340 } 341 } 342 343 if len(dirs) > 1 { 344 return module.Version{}, modRoot, "", nil, &AmbiguousImportError{importPath: path, Dirs: dirs} 345 } 346 347 if mainErr != nil { 348 return module.Version{}, "", "", nil, mainErr 349 } 350 351 if len(dirs) == 0 { 352 return module.Version{}, modRoot, "", nil, &ImportMissingError{Path: path} 353 } 354 355 return mods[0], roots[0], dirs[0], nil, nil 356 } 357 358 // Iterate over possible modules for the path, not all selected modules. 359 // Iterating over selected modules would make the overall loading time 360 // O(M × P) for M modules providing P imported packages, whereas iterating 361 // over path prefixes is only O(P × k) with maximum path depth k. For 362 // large projects both M and P may be very large (note that M ≤ P), but k 363 // will tend to remain smallish (if for no other reason than filesystem 364 // path limitations). 365 // 366 // We perform this iteration either one or two times. If mg is initially nil, 367 // then we first attempt to load the package using only the main module and 368 // its root requirements. If that does not identify the package, or if mg is 369 // already non-nil, then we attempt to load the package using the full 370 // requirements in mg. 371 for { 372 var sumErrMods, altMods []module.Version 373 for prefix := path; prefix != "."; prefix = pathpkg.Dir(prefix) { 374 var ( 375 v string 376 ok bool 377 ) 378 if mg == nil { 379 v, ok = rs.rootSelected(prefix) 380 } else { 381 v, ok = mg.Selected(prefix), true 382 } 383 if !ok || v == "none" { 384 continue 385 } 386 m := module.Version{Path: prefix, Version: v} 387 388 root, isLocal, err := fetch(ctx, m) 389 if err != nil { 390 if sumErr := (*sumMissingError)(nil); errors.As(err, &sumErr) { 391 // We are missing a sum needed to fetch a module in the build list. 392 // We can't verify that the package is unique, and we may not find 393 // the package at all. Keep checking other modules to decide which 394 // error to report. Multiple sums may be missing if we need to look in 395 // multiple nested modules to resolve the import; we'll report them all. 396 sumErrMods = append(sumErrMods, m) 397 continue 398 } 399 // Report fetch error. 400 // Note that we don't know for sure this module is necessary, 401 // but it certainly _could_ provide the package, and even if we 402 // continue the loop and find the package in some other module, 403 // we need to look at this module to make sure the import is 404 // not ambiguous. 405 return module.Version{}, "", "", nil, err 406 } 407 if dir, ok, err := dirInModule(path, m.Path, root, isLocal); err != nil { 408 return module.Version{}, "", "", nil, err 409 } else if ok { 410 mods = append(mods, m) 411 roots = append(roots, root) 412 dirs = append(dirs, dir) 413 } else { 414 altMods = append(altMods, m) 415 } 416 } 417 418 if len(mods) > 1 { 419 // We produce the list of directories from longest to shortest candidate 420 // module path, but the AmbiguousImportError should report them from 421 // shortest to longest. Reverse them now. 422 for i := 0; i < len(mods)/2; i++ { 423 j := len(mods) - 1 - i 424 mods[i], mods[j] = mods[j], mods[i] 425 roots[i], roots[j] = roots[j], roots[i] 426 dirs[i], dirs[j] = dirs[j], dirs[i] 427 } 428 return module.Version{}, "", "", nil, &AmbiguousImportError{importPath: path, Dirs: dirs, Modules: mods} 429 } 430 431 if len(sumErrMods) > 0 { 432 for i := 0; i < len(sumErrMods)/2; i++ { 433 j := len(sumErrMods) - 1 - i 434 sumErrMods[i], sumErrMods[j] = sumErrMods[j], sumErrMods[i] 435 } 436 return module.Version{}, "", "", nil, &ImportMissingSumError{ 437 importPath: path, 438 mods: sumErrMods, 439 found: len(mods) > 0, 440 } 441 } 442 443 if len(mods) == 1 { 444 // We've found the unique module containing the package. 445 // However, in order to actually compile it we need to know what 446 // Go language version to use, which requires its go.mod file. 447 // 448 // If the module graph is pruned and this is a test-only dependency 449 // of a package in "all", we didn't necessarily load that file 450 // when we read the module graph, so do it now to be sure. 451 if !skipModFile && cfg.BuildMod != "vendor" && mods[0].Path != "" && !MainModules.Contains(mods[0].Path) { 452 if _, err := goModSummary(mods[0]); err != nil { 453 return module.Version{}, "", "", nil, err 454 } 455 } 456 return mods[0], roots[0], dirs[0], altMods, nil 457 } 458 459 if mg != nil { 460 // We checked the full module graph and still didn't find the 461 // requested package. 462 var queryErr error 463 if !HasModRoot() { 464 queryErr = ErrNoModRoot 465 } 466 return module.Version{}, "", "", nil, &ImportMissingError{Path: path, QueryErr: queryErr, isStd: pathIsStd} 467 } 468 469 // So far we've checked the root dependencies. 470 // Load the full module graph and try again. 471 mg, err = rs.Graph(ctx) 472 if err != nil { 473 // We might be missing one or more transitive (implicit) dependencies from 474 // the module graph, so we can't return an ImportMissingError here — one 475 // of the missing modules might actually contain the package in question, 476 // in which case we shouldn't go looking for it in some new dependency. 477 return module.Version{}, "", "", nil, err 478 } 479 } 480 } 481 482 // queryImport attempts to locate a module that can be added to the current 483 // build list to provide the package with the given import path. 484 // 485 // Unlike QueryPattern, queryImport prefers to add a replaced version of a 486 // module *before* checking the proxies for a version to add. 487 func queryImport(ctx context.Context, path string, rs *Requirements) (module.Version, error) { 488 // To avoid spurious remote fetches, try the latest replacement for each 489 // module (golang.org/issue/26241). 490 var mods []module.Version 491 if MainModules != nil { // TODO(#48912): Ensure MainModules exists at this point, and remove the check. 492 for mp, mv := range MainModules.HighestReplaced() { 493 if !maybeInModule(path, mp) { 494 continue 495 } 496 if mv == "" { 497 // The only replacement is a wildcard that doesn't specify a version, so 498 // synthesize a pseudo-version with an appropriate major version and a 499 // timestamp below any real timestamp. That way, if the main module is 500 // used from within some other module, the user will be able to upgrade 501 // the requirement to any real version they choose. 502 if _, pathMajor, ok := module.SplitPathVersion(mp); ok && len(pathMajor) > 0 { 503 mv = module.ZeroPseudoVersion(pathMajor[1:]) 504 } else { 505 mv = module.ZeroPseudoVersion("v0") 506 } 507 } 508 mg, err := rs.Graph(ctx) 509 if err != nil { 510 return module.Version{}, err 511 } 512 if cmpVersion(mg.Selected(mp), mv) >= 0 { 513 // We can't resolve the import by adding mp@mv to the module graph, 514 // because the selected version of mp is already at least mv. 515 continue 516 } 517 mods = append(mods, module.Version{Path: mp, Version: mv}) 518 } 519 } 520 521 // Every module path in mods is a prefix of the import path. 522 // As in QueryPattern, prefer the longest prefix that satisfies the import. 523 sort.Slice(mods, func(i, j int) bool { 524 return len(mods[i].Path) > len(mods[j].Path) 525 }) 526 for _, m := range mods { 527 root, isLocal, err := fetch(ctx, m) 528 if err != nil { 529 if sumErr := (*sumMissingError)(nil); errors.As(err, &sumErr) { 530 return module.Version{}, &ImportMissingSumError{importPath: path} 531 } 532 return module.Version{}, err 533 } 534 if _, ok, err := dirInModule(path, m.Path, root, isLocal); err != nil { 535 return m, err 536 } else if ok { 537 if cfg.BuildMod == "readonly" { 538 return module.Version{}, &ImportMissingError{Path: path, replaced: m} 539 } 540 return m, nil 541 } 542 } 543 if len(mods) > 0 && module.CheckPath(path) != nil { 544 // The package path is not valid to fetch remotely, 545 // so it can only exist in a replaced module, 546 // and we know from the above loop that it is not. 547 replacement := Replacement(mods[0]) 548 return module.Version{}, &PackageNotInModuleError{ 549 Mod: mods[0], 550 Query: "latest", 551 Pattern: path, 552 Replacement: replacement, 553 } 554 } 555 556 if search.IsStandardImportPath(path) { 557 // This package isn't in the standard library, isn't in any module already 558 // in the build list, and isn't in any other module that the user has 559 // shimmed in via a "replace" directive. 560 // Moreover, the import path is reserved for the standard library, so 561 // QueryPattern cannot possibly find a module containing this package. 562 // 563 // Instead of trying QueryPattern, report an ImportMissingError immediately. 564 return module.Version{}, &ImportMissingError{Path: path, isStd: true} 565 } 566 567 if cfg.BuildMod == "readonly" && !allowMissingModuleImports { 568 // In readonly mode, we can't write go.mod, so we shouldn't try to look up 569 // the module. If readonly mode was enabled explicitly, include that in 570 // the error message. 571 var queryErr error 572 if cfg.BuildModExplicit { 573 queryErr = fmt.Errorf("import lookup disabled by -mod=%s", cfg.BuildMod) 574 } else if cfg.BuildModReason != "" { 575 queryErr = fmt.Errorf("import lookup disabled by -mod=%s\n\t(%s)", cfg.BuildMod, cfg.BuildModReason) 576 } 577 return module.Version{}, &ImportMissingError{Path: path, QueryErr: queryErr} 578 } 579 580 // Look up module containing the package, for addition to the build list. 581 // Goal is to determine the module, download it to dir, 582 // and return m, dir, ImpportMissingError. 583 fmt.Fprintf(os.Stderr, "go: finding module for package %s\n", path) 584 585 mg, err := rs.Graph(ctx) 586 if err != nil { 587 return module.Version{}, err 588 } 589 590 candidates, err := QueryPackages(ctx, path, "latest", mg.Selected, CheckAllowed) 591 if err != nil { 592 if errors.Is(err, fs.ErrNotExist) { 593 // Return "cannot find module providing package […]" instead of whatever 594 // low-level error QueryPattern produced. 595 return module.Version{}, &ImportMissingError{Path: path, QueryErr: err} 596 } else { 597 return module.Version{}, err 598 } 599 } 600 601 candidate0MissingVersion := "" 602 for i, c := range candidates { 603 if v := mg.Selected(c.Mod.Path); semver.Compare(v, c.Mod.Version) > 0 { 604 // QueryPattern proposed that we add module c.Mod to provide the package, 605 // but we already depend on a newer version of that module (and that 606 // version doesn't have the package). 607 // 608 // This typically happens when a package is present at the "@latest" 609 // version (e.g., v1.0.0) of a module, but we have a newer version 610 // of the same module in the build list (e.g., v1.0.1-beta), and 611 // the package is not present there. 612 if i == 0 { 613 candidate0MissingVersion = v 614 } 615 continue 616 } 617 return c.Mod, nil 618 } 619 return module.Version{}, &ImportMissingError{ 620 Path: path, 621 Module: candidates[0].Mod, 622 newMissingVersion: candidate0MissingVersion, 623 } 624 } 625 626 // maybeInModule reports whether, syntactically, 627 // a package with the given import path could be supplied 628 // by a module with the given module path (mpath). 629 func maybeInModule(path, mpath string) bool { 630 return mpath == path || 631 len(path) > len(mpath) && path[len(mpath)] == '/' && path[:len(mpath)] == mpath 632 } 633 634 var ( 635 haveGoModCache par.Cache // dir → bool 636 haveGoFilesCache par.Cache // dir → goFilesEntry 637 ) 638 639 type goFilesEntry struct { 640 haveGoFiles bool 641 err error 642 } 643 644 // dirInModule locates the directory that would hold the package named by the given path, 645 // if it were in the module with module path mpath and root mdir. 646 // If path is syntactically not within mpath, 647 // or if mdir is a local file tree (isLocal == true) and the directory 648 // that would hold path is in a sub-module (covered by a go.mod below mdir), 649 // dirInModule returns "", false, nil. 650 // 651 // Otherwise, dirInModule returns the name of the directory where 652 // Go source files would be expected, along with a boolean indicating 653 // whether there are in fact Go source files in that directory. 654 // A non-nil error indicates that the existence of the directory and/or 655 // source files could not be determined, for example due to a permission error. 656 func dirInModule(path, mpath, mdir string, isLocal bool) (dir string, haveGoFiles bool, err error) { 657 // Determine where to expect the package. 658 if path == mpath { 659 dir = mdir 660 } else if mpath == "" { // vendor directory 661 dir = filepath.Join(mdir, path) 662 } else if len(path) > len(mpath) && path[len(mpath)] == '/' && path[:len(mpath)] == mpath { 663 dir = filepath.Join(mdir, path[len(mpath)+1:]) 664 } else { 665 return "", false, nil 666 } 667 668 // Check that there aren't other modules in the way. 669 // This check is unnecessary inside the module cache 670 // and important to skip in the vendor directory, 671 // where all the module trees have been overlaid. 672 // So we only check local module trees 673 // (the main module, and any directory trees pointed at by replace directives). 674 if isLocal { 675 for d := dir; d != mdir && len(d) > len(mdir); { 676 haveGoMod := haveGoModCache.Do(d, func() any { 677 fi, err := fsys.Stat(filepath.Join(d, "go.mod")) 678 return err == nil && !fi.IsDir() 679 }).(bool) 680 681 if haveGoMod { 682 return "", false, nil 683 } 684 parent := filepath.Dir(d) 685 if parent == d { 686 // Break the loop, as otherwise we'd loop 687 // forever if d=="." and mdir=="". 688 break 689 } 690 d = parent 691 } 692 } 693 694 // Now committed to returning dir (not ""). 695 696 // Are there Go source files in the directory? 697 // We don't care about build tags, not even "+build ignore". 698 // We're just looking for a plausible directory. 699 res := haveGoFilesCache.Do(dir, func() any { 700 // modindex.GetPackage will return ErrNotIndexed for any directories which 701 // are reached through a symlink, so that they will be handled by 702 // fsys.IsDirWithGoFiles below. 703 if ip, err := modindex.GetPackage(mdir, dir); err == nil { 704 isDirWithGoFiles, err := ip.IsDirWithGoFiles() 705 return goFilesEntry{isDirWithGoFiles, err} 706 } else if !errors.Is(err, modindex.ErrNotIndexed) { 707 return goFilesEntry{err: err} 708 } 709 ok, err := fsys.IsDirWithGoFiles(dir) 710 return goFilesEntry{haveGoFiles: ok, err: err} 711 }).(goFilesEntry) 712 713 return dir, res.haveGoFiles, res.err 714 } 715 716 // fetch downloads the given module (or its replacement) 717 // and returns its location. 718 // 719 // The isLocal return value reports whether the replacement, 720 // if any, is local to the filesystem. 721 func fetch(ctx context.Context, mod module.Version) (dir string, isLocal bool, err error) { 722 if modRoot := MainModules.ModRoot(mod); modRoot != "" { 723 return modRoot, true, nil 724 } 725 if r := Replacement(mod); r.Path != "" { 726 if r.Version == "" { 727 dir = r.Path 728 if !filepath.IsAbs(dir) { 729 dir = filepath.Join(replaceRelativeTo(), dir) 730 } 731 // Ensure that the replacement directory actually exists: 732 // dirInModule does not report errors for missing modules, 733 // so if we don't report the error now, later failures will be 734 // very mysterious. 735 if _, err := fsys.Stat(dir); err != nil { 736 if os.IsNotExist(err) { 737 // Semantically the module version itself “exists” — we just don't 738 // have its source code. Remove the equivalence to os.ErrNotExist, 739 // and make the message more concise while we're at it. 740 err = fmt.Errorf("replacement directory %s does not exist", r.Path) 741 } else { 742 err = fmt.Errorf("replacement directory %s: %w", r.Path, err) 743 } 744 return dir, true, module.VersionError(mod, err) 745 } 746 return dir, true, nil 747 } 748 mod = r 749 } 750 751 if mustHaveSums() && !modfetch.HaveSum(mod) { 752 return "", false, module.VersionError(mod, &sumMissingError{}) 753 } 754 755 dir, err = modfetch.Download(ctx, mod) 756 return dir, false, err 757 } 758 759 // mustHaveSums reports whether we require that all checksums 760 // needed to load or build packages are already present in the go.sum file. 761 func mustHaveSums() bool { 762 return HasModRoot() && cfg.BuildMod == "readonly" && !inWorkspaceMode() 763 } 764 765 type sumMissingError struct { 766 suggestion string 767 } 768 769 func (e *sumMissingError) Error() string { 770 return "missing go.sum entry" + e.suggestion 771 }