Skip to content

Commit 1e55d87

Browse files
authored
Merge pull request #110 from kcmvp/deps
Deps
2 parents f500d81 + 4118493 commit 1e55d87

File tree

8 files changed

+95
-159
lines changed

8 files changed

+95
-159
lines changed

cmd/gbc/artifact/internal_plugin_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ func (suite *InternalPluginTestSuit) TestNewPlugin() {
108108
assert.True(t, test.wantErr == (err != nil))
109109
if !test.wantErr {
110110
assert.Equal(t, test.module, plugin.module)
111-
assert.True(t, lo.Contains([]string{"v1.58.1", "v1.57.2", "v1.1.1", "v1.11.0"}, plugin.Version()))
112111
}
113112
})
114113
}

cmd/gbc/artifact/project.go

Lines changed: 44 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ import (
88
"github.com/kcmvp/gob/utils"
99
"github.com/samber/lo" //nolint
1010
"github.com/spf13/viper" //nolint
11-
"io/fs"
11+
"go/types"
12+
"golang.org/x/mod/modfile"
13+
"golang.org/x/tools/go/packages"
1214
"log"
1315
"os"
1416
"os/exec"
1517
"path/filepath"
16-
"regexp"
1718
"runtime"
1819
"strings"
1920
"sync"
@@ -30,10 +31,10 @@ var (
3031
)
3132

3233
type Project struct {
33-
root string
34-
module string
35-
deps []string
36-
cfgs sync.Map // store all the configuration
34+
root string
35+
mod *modfile.File
36+
cfgs sync.Map // store all the configuration
37+
pkgs []*packages.Package
3738
}
3839

3940
func (project *Project) load() *viper.Viper {
@@ -77,32 +78,28 @@ func (project *Project) HookDir() string {
7778
}
7879

7980
func init() {
80-
cmd := exec.Command("go", "list", "-m", "-f", "{{.Dir}}_:_{{.Path}}")
81-
output, err := cmd.Output()
81+
output, err := exec.Command("go", "list", "-m", "-f", "{{.Dir}}_:_{{.Path}}").CombinedOutput()
8282
if err != nil || len(string(output)) == 0 {
83-
log.Fatal(color.RedString("Error: please execute command in project root directory %s", string(output)))
83+
log.Fatal(color.RedString("please execute command in project root directory %s", string(output)))
8484
}
85-
8685
item := strings.Split(strings.TrimSpace(string(output)), "_:_")
87-
project = Project{
88-
root: item[0],
89-
module: item[1],
90-
cfgs: sync.Map{},
86+
project = Project{cfgs: sync.Map{}, root: item[0]}
87+
data, err := os.ReadFile(filepath.Join(project.root, "go.mod"))
88+
if err != nil {
89+
log.Fatal(color.RedString(err.Error()))
9190
}
92-
cmd = exec.Command("go", "list", "-f", "{{if not .Standard}}{{.ImportPath}}{{end}}", "-deps", "./...")
93-
output, err = cmd.Output()
91+
project.mod, err = modfile.Parse("go.mod", data, nil)
9492
if err != nil {
95-
log.Fatal(color.RedString("Error: please execute command in project root directory"))
93+
log.Fatal(color.RedString("please execute command in project root directory %s", string(output)))
9694
}
97-
scanner := bufio.NewScanner(strings.NewReader(string(output)))
98-
var deps []string
99-
for scanner.Scan() {
100-
line := strings.TrimSpace(scanner.Text())
101-
if len(line) > 0 {
102-
deps = append(deps, line)
103-
}
95+
cfg := &packages.Config{
96+
Mode: packages.NeedName | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedFiles | packages.NeedTypesInfo | packages.NeedDeps | packages.NeedImports | packages.NeedSyntax,
97+
Dir: project.root,
98+
}
99+
project.pkgs, err = packages.Load(cfg, "./...")
100+
if err != nil {
101+
log.Fatal(color.RedString("failed to load project %s", err.Error()))
104102
}
105-
project.deps = deps
106103
}
107104

108105
// CurProject return Project struct
@@ -117,7 +114,7 @@ func (project *Project) Root() string {
117114

118115
// Module return current project module name
119116
func (project *Project) Module() string {
120-
return project.module
117+
return project.mod.Module.Mod.Path
121118
}
122119

123120
func (project *Project) Target() string {
@@ -148,37 +145,22 @@ func (project *Project) sourceFileInPkg(pkg string) ([]string, error) {
148145
}
149146

150147
func (project *Project) MainFiles() []string {
151-
var mainFiles []string
152-
dirs, _ := project.sourceFileInPkg("main")
153-
re := regexp.MustCompile(`func\s+main\s*\(\s*\)`)
154-
lo.ForEach(dirs, func(dir string, _ int) {
155-
_ = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
156-
if err != nil {
157-
return err
158-
}
159-
if d.IsDir() && dir != path {
160-
return filepath.SkipDir
161-
}
162-
if d.IsDir() || !strings.HasSuffix(d.Name(), ".go") || strings.HasSuffix(d.Name(), "_test.go") {
163-
return nil
164-
}
165-
file, err := os.Open(path)
166-
if err != nil {
167-
return err
168-
}
169-
defer file.Close()
170-
scanner := bufio.NewScanner(file)
171-
for scanner.Scan() {
172-
line := strings.TrimSpace(scanner.Text())
173-
if re.MatchString(line) {
174-
mainFiles = append(mainFiles, path)
175-
return filepath.SkipDir
148+
return lo.FilterMap(project.pkgs, func(pkg *packages.Package, _ int) (string, bool) {
149+
if pkg.Name != "main" {
150+
return "", false
151+
}
152+
scope := pkg.Types.Scope()
153+
for _, name := range scope.Names() {
154+
obj := scope.Lookup(name)
155+
if f, ok := obj.(*types.Func); ok {
156+
signature := f.Type().(*types.Signature)
157+
if f.Name() == "main" && signature.Params().Len() == 0 && signature.Results().Len() == 0 {
158+
return pkg.Fset.Position(obj.Pos()).Filename, true
176159
}
177160
}
178-
return scanner.Err()
179-
})
161+
}
162+
return "", false
180163
})
181-
return mainFiles
182164
}
183165

184166
func (project *Project) Plugins() []Plugin {
@@ -201,15 +183,18 @@ func (project *Project) Plugins() []Plugin {
201183
}
202184
}
203185

204-
func (project *Project) Dependencies() []string {
205-
return project.deps
186+
func (project *Project) Dependencies() []*modfile.Require {
187+
return project.mod.Require
206188
}
207189

208190
func (project *Project) InstallDependency(dep string) error {
209-
if !lo.Contains(project.deps, dep) {
210-
exec.Command("go", "get", "-u", dep).CombinedOutput() //nolint
191+
var err error
192+
if lo.NoneBy(project.mod.Require, func(r *modfile.Require) bool {
193+
return lo.Contains(r.Syntax.Token, dep)
194+
}) {
195+
_, err = exec.Command("go", "get", "-u", dep).CombinedOutput() //nolint
211196
}
212-
return nil
197+
return err
213198
}
214199

215200
func (project *Project) InstallPlugin(plugin Plugin) error {

cmd/gbc/artifact/project_test.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/samber/lo"
77
"github.com/stretchr/testify/assert"
88
"github.com/stretchr/testify/suite"
9+
"golang.org/x/mod/modfile"
910
"io"
1011
"os"
1112
"path/filepath"
@@ -49,8 +50,10 @@ func TestBasic(t *testing.T) {
4950

5051
func (suite *ProjectTestSuite) TestDeps() {
5152
deps := CurProject().Dependencies()
52-
assert.Equal(suite.T(), 58, len(deps))
53-
assert.True(suite.T(), lo.Contains(deps, "github.com/spf13/viper"))
53+
assert.Equal(suite.T(), 50, len(deps))
54+
assert.True(suite.T(), lo.ContainsBy(deps, func(require *modfile.Require) bool {
55+
return require.Mod.Path == "github.com/spf13/viper"
56+
}))
5457
}
5558

5659
func (suite *ProjectTestSuite) TestPlugins() {

cmd/gbc/command/deps.go

Lines changed: 28 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package command
22

33
import (
4-
"bufio"
54
"fmt"
5+
"golang.org/x/mod/modfile" //nolint
66
"os"
77
"os/exec"
88
"path/filepath"
@@ -21,93 +21,50 @@ var (
2121
yellow = color.New(color.FgYellow)
2222
)
2323

24-
// parseMod return a tuple which the fourth element is the indicator of direct or indirect reference
25-
func parseMod(mod *os.File) (string, string, []*lo.Tuple4[string, string, string, int], error) {
26-
scanner := bufio.NewScanner(mod)
27-
var deps []*lo.Tuple4[string, string, string, int]
28-
var module, version string
29-
for scanner.Scan() {
30-
line := strings.TrimSpace(scanner.Text())
31-
if len(line) == 0 || line == ")" || line == "//" || strings.HasPrefix(line, "require") {
32-
continue
33-
}
34-
if strings.HasPrefix(line, "module ") {
35-
module = strings.Split(line, " ")[1]
36-
} else if strings.HasPrefix(line, "go ") {
37-
version = strings.Split(line, " ")[1]
38-
} else {
39-
entry := strings.Split(line, " ")
40-
m := strings.TrimSpace(entry[0])
41-
v := strings.TrimSpace(entry[1])
42-
dep := lo.T4(m, v, v, lo.If(len(entry) > 2, 0).Else(1))
43-
deps = append(deps, &dep)
44-
}
45-
}
46-
return module, version, deps, scanner.Err()
47-
}
48-
4924
// dependencyTree build dependency tree of the project, an empty tree returns when runs into error
5025
func dependencyTree() (treeprint.Tree, error) {
51-
mod, err := os.Open(filepath.Join(artifact.CurProject().Root(), "go.mod"))
52-
if err != nil {
53-
return nil, fmt.Errorf(color.RedString(err.Error()))
54-
}
5526
exec.Command("go", "mod", "tidy").CombinedOutput() //nolint
56-
if output, err := exec.Command("go", "build", "./...").CombinedOutput(); err != nil {
57-
return nil, fmt.Errorf(color.RedString(string(output)))
58-
}
59-
module, _, dependencies, err := parseMod(mod)
60-
if len(dependencies) < 1 {
61-
return nil, nil
62-
}
63-
if err != nil {
64-
return nil, fmt.Errorf(err.Error())
65-
}
6627
tree := treeprint.New()
67-
tree.SetValue(module)
68-
direct := lo.FilterMap(dependencies, func(item *lo.Tuple4[string, string, string, int], _ int) (string, bool) {
69-
return item.A, item.D == 1
28+
tree.SetValue(artifact.CurProject().Module())
29+
directs := lo.FilterMap(artifact.CurProject().Dependencies(), func(item *modfile.Require, _ int) (lo.Tuple2[string, string], bool) {
30+
return lo.Tuple2[string, string]{A: item.Mod.Path, B: item.Mod.Version}, !item.Indirect
7031
})
7132
// get the latest version
72-
versions := artifact.LatestVersion(direct...)
73-
for _, dep := range dependencies {
74-
if version, ok := lo.Find(versions, func(t lo.Tuple2[string, string]) bool {
75-
return dep.A == t.A && dep.B != t.B
76-
}); ok {
77-
dep.C = version.B
78-
}
79-
}
33+
versions := artifact.LatestVersion(lo.Map(directs, func(item lo.Tuple2[string, string], _ int) string {
34+
return item.A
35+
})...)
8036
// parse the dependency tree
8137
cache := []string{os.Getenv("GOPATH"), "pkg", "mod", "cache", "download"}
82-
for _, dependency := range dependencies {
83-
if dependency.D == 1 {
84-
label := lo.IfF(dependency.B == dependency.C, func() string {
85-
return fmt.Sprintf("%s@%s", dependency.A, dependency.B)
38+
for _, dependency := range artifact.CurProject().Dependencies() {
39+
if !dependency.Indirect {
40+
m, ok := lo.Find(versions, func(item lo.Tuple2[string, string]) bool {
41+
return dependency.Mod.Path == item.A && dependency.Mod.Version != item.B
42+
})
43+
label := lo.IfF(!ok, func() string {
44+
return dependency.Mod.String()
8645
}).ElseF(func() string {
87-
return yellow.Sprintf("* %s@%s (%s)", dependency.A, dependency.B, dependency.C)
46+
return yellow.Sprintf("* %s (%s)", dependency.Mod.String(), m.B)
8847
})
89-
child := tree.AddBranch(label)
90-
dir := append(cache, strings.Split(dependency.A, "/")...)
91-
dir = append(dir, []string{"@v", fmt.Sprintf("%s.mod", dependency.B)}...)
92-
mod, err = os.Open(filepath.Join(dir...))
93-
if err != nil {
94-
return tree, fmt.Errorf(color.RedString(err.Error()))
95-
}
96-
_, _, cDeps, err := parseMod(mod)
48+
direct := tree.AddBranch(label)
49+
dir := append(cache, strings.Split(dependency.Mod.Path, "/")...)
50+
dir = append(dir, []string{"@v", fmt.Sprintf("%s.mod", dependency.Mod.Version)}...)
51+
data, err := os.ReadFile(filepath.Join(dir...))
9752
if err != nil {
98-
return tree, fmt.Errorf(color.RedString(err.Error()))
53+
color.Yellow("failed to get latest version of %s", dependency.Mod.Path)
54+
continue
9955
}
100-
inter := lo.Filter(cDeps, func(c *lo.Tuple4[string, string, string, int], _ int) bool {
101-
return lo.ContainsBy(dependencies, func(p *lo.Tuple4[string, string, string, int]) bool {
102-
return p.A == c.A
56+
mod, _ := modfile.Parse("go.mod", data, nil)
57+
children := lo.Filter(artifact.CurProject().Dependencies(), func(p *modfile.Require, _ int) bool {
58+
return p.Indirect && lo.ContainsBy(mod.Require, func(c *modfile.Require) bool {
59+
return !c.Indirect && p.Mod.Path == c.Mod.Path
10360
})
10461
})
105-
for _, l := range inter {
106-
child.AddNode(fmt.Sprintf("%s@%s", l.A, l.B))
62+
for _, c := range children {
63+
direct.AddNode(c.Mod.String())
10764
}
10865
}
10966
}
110-
return tree, err
67+
return tree, nil
11168
}
11269

11370
// depCmd represents the dep command

cmd/gbc/command/deps_test.go

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,19 @@ import (
66
"github.com/samber/lo"
77
"github.com/stretchr/testify/assert"
88
"github.com/xlab/treeprint"
9+
"golang.org/x/mod/modfile"
910
"os"
10-
"path/filepath"
1111
"strings"
1212
"testing"
1313
)
1414

15-
func TestParseMod(t *testing.T) {
16-
os.Chdir(artifact.CurProject().Root())
17-
mod, _ := os.Open(filepath.Join(artifact.CurProject().Root(), "go.mod"))
18-
m, _, deps, err := parseMod(mod)
19-
assert.NoError(t, err)
20-
assert.Equal(t, m, "github.com/kcmvp/gob")
21-
assert.Equal(t, 15, len(lo.Filter(deps, func(item *lo.Tuple4[string, string, string, int], _ int) bool {
22-
return item.D == 1
23-
})))
24-
assert.Equal(t, 48, len(deps))
25-
}
26-
2715
func TestDependency(t *testing.T) {
2816
os.Chdir(artifact.CurProject().Root())
29-
mod, _ := os.Open(filepath.Join(artifact.CurProject().Root(), "go.mod"))
30-
_, _, deps, _ := parseMod(mod)
3117
tree, err := dependencyTree()
3218
assert.NoError(t, err)
3319
tree.VisitAll(func(item *treeprint.Node) {
34-
contains := lo.ContainsBy(deps, func(dep *lo.Tuple4[string, string, string, int]) bool {
35-
return strings.Contains(fmt.Sprintf("%s", item.Value), fmt.Sprintf("%s", dep.A))
20+
contains := lo.ContainsBy(artifact.CurProject().Dependencies(), func(dep *modfile.Require) bool {
21+
return strings.Contains(fmt.Sprintf("%s", item.Value), dep.Mod.Path)
3622
})
3723
assert.True(t, contains)
3824
})

cmd/gbc/command/root.go

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -94,21 +94,19 @@ func installPlugins(cmd *cobra.Command, args []string) error {
9494

9595
func installDeps(cmd *cobra.Command, args []string) error {
9696
result, err := parseArtifacts(cmd, args, "deps")
97+
if err != nil {
98+
return err
99+
}
97100
if result.Exists() {
98101
var cfgDeps []string
99102
err = json.Unmarshal([]byte(result.Raw), &cfgDeps)
100-
for _, dep := range lo.Filter(cfgDeps, func(url string, _ int) bool {
101-
return !lo.Contains(artifact.CurProject().Dependencies(), url)
102-
}) {
103-
if err = artifact.CurProject().InstallDependency(dep); err != nil {
104-
break
103+
for _, dep := range cfgDeps {
104+
if err := artifact.CurProject().InstallDependency(dep); err != nil {
105+
return err
105106
}
106107
}
107108
}
108-
if err != nil {
109-
return errors.New(color.RedString(err.Error()))
110-
}
111-
return err
109+
return nil
112110
}
113111

114112
// rootCmd represents the base command when called without any subcommands

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ require (
1818
github.com/stretchr/testify v1.9.0
1919
github.com/tidwall/gjson v1.17.1
2020
github.com/xlab/treeprint v1.2.0
21+
golang.org/x/mod v0.12.0
22+
golang.org/x/tools v0.13.0
2123
)
2224

2325
require (

0 commit comments

Comments
 (0)