From e0ec68febce18f9935d120a318a98d192d6dfee6 Mon Sep 17 00:00:00 2001 From: Aleksandr Zelenin Date: Thu, 23 Aug 2018 03:47:26 +0300 Subject: [PATCH] go modules support --- bootstrap/bootstrap.go | 1 + parser/parser.go | 3 +- parser/parser_unix.go | 42 ---------- parser/parser_windows.go | 48 ------------ parser/pkgpath.go | 163 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 166 insertions(+), 91 deletions(-) delete mode 100644 parser/parser_unix.go delete mode 100644 parser/parser_windows.go create mode 100644 parser/pkgpath.go diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go index 95e5d1e..afb4d3f 100644 --- a/bootstrap/bootstrap.go +++ b/bootstrap/bootstrap.go @@ -172,6 +172,7 @@ func (g *Generator) Run() error { cmd := exec.Command("go", "run", "-tags", g.BuildTags, path) cmd.Stdout = f cmd.Stderr = os.Stderr + cmd.Dir = filepath.Dir(path) if err = cmd.Run(); err != nil { return err } diff --git a/parser/parser.go b/parser/parser.go index babb84c..3639ed0 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -1,6 +1,7 @@ package parser import ( + "bytes" "go/ast" "go/parser" "go/token" @@ -93,5 +94,5 @@ func (p *Parser) Parse(fname string, isDir bool) error { func getDefaultGoPath() (string, error) { output, err := exec.Command("go", "env", "GOPATH").Output() - return strings.TrimSpace(string(output)), err + return string(bytes.TrimSpace(output)), err } diff --git a/parser/parser_unix.go b/parser/parser_unix.go deleted file mode 100644 index cc0686e..0000000 --- a/parser/parser_unix.go +++ /dev/null @@ -1,42 +0,0 @@ -// +build !windows - -package parser - -import ( - "fmt" - "os" - "path" - "strings" -) - -func getPkgPath(fname string, isDir bool) (string, error) { - if !path.IsAbs(fname) { - pwd, err := os.Getwd() - if err != nil { - return "", err - } - fname = path.Join(pwd, fname) - } - - 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, ":") { - prefix := path.Join(p, "src") + "/" - if rel := strings.TrimPrefix(fname, prefix); rel != fname { - if !isDir { - return path.Dir(rel), nil - } else { - return path.Clean(rel), nil - } - } - } - - return "", fmt.Errorf("file '%v' is not in GOPATH", fname) -} diff --git a/parser/parser_windows.go b/parser/parser_windows.go deleted file mode 100644 index 73da3ee..0000000 --- a/parser/parser_windows.go +++ /dev/null @@ -1,48 +0,0 @@ -package parser - -import ( - "fmt" - "os" - "path" - "path/filepath" - "strings" -) - -func normalizePath(path string) string { - return strings.Replace(path, "\\", "/", -1) -} - -func getPkgPath(fname string, isDir bool) (string, error) { - // path.IsAbs doesn't work properly on Windows; use filepath.IsAbs instead - if !filepath.IsAbs(fname) { - pwd, err := os.Getwd() - if err != nil { - return "", err - } - fname = path.Join(pwd, fname) - } - - fname = normalizePath(fname) - - 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, ";") { - prefix := path.Join(normalizePath(p), "src") + "/" - if rel := strings.TrimPrefix(fname, prefix); rel != fname { - if !isDir { - return path.Dir(rel), nil - } else { - return path.Clean(rel), nil - } - } - } - - return "", fmt.Errorf("file '%v' is not in GOPATH", fname) -} diff --git a/parser/pkgpath.go b/parser/pkgpath.go new file mode 100644 index 0000000..489b392 --- /dev/null +++ b/parser/pkgpath.go @@ -0,0 +1,163 @@ +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) +}