Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 73 additions & 3 deletions lang/golang/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package parser
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"go/ast"
"go/parser"
Expand Down Expand Up @@ -94,12 +95,11 @@ func newGoParser(name string, homePageDir string, opts Options) *GoParser {
}

func (p *GoParser) collectGoMods(startDir string) error {

err := filepath.Walk(startDir, func(path string, info fs.FileInfo, err error) error {
if err != nil || !strings.HasSuffix(path, "go.mod") {
return nil
}
name, content, err := getModuleName(path)
name, _, err := getModuleName(path)
if err != nil {
return err
}
Expand All @@ -109,7 +109,8 @@ func (p *GoParser) collectGoMods(startDir string) error {
}
p.repo.Modules[name] = newModule(name, rel)
p.modules = append(p.modules, newModuleInfo(name, rel, name))
deps, err := parseModuleFile(content)

deps, err := getDeps(filepath.Dir(path))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

之前是 parse go.mod 收集依赖信息,现在是 收集 go list,这块有啥区别吗?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

go mod 不全

if err != nil {
return err
}
Expand All @@ -126,6 +127,75 @@ func (p *GoParser) collectGoMods(startDir string) error {
return nil
}

type replace struct {
Path string `json:"Path"`
Version string `json:"Version"`
Dir string `json:"Dir"`
GoMod string `json:"GoMod"`
}

type dep struct {
Module struct {
Path string `json:"Path"`
Version string `json:"Version"`
Replace *replace `json:"Replace,omitempty"`
Indirect bool `json:"Indirect"`
Dir string `json:"Dir"`
GoMod string `json:"GoMod"`
} `json:"Module"`
}

func getDeps(dir string) (map[string]string, error) {
// run go mod tidy first to ensure all dependencies are resolved
cmd := exec.Command("go", "mod", "tidy", "-e")
cmd.Dir = dir
output, err := cmd.CombinedOutput()
if err != nil {
return nil, fmt.Errorf("failed to execute 'go mod tidy', err: %v, output: %s", err, string(output))
}

cmd = exec.Command("go", "list", "-json", "all")
cmd.Dir = dir
output, err = cmd.CombinedOutput()
if err != nil {
return nil, fmt.Errorf("failed to execute 'go list -json all', err: %v, output: %s", err, string(output))
}

deps := make(map[string]string)
decoder := json.NewDecoder(bytes.NewReader(output))
for {
var mod dep
if err := decoder.Decode(&mod); err != nil {
if err.Error() == "EOF" {
break
}
return nil, fmt.Errorf("failed to decode json: %v", err)
}
module := mod.Module
// golang internal package, ignore it.
if module.Path == "" {
continue
}
if module.Replace != nil {
deps[module.Path] = module.Replace.Path + "@" + module.Replace.Version
} else {
if module.Version != "" {
deps[module.Path] = module.Path + "@" + module.Version
} else {
// If no version, it's a local package. So we use local commit as version
commit, err := getCommitHash(dir)
if err != nil {
deps[module.Path] = module.Path
} else {
deps[module.Path] = module.Path + "@" + commit
}
}
}
}

return deps, nil
}

// ParseRepo parse the entiry repo from homePageDir recursively until end
func (p *GoParser) ParseRepo() (Repository, error) {
for _, lib := range p.modules {
Expand Down
32 changes: 11 additions & 21 deletions lang/golang/parser/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ import (
"go/types"
"io"
"os"
"os/exec"
"path"
"regexp"
"strings"
"sync"

"github.com/Knetic/govaluate"
. "github.com/cloudwego/abcoder/lang/uniast"
"golang.org/x/mod/modfile"
)

func shouldIgnoreDir(path string) bool {
Expand Down Expand Up @@ -181,26 +181,6 @@ func getModuleName(modFilePath string) (string, []byte, error) {
return "", data, nil
}

// parse go.mod and get a map of module name to module_path@version
func parseModuleFile(data []byte) (map[string]string, error) {
ast, err := modfile.Parse("go.mod", data, nil)
if err != nil {
return nil, fmt.Errorf("failed to parse go.mod file: %v", err)
}
modules := make(map[string]string)
for _, req := range ast.Require {
// if req.Indirect {
// continue
// }
modules[req.Mod.Path] = req.Mod.Path + "@" + req.Mod.Version
}
// replaces
for _, replace := range ast.Replace {
modules[replace.Old.Path] = replace.New.Path + "@" + replace.New.Version
}
return modules, nil
}

func isGoBuiltins(name string) bool {
switch name {
case "append", "cap", "close", "complex", "copy", "delete", "imag", "len", "make", "new", "panic", "print", "println", "real", "recover":
Expand Down Expand Up @@ -337,3 +317,13 @@ func newIdentity(mod, pkg, name string) Identity {
func isUpperCase(c byte) bool {
return c >= 'A' && c <= 'Z'
}

func getCommitHash(dir string) (string, error) {
cmd := exec.Command("git", "rev-parse", "HEAD")
cmd.Dir = dir
output, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("failed to get commit hash: %v", err)
}
return strings.TrimSpace(string(output)), nil
}
Loading