1package parser 2 3import ( 4 "bytes" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "os/exec" 9 "path" 10 "path/filepath" 11 "strconv" 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 ( 94 modulePrefix = []byte("\nmodule ") 95 pkgPathFromGoModCache = make(map[string]string) 96) 97 98func getModulePath(goModPath string) string { 99 pkgPath, ok := pkgPathFromGoModCache[goModPath] 100 if ok { 101 return pkgPath 102 } 103 104 defer func() { 105 pkgPathFromGoModCache[goModPath] = pkgPath 106 }() 107 108 data, err := ioutil.ReadFile(goModPath) 109 if err != nil { 110 return "" 111 } 112 var i int 113 if bytes.HasPrefix(data, modulePrefix[1:]) { 114 i = 0 115 } else { 116 i = bytes.Index(data, modulePrefix) 117 if i < 0 { 118 return "" 119 } 120 i++ 121 } 122 line := data[i:] 123 124 // Cut line at \n, drop trailing \r if present. 125 if j := bytes.IndexByte(line, '\n'); j >= 0 { 126 line = line[:j] 127 } 128 if line[len(line)-1] == '\r' { 129 line = line[:len(line)-1] 130 } 131 line = line[len("module "):] 132 133 // If quoted, unquote. 134 pkgPath = strings.TrimSpace(string(line)) 135 if pkgPath != "" && pkgPath[0] == '"' { 136 s, err := strconv.Unquote(pkgPath) 137 if err != nil { 138 return "" 139 } 140 pkgPath = s 141 } 142 return pkgPath 143} 144 145func getPkgPathFromGOPATH(fname string, isDir bool) (string, error) { 146 gopath := os.Getenv("GOPATH") 147 if gopath == "" { 148 var err error 149 gopath, err = getDefaultGoPath() 150 if err != nil { 151 return "", fmt.Errorf("cannot determine GOPATH: %s", err) 152 } 153 } 154 155 for _, p := range strings.Split(gopath, string(filepath.ListSeparator)) { 156 prefix := filepath.Join(p, "src") + string(filepath.Separator) 157 if rel := strings.TrimPrefix(fname, prefix); rel != fname { 158 if !isDir { 159 return path.Dir(filePathToPackagePath(rel)), nil 160 } else { 161 return path.Clean(filePathToPackagePath(rel)), nil 162 } 163 } 164 } 165 166 return "", fmt.Errorf("file '%v' is not in GOPATH", fname) 167} 168 169func filePathToPackagePath(path string) string { 170 return filepath.ToSlash(path) 171} 172