mod.go 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. package imports
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "io/ioutil"
  6. "log"
  7. "os"
  8. "path"
  9. "path/filepath"
  10. "regexp"
  11. "sort"
  12. "strconv"
  13. "strings"
  14. "sync"
  15. "time"
  16. "golang.org/x/tools/internal/gopathwalk"
  17. "golang.org/x/tools/internal/module"
  18. )
  19. // moduleResolver implements resolver for modules using the go command as little
  20. // as feasible.
  21. type moduleResolver struct {
  22. env *fixEnv
  23. initialized bool
  24. main *moduleJSON
  25. modsByModPath []*moduleJSON // All modules, ordered by # of path components in module Path...
  26. modsByDir []*moduleJSON // ...or Dir.
  27. }
  28. type moduleJSON struct {
  29. Path string // module path
  30. Version string // module version
  31. Versions []string // available module versions (with -versions)
  32. Replace *moduleJSON // replaced by this module
  33. Time *time.Time // time version was created
  34. Update *moduleJSON // available update, if any (with -u)
  35. Main bool // is this the main module?
  36. Indirect bool // is this module only an indirect dependency of main module?
  37. Dir string // directory holding files for this module, if any
  38. GoMod string // path to go.mod file for this module, if any
  39. Error *moduleErrorJSON // error loading module
  40. }
  41. type moduleErrorJSON struct {
  42. Err string // the error itself
  43. }
  44. func (r *moduleResolver) init() error {
  45. if r.initialized {
  46. return nil
  47. }
  48. stdout, err := r.env.invokeGo("list", "-m", "-json", "...")
  49. if err != nil {
  50. return err
  51. }
  52. for dec := json.NewDecoder(stdout); dec.More(); {
  53. mod := &moduleJSON{}
  54. if err := dec.Decode(mod); err != nil {
  55. return err
  56. }
  57. if mod.Dir == "" {
  58. if Debug {
  59. log.Printf("module %v has not been downloaded and will be ignored", mod.Path)
  60. }
  61. // Can't do anything with a module that's not downloaded.
  62. continue
  63. }
  64. r.modsByModPath = append(r.modsByModPath, mod)
  65. r.modsByDir = append(r.modsByDir, mod)
  66. if mod.Main {
  67. r.main = mod
  68. }
  69. }
  70. sort.Slice(r.modsByModPath, func(i, j int) bool {
  71. count := func(x int) int {
  72. return strings.Count(r.modsByModPath[x].Path, "/")
  73. }
  74. return count(j) < count(i) // descending order
  75. })
  76. sort.Slice(r.modsByDir, func(i, j int) bool {
  77. count := func(x int) int {
  78. return strings.Count(r.modsByDir[x].Dir, "/")
  79. }
  80. return count(j) < count(i) // descending order
  81. })
  82. r.initialized = true
  83. return nil
  84. }
  85. // findPackage returns the module and directory that contains the package at
  86. // the given import path, or returns nil, "" if no module is in scope.
  87. func (r *moduleResolver) findPackage(importPath string) (*moduleJSON, string) {
  88. for _, m := range r.modsByModPath {
  89. if !strings.HasPrefix(importPath, m.Path) {
  90. continue
  91. }
  92. pathInModule := importPath[len(m.Path):]
  93. pkgDir := filepath.Join(m.Dir, pathInModule)
  94. if dirIsNestedModule(pkgDir, m) {
  95. continue
  96. }
  97. pkgFiles, err := ioutil.ReadDir(pkgDir)
  98. if err != nil {
  99. continue
  100. }
  101. // A module only contains a package if it has buildable go
  102. // files in that directory. If not, it could be provided by an
  103. // outer module. See #29736.
  104. for _, fi := range pkgFiles {
  105. if ok, _ := r.env.buildContext().MatchFile(pkgDir, fi.Name()); ok {
  106. return m, pkgDir
  107. }
  108. }
  109. }
  110. return nil, ""
  111. }
  112. // findModuleByDir returns the module that contains dir, or nil if no such
  113. // module is in scope.
  114. func (r *moduleResolver) findModuleByDir(dir string) *moduleJSON {
  115. // This is quite tricky and may not be correct. dir could be:
  116. // - a package in the main module.
  117. // - a replace target underneath the main module's directory.
  118. // - a nested module in the above.
  119. // - a replace target somewhere totally random.
  120. // - a nested module in the above.
  121. // - in the mod cache.
  122. // - in /vendor/ in -mod=vendor mode.
  123. // - nested module? Dunno.
  124. // Rumor has it that replace targets cannot contain other replace targets.
  125. for _, m := range r.modsByDir {
  126. if !strings.HasPrefix(dir, m.Dir) {
  127. continue
  128. }
  129. if dirIsNestedModule(dir, m) {
  130. continue
  131. }
  132. return m
  133. }
  134. return nil
  135. }
  136. // dirIsNestedModule reports if dir is contained in a nested module underneath
  137. // mod, not actually in mod.
  138. func dirIsNestedModule(dir string, mod *moduleJSON) bool {
  139. if !strings.HasPrefix(dir, mod.Dir) {
  140. return false
  141. }
  142. mf := findModFile(dir)
  143. if mf == "" {
  144. return false
  145. }
  146. return filepath.Dir(mf) != mod.Dir
  147. }
  148. func findModFile(dir string) string {
  149. for {
  150. f := filepath.Join(dir, "go.mod")
  151. info, err := os.Stat(f)
  152. if err == nil && !info.IsDir() {
  153. return f
  154. }
  155. d := filepath.Dir(dir)
  156. if len(d) >= len(dir) {
  157. return "" // reached top of file system, no go.mod
  158. }
  159. dir = d
  160. }
  161. }
  162. func (r *moduleResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) {
  163. if err := r.init(); err != nil {
  164. return nil, err
  165. }
  166. names := map[string]string{}
  167. for _, path := range importPaths {
  168. _, packageDir := r.findPackage(path)
  169. if packageDir == "" {
  170. continue
  171. }
  172. name, err := packageDirToName(packageDir)
  173. if err != nil {
  174. continue
  175. }
  176. names[path] = name
  177. }
  178. return names, nil
  179. }
  180. func (r *moduleResolver) scan(_ references) ([]*pkg, error) {
  181. if err := r.init(); err != nil {
  182. return nil, err
  183. }
  184. // Walk GOROOT, GOPATH/pkg/mod, and the main module.
  185. roots := []gopathwalk.Root{
  186. {filepath.Join(r.env.GOROOT, "/src"), gopathwalk.RootGOROOT},
  187. }
  188. if r.main != nil {
  189. roots = append(roots, gopathwalk.Root{r.main.Dir, gopathwalk.RootCurrentModule})
  190. }
  191. for _, p := range filepath.SplitList(r.env.GOPATH) {
  192. roots = append(roots, gopathwalk.Root{filepath.Join(p, "/pkg/mod"), gopathwalk.RootModuleCache})
  193. }
  194. // Walk replace targets, just in case they're not in any of the above.
  195. for _, mod := range r.modsByModPath {
  196. if mod.Replace != nil {
  197. roots = append(roots, gopathwalk.Root{mod.Dir, gopathwalk.RootOther})
  198. }
  199. }
  200. var result []*pkg
  201. dupCheck := make(map[string]bool)
  202. var mu sync.Mutex
  203. gopathwalk.Walk(roots, func(root gopathwalk.Root, dir string) {
  204. mu.Lock()
  205. defer mu.Unlock()
  206. if _, dup := dupCheck[dir]; dup {
  207. return
  208. }
  209. dupCheck[dir] = true
  210. subdir := ""
  211. if dir != root.Path {
  212. subdir = dir[len(root.Path)+len("/"):]
  213. }
  214. importPath := filepath.ToSlash(subdir)
  215. if strings.HasPrefix(importPath, "vendor/") {
  216. // Ignore vendor dirs. If -mod=vendor is on, then things
  217. // should mostly just work, but when it's not vendor/
  218. // is a mess. There's no easy way to tell if it's on.
  219. // We can still find things in the mod cache and
  220. // map them into /vendor when -mod=vendor is on.
  221. return
  222. }
  223. switch root.Type {
  224. case gopathwalk.RootCurrentModule:
  225. importPath = path.Join(r.main.Path, filepath.ToSlash(subdir))
  226. case gopathwalk.RootModuleCache:
  227. matches := modCacheRegexp.FindStringSubmatch(subdir)
  228. modPath, err := module.DecodePath(filepath.ToSlash(matches[1]))
  229. if err != nil {
  230. if Debug {
  231. log.Printf("decoding module cache path %q: %v", subdir, err)
  232. }
  233. return
  234. }
  235. importPath = path.Join(modPath, filepath.ToSlash(matches[3]))
  236. case gopathwalk.RootGOROOT:
  237. importPath = subdir
  238. }
  239. // Check if the directory is underneath a module that's in scope.
  240. if mod := r.findModuleByDir(dir); mod != nil {
  241. // It is. If dir is the target of a replace directive,
  242. // our guessed import path is wrong. Use the real one.
  243. if mod.Dir == dir {
  244. importPath = mod.Path
  245. } else {
  246. dirInMod := dir[len(mod.Dir)+len("/"):]
  247. importPath = path.Join(mod.Path, filepath.ToSlash(dirInMod))
  248. }
  249. } else {
  250. // The package is in an unknown module. Check that it's
  251. // not obviously impossible to import.
  252. var modFile string
  253. switch root.Type {
  254. case gopathwalk.RootModuleCache:
  255. matches := modCacheRegexp.FindStringSubmatch(subdir)
  256. modFile = filepath.Join(matches[1], "@", matches[2], "go.mod")
  257. default:
  258. modFile = findModFile(dir)
  259. }
  260. modBytes, err := ioutil.ReadFile(modFile)
  261. if err == nil && !strings.HasPrefix(importPath, modulePath(modBytes)) {
  262. // The module's declared path does not match
  263. // its expected path. It probably needs a
  264. // replace directive we don't have.
  265. return
  266. }
  267. }
  268. // We may have discovered a package that has a different version
  269. // in scope already. Canonicalize to that one if possible.
  270. if _, canonicalDir := r.findPackage(importPath); canonicalDir != "" {
  271. dir = canonicalDir
  272. }
  273. result = append(result, &pkg{
  274. importPathShort: VendorlessPath(importPath),
  275. dir: dir,
  276. })
  277. }, gopathwalk.Options{Debug: Debug, ModulesEnabled: true})
  278. return result, nil
  279. }
  280. // modCacheRegexp splits a path in a module cache into module, module version, and package.
  281. var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`)
  282. var (
  283. slashSlash = []byte("//")
  284. moduleStr = []byte("module")
  285. )
  286. // modulePath returns the module path from the gomod file text.
  287. // If it cannot find a module path, it returns an empty string.
  288. // It is tolerant of unrelated problems in the go.mod file.
  289. //
  290. // Copied from cmd/go/internal/modfile.
  291. func modulePath(mod []byte) string {
  292. for len(mod) > 0 {
  293. line := mod
  294. mod = nil
  295. if i := bytes.IndexByte(line, '\n'); i >= 0 {
  296. line, mod = line[:i], line[i+1:]
  297. }
  298. if i := bytes.Index(line, slashSlash); i >= 0 {
  299. line = line[:i]
  300. }
  301. line = bytes.TrimSpace(line)
  302. if !bytes.HasPrefix(line, moduleStr) {
  303. continue
  304. }
  305. line = line[len(moduleStr):]
  306. n := len(line)
  307. line = bytes.TrimSpace(line)
  308. if len(line) == n || len(line) == 0 {
  309. continue
  310. }
  311. if line[0] == '"' || line[0] == '`' {
  312. p, err := strconv.Unquote(string(line))
  313. if err != nil {
  314. return "" // malformed quoted string or multiline module path
  315. }
  316. return p
  317. }
  318. return string(line)
  319. }
  320. return "" // missing module path
  321. }