src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

directories.go (3754B)


      1 // Copyright 2024 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 modindex
      6 
      7 import (
      8 	"fmt"
      9 	"log"
     10 	"os"
     11 	"path/filepath"
     12 	"regexp"
     13 	"strings"
     14 	"sync"
     15 	"time"
     16 
     17 	"golang.org/x/mod/semver"
     18 	"golang.org/x/tools/internal/gopathwalk"
     19 )
     20 
     21 type directory struct {
     22 	path       string // relative to GOMODCACHE
     23 	importPath string
     24 	version    string // semantic version
     25 }
     26 
     27 // bestDirByImportPath returns the best directory for each import
     28 // path, where "best" means most recent semantic version. These import
     29 // paths are inferred from the GOMODCACHE-relative dir names in dirs.
     30 func bestDirByImportPath(dirs []string) (map[string]directory, error) {
     31 	dirsByPath := make(map[string]directory)
     32 	for _, dir := range dirs {
     33 		importPath, version, err := dirToImportPathVersion(dir)
     34 		if err != nil {
     35 			return nil, err
     36 		}
     37 		new := directory{
     38 			path:       dir,
     39 			importPath: importPath,
     40 			version:    version,
     41 		}
     42 		if old, ok := dirsByPath[importPath]; !ok || compareDirectory(new, old) < 0 {
     43 			dirsByPath[importPath] = new
     44 		}
     45 	}
     46 	return dirsByPath, nil
     47 }
     48 
     49 // compareDirectory defines an ordering of path@version directories,
     50 // by descending version, then by ascending path.
     51 func compareDirectory(x, y directory) int {
     52 	if sign := -semver.Compare(x.version, y.version); sign != 0 {
     53 		return sign // latest first
     54 	}
     55 	return strings.Compare(string(x.path), string(y.path))
     56 }
     57 
     58 // modCacheRegexp splits a relpathpath into module, module version, and package.
     59 var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`)
     60 
     61 // dirToImportPathVersion computes import path and semantic version
     62 // from a GOMODCACHE-relative directory name.
     63 func dirToImportPathVersion(dir string) (string, string, error) {
     64 	m := modCacheRegexp.FindStringSubmatch(string(dir))
     65 	// m[1] is the module path
     66 	// m[2] is the version major.minor.patch(-<pre release identifier)
     67 	// m[3] is the rest of the package path
     68 	if len(m) != 4 {
     69 		return "", "", fmt.Errorf("bad dir %s", dir)
     70 	}
     71 	if !semver.IsValid(m[2]) {
     72 		return "", "", fmt.Errorf("bad semantic version %s", m[2])
     73 	}
     74 	// ToSlash is required to convert Windows file paths
     75 	// into Go package import paths.
     76 	return filepath.ToSlash(m[1] + m[3]), m[2], nil
     77 }
     78 
     79 // findDirs returns an unordered list of relevant package directories,
     80 // relative to the specified module cache root. The result includes only
     81 // module dirs whose mtime is within (start, end).
     82 func findDirs(root string, start, end time.Time) []string {
     83 	var (
     84 		resMu sync.Mutex
     85 		res   []string
     86 	)
     87 
     88 	addDir := func(root gopathwalk.Root, dir string) {
     89 		// TODO(pjw): do we need to check times?
     90 		resMu.Lock()
     91 		defer resMu.Unlock()
     92 		res = append(res, relative(root.Path, dir))
     93 	}
     94 
     95 	skipDir := func(_ gopathwalk.Root, dir string) bool {
     96 		// The cache directory is already ignored in gopathwalk.
     97 		if filepath.Base(dir) == "internal" {
     98 			return true
     99 		}
    100 
    101 		// Skip toolchains.
    102 		if strings.Contains(dir, "toolchain@") {
    103 			return true
    104 		}
    105 
    106 		// Don't look inside @ directories that are too old/new.
    107 		if strings.Contains(filepath.Base(dir), "@") {
    108 			st, err := os.Stat(dir)
    109 			if err != nil {
    110 				log.Printf("can't stat dir %s %v", dir, err)
    111 				return true
    112 			}
    113 			mtime := st.ModTime()
    114 			return mtime.Before(start) || mtime.After(end)
    115 		}
    116 
    117 		return false
    118 	}
    119 
    120 	// TODO(adonovan): parallelize this. Even with a hot buffer cache,
    121 	//   find $(go env GOMODCACHE) -type d
    122 	// can easily take up a minute.
    123 	roots := []gopathwalk.Root{{Path: root, Type: gopathwalk.RootModuleCache}}
    124 	gopathwalk.WalkSkip(roots, addDir, skipDir, gopathwalk.Options{
    125 		ModulesEnabled: true,
    126 		Concurrency:    1, // TODO(pjw): adjust concurrency
    127 		// Logf: log.Printf,
    128 	})
    129 
    130 	return res
    131 }