package parser import ( "bytes" "fmt" "io/ioutil" "os" "os/exec" "path" "path/filepath" "strconv" "strings" ) func getPkgPath(fname string, isDir bool) (string, error) { if !filepath.IsAbs(fname) { pwd, err := os.Getwd() if err != nil { return "", err } fname = filepath.Join(pwd, fname) } goModPath, _ := goModPath(fname, isDir) if strings.Contains(goModPath, "go.mod") { pkgPath, err := getPkgPathFromGoMod(fname, isDir, goModPath) if err != nil { return "", err } return pkgPath, nil } return getPkgPathFromGOPATH(fname, isDir) } var ( goModPathCache = make(map[string]string) ) // empty if no go.mod, GO111MODULE=off or go without go modules support func goModPath(fname string, isDir bool) (string, error) { root := fname if !isDir { root = filepath.Dir(fname) } goModPath, ok := goModPathCache[root] if ok { return goModPath, nil } defer func() { goModPathCache[root] = goModPath }() cmd := exec.Command("go", "env", "GOMOD") cmd.Dir = root stdout, err := cmd.Output() if err != nil { return "", err } goModPath = string(bytes.TrimSpace(stdout)) return goModPath, nil } func getPkgPathFromGoMod(fname string, isDir bool, goModPath string) (string, error) { modulePath := getModulePath(goModPath) if modulePath == "" { return "", fmt.Errorf("cannot determine module path from %s", goModPath) } rel := path.Join(modulePath, filePathToPackagePath(strings.TrimPrefix(fname, filepath.Dir(goModPath)))) if !isDir { return path.Dir(rel), nil } return path.Clean(rel), nil } var ( modulePrefix = []byte("\nmodule ") pkgPathFromGoModCache = make(map[string]string) ) func getModulePath(goModPath string) string { pkgPath, ok := pkgPathFromGoModCache[goModPath] if ok { return pkgPath } defer func() { pkgPathFromGoModCache[goModPath] = pkgPath }() data, err := ioutil.ReadFile(goModPath) if err != nil { return "" } var i int if bytes.HasPrefix(data, modulePrefix[1:]) { i = 0 } else { i = bytes.Index(data, modulePrefix) if i < 0 { return "" } i++ } line := data[i:] // Cut line at \n, drop trailing \r if present. if j := bytes.IndexByte(line, '\n'); j >= 0 { line = line[:j] } if line[len(line)-1] == '\r' { line = line[:len(line)-1] } line = line[len("module "):] // If quoted, unquote. pkgPath = strings.TrimSpace(string(line)) if pkgPath != "" && pkgPath[0] == '"' { s, err := strconv.Unquote(pkgPath) if err != nil { return "" } pkgPath = s } return pkgPath } func getPkgPathFromGOPATH(fname string, isDir bool) (string, error) { gopath := os.Getenv("GOPATH") if gopath == "" { var err error gopath, err = getDefaultGoPath() if err != nil { return "", fmt.Errorf("cannot determine GOPATH: %s", err) } } for _, p := range strings.Split(gopath, string(filepath.ListSeparator)) { prefix := filepath.Join(p, "src") + string(filepath.Separator) if rel := strings.TrimPrefix(fname, prefix); rel != fname { if !isDir { return path.Dir(filePathToPackagePath(rel)), nil } else { return path.Clean(filePathToPackagePath(rel)), nil } } } return "", fmt.Errorf("file '%v' is not in GOPATH", fname) } func filePathToPackagePath(path string) string { return filepath.ToSlash(path) }