Skip to content

Commit d11f880

Browse files
committed
feat: parse go work to get effective go work dir
1 parent d4c14b3 commit d11f880

File tree

2 files changed

+103
-15
lines changed

2 files changed

+103
-15
lines changed

lang/golang/parser/parser.go

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ type GoParser struct {
4848
files map[string][]byte
4949
exclues []*regexp.Regexp
5050
cgoPkgs map[string]bool // CGO packages
51+
workDirs map[string]bool // directories that are in go.work scope
5152
}
5253

5354
type moduleInfo struct {
@@ -97,10 +98,33 @@ func newGoParser(name string, homePageDir string, opts Options) *GoParser {
9798
}
9899

99100
func (p *GoParser) collectGoMods(startDir string) error {
100-
hasGoWork := false
101+
var workFiles []string
102+
err := filepath.Walk(startDir, func(path string, info fs.FileInfo, err error) error {
103+
if err != nil || !strings.HasSuffix(path, "go.work") {
104+
return nil
105+
}
106+
workFiles = append(workFiles, path)
107+
return nil
108+
})
109+
if err != nil {
110+
return err
111+
}
112+
113+
p.workDirs = make(map[string]bool)
114+
for _, workPath := range workFiles {
115+
wf, err := parseGoWork(workPath)
116+
if err != nil {
117+
fmt.Fprintf(os.Stderr, "failed to parse go.work file %s: %v\n", workPath, err)
118+
continue
119+
}
120+
for _, useDir := range wf.UseDirs {
121+
p.workDirs[useDir] = true
122+
}
123+
}
124+
fmt.Printf("go work effective dirs: %v\n", p.workDirs)
101125
deps := map[string]string{}
102126
var cgoPkgs map[string]bool
103-
err := filepath.Walk(startDir, func(path string, info fs.FileInfo, err error) error {
127+
err = filepath.Walk(startDir, func(path string, info fs.FileInfo, err error) error {
104128
if err != nil || !strings.HasSuffix(path, "go.mod") {
105129
return nil
106130
}
@@ -117,7 +141,7 @@ func (p *GoParser) collectGoMods(startDir string) error {
117141
p.repo.Modules[name] = newModule(name, rel)
118142
p.modules = append(p.modules, newModuleInfo(name, rel, name))
119143

120-
deps, hasGoWork, cgoPkgs, err = getDeps(filepath.Dir(path), hasGoWork)
144+
deps, cgoPkgs, err = getDeps(filepath.Dir(path), p.workDirs)
121145
if err != nil {
122146
return err
123147
}
@@ -161,9 +185,21 @@ type dep struct {
161185
CgoFiles []string `json:"CgoFiles"`
162186
}
163187

164-
func getDeps(dir string, goWork bool) (a map[string]string, hasGoWork bool, cgoPkgs map[string]bool, err error) {
188+
func getDeps(dir string, workDirs map[string]bool) (a map[string]string, cgoPkgs map[string]bool, err error) {
165189
cgoPkgs = make(map[string]bool)
166-
// run go mod tidy first to ensure all dependencies are resolved
190+
absDir, err := filepath.Abs(dir)
191+
if err != nil {
192+
return nil, cgoPkgs, fmt.Errorf("failed to get absolute path: %w", err)
193+
}
194+
195+
inWorkSpace := false
196+
for workDir := range workDirs {
197+
if absDir == workDir || strings.HasPrefix(absDir, workDir+string(filepath.Separator)) {
198+
inWorkSpace = true
199+
break
200+
}
201+
}
202+
167203
cmd := exec.Command("go", "mod", "tidy", "-e")
168204
cmd.Dir = dir
169205
cmd.Env = append(os.Environ(), "GONOSUMDB=*")
@@ -176,16 +212,13 @@ func getDeps(dir string, goWork bool) (a map[string]string, hasGoWork bool, cgoP
176212
cmd.Env = append(os.Environ(), "GOSUMDB=off")
177213
output, err = cmd.CombinedOutput()
178214
if err != nil {
179-
return nil, hasGoWork, cgoPkgs, fmt.Errorf("failed to execute 'go mod tidy', err: %v, output: %s", err, string(output))
215+
return nil, cgoPkgs, fmt.Errorf("failed to execute 'go mod tidy', err: %v, output: %s", err, string(output))
180216
}
181217
}
182218
if hasNoDeps(filepath.Join(dir, "go.mod")) {
183-
return map[string]string{}, hasGoWork, cgoPkgs, nil
219+
return map[string]string{}, cgoPkgs, nil
184220
}
185-
// -mod=mod to use go mod when go mod is inconsistent with go vendor
186-
// if go.work exist, it's no need to set -mod=mod
187-
if _, err = os.Stat(filepath.Join(dir, "go.work")); err == nil || goWork {
188-
hasGoWork = true
221+
if inWorkSpace {
189222
cmd = exec.Command("go", "list", "-e", "-json", "all")
190223
} else {
191224
cmd = exec.Command("go", "list", "-e", "-json", "-mod=mod", "all")
@@ -194,12 +227,12 @@ func getDeps(dir string, goWork bool) (a map[string]string, hasGoWork bool, cgoP
194227
cmd.Env = append(os.Environ(), "GOSUMDB=off")
195228
output, err = cmd.CombinedOutput()
196229
if err != nil {
197-
return nil, hasGoWork, cgoPkgs, fmt.Errorf("failed to execute 'go list -json all', err: %v, output: %s, cmd string: %s, dir: %s", err, string(output), cmd.String(), dir)
230+
return nil, cgoPkgs, fmt.Errorf("failed to execute 'go list -json all', err: %v, output: %s, cmd string: %s, dir: %s", err, string(output), cmd.String(), dir)
198231
}
199232
// ignore content until first open
200233
index := strings.Index(string(output), "{")
201234
if index == -1 {
202-
return nil, hasGoWork, cgoPkgs, fmt.Errorf("failed to find '{' in output, output: %s", string(output))
235+
return nil, cgoPkgs, fmt.Errorf("failed to find '{' in output, output: %s", string(output))
203236
}
204237
if index > 0 {
205238
log.Info("go list skip prefix, output: %s", string(output[:index]))
@@ -213,7 +246,7 @@ func getDeps(dir string, goWork bool) (a map[string]string, hasGoWork bool, cgoP
213246
if err.Error() == "EOF" {
214247
break
215248
}
216-
return nil, hasGoWork, cgoPkgs, fmt.Errorf("failed to decode json: %v, output: %s", err, string(output))
249+
return nil, cgoPkgs, fmt.Errorf("failed to decode json: %v, output: %s", err, string(output))
217250
}
218251
module := mod.Module
219252
// golang internal package, ignore it.
@@ -239,7 +272,7 @@ func getDeps(dir string, goWork bool) (a map[string]string, hasGoWork bool, cgoP
239272
}
240273
}
241274
}
242-
return deps, hasGoWork, cgoPkgs, nil
275+
return deps, cgoPkgs, nil
243276
}
244277

245278
// ParseRepo parse the entiry repo from homePageDir recursively until end

lang/golang/parser/utils.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"os"
2424
"os/exec"
2525
"path"
26+
"path/filepath"
2627
"regexp"
2728
"strings"
2829
"sync"
@@ -319,3 +320,57 @@ func getCommitHash(dir string) (string, error) {
319320
}
320321
return strings.TrimSpace(string(output)), nil
321322
}
323+
324+
type workFile struct {
325+
Dir string
326+
UseDirs []string
327+
Replaces []workReplace
328+
}
329+
330+
type workReplace struct {
331+
OldPath string
332+
NewPath string
333+
}
334+
335+
func parseGoWork(workFilePath string) (*workFile, error) {
336+
content, err := os.ReadFile(workFilePath)
337+
if err != nil {
338+
return nil, fmt.Errorf("failed to read go.work file: %w", err)
339+
}
340+
341+
wf, err := modfile.ParseWork(workFilePath, content, nil)
342+
if err != nil {
343+
return nil, fmt.Errorf("failed to parse go.work file: %w", err)
344+
}
345+
346+
workDir := filepath.Dir(workFilePath)
347+
result := &workFile{
348+
Dir: workDir,
349+
UseDirs: make([]string, 0, len(wf.Use)),
350+
Replaces: make([]workReplace, 0, len(wf.Replace)),
351+
}
352+
353+
for _, use := range wf.Use {
354+
if use.Path == "" {
355+
continue
356+
}
357+
usePath := use.Path
358+
if !filepath.IsAbs(usePath) {
359+
usePath = filepath.Join(workDir, usePath)
360+
}
361+
absPath, err := filepath.Abs(usePath)
362+
if err != nil {
363+
continue
364+
}
365+
result.UseDirs = append(result.UseDirs, absPath)
366+
}
367+
368+
for _, rep := range wf.Replace {
369+
result.Replaces = append(result.Replaces, workReplace{
370+
OldPath: rep.Old.Path,
371+
NewPath: rep.New.Path,
372+
})
373+
}
374+
375+
return result, nil
376+
}

0 commit comments

Comments
 (0)