1package parser 2 3import ( 4 "bytes" 5 "fmt" 6 "go/build" 7 "io/ioutil" 8 "os" 9 "os/exec" 10 "path" 11 "path/filepath" 12 "strings" 13 "sync" 14) 15 16func getPkgPath(fname string, isDir bool) (string, error) { 17 if !filepath.IsAbs(fname) { 18 pwd, err := os.Getwd() 19 if err != nil { 20 return "", err 21 } 22 fname = filepath.Join(pwd, fname) 23 } 24 25 goModPath, _ := goModPath(fname, isDir) 26 if strings.Contains(goModPath, "go.mod") { 27 pkgPath, err := getPkgPathFromGoMod(fname, isDir, goModPath) 28 if err != nil { 29 return "", err 30 } 31 32 return pkgPath, nil 33 } 34 35 return getPkgPathFromGOPATH(fname, isDir) 36} 37 38var goModPathCache = struct { 39 paths map[string]string 40 sync.RWMutex 41}{ 42 paths: make(map[string]string), 43} 44 45// empty if no go.mod, GO111MODULE=off or go without go modules support 46func goModPath(fname string, isDir bool) (string, error) { 47 root := fname 48 if !isDir { 49 root = filepath.Dir(fname) 50 } 51 52 goModPathCache.RLock() 53 goModPath, ok := goModPathCache.paths[root] 54 goModPathCache.RUnlock() 55 if ok { 56 return goModPath, nil 57 } 58 59 defer func() { 60 goModPathCache.Lock() 61 goModPathCache.paths[root] = goModPath 62 goModPathCache.Unlock() 63 }() 64 65 cmd := exec.Command("go", "env", "GOMOD") 66 cmd.Dir = root 67 68 stdout, err := cmd.Output() 69 if err != nil { 70 return "", err 71 } 72 73 goModPath = string(bytes.TrimSpace(stdout)) 74 75 return goModPath, nil 76} 77 78func getPkgPathFromGoMod(fname string, isDir bool, goModPath string) (string, error) { 79 modulePath := getModulePath(goModPath) 80 if modulePath == "" { 81 return "", fmt.Errorf("cannot determine module path from %s", goModPath) 82 } 83 84 rel := path.Join(modulePath, filePathToPackagePath(strings.TrimPrefix(fname, filepath.Dir(goModPath)))) 85 86 if !isDir { 87 return path.Dir(rel), nil 88 } 89 90 return path.Clean(rel), nil 91} 92 93var pkgPathFromGoModCache = struct { 94 paths map[string]string 95 sync.RWMutex 96}{ 97 paths: make(map[string]string), 98} 99 100func getModulePath(goModPath string) string { 101 pkgPathFromGoModCache.RLock() 102 pkgPath, ok := pkgPathFromGoModCache.paths[goModPath] 103 pkgPathFromGoModCache.RUnlock() 104 if ok { 105 return pkgPath 106 } 107 108 defer func() { 109 pkgPathFromGoModCache.Lock() 110 pkgPathFromGoModCache.paths[goModPath] = pkgPath 111 pkgPathFromGoModCache.Unlock() 112 }() 113 114 data, err := ioutil.ReadFile(goModPath) 115 if err != nil { 116 return "" 117 } 118 pkgPath = modulePath(data) 119 return pkgPath 120} 121 122func getPkgPathFromGOPATH(fname string, isDir bool) (string, error) { 123 gopath := os.Getenv("GOPATH") 124 if gopath == "" { 125 gopath = build.Default.GOPATH 126 } 127 128 for _, p := range strings.Split(gopath, string(filepath.ListSeparator)) { 129 prefix := filepath.Join(p, "src") + string(filepath.Separator) 130 rel, err := filepath.Rel(prefix, fname) 131 if err == nil && !strings.HasPrefix(rel, ".."+string(filepath.Separator)) { 132 if !isDir { 133 return path.Dir(filePathToPackagePath(rel)), nil 134 } else { 135 return path.Clean(filePathToPackagePath(rel)), nil 136 } 137 } 138 } 139 140 return "", fmt.Errorf("file '%v' is not in GOPATH '%v'", fname, gopath) 141} 142 143func filePathToPackagePath(path string) string { 144 return filepath.ToSlash(path) 145} 146