github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/internal/imports/mod.go (about) 1 // Copyright 2019 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 imports 6 7 import ( 8 "bytes" 9 "context" 10 "encoding/json" 11 "fmt" 12 "io/ioutil" 13 "os" 14 "path" 15 "path/filepath" 16 "regexp" 17 "sort" 18 "strconv" 19 "strings" 20 21 "golang.org/x/mod/module" 22 "golang.org/x/tools/internal/gocommand" 23 "golang.org/x/tools/internal/gopathwalk" 24 ) 25 26 // ModuleResolver implements resolver for modules using the go command as little 27 // as feasible. 28 type ModuleResolver struct { 29 env *ProcessEnv 30 moduleCacheDir string 31 dummyVendorMod *gocommand.ModuleJSON // If vendoring is enabled, the pseudo-module that represents the /vendor directory. 32 roots []gopathwalk.Root 33 scanSema chan struct{} // scanSema prevents concurrent scans and guards scannedRoots. 34 scannedRoots map[gopathwalk.Root]bool 35 36 initialized bool 37 mains []*gocommand.ModuleJSON 38 mainByDir map[string]*gocommand.ModuleJSON 39 modsByModPath []*gocommand.ModuleJSON // All modules, ordered by # of path components in module Path... 40 modsByDir []*gocommand.ModuleJSON // ...or Dir. 41 42 // moduleCacheCache stores information about the module cache. 43 moduleCacheCache *dirInfoCache 44 otherCache *dirInfoCache 45 } 46 47 func newModuleResolver(e *ProcessEnv) *ModuleResolver { 48 r := &ModuleResolver{ 49 env: e, 50 scanSema: make(chan struct{}, 1), 51 } 52 r.scanSema <- struct{}{} 53 return r 54 } 55 56 func (r *ModuleResolver) init() error { 57 if r.initialized { 58 return nil 59 } 60 61 goenv, err := r.env.goEnv() 62 if err != nil { 63 return err 64 } 65 inv := gocommand.Invocation{ 66 BuildFlags: r.env.BuildFlags, 67 ModFlag: r.env.ModFlag, 68 ModFile: r.env.ModFile, 69 Env: r.env.env(), 70 Logf: r.env.Logf, 71 WorkingDir: r.env.WorkingDir, 72 } 73 74 vendorEnabled := false 75 var mainModVendor *gocommand.ModuleJSON 76 77 // Module vendor directories are ignored in workspace mode: 78 // https://go.googlesource.com/proposal/+/master/design/45713-workspace.md 79 if len(r.env.Env["GOWORK"]) == 0 { 80 vendorEnabled, mainModVendor, err = gocommand.VendorEnabled(context.TODO(), inv, r.env.GocmdRunner) 81 if err != nil { 82 return err 83 } 84 } 85 86 if mainModVendor != nil && vendorEnabled { 87 // Vendor mode is on, so all the non-Main modules are irrelevant, 88 // and we need to search /vendor for everything. 89 r.mains = []*gocommand.ModuleJSON{mainModVendor} 90 r.dummyVendorMod = &gocommand.ModuleJSON{ 91 Path: "", 92 Dir: filepath.Join(mainModVendor.Dir, "vendor"), 93 } 94 r.modsByModPath = []*gocommand.ModuleJSON{mainModVendor, r.dummyVendorMod} 95 r.modsByDir = []*gocommand.ModuleJSON{mainModVendor, r.dummyVendorMod} 96 } else { 97 // Vendor mode is off, so run go list -m ... to find everything. 98 err := r.initAllMods() 99 // We expect an error when running outside of a module with 100 // GO111MODULE=on. Other errors are fatal. 101 if err != nil { 102 if errMsg := err.Error(); !strings.Contains(errMsg, "working directory is not part of a module") && !strings.Contains(errMsg, "go.mod file not found") { 103 return err 104 } 105 } 106 } 107 108 if gmc := r.env.Env["GOMODCACHE"]; gmc != "" { 109 r.moduleCacheDir = gmc 110 } else { 111 gopaths := filepath.SplitList(goenv["GOPATH"]) 112 if len(gopaths) == 0 { 113 return fmt.Errorf("empty GOPATH") 114 } 115 r.moduleCacheDir = filepath.Join(gopaths[0], "/pkg/mod") 116 } 117 118 sort.Slice(r.modsByModPath, func(i, j int) bool { 119 count := func(x int) int { 120 return strings.Count(r.modsByModPath[x].Path, "/") 121 } 122 return count(j) < count(i) // descending order 123 }) 124 sort.Slice(r.modsByDir, func(i, j int) bool { 125 count := func(x int) int { 126 return strings.Count(r.modsByDir[x].Dir, "/") 127 } 128 return count(j) < count(i) // descending order 129 }) 130 131 r.roots = []gopathwalk.Root{ 132 {Path: filepath.Join(goenv["GOROOT"], "/src"), Type: gopathwalk.RootGOROOT}, 133 } 134 r.mainByDir = make(map[string]*gocommand.ModuleJSON) 135 for _, main := range r.mains { 136 r.roots = append(r.roots, gopathwalk.Root{Path: main.Dir, Type: gopathwalk.RootCurrentModule}) 137 r.mainByDir[main.Dir] = main 138 } 139 if vendorEnabled { 140 r.roots = append(r.roots, gopathwalk.Root{Path: r.dummyVendorMod.Dir, Type: gopathwalk.RootOther}) 141 } else { 142 addDep := func(mod *gocommand.ModuleJSON) { 143 if mod.Replace == nil { 144 // This is redundant with the cache, but we'll skip it cheaply enough. 145 r.roots = append(r.roots, gopathwalk.Root{Path: mod.Dir, Type: gopathwalk.RootModuleCache}) 146 } else { 147 r.roots = append(r.roots, gopathwalk.Root{Path: mod.Dir, Type: gopathwalk.RootOther}) 148 } 149 } 150 // Walk dependent modules before scanning the full mod cache, direct deps first. 151 for _, mod := range r.modsByModPath { 152 if !mod.Indirect && !mod.Main { 153 addDep(mod) 154 } 155 } 156 for _, mod := range r.modsByModPath { 157 if mod.Indirect && !mod.Main { 158 addDep(mod) 159 } 160 } 161 r.roots = append(r.roots, gopathwalk.Root{Path: r.moduleCacheDir, Type: gopathwalk.RootModuleCache}) 162 } 163 164 r.scannedRoots = map[gopathwalk.Root]bool{} 165 if r.moduleCacheCache == nil { 166 r.moduleCacheCache = &dirInfoCache{ 167 dirs: map[string]*directoryPackageInfo{}, 168 listeners: map[*int]cacheListener{}, 169 } 170 } 171 if r.otherCache == nil { 172 r.otherCache = &dirInfoCache{ 173 dirs: map[string]*directoryPackageInfo{}, 174 listeners: map[*int]cacheListener{}, 175 } 176 } 177 r.initialized = true 178 return nil 179 } 180 181 func (r *ModuleResolver) initAllMods() error { 182 stdout, err := r.env.invokeGo(context.TODO(), "list", "-m", "-e", "-json", "...") 183 if err != nil { 184 return err 185 } 186 for dec := json.NewDecoder(stdout); dec.More(); { 187 mod := &gocommand.ModuleJSON{} 188 if err := dec.Decode(mod); err != nil { 189 return err 190 } 191 if mod.Dir == "" { 192 if r.env.Logf != nil { 193 r.env.Logf("module %v has not been downloaded and will be ignored", mod.Path) 194 } 195 // Can't do anything with a module that's not downloaded. 196 continue 197 } 198 // golang/go#36193: the go command doesn't always clean paths. 199 mod.Dir = filepath.Clean(mod.Dir) 200 r.modsByModPath = append(r.modsByModPath, mod) 201 r.modsByDir = append(r.modsByDir, mod) 202 if mod.Main { 203 r.mains = append(r.mains, mod) 204 } 205 } 206 return nil 207 } 208 209 func (r *ModuleResolver) ClearForNewScan() { 210 <-r.scanSema 211 r.scannedRoots = map[gopathwalk.Root]bool{} 212 r.otherCache = &dirInfoCache{ 213 dirs: map[string]*directoryPackageInfo{}, 214 listeners: map[*int]cacheListener{}, 215 } 216 r.scanSema <- struct{}{} 217 } 218 219 func (r *ModuleResolver) ClearForNewMod() { 220 <-r.scanSema 221 *r = ModuleResolver{ 222 env: r.env, 223 moduleCacheCache: r.moduleCacheCache, 224 otherCache: r.otherCache, 225 scanSema: r.scanSema, 226 } 227 r.init() 228 r.scanSema <- struct{}{} 229 } 230 231 // findPackage returns the module and directory that contains the package at 232 // the given import path, or returns nil, "" if no module is in scope. 233 func (r *ModuleResolver) findPackage(importPath string) (*gocommand.ModuleJSON, string) { 234 // This can't find packages in the stdlib, but that's harmless for all 235 // the existing code paths. 236 for _, m := range r.modsByModPath { 237 if !strings.HasPrefix(importPath, m.Path) { 238 continue 239 } 240 pathInModule := importPath[len(m.Path):] 241 pkgDir := filepath.Join(m.Dir, pathInModule) 242 if r.dirIsNestedModule(pkgDir, m) { 243 continue 244 } 245 246 if info, ok := r.cacheLoad(pkgDir); ok { 247 if loaded, err := info.reachedStatus(nameLoaded); loaded { 248 if err != nil { 249 continue // No package in this dir. 250 } 251 return m, pkgDir 252 } 253 if scanned, err := info.reachedStatus(directoryScanned); scanned && err != nil { 254 continue // Dir is unreadable, etc. 255 } 256 // This is slightly wrong: a directory doesn't have to have an 257 // importable package to count as a package for package-to-module 258 // resolution. package main or _test files should count but 259 // don't. 260 // TODO(heschi): fix this. 261 if _, err := r.cachePackageName(info); err == nil { 262 return m, pkgDir 263 } 264 } 265 266 // Not cached. Read the filesystem. 267 pkgFiles, err := ioutil.ReadDir(pkgDir) 268 if err != nil { 269 continue 270 } 271 // A module only contains a package if it has buildable go 272 // files in that directory. If not, it could be provided by an 273 // outer module. See #29736. 274 for _, fi := range pkgFiles { 275 if ok, _ := r.env.matchFile(pkgDir, fi.Name()); ok { 276 return m, pkgDir 277 } 278 } 279 } 280 return nil, "" 281 } 282 283 func (r *ModuleResolver) cacheLoad(dir string) (directoryPackageInfo, bool) { 284 if info, ok := r.moduleCacheCache.Load(dir); ok { 285 return info, ok 286 } 287 return r.otherCache.Load(dir) 288 } 289 290 func (r *ModuleResolver) cacheStore(info directoryPackageInfo) { 291 if info.rootType == gopathwalk.RootModuleCache { 292 r.moduleCacheCache.Store(info.dir, info) 293 } else { 294 r.otherCache.Store(info.dir, info) 295 } 296 } 297 298 func (r *ModuleResolver) cacheKeys() []string { 299 return append(r.moduleCacheCache.Keys(), r.otherCache.Keys()...) 300 } 301 302 // cachePackageName caches the package name for a dir already in the cache. 303 func (r *ModuleResolver) cachePackageName(info directoryPackageInfo) (string, error) { 304 if info.rootType == gopathwalk.RootModuleCache { 305 return r.moduleCacheCache.CachePackageName(info) 306 } 307 return r.otherCache.CachePackageName(info) 308 } 309 310 func (r *ModuleResolver) cacheExports(ctx context.Context, env *ProcessEnv, info directoryPackageInfo) (string, []string, error) { 311 if info.rootType == gopathwalk.RootModuleCache { 312 return r.moduleCacheCache.CacheExports(ctx, env, info) 313 } 314 return r.otherCache.CacheExports(ctx, env, info) 315 } 316 317 // findModuleByDir returns the module that contains dir, or nil if no such 318 // module is in scope. 319 func (r *ModuleResolver) findModuleByDir(dir string) *gocommand.ModuleJSON { 320 // This is quite tricky and may not be correct. dir could be: 321 // - a package in the main module. 322 // - a replace target underneath the main module's directory. 323 // - a nested module in the above. 324 // - a replace target somewhere totally random. 325 // - a nested module in the above. 326 // - in the mod cache. 327 // - in /vendor/ in -mod=vendor mode. 328 // - nested module? Dunno. 329 // Rumor has it that replace targets cannot contain other replace targets. 330 for _, m := range r.modsByDir { 331 if !strings.HasPrefix(dir, m.Dir) { 332 continue 333 } 334 335 if r.dirIsNestedModule(dir, m) { 336 continue 337 } 338 339 return m 340 } 341 return nil 342 } 343 344 // dirIsNestedModule reports if dir is contained in a nested module underneath 345 // mod, not actually in mod. 346 func (r *ModuleResolver) dirIsNestedModule(dir string, mod *gocommand.ModuleJSON) bool { 347 if !strings.HasPrefix(dir, mod.Dir) { 348 return false 349 } 350 if r.dirInModuleCache(dir) { 351 // Nested modules in the module cache are pruned, 352 // so it cannot be a nested module. 353 return false 354 } 355 if mod != nil && mod == r.dummyVendorMod { 356 // The /vendor pseudomodule is flattened and doesn't actually count. 357 return false 358 } 359 modDir, _ := r.modInfo(dir) 360 if modDir == "" { 361 return false 362 } 363 return modDir != mod.Dir 364 } 365 366 func (r *ModuleResolver) modInfo(dir string) (modDir string, modName string) { 367 readModName := func(modFile string) string { 368 modBytes, err := ioutil.ReadFile(modFile) 369 if err != nil { 370 return "" 371 } 372 return modulePath(modBytes) 373 } 374 375 if r.dirInModuleCache(dir) { 376 if matches := modCacheRegexp.FindStringSubmatch(dir); len(matches) == 3 { 377 index := strings.Index(dir, matches[1]+"@"+matches[2]) 378 modDir := filepath.Join(dir[:index], matches[1]+"@"+matches[2]) 379 return modDir, readModName(filepath.Join(modDir, "go.mod")) 380 } 381 } 382 for { 383 if info, ok := r.cacheLoad(dir); ok { 384 return info.moduleDir, info.moduleName 385 } 386 f := filepath.Join(dir, "go.mod") 387 info, err := os.Stat(f) 388 if err == nil && !info.IsDir() { 389 return dir, readModName(f) 390 } 391 392 d := filepath.Dir(dir) 393 if len(d) >= len(dir) { 394 return "", "" // reached top of file system, no go.mod 395 } 396 dir = d 397 } 398 } 399 400 func (r *ModuleResolver) dirInModuleCache(dir string) bool { 401 if r.moduleCacheDir == "" { 402 return false 403 } 404 return strings.HasPrefix(dir, r.moduleCacheDir) 405 } 406 407 func (r *ModuleResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) { 408 if err := r.init(); err != nil { 409 return nil, err 410 } 411 names := map[string]string{} 412 for _, path := range importPaths { 413 _, packageDir := r.findPackage(path) 414 if packageDir == "" { 415 continue 416 } 417 name, err := packageDirToName(packageDir) 418 if err != nil { 419 continue 420 } 421 names[path] = name 422 } 423 return names, nil 424 } 425 426 func (r *ModuleResolver) scan(ctx context.Context, callback *scanCallback) error { 427 if err := r.init(); err != nil { 428 return err 429 } 430 431 processDir := func(info directoryPackageInfo) { 432 // Skip this directory if we were not able to get the package information successfully. 433 if scanned, err := info.reachedStatus(directoryScanned); !scanned || err != nil { 434 return 435 } 436 pkg, err := r.canonicalize(info) 437 if err != nil { 438 return 439 } 440 441 if !callback.dirFound(pkg) { 442 return 443 } 444 pkg.packageName, err = r.cachePackageName(info) 445 if err != nil { 446 return 447 } 448 449 if !callback.packageNameLoaded(pkg) { 450 return 451 } 452 _, exports, err := r.loadExports(ctx, pkg, false) 453 if err != nil { 454 return 455 } 456 callback.exportsLoaded(pkg, exports) 457 } 458 459 // Start processing everything in the cache, and listen for the new stuff 460 // we discover in the walk below. 461 stop1 := r.moduleCacheCache.ScanAndListen(ctx, processDir) 462 defer stop1() 463 stop2 := r.otherCache.ScanAndListen(ctx, processDir) 464 defer stop2() 465 466 // We assume cached directories are fully cached, including all their 467 // children, and have not changed. We can skip them. 468 skip := func(root gopathwalk.Root, dir string) bool { 469 if r.env.SkipPathInScan != nil && root.Type == gopathwalk.RootCurrentModule { 470 if root.Path == dir { 471 return false 472 } 473 474 if r.env.SkipPathInScan(filepath.Clean(dir)) { 475 return true 476 } 477 } 478 479 info, ok := r.cacheLoad(dir) 480 if !ok { 481 return false 482 } 483 // This directory can be skipped as long as we have already scanned it. 484 // Packages with errors will continue to have errors, so there is no need 485 // to rescan them. 486 packageScanned, _ := info.reachedStatus(directoryScanned) 487 return packageScanned 488 } 489 490 // Add anything new to the cache, and process it if we're still listening. 491 add := func(root gopathwalk.Root, dir string) { 492 r.cacheStore(r.scanDirForPackage(root, dir)) 493 } 494 495 // r.roots and the callback are not necessarily safe to use in the 496 // goroutine below. Process them eagerly. 497 roots := filterRoots(r.roots, callback.rootFound) 498 // We can't cancel walks, because we need them to finish to have a usable 499 // cache. Instead, run them in a separate goroutine and detach. 500 scanDone := make(chan struct{}) 501 go func() { 502 select { 503 case <-ctx.Done(): 504 return 505 case <-r.scanSema: 506 } 507 defer func() { r.scanSema <- struct{}{} }() 508 // We have the lock on r.scannedRoots, and no other scans can run. 509 for _, root := range roots { 510 if ctx.Err() != nil { 511 return 512 } 513 514 if r.scannedRoots[root] { 515 continue 516 } 517 gopathwalk.WalkSkip([]gopathwalk.Root{root}, add, skip, gopathwalk.Options{Logf: r.env.Logf, ModulesEnabled: true}) 518 r.scannedRoots[root] = true 519 } 520 close(scanDone) 521 }() 522 select { 523 case <-ctx.Done(): 524 case <-scanDone: 525 } 526 return nil 527 } 528 529 func (r *ModuleResolver) scoreImportPath(ctx context.Context, path string) float64 { 530 if _, ok := stdlib[path]; ok { 531 return MaxRelevance 532 } 533 mod, _ := r.findPackage(path) 534 return modRelevance(mod) 535 } 536 537 func modRelevance(mod *gocommand.ModuleJSON) float64 { 538 var relevance float64 539 switch { 540 case mod == nil: // out of scope 541 return MaxRelevance - 4 542 case mod.Indirect: 543 relevance = MaxRelevance - 3 544 case !mod.Main: 545 relevance = MaxRelevance - 2 546 default: 547 relevance = MaxRelevance - 1 // main module ties with stdlib 548 } 549 550 _, versionString, ok := module.SplitPathVersion(mod.Path) 551 if ok { 552 index := strings.Index(versionString, "v") 553 if index == -1 { 554 return relevance 555 } 556 if versionNumber, err := strconv.ParseFloat(versionString[index+1:], 64); err == nil { 557 relevance += versionNumber / 1000 558 } 559 } 560 561 return relevance 562 } 563 564 // canonicalize gets the result of canonicalizing the packages using the results 565 // of initializing the resolver from 'go list -m'. 566 func (r *ModuleResolver) canonicalize(info directoryPackageInfo) (*pkg, error) { 567 // Packages in GOROOT are already canonical, regardless of the std/cmd modules. 568 if info.rootType == gopathwalk.RootGOROOT { 569 return &pkg{ 570 importPathShort: info.nonCanonicalImportPath, 571 dir: info.dir, 572 packageName: path.Base(info.nonCanonicalImportPath), 573 relevance: MaxRelevance, 574 }, nil 575 } 576 577 importPath := info.nonCanonicalImportPath 578 mod := r.findModuleByDir(info.dir) 579 // Check if the directory is underneath a module that's in scope. 580 if mod != nil { 581 // It is. If dir is the target of a replace directive, 582 // our guessed import path is wrong. Use the real one. 583 if mod.Dir == info.dir { 584 importPath = mod.Path 585 } else { 586 dirInMod := info.dir[len(mod.Dir)+len("/"):] 587 importPath = path.Join(mod.Path, filepath.ToSlash(dirInMod)) 588 } 589 } else if !strings.HasPrefix(importPath, info.moduleName) { 590 // The module's name doesn't match the package's import path. It 591 // probably needs a replace directive we don't have. 592 return nil, fmt.Errorf("package in %q is not valid without a replace statement", info.dir) 593 } 594 595 res := &pkg{ 596 importPathShort: importPath, 597 dir: info.dir, 598 relevance: modRelevance(mod), 599 } 600 // We may have discovered a package that has a different version 601 // in scope already. Canonicalize to that one if possible. 602 if _, canonicalDir := r.findPackage(importPath); canonicalDir != "" { 603 res.dir = canonicalDir 604 } 605 return res, nil 606 } 607 608 func (r *ModuleResolver) loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []string, error) { 609 if err := r.init(); err != nil { 610 return "", nil, err 611 } 612 if info, ok := r.cacheLoad(pkg.dir); ok && !includeTest { 613 return r.cacheExports(ctx, r.env, info) 614 } 615 return loadExportsFromFiles(ctx, r.env, pkg.dir, includeTest) 616 } 617 618 func (r *ModuleResolver) scanDirForPackage(root gopathwalk.Root, dir string) directoryPackageInfo { 619 subdir := "" 620 if dir != root.Path { 621 subdir = dir[len(root.Path)+len("/"):] 622 } 623 importPath := filepath.ToSlash(subdir) 624 if strings.HasPrefix(importPath, "vendor/") { 625 // Only enter vendor directories if they're explicitly requested as a root. 626 return directoryPackageInfo{ 627 status: directoryScanned, 628 err: fmt.Errorf("unwanted vendor directory"), 629 } 630 } 631 switch root.Type { 632 case gopathwalk.RootCurrentModule: 633 importPath = path.Join(r.mainByDir[root.Path].Path, filepath.ToSlash(subdir)) 634 case gopathwalk.RootModuleCache: 635 matches := modCacheRegexp.FindStringSubmatch(subdir) 636 if len(matches) == 0 { 637 return directoryPackageInfo{ 638 status: directoryScanned, 639 err: fmt.Errorf("invalid module cache path: %v", subdir), 640 } 641 } 642 modPath, err := module.UnescapePath(filepath.ToSlash(matches[1])) 643 if err != nil { 644 if r.env.Logf != nil { 645 r.env.Logf("decoding module cache path %q: %v", subdir, err) 646 } 647 return directoryPackageInfo{ 648 status: directoryScanned, 649 err: fmt.Errorf("decoding module cache path %q: %v", subdir, err), 650 } 651 } 652 importPath = path.Join(modPath, filepath.ToSlash(matches[3])) 653 } 654 655 modDir, modName := r.modInfo(dir) 656 result := directoryPackageInfo{ 657 status: directoryScanned, 658 dir: dir, 659 rootType: root.Type, 660 nonCanonicalImportPath: importPath, 661 moduleDir: modDir, 662 moduleName: modName, 663 } 664 if root.Type == gopathwalk.RootGOROOT { 665 // stdlib packages are always in scope, despite the confusing go.mod 666 return result 667 } 668 return result 669 } 670 671 // modCacheRegexp splits a path in a module cache into module, module version, and package. 672 var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`) 673 674 var ( 675 slashSlash = []byte("//") 676 moduleStr = []byte("module") 677 ) 678 679 // modulePath returns the module path from the gomod file text. 680 // If it cannot find a module path, it returns an empty string. 681 // It is tolerant of unrelated problems in the go.mod file. 682 // 683 // Copied from cmd/go/internal/modfile. 684 func modulePath(mod []byte) string { 685 for len(mod) > 0 { 686 line := mod 687 mod = nil 688 if i := bytes.IndexByte(line, '\n'); i >= 0 { 689 line, mod = line[:i], line[i+1:] 690 } 691 if i := bytes.Index(line, slashSlash); i >= 0 { 692 line = line[:i] 693 } 694 line = bytes.TrimSpace(line) 695 if !bytes.HasPrefix(line, moduleStr) { 696 continue 697 } 698 line = line[len(moduleStr):] 699 n := len(line) 700 line = bytes.TrimSpace(line) 701 if len(line) == n || len(line) == 0 { 702 continue 703 } 704 705 if line[0] == '"' || line[0] == '`' { 706 p, err := strconv.Unquote(string(line)) 707 if err != nil { 708 return "" // malformed quoted string or multiline module path 709 } 710 return p 711 } 712 713 return string(line) 714 } 715 return "" // missing module path 716 }